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 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.2";
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.2";
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 signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, await sha256(bodyBytes), timestamp, nonce));
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.2",
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.2";
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 timestamp = new Date().toISOString(); const nonce = crypto.randomUUID(); const requestId = crypto.randomUUID();
110
+ const requestId = crypto.randomUUID();
104
111
  const signingPath = new URL(this.baseUrl).pathname.replace(/\/+$/, "") + path;
105
- const signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, await sha256(bodyBytes), timestamp, nonce));
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);