@healthcloudai/hc-safe-cdx 0.2.2 → 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,12 +1,5 @@
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 { HCBaseConnector, APIError, ConfigError, HCServiceError, ValidationError } from "@healthcloudai/hc-http";
10
3
  var ENV_HOST = {
11
4
  dev: "dev-api-hcs.healthcloud-services.com",
12
5
  uat: "uat-api-hcs.healthcloud-services.com",
@@ -16,7 +9,7 @@ var ROUTE_PREFIX = "api/console/hcservice/safecdx";
16
9
  function buildSafeCdxBaseUrl(environment) {
17
10
  const host = ENV_HOST[environment];
18
11
  if (!host) {
19
- throw new SafeCDXError("Invalid Safe CDX environment.");
12
+ throw new ConfigError("Invalid Safe CDX environment.");
20
13
  }
21
14
  return `https://${host}/${ROUTE_PREFIX}`;
22
15
  }
@@ -31,24 +24,12 @@ function buildUrl(baseUrl, route, query) {
31
24
  }
32
25
  return url.toString();
33
26
  }
34
- var HCSafeCDXClient = class {
27
+ var HCSafeCDXClient = class extends HCBaseConnector {
35
28
  constructor(httpClient, loginClient) {
36
- this.http = httpClient;
37
- this.loginClient = loginClient;
29
+ super(httpClient);
30
+ this.auth = loginClient;
38
31
  this.baseUrl = buildSafeCdxBaseUrl(loginClient.getEnvironment());
39
32
  }
40
- setApiKey(headerName, value) {
41
- const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
42
- const trimmedValue = value == null ? void 0 : value.trim();
43
- if (!trimmedHeaderName) {
44
- throw new SafeCDXError("API key header name is required.");
45
- }
46
- if (!trimmedValue) {
47
- throw new SafeCDXError("API key value is required.");
48
- }
49
- this.apiKeyHeaderName = trimmedHeaderName;
50
- this.apiKeyValue = trimmedValue;
51
- }
52
33
  // -------------------------------------------------------------------------
53
34
  // Test Profiles
54
35
  // -------------------------------------------------------------------------
@@ -57,27 +38,32 @@ var HCSafeCDXClient = class {
57
38
  * Resolves the SafeCDX test profile associated with a GTIN barcode.
58
39
  */
59
40
  async getTestProfileByGTIN(gtin) {
60
- return this.http.get(
61
- this.url(`gs1/${encodeURIComponent(gtin)}`),
62
- this.getAuthHeaders()
41
+ const resolvedGtin = this.requireValue(gtin, "GTIN");
42
+ return this.execute(
43
+ "getTestProfileByGTIN",
44
+ () => this.http.get(
45
+ this.url(`gs1/${encodeURIComponent(resolvedGtin)}`),
46
+ this.getAuthHeaders()
47
+ )
63
48
  );
64
49
  }
65
50
  /**
66
- * POST test/profiles/by-account
67
-
68
- *
69
- * TenantId is resolved by the backend from the authenticated patient context.
70
- */
71
- 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) {
72
57
  const request = {
73
- Data: {
74
- IncludeRegisterTestDetails: includeRegisterTestDetails
75
- }
58
+ Data: { IncludeRegisterTestDetails: includeRegisterTestDetails }
76
59
  };
77
- return this.http.post(
78
- this.url("test/profiles/by-account"),
79
- request,
80
- this.getJsonHeaders()
60
+ return this.executeSafe(
61
+ "listTestProfilesByAccount",
62
+ () => this.http.post(
63
+ this.url("test/profiles/by-account"),
64
+ request,
65
+ this.getAuthHeaders()
66
+ )
81
67
  );
82
68
  }
83
69
  // -------------------------------------------------------------------------
@@ -88,34 +74,42 @@ var HCSafeCDXClient = class {
88
74
  * Requests a pre-signed URL used to upload a test image.
89
75
  */
90
76
  async createUploadUrl(userTestResultId, gtin, imageType = "jpg") {
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");
91
80
  const request = {
92
81
  Data: {
93
- Gtin: gtin,
94
- UserTestResultId: userTestResultId,
95
- Metadata: {
96
- ImageType: imageType
97
- }
82
+ Gtin: resolvedGtin,
83
+ UserTestResultId: resolvedUserTestResultId,
84
+ Metadata: { ImageType: resolvedImageType }
98
85
  }
99
86
  };
100
- return this.http.post(
101
- this.url("upload/url"),
102
- request,
103
- this.getJsonHeaders()
87
+ return this.execute(
88
+ "createUploadUrl",
89
+ () => this.http.post(
90
+ this.url("upload/url"),
91
+ request,
92
+ this.getAuthHeaders()
93
+ )
104
94
  );
105
95
  }
106
96
  /**
107
97
  * PUT preSignedURL
108
98
  * Uploads the image directly to the pre-signed storage URL.
109
99
  *
110
- * This request does not call a SafeCDX API route and does not send
111
- * 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.
112
103
  */
113
104
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
114
- const response = await fetch(preSignedURL, {
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
+ }
110
+ const response = await fetch(resolvedPreSignedURL, {
115
111
  method: "PUT",
116
- headers: {
117
- "Content-Type": contentType
118
- },
112
+ headers: { "Content-Type": resolvedContentType },
119
113
  body: image
120
114
  });
121
115
  if (!response.ok) {
@@ -125,9 +119,12 @@ var HCSafeCDXClient = class {
125
119
  } catch {
126
120
  body = void 0;
127
121
  }
128
- throw new SafeCDXError(`Image upload failed with HTTP ${response.status}.`, {
129
- status: response.status,
130
- body
122
+ throw new APIError({
123
+ message: `uploadImage: image upload failed with HTTP ${response.status}.`,
124
+ code: response.status >= 500 ? "SERVER_ERROR" : "UNKNOWN_ERROR",
125
+ statusCode: response.status,
126
+ backendMessage: body,
127
+ details: { status: response.status, body }
131
128
  });
132
129
  }
133
130
  }
@@ -136,138 +133,188 @@ var HCSafeCDXClient = class {
136
133
  // -------------------------------------------------------------------------
137
134
  /**
138
135
  * POST cvml/status
139
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
136
+ * Updates the CVML processing status for an image capture.
140
137
  */
141
138
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
139
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID");
140
+ const resolvedCvmlStatus = this.requireValue(cvmlStatus, "CVML status");
142
141
  const request = {
143
142
  Data: {
144
- ImageOfCaptureId: imageOfCaptureId,
145
- CvmlStatus: cvmlStatus
143
+ ImageOfCaptureId: resolvedImageOfCaptureId,
144
+ CvmlStatus: resolvedCvmlStatus
146
145
  }
147
146
  };
148
- return this.http.post(this.url("cvml/status"), request, this.getJsonHeaders());
147
+ return this.executeSafe(
148
+ "updateCvmlStatus",
149
+ () => this.http.post(this.url("cvml/status"), request, this.getAuthHeaders())
150
+ );
149
151
  }
150
152
  /**
151
153
  * GET cvml/results
152
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
154
+ * Returns the CVML analysis results for an image capture.
153
155
  */
154
156
  async getCvmlResults(imageOfCaptureId) {
155
- return this.http.get(this.url("cvml/results", { imageOfCaptureId }), this.getAuthHeaders());
157
+ const resolvedImageOfCaptureId = this.requireValue(imageOfCaptureId, "Image capture ID");
158
+ return this.executeSafe(
159
+ "getCvmlResults",
160
+ () => this.http.get(
161
+ this.url("cvml/results", { imageOfCaptureId: resolvedImageOfCaptureId }),
162
+ this.getAuthHeaders()
163
+ )
164
+ );
156
165
  }
157
166
  // -------------------------------------------------------------------------
158
167
  // Test Results
159
168
  // -------------------------------------------------------------------------
160
- /** POST test/result/pending */
161
- async getPendingResults(excludeStatus = "Invalid,Canceled") {
169
+ /** POST test/result/pending — returns pending test results excluding the given statuses. */
170
+ async listPendingResults(excludeStatus = "Invalid,Canceled") {
162
171
  const request = {
163
- Data: {
164
- ExcludeStatus: excludeStatus
165
- }
172
+ Data: { ExcludeStatus: excludeStatus }
166
173
  };
167
- return 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())
177
+ );
168
178
  }
169
- /** POST test/result/last */
179
+ /** POST test/result/last — returns the most recent test result. */
170
180
  async getLastResults(excludeStatus = "Invalid") {
171
181
  const request = {
172
- Data: {
173
- ExcludeStatus: excludeStatus
174
- }
182
+ Data: { ExcludeStatus: excludeStatus }
175
183
  };
176
- return this.http.post(this.url("test/result/last"), request, this.getJsonHeaders());
184
+ return this.executeSafe(
185
+ "getLastResults",
186
+ () => this.http.post(this.url("test/result/last"), request, this.getAuthHeaders())
187
+ );
177
188
  }
178
- /** POST test/result/history */
179
- async getTestHistory(excludeStatus = "Invalid") {
189
+ /** POST test/result/history — returns all test results excluding the given statuses. */
190
+ async listTestHistory(excludeStatus = "Invalid") {
180
191
  const request = {
181
- Data: {
182
- ExcludeStatus: excludeStatus
183
- }
192
+ Data: { ExcludeStatus: excludeStatus }
184
193
  };
185
- return 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())
197
+ );
186
198
  }
187
199
  /**
188
200
  * POST test/result/details
189
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
201
+ * Returns full details for a single test result.
190
202
  */
191
203
  async getResultDetails(userTestResultId) {
204
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
192
205
  const request = {
193
- Data: {
194
- UserTestResultId: userTestResultId
195
- }
206
+ Data: { UserTestResultId: resolvedUserTestResultId }
196
207
  };
197
- return this.http.post(this.url("test/result/details"), request, this.getJsonHeaders());
208
+ return this.executeSafe(
209
+ "getResultDetails",
210
+ () => this.http.post(this.url("test/result/details"), request, this.getAuthHeaders())
211
+ );
198
212
  }
199
- /** POST test/result/pdf */
213
+ /** POST test/result/pdf — returns the PDF report for a test result. */
200
214
  async getResultPdf(userTestResultId) {
215
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
201
216
  const request = {
202
- Data: {
203
- UserTestResultId: userTestResultId
204
- }
217
+ Data: { UserTestResultId: resolvedUserTestResultId }
205
218
  };
206
- return this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders());
219
+ return this.executeSafe(
220
+ "getResultPdf",
221
+ () => this.http.post(this.url("test/result/pdf"), request, this.getAuthHeaders())
222
+ );
207
223
  }
208
224
  /**
209
225
  * POST test/result/image/capture
210
- * Returns the direct SafeCDX response without an outer APIResponse wrapper.
226
+ * Returns a URL to capture an image for the given test result.
211
227
  */
212
228
  async getImageCaptureUrl(userTestResultId) {
229
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
213
230
  const request = {
214
- Data: {
215
- UserTestResultId: userTestResultId
216
- }
231
+ Data: { UserTestResultId: resolvedUserTestResultId }
217
232
  };
218
- return this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders());
233
+ return this.executeSafe(
234
+ "getImageCaptureUrl",
235
+ () => this.http.post(this.url("test/result/image/capture"), request, this.getAuthHeaders())
236
+ );
219
237
  }
220
238
  // -------------------------------------------------------------------------
221
239
  // Resume / Answers / Finalize
222
240
  // -------------------------------------------------------------------------
223
241
  /** POST test/resume */
224
242
  async resumeFlow(userTestResultId, resumed = true) {
243
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
225
244
  const request = {
226
- Data: {
227
- UserTestResultId: userTestResultId,
228
- Resumed: resumed
229
- }
245
+ Data: { UserTestResultId: resolvedUserTestResultId, Resumed: resumed }
230
246
  };
231
- return this.http.post(this.url("test/resume"), request, this.getJsonHeaders());
247
+ return this.executeSafe(
248
+ "resumeFlow",
249
+ () => this.http.post(this.url("test/resume"), request, this.getAuthHeaders())
250
+ );
232
251
  }
233
252
  /** POST test/answers */
234
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
+ }
235
262
  const request = {
236
- Data: submission
263
+ Data: { ...submission, UserTestResultId: resolvedId }
237
264
  };
238
- return this.http.post(this.url("test/answers"), request, this.getJsonHeaders());
265
+ return this.executeSafe(
266
+ "submitAnswers",
267
+ () => this.http.post(this.url("test/answers"), request, this.getAuthHeaders())
268
+ );
239
269
  }
240
270
  /** POST test/finalize */
241
271
  async finalizeTest(userTestResultId) {
272
+ const resolvedUserTestResultId = this.requireValue(userTestResultId, "User test result ID");
242
273
  const request = {
243
- Data: {
244
- UserTestResultId: userTestResultId
245
- }
274
+ Data: { UserTestResultId: resolvedUserTestResultId }
246
275
  };
247
- return this.http.post(this.url("test/finalize"), request, this.getJsonHeaders());
276
+ return this.execute(
277
+ "finalizeTest",
278
+ () => this.http.post(this.url("test/finalize"), request, this.getAuthHeaders())
279
+ );
248
280
  }
249
281
  // -------------------------------------------------------------------------
250
282
  // Private Helpers
251
283
  // -------------------------------------------------------------------------
252
- getAuthHeaders() {
253
- return {
254
- ...this.loginClient.getAuthHeader(),
255
- ...this.getApiKeyHeader()
256
- };
284
+ async executeSafe(operation, request) {
285
+ var _a;
286
+ let response;
287
+ try {
288
+ response = await request();
289
+ } catch (err) {
290
+ if (err instanceof APIError) throw err;
291
+ if (err instanceof Error) {
292
+ throw new APIError({ message: `${operation}: ${err.message}`, code: "UNKNOWN_ERROR", details: err });
293
+ }
294
+ throw new APIError({ message: `${operation}: unexpected runtime failure`, code: "UNKNOWN_ERROR", details: err });
295
+ }
296
+ if (response == null) {
297
+ throw new APIError({ message: `${operation}: empty response received`, code: "EMPTY_RESPONSE", details: response });
298
+ }
299
+ if (!this.isSafeApiResponse(response)) {
300
+ throw new APIError({ message: `${operation}: invalid SafeCDX response structure`, code: "INVALID_RESPONSE", details: response });
301
+ }
302
+ if (!response.success) {
303
+ throw new HCServiceError(
304
+ operation,
305
+ (_a = response.message) != null ? _a : "Backend returned success: false",
306
+ response
307
+ );
308
+ }
309
+ return response;
257
310
  }
258
- getJsonHeaders() {
259
- return {
260
- ...this.getAuthHeaders(),
261
- "Content-Type": "application/json"
262
- };
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";
263
315
  }
264
- getApiKeyHeader() {
265
- if (!this.apiKeyHeaderName || !this.apiKeyValue) {
266
- return {};
267
- }
268
- return {
269
- [this.apiKeyHeaderName]: this.apiKeyValue
270
- };
316
+ getAuthHeaders() {
317
+ return { ...this.getHeaders(), ...this.auth.getAuthHeader() };
271
318
  }
272
319
  url(route, query) {
273
320
  return buildUrl(this.baseUrl, route, query);
@@ -275,13 +322,18 @@ var HCSafeCDXClient = class {
275
322
  };
276
323
 
277
324
  // src/errors.ts
278
- var ConfigError = class extends Error {
279
- constructor(message) {
280
- super(message);
281
- this.name = "ConfigError";
282
- }
283
- };
325
+ import {
326
+ APIError as APIError2,
327
+ ConfigError as ConfigError2,
328
+ HCServiceError as HCServiceError2,
329
+ NetworkError,
330
+ ValidationError as ValidationError2
331
+ } from "@healthcloudai/hc-http";
284
332
  export {
285
- ConfigError,
286
- HCSafeCDXClient
333
+ APIError2 as APIError,
334
+ ConfigError2 as ConfigError,
335
+ HCSafeCDXClient,
336
+ HCServiceError2 as HCServiceError,
337
+ NetworkError,
338
+ ValidationError2 as ValidationError
287
339
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@healthcloudai/hc-safe-cdx",
3
- "version": "0.2.2",
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.0.6",
37
- "@healthcloudai/hc-login-connector": "^0.1.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"