@healthcloudai/hc-safe-cdx 0.1.4 → 0.2.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.cjs CHANGED
@@ -40,10 +40,15 @@ var ENV_HOST = {
40
40
  prod: "api-hcs.healthcloud-services.com"
41
41
  };
42
42
  var ROUTE_PREFIX = "api/console/hcservice/safecdx";
43
- function buildUrl(host, route, query) {
44
- const url = new URL(
45
- `https://${host}/${ROUTE_PREFIX}/${route.replace(/^\/+/, "")}`
46
- );
43
+ function buildSafeCdxBaseUrl(environment) {
44
+ const host = ENV_HOST[environment];
45
+ if (!host) {
46
+ throw new SafeCDXError("Invalid Safe CDX environment.");
47
+ }
48
+ return `https://${host}/${ROUTE_PREFIX}`;
49
+ }
50
+ function buildUrl(baseUrl, route, query) {
51
+ const url = new URL(`${baseUrl}/${route.replace(/^\/+/, "")}`);
47
52
  if (query) {
48
53
  for (const [key, value] of Object.entries(query)) {
49
54
  if (value !== void 0 && value !== "") {
@@ -53,30 +58,11 @@ function buildUrl(host, route, query) {
53
58
  }
54
59
  return url.toString();
55
60
  }
56
- function wrapData(payload) {
57
- return { Data: payload };
58
- }
59
- function assertApiResponse(envelope, route) {
60
- var _a;
61
- if (!envelope.IsOK) {
62
- throw new SafeCDXError(
63
- (_a = envelope.ErrorMessage) != null ? _a : `${route} returned IsOK=false.`,
64
- envelope
65
- );
66
- }
67
- return envelope;
68
- }
69
61
  var HCSafeCDXClient = class {
70
- constructor(httpClient, authClient, config) {
71
- var _a;
72
- if (!ENV_HOST[config.environment]) {
73
- throw new SafeCDXError(`Invalid environment: ${config.environment}`);
74
- }
62
+ constructor(httpClient, loginClient) {
75
63
  this.http = httpClient;
76
- this.auth = authClient;
77
- this.host = ENV_HOST[config.environment];
78
- this.defaultLanguage = config.defaultLanguage;
79
- this.defaultImageType = (_a = config.defaultImageType) != null ? _a : "jpg";
64
+ this.loginClient = loginClient;
65
+ this.baseUrl = buildSafeCdxBaseUrl(loginClient.getEnvironment());
80
66
  }
81
67
  setApiKey(headerName, value) {
82
68
  const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
@@ -90,106 +76,66 @@ var HCSafeCDXClient = class {
90
76
  this.apiKeyHeaderName = trimmedHeaderName;
91
77
  this.apiKeyValue = trimmedValue;
92
78
  }
93
- // ---------------------------------------------------------------------------
94
- // Private helpers
95
- // ---------------------------------------------------------------------------
96
- getAuthHeaders() {
97
- return {
98
- ...this.auth.getAuthHeader(),
99
- ...this.getApiKeyHeader()
100
- };
101
- }
102
- getJsonHeaders() {
103
- return {
104
- ...this.getAuthHeaders(),
105
- "Content-Type": "application/json"
106
- };
107
- }
108
- getApiKeyHeader() {
109
- if (!this.apiKeyHeaderName || !this.apiKeyValue) {
110
- return {};
111
- }
112
- return {
113
- [this.apiKeyHeaderName]: this.apiKeyValue
114
- };
115
- }
116
- url(route, query) {
117
- return buildUrl(this.host, route, query);
118
- }
119
- // ---------------------------------------------------------------------------
120
- // Scan
121
- // ---------------------------------------------------------------------------
79
+ // -------------------------------------------------------------------------
80
+ // Test Profiles
81
+ // -------------------------------------------------------------------------
122
82
  /**
123
83
  * GET gs1/:gtin
124
- * Resolves a test profile by GTIN barcode and creates a UserTestResultId.
84
+ * Resolves the SafeCDX test profile associated with a GTIN barcode.
125
85
  */
126
- async getTestProfileByGTIN(gtin, language) {
127
- const envelope = await this.http.get(
128
- this.url(`gs1/${encodeURIComponent(gtin)}`, {
129
- language: language != null ? language : this.defaultLanguage
130
- }),
86
+ async getTestProfileByGTIN(gtin) {
87
+ return this.http.get(
88
+ this.url(`gs1/${encodeURIComponent(gtin)}`),
131
89
  this.getAuthHeaders()
132
90
  );
133
- return assertApiResponse(envelope, "gs1/:gtin");
134
- }
135
- // ---------------------------------------------------------------------------
136
- // Test Profiles
137
- // ---------------------------------------------------------------------------
138
- /**
139
- * POST test/profile
140
- * Returns the full test profile for a given GTIN.
141
- */
142
- async getTestProfile(payload) {
143
- const envelope = await this.http.post(
144
- this.url("test/profile"),
145
- wrapData(payload),
146
- this.getJsonHeaders()
147
- );
148
- return assertApiResponse(envelope, "test/profile");
149
91
  }
150
92
  /**
151
93
  * POST test/profiles/by-account
152
- * Lists all test profiles available to the authenticated account.
94
+ * Lists test profiles available to the authenticated patient account.
95
+ *
96
+ * TenantId is resolved by the backend from the authenticated patient context.
153
97
  */
154
- async getTestProfilesByAccount(payload = {
155
- IncludeRegisterTestDetails: true
156
- }) {
157
- const envelope = await this.http.post(
98
+ async getTestProfilesByAccount(includeRegisterTestDetails = true) {
99
+ const request = {
100
+ Data: {
101
+ IncludeRegisterTestDetails: includeRegisterTestDetails
102
+ }
103
+ };
104
+ return this.http.post(
158
105
  this.url("test/profiles/by-account"),
159
- wrapData(payload),
106
+ request,
160
107
  this.getJsonHeaders()
161
108
  );
162
- return assertApiResponse(envelope, "test/profiles/by-account");
163
109
  }
164
- // ---------------------------------------------------------------------------
165
- // Upload
166
- // ---------------------------------------------------------------------------
110
+ // -------------------------------------------------------------------------
111
+ // Image Upload
112
+ // -------------------------------------------------------------------------
167
113
  /**
168
114
  * POST upload/url
169
- * Requests a pre-signed S3 URL for image upload.
170
- * Use the returned Data.preSignedURL and Data.Metadata.UploadId in the next steps.
115
+ * Requests a pre-signed URL used to upload a test image.
171
116
  */
172
- async createUploadUrl(userTestResultId, gtin, imageType) {
173
- const payload = {
174
- Gtin: gtin,
175
- UserTestResultId: userTestResultId,
176
- Metadata: {
177
- ImageType: imageType != null ? imageType : this.defaultImageType
117
+ async createUploadUrl(userTestResultId, gtin, imageType = "jpg") {
118
+ const request = {
119
+ Data: {
120
+ Gtin: gtin,
121
+ UserTestResultId: userTestResultId,
122
+ Metadata: {
123
+ ImageType: imageType
124
+ }
178
125
  }
179
126
  };
180
- const envelope = await this.http.post(
127
+ return this.http.post(
181
128
  this.url("upload/url"),
182
- wrapData(payload),
129
+ request,
183
130
  this.getJsonHeaders()
184
131
  );
185
- return assertApiResponse(envelope, "upload/url");
186
132
  }
187
133
  /**
188
- * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
189
- * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
134
+ * PUT preSignedURL
135
+ * Uploads the image directly to the pre-signed storage URL.
190
136
  *
191
- * This intentionally does not use the shared HttpClient because the existing
192
- * HttpClient.put() is JSON-oriented and stringifies the request body.
137
+ * This request does not call a SafeCDX API route and does not send
138
+ * authenticated Health Cloud headers.
193
139
  */
194
140
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
195
141
  const response = await fetch(preSignedURL, {
@@ -204,185 +150,154 @@ var HCSafeCDXClient = class {
204
150
  try {
205
151
  body = (await response.text()).slice(0, 500);
206
152
  } catch {
153
+ body = void 0;
207
154
  }
208
- throw new SafeCDXError(
209
- `Image upload failed with HTTP ${response.status}.`,
210
- {
211
- status: response.status,
212
- body
213
- }
214
- );
155
+ throw new SafeCDXError(`Image upload failed with HTTP ${response.status}.`, {
156
+ status: response.status,
157
+ body
158
+ });
215
159
  }
216
160
  }
217
- // ---------------------------------------------------------------------------
161
+ // -------------------------------------------------------------------------
218
162
  // CVML
219
- // ---------------------------------------------------------------------------
163
+ // -------------------------------------------------------------------------
220
164
  /**
221
165
  * POST cvml/status
222
- * Updates the ML processing status for a captured image.
223
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
166
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
224
167
  */
225
168
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
226
- const payload = {
227
- ImageOfCaptureId: imageOfCaptureId,
228
- CvmlStatus: cvmlStatus
169
+ const request = {
170
+ Data: {
171
+ ImageOfCaptureId: imageOfCaptureId,
172
+ CvmlStatus: cvmlStatus
173
+ }
229
174
  };
230
- return this.http.post(
231
- this.url("cvml/status"),
232
- wrapData(payload),
233
- this.getJsonHeaders()
234
- );
175
+ return this.http.post(this.url("cvml/status"), request, this.getJsonHeaders());
235
176
  }
236
177
  /**
237
178
  * GET cvml/results
238
- * Polls ML analysis results for a captured image.
239
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
179
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
240
180
  */
241
181
  async getCvmlResults(imageOfCaptureId) {
242
- return this.http.get(
243
- this.url("cvml/results", {
244
- imageOfCaptureId
245
- }),
246
- this.getAuthHeaders()
247
- );
182
+ return this.http.get(this.url("cvml/results", { imageOfCaptureId }), this.getAuthHeaders());
248
183
  }
249
- // ---------------------------------------------------------------------------
184
+ // -------------------------------------------------------------------------
250
185
  // Test Results
251
- // ---------------------------------------------------------------------------
252
- /**
253
- * POST test/result/pending
254
- * Returns test results in a pending/incomplete state.
255
- */
256
- async getPendingResults(payload = {
257
- ExcludeStatus: "Started"
258
- }) {
259
- const envelope = await this.http.post(
260
- this.url("test/result/pending"),
261
- wrapData(payload),
262
- this.getJsonHeaders()
263
- );
264
- return assertApiResponse(envelope, "test/result/pending");
186
+ // -------------------------------------------------------------------------
187
+ /** POST test/result/pending */
188
+ async getPendingResults(excludeStatus = "Invalid,Canceled") {
189
+ const request = {
190
+ Data: {
191
+ ExcludeStatus: excludeStatus
192
+ }
193
+ };
194
+ return this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders());
265
195
  }
266
- /**
267
- * POST test/result/last
268
- * Returns the most recent test result for the authenticated patient.
269
- */
270
- async getLastResults(payload = {}) {
271
- const envelope = await this.http.post(
272
- this.url("test/result/last"),
273
- wrapData(payload),
274
- this.getJsonHeaders()
275
- );
276
- return assertApiResponse(envelope, "test/result/last");
196
+ /** POST test/result/last */
197
+ async getLastResults(excludeStatus = "Invalid") {
198
+ const request = {
199
+ Data: {
200
+ ExcludeStatus: excludeStatus
201
+ }
202
+ };
203
+ return this.http.post(this.url("test/result/last"), request, this.getJsonHeaders());
277
204
  }
278
- /**
279
- * POST test/result/history
280
- * Returns the full test history for the authenticated patient.
281
- */
282
- async getTestHistory(payload = {}) {
283
- const envelope = await this.http.post(
284
- this.url("test/result/history"),
285
- wrapData(payload),
286
- this.getJsonHeaders()
287
- );
288
- return assertApiResponse(envelope, "test/result/history");
205
+ /** POST test/result/history */
206
+ async getTestHistory(excludeStatus = "Invalid") {
207
+ const request = {
208
+ Data: {
209
+ ExcludeStatus: excludeStatus
210
+ }
211
+ };
212
+ return this.http.post(this.url("test/result/history"), request, this.getJsonHeaders());
289
213
  }
290
214
  /**
291
215
  * POST test/result/details
292
- * Returns detailed result data for a specific test attempt.
293
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
216
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
294
217
  */
295
218
  async getResultDetails(userTestResultId) {
296
- const payload = {
297
- UserTestResultId: userTestResultId
219
+ const request = {
220
+ Data: {
221
+ UserTestResultId: userTestResultId
222
+ }
298
223
  };
299
- return this.http.post(
300
- this.url("test/result/details"),
301
- wrapData(payload),
302
- this.getJsonHeaders()
303
- );
224
+ return this.http.post(this.url("test/result/details"), request, this.getJsonHeaders());
304
225
  }
305
- /**
306
- * POST test/result/pdf
307
- * Generates a PDF report for a specific test result.
308
- */
309
- async getResultPdf(payload) {
310
- const envelope = await this.http.post(
311
- this.url("test/result/pdf"),
312
- wrapData(payload),
313
- this.getJsonHeaders()
314
- );
315
- return assertApiResponse(envelope, "test/result/pdf");
226
+ /** POST test/result/pdf */
227
+ async getResultPdf(userTestResultId) {
228
+ const request = {
229
+ Data: {
230
+ UserTestResultId: userTestResultId
231
+ }
232
+ };
233
+ return this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders());
316
234
  }
317
235
  /**
318
236
  * POST test/result/image/capture
319
- * Returns the image capture URL for a specific test result.
320
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
237
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
321
238
  */
322
239
  async getImageCaptureUrl(userTestResultId) {
323
- const payload = {
324
- UserTestResultId: userTestResultId
240
+ const request = {
241
+ Data: {
242
+ UserTestResultId: userTestResultId
243
+ }
325
244
  };
326
- return this.http.post(
327
- this.url("test/result/image/capture"),
328
- wrapData(payload),
329
- this.getJsonHeaders()
330
- );
245
+ return this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders());
331
246
  }
332
- /**
333
- * POST test/result/analytics
334
- * Returns analytics data for test results.
335
- */
336
- async getAnalytics(payload = {}) {
337
- const envelope = await this.http.post(
338
- this.url("test/result/analytics"),
339
- wrapData(payload),
340
- this.getJsonHeaders()
341
- );
342
- return assertApiResponse(envelope, "test/result/analytics");
343
- }
344
- // ---------------------------------------------------------------------------
247
+ // -------------------------------------------------------------------------
345
248
  // Resume / Answers / Finalize
346
- // ---------------------------------------------------------------------------
347
- /**
348
- * POST test/resume
349
- * Marks a test attempt as resumed (or not).
350
- */
249
+ // -------------------------------------------------------------------------
250
+ /** POST test/resume */
351
251
  async resumeFlow(userTestResultId, resumed = true) {
352
- const payload = {
353
- UserTestResultId: userTestResultId,
354
- Resumed: resumed
252
+ const request = {
253
+ Data: {
254
+ UserTestResultId: userTestResultId,
255
+ Resumed: resumed
256
+ }
355
257
  };
356
- const envelope = await this.http.post(
357
- this.url("test/resume"),
358
- wrapData(payload),
359
- this.getJsonHeaders()
360
- );
361
- return assertApiResponse(envelope, "test/resume");
258
+ return this.http.post(this.url("test/resume"), request, this.getJsonHeaders());
362
259
  }
363
- /**
364
- * POST test/answers
365
- * Submits analyte answers for a test attempt.
366
- */
367
- async submitAnswers(payload) {
368
- const envelope = await this.http.post(
369
- this.url("test/answers"),
370
- wrapData(payload),
371
- this.getJsonHeaders()
372
- );
373
- return assertApiResponse(envelope, "test/answers");
260
+ /** POST test/answers */
261
+ async submitAnswers(submission) {
262
+ const request = {
263
+ Data: submission
264
+ };
265
+ return this.http.post(this.url("test/answers"), request, this.getJsonHeaders());
374
266
  }
375
- /**
376
- * POST test/finalize
377
- * Finalizes a test attempt with CTA, indication, and decision results.
378
- */
379
- async finalizeTest(payload) {
380
- const envelope = await this.http.post(
381
- this.url("test/finalize"),
382
- wrapData(payload),
383
- this.getJsonHeaders()
384
- );
385
- return assertApiResponse(envelope, "test/finalize");
267
+ /** POST test/finalize */
268
+ async finalizeTest(userTestResultId) {
269
+ const request = {
270
+ Data: {
271
+ UserTestResultId: userTestResultId
272
+ }
273
+ };
274
+ return this.http.post(this.url("test/finalize"), request, this.getJsonHeaders());
275
+ }
276
+ // -------------------------------------------------------------------------
277
+ // Private Helpers
278
+ // -------------------------------------------------------------------------
279
+ getAuthHeaders() {
280
+ return {
281
+ ...this.loginClient.getAuthHeader(),
282
+ ...this.getApiKeyHeader()
283
+ };
284
+ }
285
+ getJsonHeaders() {
286
+ return {
287
+ ...this.getAuthHeaders(),
288
+ "Content-Type": "application/json"
289
+ };
290
+ }
291
+ getApiKeyHeader() {
292
+ if (!this.apiKeyHeaderName || !this.apiKeyValue) {
293
+ return {};
294
+ }
295
+ return {
296
+ [this.apiKeyHeaderName]: this.apiKeyValue
297
+ };
298
+ }
299
+ url(route, query) {
300
+ return buildUrl(this.baseUrl, route, query);
386
301
  }
387
302
  };
388
303