@ythalorossy/openfda 1.0.13 → 1.0.14
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/package.json +1 -1
- package/bin/OpenFDABuilder.js +0 -49
- package/bin/OpenFDAClient.js +0 -178
- package/bin/types.js +0 -5
package/package.json
CHANGED
package/bin/OpenFDABuilder.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2025 Ythalo Saldanha
|
|
3
|
-
* Licensed under the MIT License
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* The OpenFDABuilder class helps construct URLs for the OpenFDA API.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* - Set the context (such as 'label', 'ndc', or 'event') using the context() method.
|
|
10
|
-
* - Set the search query using the search() method.
|
|
11
|
-
* - Optionally set the result limit using the limit() method (default is 1).
|
|
12
|
-
* - Call build() to assemble and return the final API URL.
|
|
13
|
-
*
|
|
14
|
-
* Example:
|
|
15
|
-
* const url = new OpenFDABuilder()
|
|
16
|
-
* .context('label')
|
|
17
|
-
* .search('openfda.brand_name:"Advil"')
|
|
18
|
-
* .limit(1)
|
|
19
|
-
* .build();
|
|
20
|
-
*
|
|
21
|
-
* The build() method will throw an error if any required parameter is missing.
|
|
22
|
-
* The API key is read from the OPENFDA_API_KEY environment variable.
|
|
23
|
-
*/
|
|
24
|
-
export class OpenFDABuilder {
|
|
25
|
-
url = "https://api.fda.gov/drug/";
|
|
26
|
-
params = new Map();
|
|
27
|
-
context(context) {
|
|
28
|
-
this.params.set("context", context);
|
|
29
|
-
return this;
|
|
30
|
-
}
|
|
31
|
-
search(query) {
|
|
32
|
-
this.params.set("search", query);
|
|
33
|
-
return this;
|
|
34
|
-
}
|
|
35
|
-
limit(max = 1) {
|
|
36
|
-
this.params.set("limit", max);
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
build() {
|
|
40
|
-
const context = this.params.get("context");
|
|
41
|
-
const search = this.params.get("search");
|
|
42
|
-
const limit = this.params.get("limit");
|
|
43
|
-
const apiKey = process.env.OPENFDA_API_KEY;
|
|
44
|
-
if (!context || !search || !limit) {
|
|
45
|
-
throw new Error("Missing required parameters: context, search, or limit");
|
|
46
|
-
}
|
|
47
|
-
return `${this.url}${context}.json?api_key=${apiKey}&search=${search}&limit=${limit}`;
|
|
48
|
-
}
|
|
49
|
-
}
|
package/bin/OpenFDAClient.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2025 Ythalo Saldanha
|
|
3
|
-
* Licensed under the MIT License
|
|
4
|
-
*/
|
|
5
|
-
const DEFAULT_CONFIG = {
|
|
6
|
-
maxRetries: 3,
|
|
7
|
-
retryDelay: 1000, // 1 second
|
|
8
|
-
timeout: 30000, // 30 seconds
|
|
9
|
-
};
|
|
10
|
-
// Helper function to determine if error is retryable
|
|
11
|
-
function isRetryableError(error) {
|
|
12
|
-
// Network errors, timeouts, and 5xx server errors are retryable
|
|
13
|
-
if (error.name === "TypeError" && error.message.includes("fetch"))
|
|
14
|
-
return true;
|
|
15
|
-
if (error.name === "AbortError")
|
|
16
|
-
return true;
|
|
17
|
-
if (error.status >= 500 && error.status <= 599)
|
|
18
|
-
return true;
|
|
19
|
-
if (error.status === 429)
|
|
20
|
-
return true; // Rate limit
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
// Sleep utility for retry delays
|
|
24
|
-
function sleep(ms) {
|
|
25
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
|
-
}
|
|
27
|
-
// Enhanced OpenFDA request function
|
|
28
|
-
async function makeOpenFDARequest(url, config = {}) {
|
|
29
|
-
const { maxRetries, retryDelay, timeout } = { ...DEFAULT_CONFIG, ...config };
|
|
30
|
-
const headers = {
|
|
31
|
-
"User-Agent": "@ythalorossy/openfda",
|
|
32
|
-
Accept: "application/json",
|
|
33
|
-
};
|
|
34
|
-
let lastError = null;
|
|
35
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
36
|
-
try {
|
|
37
|
-
// Create abort controller for timeout handling
|
|
38
|
-
const controller = new AbortController();
|
|
39
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
40
|
-
console.log(`Making OpenFDA request (attempt ${attempt + 1}/${maxRetries + 1}): ${url}`);
|
|
41
|
-
const response = await fetch(url, {
|
|
42
|
-
headers,
|
|
43
|
-
signal: controller.signal,
|
|
44
|
-
});
|
|
45
|
-
clearTimeout(timeoutId);
|
|
46
|
-
// Handle HTTP errors with OpenFDA-specific context
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
const errorText = await response
|
|
49
|
-
.text()
|
|
50
|
-
.catch(() => "Unable to read error response");
|
|
51
|
-
const httpError = {
|
|
52
|
-
type: "http",
|
|
53
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
54
|
-
status: response.status,
|
|
55
|
-
details: errorText,
|
|
56
|
-
};
|
|
57
|
-
console.error(`OpenFDA HTTP Error (${response.status}):`, {
|
|
58
|
-
url,
|
|
59
|
-
status: response.status,
|
|
60
|
-
statusText: response.statusText,
|
|
61
|
-
errorText: errorText.substring(0, 200), // Truncate long error messages
|
|
62
|
-
});
|
|
63
|
-
// OpenFDA-specific status code handling
|
|
64
|
-
switch (response.status) {
|
|
65
|
-
case 400:
|
|
66
|
-
httpError.message = `Bad Request: Invalid search query or parameters`;
|
|
67
|
-
break;
|
|
68
|
-
case 401:
|
|
69
|
-
httpError.message = `Unauthorized: Invalid or missing API key`;
|
|
70
|
-
break;
|
|
71
|
-
case 403:
|
|
72
|
-
httpError.message = `Forbidden: API key may be invalid or quota exceeded`;
|
|
73
|
-
break;
|
|
74
|
-
case 404:
|
|
75
|
-
httpError.message = `Not Found: No results found for the specified query`;
|
|
76
|
-
break;
|
|
77
|
-
case 429:
|
|
78
|
-
httpError.message = `Rate Limited: Too many requests. Retrying...`;
|
|
79
|
-
break;
|
|
80
|
-
case 500:
|
|
81
|
-
httpError.message = `Server Error: OpenFDA service is experiencing issues`;
|
|
82
|
-
break;
|
|
83
|
-
default:
|
|
84
|
-
httpError.message = `HTTP Error ${response.status}: ${response.statusText}`;
|
|
85
|
-
}
|
|
86
|
-
lastError = httpError;
|
|
87
|
-
// Don't retry client errors (4xx) except rate limiting
|
|
88
|
-
if (response.status >= 400 &&
|
|
89
|
-
response.status < 500 &&
|
|
90
|
-
response.status !== 429) {
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
// Retry server errors and rate limits
|
|
94
|
-
if (attempt < maxRetries &&
|
|
95
|
-
isRetryableError({ status: response.status })) {
|
|
96
|
-
const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
|
|
97
|
-
console.log(`Retrying in ${delay}ms...`);
|
|
98
|
-
await sleep(delay);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
// Parse JSON response
|
|
104
|
-
let parsedData;
|
|
105
|
-
try {
|
|
106
|
-
parsedData = await response.json();
|
|
107
|
-
}
|
|
108
|
-
catch (parseError) {
|
|
109
|
-
const parsingError = {
|
|
110
|
-
type: "parsing",
|
|
111
|
-
message: `Failed to parse JSON response: ${parseError instanceof Error
|
|
112
|
-
? parseError.message
|
|
113
|
-
: "Unknown parsing error"}`,
|
|
114
|
-
details: parseError,
|
|
115
|
-
};
|
|
116
|
-
console.error("OpenFDA JSON Parsing Error:", {
|
|
117
|
-
url,
|
|
118
|
-
parseError: parseError instanceof Error ? parseError.message : parseError,
|
|
119
|
-
});
|
|
120
|
-
lastError = parsingError;
|
|
121
|
-
break; // Don't retry parsing errors
|
|
122
|
-
}
|
|
123
|
-
// Check for empty response
|
|
124
|
-
if (!parsedData) {
|
|
125
|
-
const emptyError = {
|
|
126
|
-
type: "empty_response",
|
|
127
|
-
message: "Received empty response from OpenFDA API",
|
|
128
|
-
};
|
|
129
|
-
lastError = emptyError;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
console.log(`OpenFDA request successful on attempt ${attempt + 1}`);
|
|
133
|
-
return { data: parsedData, error: null };
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
// Handle network errors, timeouts, and other fetch errors
|
|
137
|
-
let networkError;
|
|
138
|
-
if (error.name === "AbortError") {
|
|
139
|
-
networkError = {
|
|
140
|
-
type: "timeout",
|
|
141
|
-
message: `Request timeout after ${timeout}ms`,
|
|
142
|
-
details: error,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
else if (error instanceof TypeError &&
|
|
146
|
-
error.message.includes("fetch")) {
|
|
147
|
-
networkError = {
|
|
148
|
-
type: "network",
|
|
149
|
-
message: `Network error: Unable to connect to OpenFDA API`,
|
|
150
|
-
details: error.message,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
networkError = {
|
|
155
|
-
type: "unknown",
|
|
156
|
-
message: `Unexpected error: ${error.message || "Unknown error occurred"}`,
|
|
157
|
-
details: error,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
console.error(`OpenFDA Request Error (attempt ${attempt + 1}):`, {
|
|
161
|
-
url,
|
|
162
|
-
error: error.message,
|
|
163
|
-
type: error.name,
|
|
164
|
-
});
|
|
165
|
-
lastError = networkError;
|
|
166
|
-
// Retry network errors and timeouts
|
|
167
|
-
if (attempt < maxRetries && isRetryableError(error)) {
|
|
168
|
-
const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
|
|
169
|
-
console.log(`Network error, retrying in ${delay}ms...`);
|
|
170
|
-
await sleep(delay);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return { data: null, error: lastError };
|
|
177
|
-
}
|
|
178
|
-
export { makeOpenFDARequest };
|