@healthcloudai/hc-safe-cdx 0.0.1 → 0.1.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
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
6
10
  var __copyProps = (to, from, except, desc) => {
7
11
  if (from && typeof from === "object" || typeof from === "function") {
8
12
  for (let key of __getOwnPropNames(from))
@@ -15,4 +19,386 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
15
19
 
16
20
  // src/index.ts
17
21
  var index_exports = {};
22
+ __export(index_exports, {
23
+ ConfigError: () => ConfigError,
24
+ HCSafeCDXClient: () => HCSafeCDXClient
25
+ });
18
26
  module.exports = __toCommonJS(index_exports);
27
+
28
+ // 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
+ };
37
+ var ENV_HOST = {
38
+ dev: "dev-api-hcs.healthcloud-services.com",
39
+ uat: "uat-api-hcs.healthcloud-services.com",
40
+ prod: "api-hcs.healthcloud-services.com"
41
+ };
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
+ );
47
+ if (query) {
48
+ for (const [key, value] of Object.entries(query)) {
49
+ if (value !== void 0 && value !== "") {
50
+ url.searchParams.set(key, value);
51
+ }
52
+ }
53
+ }
54
+ return url.toString();
55
+ }
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
+ var HCSafeCDXClient = class {
70
+ constructor(httpClient, config) {
71
+ var _a;
72
+ if (!ENV_HOST[config.environment]) {
73
+ throw new SafeCDXError(`Invalid environment: ${config.environment}`);
74
+ }
75
+ this.http = httpClient;
76
+ this.host = ENV_HOST[config.environment];
77
+ this.defaultLanguage = config.defaultLanguage;
78
+ this.defaultImageType = (_a = config.defaultImageType) != null ? _a : "jpg";
79
+ }
80
+ setAccessToken(token) {
81
+ const trimmedToken = token == null ? void 0 : token.trim();
82
+ if (!trimmedToken) {
83
+ throw new SafeCDXError("Access token is required.");
84
+ }
85
+ this.accessToken = trimmedToken;
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Private helpers
89
+ // ---------------------------------------------------------------------------
90
+ getAuthorizationHeader() {
91
+ if (!this.accessToken) {
92
+ throw new SafeCDXError(
93
+ "SafeCDX requires an access token. Call setAccessToken() first."
94
+ );
95
+ }
96
+ const token = this.accessToken.replace(/^Bearer\s+/i, "").trim();
97
+ return `Bearer ${token}`;
98
+ }
99
+ getAuthHeaders() {
100
+ return {
101
+ Authorization: this.getAuthorizationHeader()
102
+ };
103
+ }
104
+ getJsonAuthHeaders() {
105
+ return {
106
+ Authorization: this.getAuthorizationHeader(),
107
+ "Content-Type": "application/json"
108
+ };
109
+ }
110
+ url(route, query) {
111
+ return buildUrl(this.host, route, query);
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // Scan
115
+ // ---------------------------------------------------------------------------
116
+ /**
117
+ * GET gs1/:gtin
118
+ * Resolves a test profile by GTIN barcode and creates a UserTestResultId.
119
+ */
120
+ async getTestProfileByGTIN(gtin, language) {
121
+ const envelope = await this.http.get(
122
+ this.url(`gs1/${encodeURIComponent(gtin)}`, {
123
+ language: language != null ? language : this.defaultLanguage
124
+ }),
125
+ this.getAuthHeaders()
126
+ );
127
+ return assertApiResponse(envelope, "gs1/:gtin");
128
+ }
129
+ /**
130
+ * POST scan/2ddm
131
+ * Scans a 2D data matrix barcode and resolves a UserTestResultId.
132
+ */
133
+ async scan2Ddm(payload) {
134
+ const envelope = await this.http.post(
135
+ this.url("scan/2ddm"),
136
+ wrapData(payload),
137
+ this.getJsonAuthHeaders()
138
+ );
139
+ return assertApiResponse(envelope, "scan/2ddm");
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // Test Profiles
143
+ // ---------------------------------------------------------------------------
144
+ /**
145
+ * POST test/profile
146
+ * Returns the full test profile for a given GTIN.
147
+ */
148
+ async getTestProfile(payload) {
149
+ const envelope = await this.http.post(
150
+ this.url("test/profile"),
151
+ wrapData(payload),
152
+ this.getJsonAuthHeaders()
153
+ );
154
+ return assertApiResponse(envelope, "test/profile");
155
+ }
156
+ /**
157
+ * POST test/profiles/by-account
158
+ * Lists all test profiles available to the authenticated account.
159
+ */
160
+ async getTestProfilesByAccount(payload = {
161
+ IncludeRegisterTestDetails: true
162
+ }) {
163
+ const envelope = await this.http.post(
164
+ this.url("test/profiles/by-account"),
165
+ wrapData(payload),
166
+ this.getJsonAuthHeaders()
167
+ );
168
+ return assertApiResponse(envelope, "test/profiles/by-account");
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Upload
172
+ // ---------------------------------------------------------------------------
173
+ /**
174
+ * POST upload/url
175
+ * Requests a pre-signed S3 URL for image upload.
176
+ * Use the returned Data.preSignedURL and Data.Metadata.UploadId in the next steps.
177
+ */
178
+ async createUploadUrl(userTestResultId, gtin, imageType) {
179
+ const payload = {
180
+ Gtin: gtin,
181
+ UserTestResultId: userTestResultId,
182
+ Metadata: {
183
+ ImageType: imageType != null ? imageType : this.defaultImageType
184
+ }
185
+ };
186
+ const envelope = await this.http.post(
187
+ this.url("upload/url"),
188
+ wrapData(payload),
189
+ this.getJsonAuthHeaders()
190
+ );
191
+ return assertApiResponse(envelope, "upload/url");
192
+ }
193
+ /**
194
+ * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
195
+ * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
196
+
197
+ */
198
+ async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
199
+ const response = await fetch(preSignedURL, {
200
+ method: "PUT",
201
+ headers: {
202
+ "Content-Type": contentType
203
+ },
204
+ body: image
205
+ });
206
+ if (!response.ok) {
207
+ let body;
208
+ try {
209
+ body = (await response.text()).slice(0, 500);
210
+ } catch {
211
+ }
212
+ throw new SafeCDXError(
213
+ `Image upload failed with HTTP ${response.status}.`,
214
+ {
215
+ status: response.status,
216
+ body
217
+ }
218
+ );
219
+ }
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // CVML
223
+ // ---------------------------------------------------------------------------
224
+ /**
225
+ * POST cvml/status
226
+ * Updates the ML processing status for a captured image.
227
+ * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
228
+ */
229
+ async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
230
+ const payload = {
231
+ ImageOfCaptureId: imageOfCaptureId,
232
+ CvmlStatus: cvmlStatus
233
+ };
234
+ return this.http.post(
235
+ this.url("cvml/status"),
236
+ wrapData(payload),
237
+ this.getJsonAuthHeaders()
238
+ );
239
+ }
240
+ /**
241
+ * GET cvml/results
242
+ * Polls ML analysis results for a captured image.
243
+ * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
244
+ */
245
+ async getCvmlResults(imageOfCaptureId) {
246
+ return this.http.get(
247
+ this.url("cvml/results", {
248
+ imageOfCaptureId
249
+ }),
250
+ this.getAuthHeaders()
251
+ );
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Test Results
255
+ // ---------------------------------------------------------------------------
256
+ /**
257
+ * POST test/result/pending
258
+ * Returns test results in a pending/incomplete state.
259
+ */
260
+ async getPendingResults(payload = {
261
+ ExcludeStatus: "Started"
262
+ }) {
263
+ const envelope = await this.http.post(
264
+ this.url("test/result/pending"),
265
+ wrapData(payload),
266
+ this.getJsonAuthHeaders()
267
+ );
268
+ return assertApiResponse(envelope, "test/result/pending");
269
+ }
270
+ /**
271
+ * POST test/result/last
272
+ * Returns the most recent test result for the authenticated patient.
273
+ */
274
+ async getLastResults(payload = {}) {
275
+ const envelope = await this.http.post(
276
+ this.url("test/result/last"),
277
+ wrapData(payload),
278
+ this.getJsonAuthHeaders()
279
+ );
280
+ return assertApiResponse(envelope, "test/result/last");
281
+ }
282
+ /**
283
+ * POST test/result/history
284
+ * Returns the full test history for the authenticated patient.
285
+ */
286
+ async getTestHistory(payload = {}) {
287
+ const envelope = await this.http.post(
288
+ this.url("test/result/history"),
289
+ wrapData(payload),
290
+ this.getJsonAuthHeaders()
291
+ );
292
+ return assertApiResponse(envelope, "test/result/history");
293
+ }
294
+ /**
295
+ * POST test/result/details
296
+ * Returns detailed result data for a specific test attempt.
297
+ * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
298
+ */
299
+ async getResultDetails(userTestResultId) {
300
+ const payload = {
301
+ UserTestResultId: userTestResultId
302
+ };
303
+ return this.http.post(
304
+ this.url("test/result/details"),
305
+ wrapData(payload),
306
+ this.getJsonAuthHeaders()
307
+ );
308
+ }
309
+ /**
310
+ * POST test/result/pdf
311
+ * Generates a PDF report for a specific test result.
312
+ */
313
+ async getResultPdf(payload) {
314
+ const envelope = await this.http.post(
315
+ this.url("test/result/pdf"),
316
+ wrapData(payload),
317
+ this.getJsonAuthHeaders()
318
+ );
319
+ return assertApiResponse(envelope, "test/result/pdf");
320
+ }
321
+ /**
322
+ * POST test/result/image/capture
323
+ * Returns the image capture URL for a specific test result.
324
+ * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
325
+ */
326
+ async getImageCaptureUrl(userTestResultId) {
327
+ const payload = {
328
+ UserTestResultId: userTestResultId
329
+ };
330
+ return this.http.post(
331
+ this.url("test/result/image/capture"),
332
+ wrapData(payload),
333
+ this.getJsonAuthHeaders()
334
+ );
335
+ }
336
+ /**
337
+ * POST test/result/analytics
338
+ * Returns analytics data for test results.
339
+ */
340
+ async getAnalytics(payload = {}) {
341
+ const envelope = await this.http.post(
342
+ this.url("test/result/analytics"),
343
+ wrapData(payload),
344
+ this.getJsonAuthHeaders()
345
+ );
346
+ return assertApiResponse(envelope, "test/result/analytics");
347
+ }
348
+ // ---------------------------------------------------------------------------
349
+ // Resume / Answers / Finalize
350
+ // ---------------------------------------------------------------------------
351
+ /**
352
+ * POST test/resume
353
+ * Marks a test attempt as resumed (or not).
354
+ */
355
+ async resumeFlow(userTestResultId, resumed = true) {
356
+ const payload = {
357
+ UserTestResultId: userTestResultId,
358
+ Resumed: resumed
359
+ };
360
+ const envelope = await this.http.post(
361
+ this.url("test/resume"),
362
+ wrapData(payload),
363
+ this.getJsonAuthHeaders()
364
+ );
365
+ return assertApiResponse(envelope, "test/resume");
366
+ }
367
+ /**
368
+ * POST test/answers
369
+ * Submits analyte answers for a test attempt.
370
+ */
371
+ async submitAnswers(payload) {
372
+ const envelope = await this.http.post(
373
+ this.url("test/answers"),
374
+ wrapData(payload),
375
+ this.getJsonAuthHeaders()
376
+ );
377
+ return assertApiResponse(envelope, "test/answers");
378
+ }
379
+ /**
380
+ * POST test/finalize
381
+ * Finalizes a test attempt with CTA, indication, and decision results.
382
+ */
383
+ async finalizeTest(payload) {
384
+ const envelope = await this.http.post(
385
+ this.url("test/finalize"),
386
+ wrapData(payload),
387
+ this.getJsonAuthHeaders()
388
+ );
389
+ return assertApiResponse(envelope, "test/finalize");
390
+ }
391
+ };
392
+
393
+ // src/errors.ts
394
+ var ConfigError = class extends Error {
395
+ constructor(message) {
396
+ super(message);
397
+ this.name = "ConfigError";
398
+ }
399
+ };
400
+ // Annotate the CommonJS export names for ESM import in node:
401
+ 0 && (module.exports = {
402
+ ConfigError,
403
+ HCSafeCDXClient
404
+ });