saferx-pharma-sdk 2.0.2 → 2.1.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/client.d.ts +1 -1
- package/dist/client.js +19 -9
- package/package.json +2 -2
- package/src/client.ts +16 -5
package/dist/client.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type EnterpriseOperation = {
|
|
|
5
5
|
path: string;
|
|
6
6
|
idempotencyRequired: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare const SDK_VERSION = "2.0
|
|
8
|
+
export declare const SDK_VERSION = "2.1.0";
|
|
9
9
|
export declare const CLIENT_KIND = "typescript_sdk";
|
|
10
10
|
export declare const SDK_LANGUAGE = "typescript";
|
|
11
11
|
export declare const MAX_ATTEMPTS = 3;
|
package/dist/client.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
export const SDK_VERSION = "2.0
|
|
1
|
+
export const SDK_VERSION = "2.1.0";
|
|
2
2
|
export const CLIENT_KIND = "typescript_sdk";
|
|
3
3
|
export const SDK_LANGUAGE = "typescript";
|
|
4
4
|
export const MAX_ATTEMPTS = 3;
|
|
5
5
|
export const RETRY_BACKOFF_BASE_SECONDS = 0.1;
|
|
6
6
|
export const RETRY_BACKOFF_MAX_SECONDS = 2.0;
|
|
7
7
|
export const OPERATIONS = {
|
|
8
|
+
enterprise_allergy_families: { operationId: 'enterprise_allergy_families', method: 'GET', path: '/allergies/families', idempotencyRequired: false },
|
|
9
|
+
enterprise_allergy_resolve: { operationId: 'enterprise_allergy_resolve', method: 'POST', path: '/allergies/resolve', idempotencyRequired: false },
|
|
10
|
+
enterprise_allergy_substances: { operationId: 'enterprise_allergy_substances', method: 'GET', path: '/allergies/substances', idempotencyRequired: false },
|
|
11
|
+
enterprise_capabilities: { operationId: 'enterprise_capabilities', method: 'GET', path: '/capabilities', idempotencyRequired: false },
|
|
8
12
|
enterprise_erx_safety_check: { operationId: 'enterprise_erx_safety_check', method: 'POST', path: '/erx/safety-checks', idempotencyRequired: true },
|
|
9
13
|
enterprise_ocr_prescription_create: { operationId: 'enterprise_ocr_prescription_create', method: 'POST', path: '/ocr/prescriptions', idempotencyRequired: true },
|
|
10
14
|
enterprise_ocr_prescription_events: { operationId: 'enterprise_ocr_prescription_events', method: 'GET', path: '/ocr/prescriptions/{prescription_id}/events', idempotencyRequired: false },
|
|
@@ -19,6 +23,9 @@ export const OPERATIONS = {
|
|
|
19
23
|
enterprise_registry_search: { operationId: 'enterprise_registry_search', method: 'GET', path: '/registry/search', idempotencyRequired: false },
|
|
20
24
|
enterprise_safety_capabilities: { operationId: 'enterprise_safety_capabilities', method: 'GET', path: '/safety/capabilities', idempotencyRequired: false },
|
|
21
25
|
enterprise_safety_check: { operationId: 'enterprise_safety_check', method: 'POST', path: '/safety/checks', idempotencyRequired: true },
|
|
26
|
+
enterprise_safety_check_batch: { operationId: 'enterprise_safety_check_batch', method: 'POST', path: '/safety/check-batches', idempotencyRequired: true },
|
|
27
|
+
enterprise_safety_check_read: { operationId: 'enterprise_safety_check_read', method: 'GET', path: '/safety/checks/{check_id}', idempotencyRequired: false },
|
|
28
|
+
enterprise_status: { operationId: 'enterprise_status', method: 'GET', path: '/status', idempotencyRequired: false },
|
|
22
29
|
};
|
|
23
30
|
export class SafeRxProblemDetailsError extends Error {
|
|
24
31
|
status;
|
|
@@ -129,18 +136,21 @@ export class SafeRxClient {
|
|
|
129
136
|
sendBody = undefined;
|
|
130
137
|
}
|
|
131
138
|
const query = canonicalQuery(options.query);
|
|
132
|
-
const timestamp = new Date().toISOString();
|
|
133
|
-
const nonce = crypto.randomUUID();
|
|
134
139
|
const requestId = crypto.randomUUID();
|
|
135
140
|
const signingPath = new URL(this.baseUrl).pathname.replace(/\/+$/, "") + path;
|
|
136
|
-
const
|
|
137
|
-
const headers = { Accept: "application/json", "X-SafeRx-API-Key": this.apiKey, "X-SafeRx-Timestamp": timestamp, "X-SafeRx-Nonce": nonce, "X-SafeRx-Signature": signature, "X-Request-ID": requestId, "X-SafeRx-Client-Kind": CLIENT_KIND, "X-SafeRx-Client-Name": this.clientName, "X-SafeRx-Client-Version": SDK_VERSION, "X-SafeRx-SDK-Language": SDK_LANGUAGE, "X-SafeRx-SDK-Version": SDK_VERSION };
|
|
138
|
-
if (contentType !== undefined)
|
|
139
|
-
headers["Content-Type"] = contentType;
|
|
140
|
-
if (options.idempotencyKey)
|
|
141
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
141
|
+
const bodyHash = await sha256(bodyBytes);
|
|
142
142
|
const target = `${this.baseUrl.replace(/\/+$/, "")}${path}${query ? `?${query}` : ""}`;
|
|
143
143
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
144
|
+
// Every attempt is signed afresh: a reused nonce is rejected as a replay (401
|
|
145
|
+
// API_KEY_NONCE_REJECTED), so a retry must carry a new timestamp, nonce and signature.
|
|
146
|
+
const timestamp = new Date().toISOString();
|
|
147
|
+
const nonce = crypto.randomUUID();
|
|
148
|
+
const signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, bodyHash, timestamp, nonce));
|
|
149
|
+
const headers = { Accept: "application/json", "X-SafeRx-API-Key": this.apiKey, "X-SafeRx-Timestamp": timestamp, "X-SafeRx-Nonce": nonce, "X-SafeRx-Signature": signature, "X-Request-ID": requestId, "X-SafeRx-Client-Kind": CLIENT_KIND, "X-SafeRx-Client-Name": this.clientName, "X-SafeRx-Client-Version": SDK_VERSION, "X-SafeRx-SDK-Language": SDK_LANGUAGE, "X-SafeRx-SDK-Version": SDK_VERSION };
|
|
150
|
+
if (contentType !== undefined)
|
|
151
|
+
headers["Content-Type"] = contentType;
|
|
152
|
+
if (options.idempotencyKey)
|
|
153
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
144
154
|
try {
|
|
145
155
|
const response = await this.fetchImpl(target, { method: operation.method, headers, body: sendBody, signal: options.signal });
|
|
146
156
|
const raw = await response.text();
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saferx-pharma-sdk",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Signed TypeScript client for the SafeRx Enterprise API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.saferx.online",
|
|
7
|
-
"repository": {"type": "git", "url": "https://github.com/KaRamHelal/SafeRx_Pharma"},
|
|
7
|
+
"repository": {"type": "git", "url": "git+https://github.com/KaRamHelal/SafeRx_Pharma.git"},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {".": "./dist/client.js"},
|
|
10
10
|
"files": ["dist", "src", "README.md"],
|
package/src/client.ts
CHANGED
|
@@ -4,13 +4,17 @@ export type EnterpriseOperation = {
|
|
|
4
4
|
operationId: string; method: string; path: string; idempotencyRequired: boolean;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
export const SDK_VERSION = "2.0
|
|
7
|
+
export const SDK_VERSION = "2.1.0";
|
|
8
8
|
export const CLIENT_KIND = "typescript_sdk";
|
|
9
9
|
export const SDK_LANGUAGE = "typescript";
|
|
10
10
|
export const MAX_ATTEMPTS = 3;
|
|
11
11
|
export const RETRY_BACKOFF_BASE_SECONDS = 0.1;
|
|
12
12
|
export const RETRY_BACKOFF_MAX_SECONDS = 2.0;
|
|
13
13
|
export const OPERATIONS: Record<string, EnterpriseOperation> = {
|
|
14
|
+
enterprise_allergy_families: { operationId: 'enterprise_allergy_families', method: 'GET', path: '/allergies/families', idempotencyRequired: false },
|
|
15
|
+
enterprise_allergy_resolve: { operationId: 'enterprise_allergy_resolve', method: 'POST', path: '/allergies/resolve', idempotencyRequired: false },
|
|
16
|
+
enterprise_allergy_substances: { operationId: 'enterprise_allergy_substances', method: 'GET', path: '/allergies/substances', idempotencyRequired: false },
|
|
17
|
+
enterprise_capabilities: { operationId: 'enterprise_capabilities', method: 'GET', path: '/capabilities', idempotencyRequired: false },
|
|
14
18
|
enterprise_erx_safety_check: { operationId: 'enterprise_erx_safety_check', method: 'POST', path: '/erx/safety-checks', idempotencyRequired: true },
|
|
15
19
|
enterprise_ocr_prescription_create: { operationId: 'enterprise_ocr_prescription_create', method: 'POST', path: '/ocr/prescriptions', idempotencyRequired: true },
|
|
16
20
|
enterprise_ocr_prescription_events: { operationId: 'enterprise_ocr_prescription_events', method: 'GET', path: '/ocr/prescriptions/{prescription_id}/events', idempotencyRequired: false },
|
|
@@ -25,6 +29,9 @@ export const OPERATIONS: Record<string, EnterpriseOperation> = {
|
|
|
25
29
|
enterprise_registry_search: { operationId: 'enterprise_registry_search', method: 'GET', path: '/registry/search', idempotencyRequired: false },
|
|
26
30
|
enterprise_safety_capabilities: { operationId: 'enterprise_safety_capabilities', method: 'GET', path: '/safety/capabilities', idempotencyRequired: false },
|
|
27
31
|
enterprise_safety_check: { operationId: 'enterprise_safety_check', method: 'POST', path: '/safety/checks', idempotencyRequired: true },
|
|
32
|
+
enterprise_safety_check_batch: { operationId: 'enterprise_safety_check_batch', method: 'POST', path: '/safety/check-batches', idempotencyRequired: true },
|
|
33
|
+
enterprise_safety_check_read: { operationId: 'enterprise_safety_check_read', method: 'GET', path: '/safety/checks/{check_id}', idempotencyRequired: false },
|
|
34
|
+
enterprise_status: { operationId: 'enterprise_status', method: 'GET', path: '/status', idempotencyRequired: false },
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
export type MultipartFile = { filename: string; content: Uint8Array; contentType?: string };
|
|
@@ -100,13 +107,17 @@ export class SafeRxClient {
|
|
|
100
107
|
bodyBytes = bytes(""); sendBody = undefined;
|
|
101
108
|
}
|
|
102
109
|
const query = canonicalQuery(options.query);
|
|
103
|
-
const
|
|
110
|
+
const requestId = crypto.randomUUID();
|
|
104
111
|
const signingPath = new URL(this.baseUrl).pathname.replace(/\/+$/, "") + path;
|
|
105
|
-
const
|
|
106
|
-
const headers: Record<string, string> = { Accept: "application/json", "X-SafeRx-API-Key": this.apiKey, "X-SafeRx-Timestamp": timestamp, "X-SafeRx-Nonce": nonce, "X-SafeRx-Signature": signature, "X-Request-ID": requestId, "X-SafeRx-Client-Kind": CLIENT_KIND, "X-SafeRx-Client-Name": this.clientName, "X-SafeRx-Client-Version": SDK_VERSION, "X-SafeRx-SDK-Language": SDK_LANGUAGE, "X-SafeRx-SDK-Version": SDK_VERSION };
|
|
107
|
-
if (contentType !== undefined) headers["Content-Type"] = contentType; if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
112
|
+
const bodyHash = await sha256(bodyBytes);
|
|
108
113
|
const target = `${this.baseUrl.replace(/\/+$/, "")}${path}${query ? `?${query}` : ""}`;
|
|
109
114
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
115
|
+
// Every attempt is signed afresh: a reused nonce is rejected as a replay (401
|
|
116
|
+
// API_KEY_NONCE_REJECTED), so a retry must carry a new timestamp, nonce and signature.
|
|
117
|
+
const timestamp = new Date().toISOString(); const nonce = crypto.randomUUID();
|
|
118
|
+
const signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, bodyHash, timestamp, nonce));
|
|
119
|
+
const headers: Record<string, string> = { Accept: "application/json", "X-SafeRx-API-Key": this.apiKey, "X-SafeRx-Timestamp": timestamp, "X-SafeRx-Nonce": nonce, "X-SafeRx-Signature": signature, "X-Request-ID": requestId, "X-SafeRx-Client-Kind": CLIENT_KIND, "X-SafeRx-Client-Name": this.clientName, "X-SafeRx-Client-Version": SDK_VERSION, "X-SafeRx-SDK-Language": SDK_LANGUAGE, "X-SafeRx-SDK-Version": SDK_VERSION };
|
|
120
|
+
if (contentType !== undefined) headers["Content-Type"] = contentType; if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
110
121
|
try { const response = await this.fetchImpl(target, { method: operation.method, headers, body: sendBody as BodyInit | undefined, signal: options.signal }); const raw = await response.text(); const parsed = raw ? JSON.parse(raw) : undefined;
|
|
111
122
|
if (response.ok) return parsed; if ([429, 502, 503, 504].includes(response.status) && attempt + 1 < MAX_ATTEMPTS) { await new Promise((resolve) => setTimeout(resolve, retryDelaySeconds(response.headers.get("Retry-After"), attempt) * 1000)); continue; }
|
|
112
123
|
throw new SafeRxProblemDetailsError(response.status, (parsed ?? { status: response.status, title: "REQUEST_FAILED" }) as JsonObject, requestId);
|