@healthcloudai/hc-safe-cdx 0.3.0 → 0.4.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.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, APIError, ConfigError, HCServiceError, ValidationError } 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");
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
- return this.execute(
80
- "getTestProfilesByAccount",
60
+ return this.executeSafe(
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");
78
+ const resolvedGtin = this.requireValue(gtin, "GTIN");
79
+ const resolvedImageType = this.requireValue(imageType, "Image type");
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");
106
+ const resolvedContentType = this.requireValue(contentType, "Content type");
107
+ if (image == null) {
108
+ throw new ValidationError({ message: "Image is required.", code: "INVALID_INPUT" });
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) {
@@ -143,10 +124,7 @@ var HCSafeCDXClient = class {
143
124
  code: response.status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR",
144
125
  statusCode: response.status,
145
126
  backendMessage: body,
146
- details: {
147
- status: response.status,
148
- body
149
- }
127
+ details: { status: response.status, body }
150
128
  });
151
129
  }
152
130
  }
@@ -155,11 +133,11 @@ var HCSafeCDXClient = class {
155
133
  // -------------------------------------------------------------------------
156
134
  /**
157
135
  * POST cvml/status
158
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
136
+ * Updates the CVML processing status for an image capture.
159
137
  */
160
138
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
161
- const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
162
- const resolvedCvmlStatus = this.requireValue(cvmlStatus, "cvmlStatus");
139
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID");
140
+ const resolvedCvmlStatus = this.requireValue(cvmlStatus, "CVML status");
163
141
  const request = {
164
142
  Data: {
165
143
  ImageOfCaptureId: resolvedImageOfCaptureId,
@@ -168,15 +146,15 @@ var HCSafeCDXClient = class {
168
146
  };
169
147
  return this.executeSafe(
170
148
  "updateCvmlStatus",
171
- () => this.http.post(this.url("cvml/status"), request, this.getJsonHeaders())
149
+ () => this.http.post(this.url("cvml/status"), request, this.getAuthHeaders())
172
150
  );
173
151
  }
174
152
  /**
175
153
  * GET cvml/results
176
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
154
+ * Returns the CVML analysis results for an image capture.
177
155
  */
178
156
  async getCvmlResults(imageOfCaptureId) {
179
- const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "imageOfCaptureId");
157
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID");
180
158
  return this.executeSafe(
181
159
  "getCvmlResults",
182
160
  () => this.http.get(
@@ -188,85 +166,73 @@ var HCSafeCDXClient = class {
188
166
  // -------------------------------------------------------------------------
189
167
  // Test Results
190
168
  // -------------------------------------------------------------------------
191
- /** POST test/result/pending */
192
- async getPendingResults(excludeStatus = "Invalid,Canceled") {
169
+ /** POST test/result/pending — returns pending test results excluding the given statuses. */
170
+ async listPendingResults(excludeStatus = "Invalid,Canceled") {
193
171
  const request = {
194
- Data: {
195
- ExcludeStatus: excludeStatus
196
- }
172
+ Data: { ExcludeStatus: excludeStatus }
197
173
  };
198
- return this.execute(
199
- "getPendingResults",
200
- () => this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders())
174
+ return this.executeSafe(
175
+ "listPendingResults",
176
+ () => this.http.post(this.url("test/result/pending"), request, this.getAuthHeaders())
201
177
  );
202
178
  }
203
- /** POST test/result/last */
179
+ /** POST test/result/last — returns the most recent test result. */
204
180
  async getLastResults(excludeStatus = "Invalid") {
205
181
  const request = {
206
- Data: {
207
- ExcludeStatus: excludeStatus
208
- }
182
+ Data: { ExcludeStatus: excludeStatus }
209
183
  };
210
- return this.execute(
184
+ return this.executeSafe(
211
185
  "getLastResults",
212
- () => this.http.post(this.url("test/result/last"), request, this.getJsonHeaders())
186
+ () => this.http.post(this.url("test/result/last"), request, this.getAuthHeaders())
213
187
  );
214
188
  }
215
- /** POST test/result/history */
216
- async getTestHistory(excludeStatus = "Invalid") {
189
+ /** POST test/result/history — returns all test results excluding the given statuses. */
190
+ async listTestHistory(excludeStatus = "Invalid") {
217
191
  const request = {
218
- Data: {
219
- ExcludeStatus: excludeStatus
220
- }
192
+ Data: { ExcludeStatus: excludeStatus }
221
193
  };
222
- return this.execute(
223
- "getTestHistory",
224
- () => this.http.post(this.url("test/result/history"), request, this.getJsonHeaders())
194
+ return this.executeSafe(
195
+ "listTestHistory",
196
+ () => this.http.post(this.url("test/result/history"), request, this.getAuthHeaders())
225
197
  );
226
198
  }
227
199
  /**
228
200
  * POST test/result/details
229
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
201
+ * Returns full details for a single test result.
230
202
  */
231
203
  async getResultDetails(userTestResultId) {
232
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
204
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
233
205
  const request = {
234
- Data: {
235
- UserTestResultId: resolvedUserTestResultId
236
- }
206
+ Data: { UserTestResultId: resolvedUserTestResultId }
237
207
  };
238
208
  return this.executeSafe(
239
209
  "getResultDetails",
240
- () => this.http.post(this.url("test/result/details"), request, this.getJsonHeaders())
210
+ () => this.http.post(this.url("test/result/details"), request, this.getAuthHeaders())
241
211
  );
242
212
  }
243
- /** POST test/result/pdf */
213
+ /** POST test/result/pdf — returns the PDF report for a test result. */
244
214
  async getResultPdf(userTestResultId) {
245
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
215
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
246
216
  const request = {
247
- Data: {
248
- UserTestResultId: resolvedUserTestResultId
249
- }
217
+ Data: { UserTestResultId: resolvedUserTestResultId }
250
218
  };
251
- return this.execute(
219
+ return this.executeSafe(
252
220
  "getResultPdf",
253
- () => this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders())
221
+ () => this.http.post(this.url("test/result/pdf"), request, this.getAuthHeaders())
254
222
  );
255
223
  }
256
224
  /**
257
225
  * POST test/result/image/capture
258
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
226
+ * Returns a URL to capture an image for the given test result.
259
227
  */
260
228
  async getImageCaptureUrl(userTestResultId) {
261
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
229
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
262
230
  const request = {
263
- Data: {
264
- UserTestResultId: resolvedUserTestResultId
265
- }
231
+ Data: { UserTestResultId: resolvedUserTestResultId }
266
232
  };
267
233
  return this.executeSafe(
268
234
  "getImageCaptureUrl",
269
- () => this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders())
235
+ () => this.http.post(this.url("test/result/image/capture"), request, this.getAuthHeaders())
270
236
  );
271
237
  }
272
238
  // -------------------------------------------------------------------------
@@ -274,152 +240,81 @@ var HCSafeCDXClient = class {
274
240
  // -------------------------------------------------------------------------
275
241
  /** POST test/resume */
276
242
  async resumeFlow(userTestResultId, resumed = true) {
277
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
243
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
278
244
  const request = {
279
- Data: {
280
- UserTestResultId: resolvedUserTestResultId,
281
- Resumed: resumed
282
- }
245
+ Data: { UserTestResultId: resolvedUserTestResultId, Resumed: resumed }
283
246
  };
284
- return this.execute(
247
+ return this.executeSafe(
285
248
  "resumeFlow",
286
- () => this.http.post(this.url("test/resume"), request, this.getJsonHeaders())
249
+ () => this.http.post(this.url("test/resume"), request, this.getAuthHeaders())
287
250
  );
288
251
  }
289
252
  /** POST test/answers */
290
253
  async submitAnswers(submission) {
254
+ const resolvedId = this.requireValue(submission == null ? void 0 : submission.UserTestResultId, "User test result ID");
255
+ if (!(submission == null ? void 0 : submission.Result) || submission.Result.length === 0) {
256
+ throw new ValidationError({
257
+ message: "At least one answer result is required.",
258
+ code: "INVALID_INPUT",
259
+ param: "Result"
260
+ });
261
+ }
291
262
  const request = {
292
- Data: submission
263
+ Data: { ...submission, UserTestResultId: resolvedId }
293
264
  };
294
- return this.execute(
265
+ return this.executeSafe(
295
266
  "submitAnswers",
296
- () => this.http.post(this.url("test/answers"), request, this.getJsonHeaders())
267
+ () => this.http.post(this.url("test/answers"), request, this.getAuthHeaders())
297
268
  );
298
269
  }
299
270
  /** POST test/finalize */
300
271
  async finalizeTest(userTestResultId) {
301
- const resolvedUserTestResultId = this.requireValue(userTestResultId, "userTestResultId");
272
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
302
273
  const request = {
303
- Data: {
304
- UserTestResultId: resolvedUserTestResultId
305
- }
274
+ Data: { UserTestResultId: resolvedUserTestResultId }
306
275
  };
307
276
  return this.execute(
308
277
  "finalizeTest",
309
- () => this.http.post(this.url("test/finalize"), request, this.getJsonHeaders())
278
+ () => this.http.post(this.url("test/finalize"), request, this.getAuthHeaders())
310
279
  );
311
280
  }
312
281
  // -------------------------------------------------------------------------
313
282
  // Private Helpers
314
283
  // -------------------------------------------------------------------------
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
284
  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) {
285
+ var _a;
351
286
  let response;
352
287
  try {
353
288
  response = await request();
354
289
  } catch (err) {
355
- if (err instanceof APIError) {
356
- throw err;
357
- }
290
+ if (err instanceof APIError) throw err;
358
291
  if (err instanceof Error) {
359
- throw new APIError({
360
- message: `${operation}: ${err.message}`,
361
- code: "UNKNOWN_ERROR",
362
- details: err
363
- });
292
+ throw new APIError({ message: `${operation}: ${err.message}`, code: "UNKNOWN_ERROR", details: err });
364
293
  }
365
- throw new APIError({
366
- message: `${operation}: unexpected runtime failure`,
367
- code: "UNKNOWN_ERROR",
368
- details: err
369
- });
294
+ throw new APIError({ message: `${operation}: unexpected runtime failure`, code: "UNKNOWN_ERROR", details: err });
370
295
  }
371
296
  if (response == null) {
372
- throw new APIError({
373
- message: `${operation}: empty response received`,
374
- code: "EMPTY_RESPONSE",
375
- details: response
376
- });
297
+ throw new APIError({ message: `${operation}: empty response received`, code: "EMPTY_RESPONSE", details: response });
377
298
  }
378
- return response;
379
- }
380
- isApiResponse(value) {
381
- if (!value || typeof value !== "object") {
382
- return false;
299
+ if (!this.isSafeApiResponse(response)) {
300
+ throw new APIError({ message: `${operation}: invalid SafeCDX response structure`, code: "INVALID_RESPONSE", details: response });
383
301
  }
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;
302
+ if (!response.success) {
303
+ throw new HCServiceError(
304
+ operation,
305
+ (_a = response.message) != null ? _a : "Backend returned success: false",
306
+ response
307
+ );
390
308
  }
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";
309
+ return response;
393
310
  }
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;
311
+ isSafeApiResponse(value) {
312
+ if (!value || typeof value !== "object") return false;
313
+ const r = value;
314
+ return "success" in r && typeof r.success === "boolean" && "data" in r && "message" in r && "code" in r && typeof r.code === "number";
403
315
  }
404
316
  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
- };
317
+ return { ...this.getHeaders(), ...this.auth.getAuthHeader() };
423
318
  }
424
319
  url(route, query) {
425
320
  return buildUrl(this.baseUrl, route, query);
@@ -432,8 +327,7 @@ import {
432
327
  ConfigError as ConfigError2,
433
328
  HCServiceError as HCServiceError2,
434
329
  NetworkError,
435
- ValidationError as ValidationError2,
436
- errorFromHttpStatus
330
+ ValidationError as ValidationError2
437
331
  } from "@healthcloudai/hc-http";
438
332
  export {
439
333
  APIError2 as APIError,
@@ -441,6 +335,5 @@ export {
441
335
  HCSafeCDXClient,
442
336
  HCServiceError2 as HCServiceError,
443
337
  NetworkError,
444
- ValidationError2 as ValidationError,
445
- errorFromHttpStatus
338
+ ValidationError2 as ValidationError
446
339
  };
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.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.1.0",
37
- "@healthcloudai/hc-login-connector": "^0.2.0"
36
+ "@healthcloudai/hc-http": "^0.2.0",
37
+ "@healthcloudai/hc-login-connector": "^0.3.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react-native": ">=0.70.0"