saferx-pharma-sdk 2.0.2 → 2.0.5

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.0.5";
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,11 @@
1
- export const SDK_VERSION = "2.0.2";
1
+ export const SDK_VERSION = "2.0.5";
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_capabilities: { operationId: 'enterprise_capabilities', method: 'GET', path: '/capabilities', idempotencyRequired: false },
8
9
  enterprise_erx_safety_check: { operationId: 'enterprise_erx_safety_check', method: 'POST', path: '/erx/safety-checks', idempotencyRequired: true },
9
10
  enterprise_ocr_prescription_create: { operationId: 'enterprise_ocr_prescription_create', method: 'POST', path: '/ocr/prescriptions', idempotencyRequired: true },
10
11
  enterprise_ocr_prescription_events: { operationId: 'enterprise_ocr_prescription_events', method: 'GET', path: '/ocr/prescriptions/{prescription_id}/events', idempotencyRequired: false },
@@ -19,6 +20,7 @@ export const OPERATIONS = {
19
20
  enterprise_registry_search: { operationId: 'enterprise_registry_search', method: 'GET', path: '/registry/search', idempotencyRequired: false },
20
21
  enterprise_safety_capabilities: { operationId: 'enterprise_safety_capabilities', method: 'GET', path: '/safety/capabilities', idempotencyRequired: false },
21
22
  enterprise_safety_check: { operationId: 'enterprise_safety_check', method: 'POST', path: '/safety/checks', idempotencyRequired: true },
23
+ enterprise_status: { operationId: 'enterprise_status', method: 'GET', path: '/status', idempotencyRequired: false },
22
24
  };
23
25
  export class SafeRxProblemDetailsError extends Error {
24
26
  status;
@@ -129,18 +131,21 @@ export class SafeRxClient {
129
131
  sendBody = undefined;
130
132
  }
131
133
  const query = canonicalQuery(options.query);
132
- const timestamp = new Date().toISOString();
133
- const nonce = crypto.randomUUID();
134
134
  const requestId = crypto.randomUUID();
135
135
  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;
136
+ const bodyHash = await sha256(bodyBytes);
142
137
  const target = `${this.baseUrl.replace(/\/+$/, "")}${path}${query ? `?${query}` : ""}`;
143
138
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
139
+ // Every attempt is signed afresh: a reused nonce is rejected as a replay (401
140
+ // API_KEY_NONCE_REJECTED), so a retry must carry a new timestamp, nonce and signature.
141
+ const timestamp = new Date().toISOString();
142
+ const nonce = crypto.randomUUID();
143
+ const signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, bodyHash, timestamp, nonce));
144
+ 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 };
145
+ if (contentType !== undefined)
146
+ headers["Content-Type"] = contentType;
147
+ if (options.idempotencyKey)
148
+ headers["Idempotency-Key"] = options.idempotencyKey;
144
149
  try {
145
150
  const response = await this.fetchImpl(target, { method: operation.method, headers, body: sendBody, signal: options.signal });
146
151
  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.0.5",
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,14 @@ 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.0.5";
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_capabilities: { operationId: 'enterprise_capabilities', method: 'GET', path: '/capabilities', idempotencyRequired: false },
14
15
  enterprise_erx_safety_check: { operationId: 'enterprise_erx_safety_check', method: 'POST', path: '/erx/safety-checks', idempotencyRequired: true },
15
16
  enterprise_ocr_prescription_create: { operationId: 'enterprise_ocr_prescription_create', method: 'POST', path: '/ocr/prescriptions', idempotencyRequired: true },
16
17
  enterprise_ocr_prescription_events: { operationId: 'enterprise_ocr_prescription_events', method: 'GET', path: '/ocr/prescriptions/{prescription_id}/events', idempotencyRequired: false },
@@ -25,6 +26,7 @@ export const OPERATIONS: Record<string, EnterpriseOperation> = {
25
26
  enterprise_registry_search: { operationId: 'enterprise_registry_search', method: 'GET', path: '/registry/search', idempotencyRequired: false },
26
27
  enterprise_safety_capabilities: { operationId: 'enterprise_safety_capabilities', method: 'GET', path: '/safety/capabilities', idempotencyRequired: false },
27
28
  enterprise_safety_check: { operationId: 'enterprise_safety_check', method: 'POST', path: '/safety/checks', idempotencyRequired: true },
29
+ enterprise_status: { operationId: 'enterprise_status', method: 'GET', path: '/status', idempotencyRequired: false },
28
30
  };
29
31
 
30
32
  export type MultipartFile = { filename: string; content: Uint8Array; contentType?: string };
@@ -100,13 +102,17 @@ export class SafeRxClient {
100
102
  bodyBytes = bytes(""); sendBody = undefined;
101
103
  }
102
104
  const query = canonicalQuery(options.query);
103
- const timestamp = new Date().toISOString(); const nonce = crypto.randomUUID(); const requestId = crypto.randomUUID();
105
+ const requestId = crypto.randomUUID();
104
106
  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;
107
+ const bodyHash = await sha256(bodyBytes);
108
108
  const target = `${this.baseUrl.replace(/\/+$/, "")}${path}${query ? `?${query}` : ""}`;
109
109
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
110
+ // Every attempt is signed afresh: a reused nonce is rejected as a replay (401
111
+ // API_KEY_NONCE_REJECTED), so a retry must carry a new timestamp, nonce and signature.
112
+ const timestamp = new Date().toISOString(); const nonce = crypto.randomUUID();
113
+ const signature = await hmac(this.apiKey, canonicalRequest(operation.method, signingPath, query, bodyHash, timestamp, nonce));
114
+ 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 };
115
+ if (contentType !== undefined) headers["Content-Type"] = contentType; if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
110
116
  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
117
  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
118
  throw new SafeRxProblemDetailsError(response.status, (parsed ?? { status: response.status, title: "REQUEST_FAILED" }) as JsonObject, requestId);