@healthcloudai/hc-safe-cdx 0.3.0 → 0.4.1

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.js CHANGED
@@ -1,10 +1,5 @@
1
1
  // src/client.ts
2
- import {
3
- APIError,
4
- ConfigError,
5
- HCServiceError,
6
- ValidationError
7
- } from "@healthcloudai/hc-http";
2
+ import { HCBaseConnector, ConfigError, ValidationError, errorFromHttpStatus } from "@healthcloudai/hc-http";
8
3
  var ENV_HOST = {
9
4
  dev: "dev-api-hcs.healthcloud-services.com",
10
5
  uat: "uat-api-hcs.healthcloud-services.com",
@@ -29,24 +24,12 @@ function buildUrl(baseUrl, route, query) {
29
24
  }
30
25
  return url.toString();
31
26
  }
32
- var HCSafeCDXClient = class {
27
+ var HCSafeCDXClient = class extends HCBaseConnector {
33
28
  constructor(httpClient, loginClient) {
34
- this.http = httpClient;
35
- this.loginClient = loginClient;
29
+ super(httpClient);
30
+ this.auth = loginClient;
36
31
  this.baseUrl = buildSafeCdxBaseUrl(loginClient.getEnvironment());
37
32
  }
38
- setApiKey(headerName, value) {
39
- const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
40
- const trimmedValue = value == null ? void 0 : value.trim();
41
- if (!trimmedHeaderName) {
42
- throw new ConfigError("API key header name is required.");
43
- }
44
- if (!trimmedValue) {
45
- throw new ConfigError("API key value is required.");
46
- }
47
- this.apiKeyHeaderName = trimmedHeaderName;
48
- this.apiKeyValue = trimmedValue;
49
- }
50
33
  // -------------------------------------------------------------------------
51
34
  // Test Profiles
52
35
  // -------------------------------------------------------------------------
@@ -55,7 +38,7 @@ var HCSafeCDXClient = class {
55
38
  * Resolves the SafeCDX test profile associated with a GTIN barcode.
56
39
  */
57
40
  async getTestProfileByGTIN(gtin) {
58
- const resolvedGtin = this.requireValue(gtin, "gtin");
41
+ const resolvedGtin = this.requireValue(gtin, "GTIN", "gtin");
59
42
  return this.execute(
60
43
  "getTestProfileByGTIN",
61
44
  () => this.http.get(
@@ -65,23 +48,21 @@ var HCSafeCDXClient = class {
65
48
  );
66
49
  }
67
50
  /**
68
- * POST test/profiles/by-account
69
-
70
- *
71
- * TenantId is resolved by the backend from the authenticated patient context.
72
- */
73
- async getTestProfilesByAccount(includeRegisterTestDetails = true) {
51
+ * POST test/profiles/by-account
52
+ *
53
+ * Returns test profiles configured for the authenticated patient's account.
54
+ * TenantId is resolved by the backend from the authenticated patient context.
55
+ */
56
+ async listTestProfilesByAccount(includeRegisterTestDetails = true) {
74
57
  const request = {
75
- Data: {
76
- IncludeRegisterTestDetails: includeRegisterTestDetails
77
- }
58
+ Data: { IncludeRegisterTestDetails: includeRegisterTestDetails }
78
59
  };
79
60
  return this.execute(
80
- "getTestProfilesByAccount",
61
+ "listTestProfilesByAccount",
81
62
  () => this.http.post(
82
63
  this.url("test/profiles/by-account"),
83
64
  request,
84
- this.getJsonHeaders()
65
+ this.getAuthHeaders()
85
66
  )
86
67
  );
87
68
  }
@@ -93,16 +74,14 @@ var HCSafeCDXClient = class {
93
74
  * Requests a pre-signed URL used to upload a test image.
94
75
  */
95
76
  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");
77
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
78
+ const resolvedGtin = this.requireValue(gtin, "GTIN", "gtin");
79
+ const resolvedImageType = this.requireValue(imageType, "Image type", "imageType");
99
80
  const request = {
100
81
  Data: {
101
82
  Gtin: resolvedGtin,
102
83
  UserTestResultId: resolvedUserTestResultId,
103
- Metadata: {
104
- ImageType: resolvedImageType
105
- }
84
+ Metadata: { ImageType: resolvedImageType }
106
85
  }
107
86
  };
108
87
  return this.execute(
@@ -110,7 +89,7 @@ var HCSafeCDXClient = class {
110
89
  () => this.http.post(
111
90
  this.url("upload/url"),
112
91
  request,
113
- this.getJsonHeaders()
92
+ this.getAuthHeaders()
114
93
  )
115
94
  );
116
95
  }
@@ -118,17 +97,19 @@ var HCSafeCDXClient = class {
118
97
  * PUT preSignedURL
119
98
  * Uploads the image directly to the pre-signed storage URL.
120
99
  *
121
- * This request does not call a SafeCDX API route and does not send
122
- * authenticated Health Cloud headers.
100
+ * This request does NOT call a SafeCDX API route and does NOT send
101
+ * Health Cloud auth headers — it targets the storage provider directly.
102
+ * The pre-signed URL embeds credentials; no Authorization header is needed.
123
103
  */
124
104
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
125
- const resolvedPreSignedURL = this.requireValue(preSignedURL, "preSignedURL");
126
- const resolvedContentType = this.requireValue(contentType, "contentType");
105
+ const resolvedPreSignedURL = this.requireValue(preSignedURL, "Pre-signed URL", "preSignedURL");
106
+ const resolvedContentType = this.requireValue(contentType, "Content type", "contentType");
107
+ if (image == null) {
108
+ throw new ValidationError({ message: "Image is required.", code: "INVALID_INPUT", param: "image" });
109
+ }
127
110
  const response = await fetch(resolvedPreSignedURL, {
128
111
  method: "PUT",
129
- headers: {
130
- "Content-Type": resolvedContentType
131
- },
112
+ headers: { "Content-Type": resolvedContentType },
132
113
  body: image
133
114
  });
134
115
  if (!response.ok) {
@@ -138,15 +119,10 @@ var HCSafeCDXClient = class {
138
119
  } catch {
139
120
  body = void 0;
140
121
  }
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,
122
+ throw errorFromHttpStatus(response.status, {
123
+ operationName: "uploadImage",
145
124
  backendMessage: body,
146
- details: {
147
- status: response.status,
148
- body
149
- }
125
+ details: { status: response.status, body }
150
126
  });
151
127
  }
152
128
  }
@@ -155,29 +131,29 @@ var HCSafeCDXClient = class {
155
131
  // -------------------------------------------------------------------------
156
132
  /**
157
133
  * POST cvml/status
158
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
134
+ * Updates the CVML processing status for an image capture.
159
135
  */
160
136
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
161
- const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
162
- const resolvedCvmlStatus = this.requireValue(cvmlStatus, "cvmlStatus");
137
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID", "imageOfCaptureId");
138
+ const resolvedCvmlStatus = this.requireValue(cvmlStatus, "CVML status", "cvmlStatus");
163
139
  const request = {
164
140
  Data: {
165
141
  ImageOfCaptureId: resolvedImageOfCaptureId,
166
142
  CvmlStatus: resolvedCvmlStatus
167
143
  }
168
144
  };
169
- return this.executeSafe(
145
+ return this.execute(
170
146
  "updateCvmlStatus",
171
- () => this.http.post(this.url("cvml/status"), request, this.getJsonHeaders())
147
+ () => this.http.post(this.url("cvml/status"), request, this.getAuthHeaders())
172
148
  );
173
149
  }
174
150
  /**
175
151
  * GET cvml/results
176
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
152
+ * Returns the CVML analysis results for an image capture.
177
153
  */
178
154
  async getCvmlResults(imageOfCaptureId) {
179
- const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
180
- return this.executeSafe(
155
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID", "imageOfCaptureId");
156
+ return this.execute(
181
157
  "getCvmlResults",
182
158
  () => this.http.get(
183
159
  this.url("cvml/results", { imageOfCaptureId: resolvedImageOfCaptureId }),
@@ -188,85 +164,73 @@ var HCSafeCDXClient = class {
188
164
  // -------------------------------------------------------------------------
189
165
  // Test Results
190
166
  // -------------------------------------------------------------------------
191
- /** POST test/result/pending */
192
- async getPendingResults(excludeStatus = "Invalid,Canceled") {
167
+ /** POST test/result/pending — returns pending test results excluding the given statuses. */
168
+ async listPendingResults(excludeStatus = "Invalid,Canceled") {
193
169
  const request = {
194
- Data: {
195
- ExcludeStatus: excludeStatus
196
- }
170
+ Data: { ExcludeStatus: excludeStatus }
197
171
  };
198
172
  return this.execute(
199
- "getPendingResults",
200
- () => this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders())
173
+ "listPendingResults",
174
+ () => this.http.post(this.url("test/result/pending"), request, this.getAuthHeaders())
201
175
  );
202
176
  }
203
- /** POST test/result/last */
177
+ /** POST test/result/last — returns the most recent test result. */
204
178
  async getLastResults(excludeStatus = "Invalid") {
205
179
  const request = {
206
- Data: {
207
- ExcludeStatus: excludeStatus
208
- }
180
+ Data: { ExcludeStatus: excludeStatus }
209
181
  };
210
182
  return this.execute(
211
183
  "getLastResults",
212
- () => this.http.post(this.url("test/result/last"), request, this.getJsonHeaders())
184
+ () => this.http.post(this.url("test/result/last"), request, this.getAuthHeaders())
213
185
  );
214
186
  }
215
- /** POST test/result/history */
216
- async getTestHistory(excludeStatus = "Invalid") {
187
+ /** POST test/result/history — returns all test results excluding the given statuses. */
188
+ async listTestHistory(excludeStatus = "Invalid") {
217
189
  const request = {
218
- Data: {
219
- ExcludeStatus: excludeStatus
220
- }
190
+ Data: { ExcludeStatus: excludeStatus }
221
191
  };
222
192
  return this.execute(
223
- "getTestHistory",
224
- () => this.http.post(this.url("test/result/history"), request, this.getJsonHeaders())
193
+ "listTestHistory",
194
+ () => this.http.post(this.url("test/result/history"), request, this.getAuthHeaders())
225
195
  );
226
196
  }
227
197
  /**
228
198
  * POST test/result/details
229
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
199
+ * Returns full details for a single test result.
230
200
  */
231
201
  async getResultDetails(userTestResultId) {
232
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
202
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
233
203
  const request = {
234
- Data: {
235
- UserTestResultId: resolvedUserTestResultId
236
- }
204
+ Data: { UserTestResultId: resolvedUserTestResultId }
237
205
  };
238
- return this.executeSafe(
206
+ return this.execute(
239
207
  "getResultDetails",
240
- () => this.http.post(this.url("test/result/details"), request, this.getJsonHeaders())
208
+ () => this.http.post(this.url("test/result/details"), request, this.getAuthHeaders())
241
209
  );
242
210
  }
243
- /** POST test/result/pdf */
211
+ /** POST test/result/pdf — returns the PDF report for a test result. */
244
212
  async getResultPdf(userTestResultId) {
245
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
213
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
246
214
  const request = {
247
- Data: {
248
- UserTestResultId: resolvedUserTestResultId
249
- }
215
+ Data: { UserTestResultId: resolvedUserTestResultId }
250
216
  };
251
217
  return this.execute(
252
218
  "getResultPdf",
253
- () => this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders())
219
+ () => this.http.post(this.url("test/result/pdf"), request, this.getAuthHeaders())
254
220
  );
255
221
  }
256
222
  /**
257
223
  * POST test/result/image/capture
258
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
224
+ * Returns a URL to capture an image for the given test result.
259
225
  */
260
226
  async getImageCaptureUrl(userTestResultId) {
261
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
227
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
262
228
  const request = {
263
- Data: {
264
- UserTestResultId: resolvedUserTestResultId
265
- }
229
+ Data: { UserTestResultId: resolvedUserTestResultId }
266
230
  };
267
- return this.executeSafe(
231
+ return this.execute(
268
232
  "getImageCaptureUrl",
269
- () => this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders())
233
+ () => this.http.post(this.url("test/result/image/capture"), request, this.getAuthHeaders())
270
234
  );
271
235
  }
272
236
  // -------------------------------------------------------------------------
@@ -274,152 +238,49 @@ var HCSafeCDXClient = class {
274
238
  // -------------------------------------------------------------------------
275
239
  /** POST test/resume */
276
240
  async resumeFlow(userTestResultId, resumed = true) {
277
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
241
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
278
242
  const request = {
279
- Data: {
280
- UserTestResultId: resolvedUserTestResultId,
281
- Resumed: resumed
282
- }
243
+ Data: { UserTestResultId: resolvedUserTestResultId, Resumed: resumed }
283
244
  };
284
245
  return this.execute(
285
246
  "resumeFlow",
286
- () => this.http.post(this.url("test/resume"), request, this.getJsonHeaders())
247
+ () => this.http.post(this.url("test/resume"), request, this.getAuthHeaders())
287
248
  );
288
249
  }
289
250
  /** POST test/answers */
290
251
  async submitAnswers(submission) {
252
+ const resolvedId = this.requireValue(submission == null ? void 0 : submission.UserTestResultId, "User test result ID", "UserTestResultId");
253
+ if (!(submission == null ? void 0 : submission.Result) || submission.Result.length === 0) {
254
+ throw new ValidationError({
255
+ message: "At least one answer result is required.",
256
+ code: "INVALID_INPUT",
257
+ param: "Result"
258
+ });
259
+ }
291
260
  const request = {
292
- Data: submission
261
+ Data: { ...submission, UserTestResultId: resolvedId }
293
262
  };
294
263
  return this.execute(
295
264
  "submitAnswers",
296
- () => this.http.post(this.url("test/answers"), request, this.getJsonHeaders())
265
+ () => this.http.post(this.url("test/answers"), request, this.getAuthHeaders())
297
266
  );
298
267
  }
299
268
  /** POST test/finalize */
300
269
  async finalizeTest(userTestResultId) {
301
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
270
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID", "userTestResultId");
302
271
  const request = {
303
- Data: {
304
- UserTestResultId: resolvedUserTestResultId
305
- }
272
+ Data: { UserTestResultId: resolvedUserTestResultId }
306
273
  };
307
274
  return this.execute(
308
275
  "finalizeTest",
309
- () => this.http.post(this.url("test/finalize"), request, this.getJsonHeaders())
276
+ () => this.http.post(this.url("test/finalize"), request, this.getAuthHeaders())
310
277
  );
311
278
  }
312
279
  // -------------------------------------------------------------------------
313
280
  // Private Helpers
314
281
  // -------------------------------------------------------------------------
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
- }
404
282
  getAuthHeaders() {
405
- return {
406
- ...this.loginClient.getAuthHeader(),
407
- ...this.getApiKeyHeader()
408
- };
409
- }
410
- getJsonHeaders() {
411
- return {
412
- ...this.getAuthHeaders(),
413
- "Content-Type": "application/json"
414
- };
415
- }
416
- getApiKeyHeader() {
417
- if (!this.apiKeyHeaderName || !this.apiKeyValue) {
418
- return {};
419
- }
420
- return {
421
- [this.apiKeyHeaderName]: this.apiKeyValue
422
- };
283
+ return { ...this.getHeaders(), ...this.auth.getAuthHeader() };
423
284
  }
424
285
  url(route, query) {
425
286
  return buildUrl(this.baseUrl, route, query);
@@ -428,19 +289,17 @@ var HCSafeCDXClient = class {
428
289
 
429
290
  // src/errors.ts
430
291
  import {
431
- APIError as APIError2,
292
+ APIError,
432
293
  ConfigError as ConfigError2,
433
- HCServiceError as HCServiceError2,
294
+ HCServiceError,
434
295
  NetworkError,
435
- ValidationError as ValidationError2,
436
- errorFromHttpStatus
296
+ ValidationError as ValidationError2
437
297
  } from "@healthcloudai/hc-http";
438
298
  export {
439
- APIError2 as APIError,
299
+ APIError,
440
300
  ConfigError2 as ConfigError,
441
301
  HCSafeCDXClient,
442
- HCServiceError2 as HCServiceError,
302
+ HCServiceError,
443
303
  NetworkError,
444
- ValidationError2 as ValidationError,
445
- errorFromHttpStatus
304
+ ValidationError2 as ValidationError
446
305
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@healthcloudai/hc-safe-cdx",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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.1.0",
37
- "@healthcloudai/hc-login-connector": "^0.2.0"
36
+ "@healthcloudai/hc-http": "^0.2.1",
37
+ "@healthcloudai/hc-login-connector": "^0.3.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react-native": ">=0.70.0"