@healthcloudai/hc-safe-cdx 0.2.1 → 0.3.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.cjs CHANGED
@@ -20,20 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- ConfigError: () => ConfigError,
24
- HCSafeCDXClient: () => HCSafeCDXClient
23
+ APIError: () => import_hc_http2.APIError,
24
+ ConfigError: () => import_hc_http2.ConfigError,
25
+ HCSafeCDXClient: () => HCSafeCDXClient,
26
+ HCServiceError: () => import_hc_http2.HCServiceError,
27
+ NetworkError: () => import_hc_http2.NetworkError,
28
+ ValidationError: () => import_hc_http2.ValidationError,
29
+ errorFromHttpStatus: () => import_hc_http2.errorFromHttpStatus
25
30
  });
26
31
  module.exports = __toCommonJS(index_exports);
27
32
 
28
33
  // src/client.ts
29
- var SafeCDXError = class _SafeCDXError extends Error {
30
- constructor(message, response) {
31
- super(message);
32
- this.name = "SafeCDXError";
33
- this.response = response;
34
- Object.setPrototypeOf(this, _SafeCDXError.prototype);
35
- }
36
- };
34
+ var import_hc_http = require("@healthcloudai/hc-http");
37
35
  var ENV_HOST = {
38
36
  dev: "dev-api-hcs.healthcloud-services.com",
39
37
  uat: "uat-api-hcs.healthcloud-services.com",
@@ -43,7 +41,7 @@ var ROUTE_PREFIX = "api/console/hcservice/safecdx";
43
41
  function buildSafeCdxBaseUrl(environment) {
44
42
  const host = ENV_HOST[environment];
45
43
  if (!host) {
46
- throw new SafeCDXError("Invalid Safe CDX environment.");
44
+ throw new import_hc_http.ConfigError("Invalid Safe CDX environment.");
47
45
  }
48
46
  return `https://${host}/${ROUTE_PREFIX}`;
49
47
  }
@@ -68,10 +66,10 @@ var HCSafeCDXClient = class {
68
66
  const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
69
67
  const trimmedValue = value == null ? void 0 : value.trim();
70
68
  if (!trimmedHeaderName) {
71
- throw new SafeCDXError("API key header name is required.");
69
+ throw new import_hc_http.ConfigError("API key header name is required.");
72
70
  }
73
71
  if (!trimmedValue) {
74
- throw new SafeCDXError("API key value is required.");
72
+ throw new import_hc_http.ConfigError("API key value is required.");
75
73
  }
76
74
  this.apiKeyHeaderName = trimmedHeaderName;
77
75
  this.apiKeyValue = trimmedValue;
@@ -84,27 +82,34 @@ var HCSafeCDXClient = class {
84
82
  * Resolves the SafeCDX test profile associated with a GTIN barcode.
85
83
  */
86
84
  async getTestProfileByGTIN(gtin) {
87
- return this.http.get(
88
- this.url(`gs1/${encodeURIComponent(gtin)}`),
89
- this.getAuthHeaders()
85
+ const resolvedGtin = this.requireValue(gtin, "gtin");
86
+ return this.execute(
87
+ "getTestProfileByGTIN",
88
+ () => this.http.get(
89
+ this.url(`gs1/${encodeURIComponent(resolvedGtin)}`),
90
+ this.getAuthHeaders()
91
+ )
90
92
  );
91
93
  }
92
94
  /**
93
- * POST test/profiles/by-account
94
- * Lists test profiles available to the authenticated patient account.
95
- *
96
- * TenantId is resolved by the backend from the authenticated patient context.
97
- */
95
+ * POST test/profiles/by-account
96
+
97
+ *
98
+ * TenantId is resolved by the backend from the authenticated patient context.
99
+ */
98
100
  async getTestProfilesByAccount(includeRegisterTestDetails = true) {
99
101
  const request = {
100
102
  Data: {
101
103
  IncludeRegisterTestDetails: includeRegisterTestDetails
102
104
  }
103
105
  };
104
- return this.http.post(
105
- this.url("test/profiles/by-account"),
106
- request,
107
- this.getJsonHeaders()
106
+ return this.execute(
107
+ "getTestProfilesByAccount",
108
+ () => this.http.post(
109
+ this.url("test/profiles/by-account"),
110
+ request,
111
+ this.getJsonHeaders()
112
+ )
108
113
  );
109
114
  }
110
115
  // -------------------------------------------------------------------------
@@ -115,19 +120,25 @@ var HCSafeCDXClient = class {
115
120
  * Requests a pre-signed URL used to upload a test image.
116
121
  */
117
122
  async createUploadUrl(userTestResultId, gtin, imageType = "jpg") {
123
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
124
+ const resolvedGtin = this.requireValue(gtin, "gtin");
125
+ const resolvedImageType = this.requireValue(imageType, "imageType");
118
126
  const request = {
119
127
  Data: {
120
- Gtin: gtin,
121
- UserTestResultId: userTestResultId,
128
+ Gtin: resolvedGtin,
129
+ UserTestResultId: resolvedUserTestResultId,
122
130
  Metadata: {
123
- ImageType: imageType
131
+ ImageType: resolvedImageType
124
132
  }
125
133
  }
126
134
  };
127
- return this.http.post(
128
- this.url("upload/url"),
129
- request,
130
- this.getJsonHeaders()
135
+ return this.execute(
136
+ "createUploadUrl",
137
+ () => this.http.post(
138
+ this.url("upload/url"),
139
+ request,
140
+ this.getJsonHeaders()
141
+ )
131
142
  );
132
143
  }
133
144
  /**
@@ -138,10 +149,12 @@ var HCSafeCDXClient = class {
138
149
  * authenticated Health Cloud headers.
139
150
  */
140
151
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
141
- const response = await fetch(preSignedURL, {
152
+ const resolvedPreSignedURL = this.requireValue(preSignedURL, "preSignedURL");
153
+ const resolvedContentType = this.requireValue(contentType, "contentType");
154
+ const response = await fetch(resolvedPreSignedURL, {
142
155
  method: "PUT",
143
156
  headers: {
144
- "Content-Type": contentType
157
+ "Content-Type": resolvedContentType
145
158
  },
146
159
  body: image
147
160
  });
@@ -152,9 +165,15 @@ var HCSafeCDXClient = class {
152
165
  } catch {
153
166
  body = void 0;
154
167
  }
155
- throw new SafeCDXError(`Image upload failed with HTTP ${response.status}.`, {
156
- status: response.status,
157
- body
168
+ throw new import_hc_http.APIError({
169
+ message: `uploadImage: image upload failed with HTTP ${response.status}.`,
170
+ code: response.status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR",
171
+ statusCode: response.status,
172
+ backendMessage: body,
173
+ details: {
174
+ status: response.status,
175
+ body
176
+ }
158
177
  });
159
178
  }
160
179
  }
@@ -166,20 +185,32 @@ var HCSafeCDXClient = class {
166
185
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
167
186
  */
168
187
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
188
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
189
+ const resolvedCvmlStatus = this.requireValue(cvmlStatus, "cvmlStatus");
169
190
  const request = {
170
191
  Data: {
171
- ImageOfCaptureId: imageOfCaptureId,
172
- CvmlStatus: cvmlStatus
192
+ ImageOfCaptureId: resolvedImageOfCaptureId,
193
+ CvmlStatus: resolvedCvmlStatus
173
194
  }
174
195
  };
175
- return this.http.post(this.url("cvml/status"), request, this.getJsonHeaders());
196
+ return this.executeSafe(
197
+ "updateCvmlStatus",
198
+ () => this.http.post(this.url("cvml/status"), request, this.getJsonHeaders())
199
+ );
176
200
  }
177
201
  /**
178
202
  * GET cvml/results
179
203
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
180
204
  */
181
205
  async getCvmlResults(imageOfCaptureId) {
182
- return this.http.get(this.url("cvml/results", { imageOfCaptureId }), this.getAuthHeaders());
206
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
207
+ return this.executeSafe(
208
+ "getCvmlResults",
209
+ () => this.http.get(
210
+ this.url("cvml/results", { imageOfCaptureId: resolvedImageOfCaptureId }),
211
+ this.getAuthHeaders()
212
+ )
213
+ );
183
214
  }
184
215
  // -------------------------------------------------------------------------
185
216
  // Test Results
@@ -191,7 +222,10 @@ var HCSafeCDXClient = class {
191
222
  ExcludeStatus: excludeStatus
192
223
  }
193
224
  };
194
- return this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders());
225
+ return this.execute(
226
+ "getPendingResults",
227
+ () => this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders())
228
+ );
195
229
  }
196
230
  /** POST test/result/last */
197
231
  async getLastResults(excludeStatus = "Invalid") {
@@ -200,7 +234,10 @@ var HCSafeCDXClient = class {
200
234
  ExcludeStatus: excludeStatus
201
235
  }
202
236
  };
203
- return this.http.post(this.url("test/result/last"), request, this.getJsonHeaders());
237
+ return this.execute(
238
+ "getLastResults",
239
+ () => this.http.post(this.url("test/result/last"), request, this.getJsonHeaders())
240
+ );
204
241
  }
205
242
  /** POST test/result/history */
206
243
  async getTestHistory(excludeStatus = "Invalid") {
@@ -209,73 +246,188 @@ var HCSafeCDXClient = class {
209
246
  ExcludeStatus: excludeStatus
210
247
  }
211
248
  };
212
- return this.http.post(this.url("test/result/history"), request, this.getJsonHeaders());
249
+ return this.execute(
250
+ "getTestHistory",
251
+ () => this.http.post(this.url("test/result/history"), request, this.getJsonHeaders())
252
+ );
213
253
  }
214
254
  /**
215
255
  * POST test/result/details
216
256
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
217
257
  */
218
258
  async getResultDetails(userTestResultId) {
259
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
219
260
  const request = {
220
261
  Data: {
221
- UserTestResultId: userTestResultId
262
+ UserTestResultId: resolvedUserTestResultId
222
263
  }
223
264
  };
224
- return this.http.post(this.url("test/result/details"), request, this.getJsonHeaders());
265
+ return this.executeSafe(
266
+ "getResultDetails",
267
+ () => this.http.post(this.url("test/result/details"), request, this.getJsonHeaders())
268
+ );
225
269
  }
226
270
  /** POST test/result/pdf */
227
271
  async getResultPdf(userTestResultId) {
272
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
228
273
  const request = {
229
274
  Data: {
230
- UserTestResultId: userTestResultId
275
+ UserTestResultId: resolvedUserTestResultId
231
276
  }
232
277
  };
233
- return this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders());
278
+ return this.execute(
279
+ "getResultPdf",
280
+ () => this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders())
281
+ );
234
282
  }
235
283
  /**
236
284
  * POST test/result/image/capture
237
285
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
238
286
  */
239
287
  async getImageCaptureUrl(userTestResultId) {
288
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
240
289
  const request = {
241
290
  Data: {
242
- UserTestResultId: userTestResultId
291
+ UserTestResultId: resolvedUserTestResultId
243
292
  }
244
293
  };
245
- return this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders());
294
+ return this.executeSafe(
295
+ "getImageCaptureUrl",
296
+ () => this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders())
297
+ );
246
298
  }
247
299
  // -------------------------------------------------------------------------
248
300
  // Resume / Answers / Finalize
249
301
  // -------------------------------------------------------------------------
250
302
  /** POST test/resume */
251
303
  async resumeFlow(userTestResultId, resumed = true) {
304
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
252
305
  const request = {
253
306
  Data: {
254
- UserTestResultId: userTestResultId,
307
+ UserTestResultId: resolvedUserTestResultId,
255
308
  Resumed: resumed
256
309
  }
257
310
  };
258
- return this.http.post(this.url("test/resume"), request, this.getJsonHeaders());
311
+ return this.execute(
312
+ "resumeFlow",
313
+ () => this.http.post(this.url("test/resume"), request, this.getJsonHeaders())
314
+ );
259
315
  }
260
316
  /** POST test/answers */
261
317
  async submitAnswers(submission) {
262
318
  const request = {
263
319
  Data: submission
264
320
  };
265
- return this.http.post(this.url("test/answers"), request, this.getJsonHeaders());
321
+ return this.execute(
322
+ "submitAnswers",
323
+ () => this.http.post(this.url("test/answers"), request, this.getJsonHeaders())
324
+ );
266
325
  }
267
326
  /** POST test/finalize */
268
327
  async finalizeTest(userTestResultId) {
328
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
269
329
  const request = {
270
330
  Data: {
271
- UserTestResultId: userTestResultId
331
+ UserTestResultId: resolvedUserTestResultId
272
332
  }
273
333
  };
274
- return this.http.post(this.url("test/finalize"), request, this.getJsonHeaders());
334
+ return this.execute(
335
+ "finalizeTest",
336
+ () => this.http.post(this.url("test/finalize"), request, this.getJsonHeaders())
337
+ );
275
338
  }
276
339
  // -------------------------------------------------------------------------
277
340
  // Private Helpers
278
341
  // -------------------------------------------------------------------------
342
+ async execute(operation, request) {
343
+ const response = await this.executeRaw(operation, request);
344
+ if (!this.isApiResponse(response)) {
345
+ throw new import_hc_http.APIError({
346
+ message: `${operation}: invalid API response structure`,
347
+ code: "INVALID_RESPONSE",
348
+ details: response
349
+ });
350
+ }
351
+ if (!response.IsOK) {
352
+ throw new import_hc_http.HCServiceError(operation, response.ErrorMessage, response);
353
+ }
354
+ return response;
355
+ }
356
+ async executeSafe(operation, request) {
357
+ var _a, _b;
358
+ const response = await this.executeRaw(operation, request);
359
+ if (!this.isSafeApiResponse(response)) {
360
+ throw new import_hc_http.APIError({
361
+ message: `${operation}: invalid SafeCDX response structure`,
362
+ code: "INVALID_RESPONSE",
363
+ details: response
364
+ });
365
+ }
366
+ if (!response.success) {
367
+ throw new import_hc_http.APIError({
368
+ message: (_a = response.message) != null ? _a : `${operation}: backend returned success: false`,
369
+ code: "BACKEND_FAILURE",
370
+ statusCode: 200,
371
+ backendMessage: (_b = response.message) != null ? _b : void 0,
372
+ details: response
373
+ });
374
+ }
375
+ return response;
376
+ }
377
+ async executeRaw(operation, request) {
378
+ let response;
379
+ try {
380
+ response = await request();
381
+ } catch (err) {
382
+ if (err instanceof import_hc_http.APIError) {
383
+ throw err;
384
+ }
385
+ if (err instanceof Error) {
386
+ throw new import_hc_http.APIError({
387
+ message: `${operation}: ${err.message}`,
388
+ code: "UNKNOWN_ERROR",
389
+ details: err
390
+ });
391
+ }
392
+ throw new import_hc_http.APIError({
393
+ message: `${operation}: unexpected runtime failure`,
394
+ code: "UNKNOWN_ERROR",
395
+ details: err
396
+ });
397
+ }
398
+ if (response == null) {
399
+ throw new import_hc_http.APIError({
400
+ message: `${operation}: empty response received`,
401
+ code: "EMPTY_RESPONSE",
402
+ details: response
403
+ });
404
+ }
405
+ return response;
406
+ }
407
+ isApiResponse(value) {
408
+ if (!value || typeof value !== "object") {
409
+ return false;
410
+ }
411
+ const response = value;
412
+ return "IsOK" in response && typeof response.IsOK === "boolean" && "Data" in response && "ErrorMessage" in response;
413
+ }
414
+ isSafeApiResponse(value) {
415
+ if (!value || typeof value !== "object") {
416
+ return false;
417
+ }
418
+ const response = value;
419
+ return "success" in response && typeof response.success === "boolean" && "data" in response && "message" in response && "code" in response && typeof response.code === "number";
420
+ }
421
+ requireValue(value, parameterName) {
422
+ const trimmedValue = value == null ? void 0 : value.trim();
423
+ if (!trimmedValue) {
424
+ throw new import_hc_http.ValidationError({
425
+ message: `${parameterName} is required.`,
426
+ code: "INVALID_INPUT"
427
+ });
428
+ }
429
+ return trimmedValue;
430
+ }
279
431
  getAuthHeaders() {
280
432
  return {
281
433
  ...this.loginClient.getAuthHeader(),
@@ -302,14 +454,14 @@ var HCSafeCDXClient = class {
302
454
  };
303
455
 
304
456
  // src/errors.ts
305
- var ConfigError = class extends Error {
306
- constructor(message) {
307
- super(message);
308
- this.name = "ConfigError";
309
- }
310
- };
457
+ var import_hc_http2 = require("@healthcloudai/hc-http");
311
458
  // Annotate the CommonJS export names for ESM import in node:
312
459
  0 && (module.exports = {
460
+ APIError,
313
461
  ConfigError,
314
- HCSafeCDXClient
462
+ HCSafeCDXClient,
463
+ HCServiceError,
464
+ NetworkError,
465
+ ValidationError,
466
+ errorFromHttpStatus
315
467
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HCLoginClient } from '@healthcloudai/hc-login-connector';
2
2
  import { HttpClient } from '@healthcloudai/hc-http';
3
+ export { APIError, ConfigError, HCServiceError, NetworkError, ValidationError, errorFromHttpStatus } from '@healthcloudai/hc-http';
3
4
 
4
5
  type Environment = "dev" | "uat" | "prod";
5
6
  /**
@@ -469,7 +470,7 @@ declare class HCSafeCDXClient {
469
470
  getTestProfileByGTIN(gtin: string): Promise<APIResponse<GetTestProfileByGTINData>>;
470
471
  /**
471
472
  * POST test/profiles/by-account
472
- * Lists test profiles available to the authenticated patient account.
473
+
473
474
  *
474
475
  * TenantId is resolved by the backend from the authenticated patient context.
475
476
  */
@@ -521,14 +522,16 @@ declare class HCSafeCDXClient {
521
522
  submitAnswers(submission: SubmitAnswersRequest): Promise<APIResponse<SubmitAnswersData>>;
522
523
  /** POST test/finalize */
523
524
  finalizeTest(userTestResultId: string): Promise<APIResponse<FinalizeTestData>>;
525
+ protected execute<T>(operation: string, request: () => Promise<APIResponse<T>>): Promise<APIResponse<T>>;
526
+ protected executeSafe<T>(operation: string, request: () => Promise<SafeAPIResponse<T>>): Promise<SafeAPIResponse<T>>;
527
+ private executeRaw;
528
+ private isApiResponse;
529
+ private isSafeApiResponse;
530
+ private requireValue;
524
531
  private getAuthHeaders;
525
532
  private getJsonHeaders;
526
533
  private getApiKeyHeader;
527
534
  private url;
528
535
  }
529
536
 
530
- declare class ConfigError extends Error {
531
- constructor(message: string);
532
- }
533
-
534
- export { type APIRequest, type APIResponse, type AnswerResult, type CaptureState, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CvmlImageOfCaptureResult, type CvmlParsedResult, type CvmlResultsData, type DiagnosticProfileCta, type DiagnosticProfileData, type DiagnosticProfileDecision, type DiagnosticProfileDecisionResult, type DiagnosticProfileIndication, type DiagnosticProfileTestInfo, type DisclaimerDetail, type EntityReferenceData, type Environment, type ExcludeStatusRequest, type FinalizeTestData, type FinalizeTestRequest, type GetCvmlResultsResponse, type GetImageCaptureUrlRequest, type GetImageCaptureUrlResponse, type GetLastResultsData, type GetLastResultsRequest, type GetPendingResultsData, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultDetailsResponse, type GetResultPdfData, type GetResultPdfRequest, type GetTestHistoryData, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfilesByAccountData, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type ImageOfCaptureResult, type ProductAssetDetail, type QuestionnaireAnalyte, type QuestionnaireAnalyteResponse, type QuestionnaireSnapshot, type QuestionnaireValidityItem, type RegisterTestDetail, type ReportedAnswerResult, type ResumeFlowData, type ResumeFlowRequest, type ResumeFlowResult, type SafeAPIResponse, type SubmitAnswersData, type SubmitAnswersRequest, type TestProfileAnalyte, type TestProfileAnalyteResponse, type TestProfileArticle, type TestProfileBillingInfo, type TestProfileByAccountItem, type TestProfileInstruction, type TestProfileResult, type TestProfileScanImageDetail, type TestProfileTerritory, type TestResultDetails, type TestResultSummary, type UpdateCvmlStatusRequest, type UpdateCvmlStatusResponse, type UploadMetadata, type UserTestResultIdRequest };
537
+ export { type APIRequest, type APIResponse, type AnswerResult, type CaptureState, type CreateUploadUrlData, type CreateUploadUrlRequest, type CvmlImageOfCaptureResult, type CvmlParsedResult, type CvmlResultsData, type DiagnosticProfileCta, type DiagnosticProfileData, type DiagnosticProfileDecision, type DiagnosticProfileDecisionResult, type DiagnosticProfileIndication, type DiagnosticProfileTestInfo, type DisclaimerDetail, type EntityReferenceData, type Environment, type ExcludeStatusRequest, type FinalizeTestData, type FinalizeTestRequest, type GetCvmlResultsResponse, type GetImageCaptureUrlRequest, type GetImageCaptureUrlResponse, type GetLastResultsData, type GetLastResultsRequest, type GetPendingResultsData, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultDetailsResponse, type GetResultPdfData, type GetResultPdfRequest, type GetTestHistoryData, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfilesByAccountData, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type ImageOfCaptureResult, type ProductAssetDetail, type QuestionnaireAnalyte, type QuestionnaireAnalyteResponse, type QuestionnaireSnapshot, type QuestionnaireValidityItem, type RegisterTestDetail, type ReportedAnswerResult, type ResumeFlowData, type ResumeFlowRequest, type ResumeFlowResult, type SafeAPIResponse, type SubmitAnswersData, type SubmitAnswersRequest, type TestProfileAnalyte, type TestProfileAnalyteResponse, type TestProfileArticle, type TestProfileBillingInfo, type TestProfileByAccountItem, type TestProfileInstruction, type TestProfileResult, type TestProfileScanImageDetail, type TestProfileTerritory, type TestResultDetails, type TestResultSummary, type UpdateCvmlStatusRequest, type UpdateCvmlStatusResponse, type UploadMetadata, type UserTestResultIdRequest };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HCLoginClient } from '@healthcloudai/hc-login-connector';
2
2
  import { HttpClient } from '@healthcloudai/hc-http';
3
+ export { APIError, ConfigError, HCServiceError, NetworkError, ValidationError, errorFromHttpStatus } from '@healthcloudai/hc-http';
3
4
 
4
5
  type Environment = "dev" | "uat" | "prod";
5
6
  /**
@@ -469,7 +470,7 @@ declare class HCSafeCDXClient {
469
470
  getTestProfileByGTIN(gtin: string): Promise<APIResponse<GetTestProfileByGTINData>>;
470
471
  /**
471
472
  * POST test/profiles/by-account
472
- * Lists test profiles available to the authenticated patient account.
473
+
473
474
  *
474
475
  * TenantId is resolved by the backend from the authenticated patient context.
475
476
  */
@@ -521,14 +522,16 @@ declare class HCSafeCDXClient {
521
522
  submitAnswers(submission: SubmitAnswersRequest): Promise<APIResponse<SubmitAnswersData>>;
522
523
  /** POST test/finalize */
523
524
  finalizeTest(userTestResultId: string): Promise<APIResponse<FinalizeTestData>>;
525
+ protected execute<T>(operation: string, request: () => Promise<APIResponse<T>>): Promise<APIResponse<T>>;
526
+ protected executeSafe<T>(operation: string, request: () => Promise<SafeAPIResponse<T>>): Promise<SafeAPIResponse<T>>;
527
+ private executeRaw;
528
+ private isApiResponse;
529
+ private isSafeApiResponse;
530
+ private requireValue;
524
531
  private getAuthHeaders;
525
532
  private getJsonHeaders;
526
533
  private getApiKeyHeader;
527
534
  private url;
528
535
  }
529
536
 
530
- declare class ConfigError extends Error {
531
- constructor(message: string);
532
- }
533
-
534
- export { type APIRequest, type APIResponse, type AnswerResult, type CaptureState, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CvmlImageOfCaptureResult, type CvmlParsedResult, type CvmlResultsData, type DiagnosticProfileCta, type DiagnosticProfileData, type DiagnosticProfileDecision, type DiagnosticProfileDecisionResult, type DiagnosticProfileIndication, type DiagnosticProfileTestInfo, type DisclaimerDetail, type EntityReferenceData, type Environment, type ExcludeStatusRequest, type FinalizeTestData, type FinalizeTestRequest, type GetCvmlResultsResponse, type GetImageCaptureUrlRequest, type GetImageCaptureUrlResponse, type GetLastResultsData, type GetLastResultsRequest, type GetPendingResultsData, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultDetailsResponse, type GetResultPdfData, type GetResultPdfRequest, type GetTestHistoryData, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfilesByAccountData, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type ImageOfCaptureResult, type ProductAssetDetail, type QuestionnaireAnalyte, type QuestionnaireAnalyteResponse, type QuestionnaireSnapshot, type QuestionnaireValidityItem, type RegisterTestDetail, type ReportedAnswerResult, type ResumeFlowData, type ResumeFlowRequest, type ResumeFlowResult, type SafeAPIResponse, type SubmitAnswersData, type SubmitAnswersRequest, type TestProfileAnalyte, type TestProfileAnalyteResponse, type TestProfileArticle, type TestProfileBillingInfo, type TestProfileByAccountItem, type TestProfileInstruction, type TestProfileResult, type TestProfileScanImageDetail, type TestProfileTerritory, type TestResultDetails, type TestResultSummary, type UpdateCvmlStatusRequest, type UpdateCvmlStatusResponse, type UploadMetadata, type UserTestResultIdRequest };
537
+ export { type APIRequest, type APIResponse, type AnswerResult, type CaptureState, type CreateUploadUrlData, type CreateUploadUrlRequest, type CvmlImageOfCaptureResult, type CvmlParsedResult, type CvmlResultsData, type DiagnosticProfileCta, type DiagnosticProfileData, type DiagnosticProfileDecision, type DiagnosticProfileDecisionResult, type DiagnosticProfileIndication, type DiagnosticProfileTestInfo, type DisclaimerDetail, type EntityReferenceData, type Environment, type ExcludeStatusRequest, type FinalizeTestData, type FinalizeTestRequest, type GetCvmlResultsResponse, type GetImageCaptureUrlRequest, type GetImageCaptureUrlResponse, type GetLastResultsData, type GetLastResultsRequest, type GetPendingResultsData, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultDetailsResponse, type GetResultPdfData, type GetResultPdfRequest, type GetTestHistoryData, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfilesByAccountData, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type ImageOfCaptureResult, type ProductAssetDetail, type QuestionnaireAnalyte, type QuestionnaireAnalyteResponse, type QuestionnaireSnapshot, type QuestionnaireValidityItem, type RegisterTestDetail, type ReportedAnswerResult, type ResumeFlowData, type ResumeFlowRequest, type ResumeFlowResult, type SafeAPIResponse, type SubmitAnswersData, type SubmitAnswersRequest, type TestProfileAnalyte, type TestProfileAnalyteResponse, type TestProfileArticle, type TestProfileBillingInfo, type TestProfileByAccountItem, type TestProfileInstruction, type TestProfileResult, type TestProfileScanImageDetail, type TestProfileTerritory, type TestResultDetails, type TestResultSummary, type UpdateCvmlStatusRequest, type UpdateCvmlStatusResponse, type UploadMetadata, type UserTestResultIdRequest };
package/dist/index.js CHANGED
@@ -1,12 +1,10 @@
1
1
  // src/client.ts
2
- var SafeCDXError = class _SafeCDXError extends Error {
3
- constructor(message, response) {
4
- super(message);
5
- this.name = "SafeCDXError";
6
- this.response = response;
7
- Object.setPrototypeOf(this, _SafeCDXError.prototype);
8
- }
9
- };
2
+ import {
3
+ APIError,
4
+ ConfigError,
5
+ HCServiceError,
6
+ ValidationError
7
+ } from "@healthcloudai/hc-http";
10
8
  var ENV_HOST = {
11
9
  dev: "dev-api-hcs.healthcloud-services.com",
12
10
  uat: "uat-api-hcs.healthcloud-services.com",
@@ -16,7 +14,7 @@ var ROUTE_PREFIX = "api/console/hcservice/safecdx";
16
14
  function buildSafeCdxBaseUrl(environment) {
17
15
  const host = ENV_HOST[environment];
18
16
  if (!host) {
19
- throw new SafeCDXError("Invalid Safe CDX environment.");
17
+ throw new ConfigError("Invalid Safe CDX environment.");
20
18
  }
21
19
  return `https://${host}/${ROUTE_PREFIX}`;
22
20
  }
@@ -41,10 +39,10 @@ var HCSafeCDXClient = class {
41
39
  const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
42
40
  const trimmedValue = value == null ? void 0 : value.trim();
43
41
  if (!trimmedHeaderName) {
44
- throw new SafeCDXError("API key header name is required.");
42
+ throw new ConfigError("API key header name is required.");
45
43
  }
46
44
  if (!trimmedValue) {
47
- throw new SafeCDXError("API key value is required.");
45
+ throw new ConfigError("API key value is required.");
48
46
  }
49
47
  this.apiKeyHeaderName = trimmedHeaderName;
50
48
  this.apiKeyValue = trimmedValue;
@@ -57,27 +55,34 @@ var HCSafeCDXClient = class {
57
55
  * Resolves the SafeCDX test profile associated with a GTIN barcode.
58
56
  */
59
57
  async getTestProfileByGTIN(gtin) {
60
- return this.http.get(
61
- this.url(`gs1/${encodeURIComponent(gtin)}`),
62
- this.getAuthHeaders()
58
+ const resolvedGtin = this.requireValue(gtin, "gtin");
59
+ return this.execute(
60
+ "getTestProfileByGTIN",
61
+ () => this.http.get(
62
+ this.url(`gs1/${encodeURIComponent(resolvedGtin)}`),
63
+ this.getAuthHeaders()
64
+ )
63
65
  );
64
66
  }
65
67
  /**
66
- * POST test/profiles/by-account
67
- * Lists test profiles available to the authenticated patient account.
68
- *
69
- * TenantId is resolved by the backend from the authenticated patient context.
70
- */
68
+ * POST test/profiles/by-account
69
+
70
+ *
71
+ * TenantId is resolved by the backend from the authenticated patient context.
72
+ */
71
73
  async getTestProfilesByAccount(includeRegisterTestDetails = true) {
72
74
  const request = {
73
75
  Data: {
74
76
  IncludeRegisterTestDetails: includeRegisterTestDetails
75
77
  }
76
78
  };
77
- return this.http.post(
78
- this.url("test/profiles/by-account"),
79
- request,
80
- this.getJsonHeaders()
79
+ return this.execute(
80
+ "getTestProfilesByAccount",
81
+ () => this.http.post(
82
+ this.url("test/profiles/by-account"),
83
+ request,
84
+ this.getJsonHeaders()
85
+ )
81
86
  );
82
87
  }
83
88
  // -------------------------------------------------------------------------
@@ -88,19 +93,25 @@ var HCSafeCDXClient = class {
88
93
  * Requests a pre-signed URL used to upload a test image.
89
94
  */
90
95
  async createUploadUrl(userTestResultId, gtin, imageType = "jpg") {
96
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
97
+ const resolvedGtin = this.requireValue(gtin, "gtin");
98
+ const resolvedImageType = this.requireValue(imageType, "imageType");
91
99
  const request = {
92
100
  Data: {
93
- Gtin: gtin,
94
- UserTestResultId: userTestResultId,
101
+ Gtin: resolvedGtin,
102
+ UserTestResultId: resolvedUserTestResultId,
95
103
  Metadata: {
96
- ImageType: imageType
104
+ ImageType: resolvedImageType
97
105
  }
98
106
  }
99
107
  };
100
- return this.http.post(
101
- this.url("upload/url"),
102
- request,
103
- this.getJsonHeaders()
108
+ return this.execute(
109
+ "createUploadUrl",
110
+ () => this.http.post(
111
+ this.url("upload/url"),
112
+ request,
113
+ this.getJsonHeaders()
114
+ )
104
115
  );
105
116
  }
106
117
  /**
@@ -111,10 +122,12 @@ var HCSafeCDXClient = class {
111
122
  * authenticated Health Cloud headers.
112
123
  */
113
124
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
114
- const response = await fetch(preSignedURL, {
125
+ const resolvedPreSignedURL = this.requireValue(preSignedURL, "preSignedURL");
126
+ const resolvedContentType = this.requireValue(contentType, "contentType");
127
+ const response = await fetch(resolvedPreSignedURL, {
115
128
  method: "PUT",
116
129
  headers: {
117
- "Content-Type": contentType
130
+ "Content-Type": resolvedContentType
118
131
  },
119
132
  body: image
120
133
  });
@@ -125,9 +138,15 @@ var HCSafeCDXClient = class {
125
138
  } catch {
126
139
  body = void 0;
127
140
  }
128
- throw new SafeCDXError(`Image upload failed with HTTP ${response.status}.`, {
129
- status: response.status,
130
- body
141
+ throw new APIError({
142
+ message: `uploadImage: image upload failed with HTTP ${response.status}.`,
143
+ code: response.status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR",
144
+ statusCode: response.status,
145
+ backendMessage: body,
146
+ details: {
147
+ status: response.status,
148
+ body
149
+ }
131
150
  });
132
151
  }
133
152
  }
@@ -139,20 +158,32 @@ var HCSafeCDXClient = class {
139
158
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
140
159
  */
141
160
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
161
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
162
+ const resolvedCvmlStatus = this.requireValue(cvmlStatus, "cvmlStatus");
142
163
  const request = {
143
164
  Data: {
144
- ImageOfCaptureId: imageOfCaptureId,
145
- CvmlStatus: cvmlStatus
165
+ ImageOfCaptureId: resolvedImageOfCaptureId,
166
+ CvmlStatus: resolvedCvmlStatus
146
167
  }
147
168
  };
148
- return this.http.post(this.url("cvml/status"), request, this.getJsonHeaders());
169
+ return this.executeSafe(
170
+ "updateCvmlStatus",
171
+ () => this.http.post(this.url("cvml/status"), request, this.getJsonHeaders())
172
+ );
149
173
  }
150
174
  /**
151
175
  * GET cvml/results
152
176
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
153
177
  */
154
178
  async getCvmlResults(imageOfCaptureId) {
155
- return this.http.get(this.url("cvml/results", { imageOfCaptureId }), this.getAuthHeaders());
179
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
180
+ return this.executeSafe(
181
+ "getCvmlResults",
182
+ () => this.http.get(
183
+ this.url("cvml/results", { imageOfCaptureId: resolvedImageOfCaptureId }),
184
+ this.getAuthHeaders()
185
+ )
186
+ );
156
187
  }
157
188
  // -------------------------------------------------------------------------
158
189
  // Test Results
@@ -164,7 +195,10 @@ var HCSafeCDXClient = class {
164
195
  ExcludeStatus: excludeStatus
165
196
  }
166
197
  };
167
- return this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders());
198
+ return this.execute(
199
+ "getPendingResults",
200
+ () => this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders())
201
+ );
168
202
  }
169
203
  /** POST test/result/last */
170
204
  async getLastResults(excludeStatus = "Invalid") {
@@ -173,7 +207,10 @@ var HCSafeCDXClient = class {
173
207
  ExcludeStatus: excludeStatus
174
208
  }
175
209
  };
176
- return this.http.post(this.url("test/result/last"), request, this.getJsonHeaders());
210
+ return this.execute(
211
+ "getLastResults",
212
+ () => this.http.post(this.url("test/result/last"), request, this.getJsonHeaders())
213
+ );
177
214
  }
178
215
  /** POST test/result/history */
179
216
  async getTestHistory(excludeStatus = "Invalid") {
@@ -182,73 +219,188 @@ var HCSafeCDXClient = class {
182
219
  ExcludeStatus: excludeStatus
183
220
  }
184
221
  };
185
- return this.http.post(this.url("test/result/history"), request, this.getJsonHeaders());
222
+ return this.execute(
223
+ "getTestHistory",
224
+ () => this.http.post(this.url("test/result/history"), request, this.getJsonHeaders())
225
+ );
186
226
  }
187
227
  /**
188
228
  * POST test/result/details
189
229
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
190
230
  */
191
231
  async getResultDetails(userTestResultId) {
232
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
192
233
  const request = {
193
234
  Data: {
194
- UserTestResultId: userTestResultId
235
+ UserTestResultId: resolvedUserTestResultId
195
236
  }
196
237
  };
197
- return this.http.post(this.url("test/result/details"), request, this.getJsonHeaders());
238
+ return this.executeSafe(
239
+ "getResultDetails",
240
+ () => this.http.post(this.url("test/result/details"), request, this.getJsonHeaders())
241
+ );
198
242
  }
199
243
  /** POST test/result/pdf */
200
244
  async getResultPdf(userTestResultId) {
245
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
201
246
  const request = {
202
247
  Data: {
203
- UserTestResultId: userTestResultId
248
+ UserTestResultId: resolvedUserTestResultId
204
249
  }
205
250
  };
206
- return this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders());
251
+ return this.execute(
252
+ "getResultPdf",
253
+ () => this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders())
254
+ );
207
255
  }
208
256
  /**
209
257
  * POST test/result/image/capture
210
258
  * Returns the direct SafeCDX response without an outer APIResponse wrapper.
211
259
  */
212
260
  async getImageCaptureUrl(userTestResultId) {
261
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
213
262
  const request = {
214
263
  Data: {
215
- UserTestResultId: userTestResultId
264
+ UserTestResultId: resolvedUserTestResultId
216
265
  }
217
266
  };
218
- return this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders());
267
+ return this.executeSafe(
268
+ "getImageCaptureUrl",
269
+ () => this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders())
270
+ );
219
271
  }
220
272
  // -------------------------------------------------------------------------
221
273
  // Resume / Answers / Finalize
222
274
  // -------------------------------------------------------------------------
223
275
  /** POST test/resume */
224
276
  async resumeFlow(userTestResultId, resumed = true) {
277
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
225
278
  const request = {
226
279
  Data: {
227
- UserTestResultId: userTestResultId,
280
+ UserTestResultId: resolvedUserTestResultId,
228
281
  Resumed: resumed
229
282
  }
230
283
  };
231
- return this.http.post(this.url("test/resume"), request, this.getJsonHeaders());
284
+ return this.execute(
285
+ "resumeFlow",
286
+ () => this.http.post(this.url("test/resume"), request, this.getJsonHeaders())
287
+ );
232
288
  }
233
289
  /** POST test/answers */
234
290
  async submitAnswers(submission) {
235
291
  const request = {
236
292
  Data: submission
237
293
  };
238
- return this.http.post(this.url("test/answers"), request, this.getJsonHeaders());
294
+ return this.execute(
295
+ "submitAnswers",
296
+ () => this.http.post(this.url("test/answers"), request, this.getJsonHeaders())
297
+ );
239
298
  }
240
299
  /** POST test/finalize */
241
300
  async finalizeTest(userTestResultId) {
301
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
242
302
  const request = {
243
303
  Data: {
244
- UserTestResultId: userTestResultId
304
+ UserTestResultId: resolvedUserTestResultId
245
305
  }
246
306
  };
247
- return this.http.post(this.url("test/finalize"), request, this.getJsonHeaders());
307
+ return this.execute(
308
+ "finalizeTest",
309
+ () => this.http.post(this.url("test/finalize"), request, this.getJsonHeaders())
310
+ );
248
311
  }
249
312
  // -------------------------------------------------------------------------
250
313
  // Private Helpers
251
314
  // -------------------------------------------------------------------------
315
+ async execute(operation, request) {
316
+ const response = await this.executeRaw(operation, request);
317
+ if (!this.isApiResponse(response)) {
318
+ throw new APIError({
319
+ message: `${operation}: invalid API response structure`,
320
+ code: "INVALID_RESPONSE",
321
+ details: response
322
+ });
323
+ }
324
+ if (!response.IsOK) {
325
+ throw new HCServiceError(operation, response.ErrorMessage, response);
326
+ }
327
+ return response;
328
+ }
329
+ async executeSafe(operation, request) {
330
+ var _a, _b;
331
+ const response = await this.executeRaw(operation, request);
332
+ if (!this.isSafeApiResponse(response)) {
333
+ throw new APIError({
334
+ message: `${operation}: invalid SafeCDX response structure`,
335
+ code: "INVALID_RESPONSE",
336
+ details: response
337
+ });
338
+ }
339
+ if (!response.success) {
340
+ throw new APIError({
341
+ message: (_a = response.message) != null ? _a : `${operation}: backend returned success: false`,
342
+ code: "BACKEND_FAILURE",
343
+ statusCode: 200,
344
+ backendMessage: (_b = response.message) != null ? _b : void 0,
345
+ details: response
346
+ });
347
+ }
348
+ return response;
349
+ }
350
+ async executeRaw(operation, request) {
351
+ let response;
352
+ try {
353
+ response = await request();
354
+ } catch (err) {
355
+ if (err instanceof APIError) {
356
+ throw err;
357
+ }
358
+ if (err instanceof Error) {
359
+ throw new APIError({
360
+ message: `${operation}: ${err.message}`,
361
+ code: "UNKNOWN_ERROR",
362
+ details: err
363
+ });
364
+ }
365
+ throw new APIError({
366
+ message: `${operation}: unexpected runtime failure`,
367
+ code: "UNKNOWN_ERROR",
368
+ details: err
369
+ });
370
+ }
371
+ if (response == null) {
372
+ throw new APIError({
373
+ message: `${operation}: empty response received`,
374
+ code: "EMPTY_RESPONSE",
375
+ details: response
376
+ });
377
+ }
378
+ return response;
379
+ }
380
+ isApiResponse(value) {
381
+ if (!value || typeof value !== "object") {
382
+ return false;
383
+ }
384
+ const response = value;
385
+ return "IsOK" in response && typeof response.IsOK === "boolean" && "Data" in response && "ErrorMessage" in response;
386
+ }
387
+ isSafeApiResponse(value) {
388
+ if (!value || typeof value !== "object") {
389
+ return false;
390
+ }
391
+ const response = value;
392
+ return "success" in response && typeof response.success === "boolean" && "data" in response && "message" in response && "code" in response && typeof response.code === "number";
393
+ }
394
+ requireValue(value, parameterName) {
395
+ const trimmedValue = value == null ? void 0 : value.trim();
396
+ if (!trimmedValue) {
397
+ throw new ValidationError({
398
+ message: `${parameterName} is required.`,
399
+ code: "INVALID_INPUT"
400
+ });
401
+ }
402
+ return trimmedValue;
403
+ }
252
404
  getAuthHeaders() {
253
405
  return {
254
406
  ...this.loginClient.getAuthHeader(),
@@ -275,13 +427,20 @@ var HCSafeCDXClient = class {
275
427
  };
276
428
 
277
429
  // src/errors.ts
278
- var ConfigError = class extends Error {
279
- constructor(message) {
280
- super(message);
281
- this.name = "ConfigError";
282
- }
283
- };
430
+ import {
431
+ APIError as APIError2,
432
+ ConfigError as ConfigError2,
433
+ HCServiceError as HCServiceError2,
434
+ NetworkError,
435
+ ValidationError as ValidationError2,
436
+ errorFromHttpStatus
437
+ } from "@healthcloudai/hc-http";
284
438
  export {
285
- ConfigError,
286
- HCSafeCDXClient
439
+ APIError2 as APIError,
440
+ ConfigError2 as ConfigError,
441
+ HCSafeCDXClient,
442
+ HCServiceError2 as HCServiceError,
443
+ NetworkError,
444
+ ValidationError2 as ValidationError,
445
+ errorFromHttpStatus
287
446
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@healthcloudai/hc-safe-cdx",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Healthcheck Safe CDX connector.",
5
5
  "author": "Healthcheck Systems Inc",
6
6
  "license": "MIT",
@@ -33,8 +33,8 @@
33
33
  "prepublishOnly": "npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@healthcloudai/hc-http": "^0.0.6",
37
- "@healthcloudai/hc-login-connector": "^0.1.0"
36
+ "@healthcloudai/hc-http": "^0.1.0",
37
+ "@healthcloudai/hc-login-connector": "^0.2.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react-native": ">=0.70.0"