oilpriceapi 0.7.0 → 0.8.2
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/README.md +43 -11
- package/dist/cjs/client.js +490 -0
- package/dist/cjs/errors.js +80 -0
- package/dist/cjs/index.js +82 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/resources/alerts.js +387 -0
- package/dist/cjs/resources/analytics.js +226 -0
- package/dist/cjs/resources/bunker-fuels.js +196 -0
- package/dist/cjs/resources/commodities.js +115 -0
- package/dist/cjs/resources/data-quality.js +144 -0
- package/dist/cjs/resources/data-sources.js +297 -0
- package/dist/cjs/resources/diesel.js +119 -0
- package/dist/cjs/resources/drilling.js +269 -0
- package/dist/cjs/resources/ei/drilling-productivity.js +108 -0
- package/dist/cjs/resources/ei/forecasts.js +106 -0
- package/dist/cjs/resources/ei/frac-focus.js +155 -0
- package/dist/cjs/resources/ei/index.js +98 -0
- package/dist/cjs/resources/ei/oil-inventories.js +97 -0
- package/dist/cjs/resources/ei/opec-production.js +97 -0
- package/dist/cjs/resources/ei/rig-counts.js +93 -0
- package/dist/cjs/resources/ei/well-permits.js +124 -0
- package/dist/cjs/resources/forecasts.js +162 -0
- package/dist/cjs/resources/futures.js +233 -0
- package/dist/cjs/resources/rig-counts.js +161 -0
- package/dist/cjs/resources/storage.js +166 -0
- package/dist/cjs/resources/webhooks.js +294 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/version.js +24 -0
- package/dist/client.d.ts +33 -2
- package/dist/client.js +70 -14
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +25 -16
- package/dist/index.d.ts +16 -2
- package/dist/index.js +24 -1
- package/dist/resources/alerts.js +31 -77
- package/dist/resources/analytics.js +8 -7
- package/dist/resources/bunker-fuels.js +5 -4
- package/dist/resources/commodities.js +2 -1
- package/dist/resources/data-quality.js +2 -1
- package/dist/resources/data-sources.js +21 -77
- package/dist/resources/diesel.d.ts +1 -1
- package/dist/resources/diesel.js +9 -38
- package/dist/resources/drilling.js +2 -1
- package/dist/resources/ei/drilling-productivity.js +2 -1
- package/dist/resources/ei/forecasts.js +2 -1
- package/dist/resources/ei/frac-focus.js +4 -3
- package/dist/resources/ei/index.js +2 -1
- package/dist/resources/ei/oil-inventories.js +2 -1
- package/dist/resources/ei/opec-production.js +2 -1
- package/dist/resources/ei/rig-counts.js +2 -1
- package/dist/resources/ei/well-permits.js +2 -1
- package/dist/resources/forecasts.js +2 -1
- package/dist/resources/futures.js +9 -8
- package/dist/resources/storage.js +2 -1
- package/dist/resources/webhooks.d.ts +36 -0
- package/dist/resources/webhooks.js +60 -67
- package/dist/types.d.ts +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +15 -6
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# Oil Price API - Node.js SDK
|
|
2
2
|
|
|
3
|
+
> **Real-time oil and commodity price data for Node.js** - Professional-grade API at 98% less cost than Bloomberg Terminal
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/oilpriceapi)
|
|
6
|
+
[](https://www.npmjs.com/package/oilpriceapi)
|
|
7
|
+
[](https://github.com/OilpriceAPI/oilpriceapi-node/actions/workflows/test.yml)
|
|
4
8
|
[](https://opensource.org/licenses/MIT)
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
**[Get Free API Key](https://www.oilpriceapi.com/signup?utm_source=npm&utm_medium=sdk&utm_campaign=readme)** | **[Documentation](https://docs.oilpriceapi.com)** | **[Pricing](https://www.oilpriceapi.com/pricing?utm_source=npm&utm_medium=sdk&utm_campaign=pricing)**
|
|
11
|
+
|
|
12
|
+
The official Node.js/TypeScript SDK for [OilPriceAPI](https://www.oilpriceapi.com) - Real-time and historical oil prices for Brent Crude, WTI, Natural Gas, and 100+ commodities.
|
|
7
13
|
|
|
8
14
|
## Features
|
|
9
15
|
|
|
@@ -33,7 +39,7 @@ import { OilPriceAPI } from "oilpriceapi";
|
|
|
33
39
|
|
|
34
40
|
// Initialize the client
|
|
35
41
|
const client = new OilPriceAPI({
|
|
36
|
-
apiKey: "your_api_key_here", // Get your free key at https://www.oilpriceapi.com
|
|
42
|
+
apiKey: "your_api_key_here", // Get your free key at https://www.oilpriceapi.com/signup
|
|
37
43
|
retries: 3, // Automatic retries (default: 3)
|
|
38
44
|
timeout: 30000, // Request timeout in ms (default: 30000)
|
|
39
45
|
});
|
|
@@ -706,7 +712,7 @@ new OilPriceAPI(config: OilPriceAPIConfig)
|
|
|
706
712
|
|
|
707
713
|
**Parameters:**
|
|
708
714
|
|
|
709
|
-
- `config.apiKey` (string, required) - Your API key from https://www.oilpriceapi.com
|
|
715
|
+
- `config.apiKey` (string, required) - Your API key from https://www.oilpriceapi.com/signup
|
|
710
716
|
- `config.baseUrl` (string, optional) - Custom API base URL (for testing)
|
|
711
717
|
|
|
712
718
|
#### Methods
|
|
@@ -928,7 +934,7 @@ See the [full list of 79 commodities](https://www.oilpriceapi.com/commodities) i
|
|
|
928
934
|
- **Business**: 200,000 requests/month - $199/mo
|
|
929
935
|
- **Enterprise**: Custom limits - Contact us
|
|
930
936
|
|
|
931
|
-
Get started with a free API key at [oilpriceapi.com](https://www.oilpriceapi.com).
|
|
937
|
+
Get started with a free API key at [oilpriceapi.com/signup](https://www.oilpriceapi.com/signup?utm_source=npm&utm_medium=sdk&utm_campaign=readme).
|
|
932
938
|
|
|
933
939
|
## Requirements
|
|
934
940
|
|
|
@@ -950,10 +956,9 @@ We welcome contributions! Please see our [Contributing Guide](https://github.com
|
|
|
950
956
|
|
|
951
957
|
## Support
|
|
952
958
|
|
|
953
|
-
-
|
|
954
|
-
-
|
|
955
|
-
-
|
|
956
|
-
- 💬 Discord: [Join our community](https://discord.gg/oilpriceapi)
|
|
959
|
+
- Email: support@oilpriceapi.com
|
|
960
|
+
- Issues: [GitHub Issues](https://github.com/OilpriceAPI/oilpriceapi-node/issues)
|
|
961
|
+
- Docs: [Documentation](https://docs.oilpriceapi.com)
|
|
957
962
|
|
|
958
963
|
## License
|
|
959
964
|
|
|
@@ -961,11 +966,38 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
961
966
|
|
|
962
967
|
## Links
|
|
963
968
|
|
|
964
|
-
- [Website](https://www.oilpriceapi.com)
|
|
965
|
-
- [API Documentation](https://
|
|
969
|
+
- [OilPriceAPI Website](https://www.oilpriceapi.com)
|
|
970
|
+
- [API Documentation](https://docs.oilpriceapi.com)
|
|
971
|
+
- [Pricing](https://www.oilpriceapi.com/pricing?utm_source=npm&utm_medium=sdk&utm_campaign=pricing)
|
|
972
|
+
- [Status Page](https://status.oilpriceapi.com)
|
|
966
973
|
- [GitHub Repository](https://github.com/OilpriceAPI/oilpriceapi-node)
|
|
967
974
|
- [npm Package](https://www.npmjs.com/package/oilpriceapi)
|
|
968
975
|
|
|
969
976
|
---
|
|
970
977
|
|
|
971
|
-
|
|
978
|
+
## Why OilPriceAPI?
|
|
979
|
+
|
|
980
|
+
[OilPriceAPI](https://www.oilpriceapi.com) provides professional-grade commodity price data at **98% less cost than Bloomberg Terminal** ($24,000/year vs $45/month). Trusted by energy traders, financial analysts, and developers worldwide.
|
|
981
|
+
|
|
982
|
+
### Key Benefits
|
|
983
|
+
|
|
984
|
+
- **Real-time data** updated every 5 minutes
|
|
985
|
+
- **Historical data** for trend analysis and backtesting
|
|
986
|
+
- **99.9% uptime** with enterprise-grade reliability
|
|
987
|
+
- **5-minute integration** with this Node.js SDK
|
|
988
|
+
- **Free tier** with 100 requests to get started
|
|
989
|
+
|
|
990
|
+
**[Start Free](https://www.oilpriceapi.com/signup?utm_source=npm&utm_medium=sdk&utm_campaign=readme)** | **[View Pricing](https://www.oilpriceapi.com/pricing?utm_source=npm&utm_medium=sdk&utm_campaign=pricing)** | **[Read Docs](https://docs.oilpriceapi.com)**
|
|
991
|
+
|
|
992
|
+
---
|
|
993
|
+
|
|
994
|
+
## Also Available As
|
|
995
|
+
|
|
996
|
+
- **[Python SDK](https://pypi.org/project/oilpriceapi/)** - Python client with Pandas integration
|
|
997
|
+
- **[Go SDK](https://github.com/OilpriceAPI/oilpriceapi-go)** - Idiomatic Go client
|
|
998
|
+
- **[MCP Server](https://www.npmjs.com/package/oilpriceapi-mcp)** - Model Context Protocol server for Claude, Cursor, and VS Code
|
|
999
|
+
- **[Google Sheets Add-on](https://github.com/OilpriceAPI/google-sheets-addin)** - Custom functions for spreadsheets
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
Made with care by the OilPriceAPI Team
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OilPriceAPI = void 0;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const diesel_js_1 = require("./resources/diesel.js");
|
|
6
|
+
const alerts_js_1 = require("./resources/alerts.js");
|
|
7
|
+
const commodities_js_1 = require("./resources/commodities.js");
|
|
8
|
+
const futures_js_1 = require("./resources/futures.js");
|
|
9
|
+
const storage_js_1 = require("./resources/storage.js");
|
|
10
|
+
const rig_counts_js_1 = require("./resources/rig-counts.js");
|
|
11
|
+
const bunker_fuels_js_1 = require("./resources/bunker-fuels.js");
|
|
12
|
+
const analytics_js_1 = require("./resources/analytics.js");
|
|
13
|
+
const forecasts_js_1 = require("./resources/forecasts.js");
|
|
14
|
+
const data_quality_js_1 = require("./resources/data-quality.js");
|
|
15
|
+
const drilling_js_1 = require("./resources/drilling.js");
|
|
16
|
+
const index_js_1 = require("./resources/ei/index.js");
|
|
17
|
+
const webhooks_js_1 = require("./resources/webhooks.js");
|
|
18
|
+
const data_sources_js_1 = require("./resources/data-sources.js");
|
|
19
|
+
const version_js_1 = require("./version.js");
|
|
20
|
+
/**
|
|
21
|
+
* Official Node.js client for Oil Price API
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { OilPriceAPI } from 'oilpriceapi';
|
|
26
|
+
*
|
|
27
|
+
* const client = new OilPriceAPI({
|
|
28
|
+
* apiKey: 'your_api_key_here',
|
|
29
|
+
* retries: 3,
|
|
30
|
+
* timeout: 30000
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Get latest prices
|
|
34
|
+
* const prices = await client.getLatestPrices();
|
|
35
|
+
*
|
|
36
|
+
* // Get WTI price only
|
|
37
|
+
* const wti = await client.getLatestPrices({ commodity: 'WTI_USD' });
|
|
38
|
+
*
|
|
39
|
+
* // Get historical data
|
|
40
|
+
* const historical = await client.getHistoricalPrices({
|
|
41
|
+
* period: 'past_week',
|
|
42
|
+
* commodity: 'BRENT_CRUDE_USD'
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
class OilPriceAPI {
|
|
47
|
+
constructor(config = {}) {
|
|
48
|
+
this.apiKey = config.apiKey || process.env.OILPRICEAPI_KEY || "";
|
|
49
|
+
if (!this.apiKey) {
|
|
50
|
+
throw new errors_js_1.OilPriceAPIError("API key required. Set OILPRICEAPI_KEY env var or pass apiKey in config.");
|
|
51
|
+
}
|
|
52
|
+
this.baseUrl = config.baseUrl || "https://api.oilpriceapi.com";
|
|
53
|
+
this.retries = config.retries !== undefined ? config.retries : 3;
|
|
54
|
+
this.retryDelay = config.retryDelay || 1000;
|
|
55
|
+
this.retryStrategy = config.retryStrategy || "exponential";
|
|
56
|
+
this.timeout = config.timeout || 90000; // 90 seconds for slow historical queries
|
|
57
|
+
this.debug = config.debug || false;
|
|
58
|
+
this.appUrl = config.appUrl;
|
|
59
|
+
this.appName = config.appName;
|
|
60
|
+
// Initialize resources
|
|
61
|
+
this.diesel = new diesel_js_1.DieselResource(this);
|
|
62
|
+
this.alerts = new alerts_js_1.AlertsResource(this);
|
|
63
|
+
this.commodities = new commodities_js_1.CommoditiesResource(this);
|
|
64
|
+
this.futures = new futures_js_1.FuturesResource(this);
|
|
65
|
+
this.storage = new storage_js_1.StorageResource(this);
|
|
66
|
+
this.rigCounts = new rig_counts_js_1.RigCountsResource(this);
|
|
67
|
+
this.bunkerFuels = new bunker_fuels_js_1.BunkerFuelsResource(this);
|
|
68
|
+
this.analytics = new analytics_js_1.AnalyticsResource(this);
|
|
69
|
+
this.forecasts = new forecasts_js_1.ForecastsResource(this);
|
|
70
|
+
this.dataQuality = new data_quality_js_1.DataQualityResource(this);
|
|
71
|
+
this.drilling = new drilling_js_1.DrillingIntelligenceResource(this);
|
|
72
|
+
this.ei = new index_js_1.EnergyIntelligenceResource(this);
|
|
73
|
+
this.webhooks = new webhooks_js_1.WebhooksResource(this);
|
|
74
|
+
this.dataSources = new data_sources_js_1.DataSourcesResource(this);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Log debug messages if debug mode is enabled
|
|
78
|
+
*/
|
|
79
|
+
log(message, data) {
|
|
80
|
+
if (this.debug) {
|
|
81
|
+
const timestamp = new Date().toISOString();
|
|
82
|
+
console.log(`[OilPriceAPI ${timestamp}] ${message}`, data || "");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Calculate delay for retry based on strategy
|
|
87
|
+
*/
|
|
88
|
+
calculateRetryDelay(attempt) {
|
|
89
|
+
switch (this.retryStrategy) {
|
|
90
|
+
case "exponential":
|
|
91
|
+
return this.retryDelay * Math.pow(2, attempt);
|
|
92
|
+
case "linear":
|
|
93
|
+
return this.retryDelay * (attempt + 1);
|
|
94
|
+
case "fixed":
|
|
95
|
+
default:
|
|
96
|
+
return this.retryDelay;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sleep for specified milliseconds
|
|
101
|
+
*/
|
|
102
|
+
sleep(ms) {
|
|
103
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Determine if error is retryable
|
|
107
|
+
*/
|
|
108
|
+
isRetryable(error) {
|
|
109
|
+
// Retry on network errors
|
|
110
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
// Retry on timeout errors
|
|
114
|
+
if (error instanceof errors_js_1.TimeoutError) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
// Retry on 5xx server errors
|
|
118
|
+
if (error instanceof errors_js_1.ServerError) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
// Retry on rate limit errors (with delay)
|
|
122
|
+
if (error instanceof errors_js_1.RateLimitError) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
// Don't retry on client errors (4xx except 429)
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Internal method to make HTTP requests with retry and timeout.
|
|
130
|
+
* Supports all HTTP methods (GET, POST, PATCH, DELETE) with consistent
|
|
131
|
+
* retry logic, timeout handling, and typed error responses.
|
|
132
|
+
*/
|
|
133
|
+
async request(endpoint, params, options) {
|
|
134
|
+
// Build URL with query parameters
|
|
135
|
+
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
136
|
+
if (params) {
|
|
137
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
138
|
+
if (value !== undefined && value !== null) {
|
|
139
|
+
url.searchParams.append(key, value);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
this.log(`Request: ${url.toString()}`);
|
|
144
|
+
let lastError = null;
|
|
145
|
+
// Retry loop
|
|
146
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
147
|
+
try {
|
|
148
|
+
// Add retry info to logs
|
|
149
|
+
if (attempt > 0) {
|
|
150
|
+
this.log(`Retry attempt ${attempt}/${this.retries}`);
|
|
151
|
+
}
|
|
152
|
+
// Create abort controller for timeout
|
|
153
|
+
const controller = new AbortController();
|
|
154
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
155
|
+
try {
|
|
156
|
+
// Build headers with optional telemetry
|
|
157
|
+
const headers = {
|
|
158
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
"User-Agent": (0, version_js_1.buildUserAgent)(),
|
|
161
|
+
"X-SDK-Name": version_js_1.SDK_NAME,
|
|
162
|
+
"X-SDK-Version": version_js_1.SDK_VERSION,
|
|
163
|
+
};
|
|
164
|
+
// Add optional telemetry headers (10% bonus for appUrl!)
|
|
165
|
+
if (this.appUrl) {
|
|
166
|
+
headers["X-App-URL"] = this.appUrl;
|
|
167
|
+
}
|
|
168
|
+
if (this.appName) {
|
|
169
|
+
headers["X-App-Name"] = this.appName;
|
|
170
|
+
}
|
|
171
|
+
const fetchOptions = {
|
|
172
|
+
method: options?.method || "GET",
|
|
173
|
+
headers,
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
};
|
|
176
|
+
if (options?.body !== undefined) {
|
|
177
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
178
|
+
}
|
|
179
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
180
|
+
clearTimeout(timeoutId);
|
|
181
|
+
this.log(`Response: ${response.status} ${response.statusText}`);
|
|
182
|
+
// Handle error responses
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
const errorBody = await response.text();
|
|
185
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
186
|
+
// Try to parse JSON error response
|
|
187
|
+
try {
|
|
188
|
+
const errorJson = JSON.parse(errorBody);
|
|
189
|
+
errorMessage = errorJson.message || errorJson.error || errorMessage;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Use default error message if response isn't JSON
|
|
193
|
+
}
|
|
194
|
+
this.log(`Error response: ${errorMessage}`);
|
|
195
|
+
// Throw specific error types based on status code
|
|
196
|
+
switch (response.status) {
|
|
197
|
+
case 401:
|
|
198
|
+
throw new errors_js_1.AuthenticationError(errorMessage);
|
|
199
|
+
case 404:
|
|
200
|
+
throw new errors_js_1.NotFoundError(errorMessage);
|
|
201
|
+
case 429:
|
|
202
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
203
|
+
const rateLimitError = new errors_js_1.RateLimitError(errorMessage, retryAfter ? parseInt(retryAfter, 10) : undefined);
|
|
204
|
+
// If rate limited and we have retries left, wait and retry
|
|
205
|
+
if (attempt < this.retries && rateLimitError.retryAfter) {
|
|
206
|
+
this.log(`Rate limited. Waiting ${rateLimitError.retryAfter}s`);
|
|
207
|
+
await this.sleep(rateLimitError.retryAfter * 1000);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
throw rateLimitError;
|
|
211
|
+
case 500:
|
|
212
|
+
case 502:
|
|
213
|
+
case 503:
|
|
214
|
+
case 504:
|
|
215
|
+
throw new errors_js_1.ServerError(errorMessage, response.status);
|
|
216
|
+
default:
|
|
217
|
+
throw new errors_js_1.OilPriceAPIError(errorMessage, response.status, "HTTP_ERROR");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Handle empty responses (e.g., 204 No Content from DELETE)
|
|
221
|
+
const responseText = await response.text();
|
|
222
|
+
if (!responseText) {
|
|
223
|
+
this.log("Empty response body");
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
// Parse successful response
|
|
227
|
+
const responseData = JSON.parse(responseText);
|
|
228
|
+
this.log("Response data received", {
|
|
229
|
+
status: responseData.status,
|
|
230
|
+
hasData: !!responseData.data,
|
|
231
|
+
});
|
|
232
|
+
// Handle different response structures
|
|
233
|
+
// Latest endpoint: { status, data: { price, ... } }
|
|
234
|
+
// Historical endpoint: { status, data: { prices: [...] } }
|
|
235
|
+
if (responseData.status === "success" && responseData.data) {
|
|
236
|
+
if (responseData.data.prices) {
|
|
237
|
+
// Historical endpoint - return prices array
|
|
238
|
+
this.log(`Returning ${responseData.data.prices.length} prices`);
|
|
239
|
+
return responseData.data.prices;
|
|
240
|
+
}
|
|
241
|
+
else if (responseData.data.price !== undefined) {
|
|
242
|
+
// Latest endpoint - wrap single price in array
|
|
243
|
+
this.log("Returning single price (wrapped in array)");
|
|
244
|
+
return [responseData.data];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Fallback - return data as-is (used by resource mutations, alerts, webhooks, etc.)
|
|
248
|
+
this.log("Returning data as-is");
|
|
249
|
+
return (responseData.data !== undefined ? responseData.data : responseData);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
// Handle abort (timeout)
|
|
253
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
254
|
+
throw new errors_js_1.TimeoutError("Request timeout", this.timeout);
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
lastError = error;
|
|
261
|
+
this.log(`Request failed: ${lastError.message}`, {
|
|
262
|
+
attempt,
|
|
263
|
+
retryable: this.isRetryable(lastError),
|
|
264
|
+
});
|
|
265
|
+
// Re-throw our custom errors if not retryable
|
|
266
|
+
if (error instanceof errors_js_1.OilPriceAPIError && !this.isRetryable(error)) {
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
// If this was our last attempt, throw the error
|
|
270
|
+
if (attempt === this.retries) {
|
|
271
|
+
if (error instanceof errors_js_1.OilPriceAPIError) {
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
// Wrap fetch errors (network issues, etc.)
|
|
275
|
+
if (error instanceof Error) {
|
|
276
|
+
throw new errors_js_1.OilPriceAPIError(`Request failed after ${this.retries + 1} attempts: ${error.message}`, undefined, "NETWORK_ERROR");
|
|
277
|
+
}
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
// Calculate delay and retry
|
|
281
|
+
const delay = this.calculateRetryDelay(attempt);
|
|
282
|
+
this.log(`Waiting ${delay}ms before retry...`);
|
|
283
|
+
await this.sleep(delay);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// This should never be reached, but TypeScript wants it
|
|
287
|
+
throw lastError || new errors_js_1.OilPriceAPIError("Unknown error occurred");
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get the latest prices for all commodities or a specific commodity
|
|
291
|
+
*
|
|
292
|
+
* @param options - Optional filters
|
|
293
|
+
* @returns Array of price objects
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* // Get all latest prices
|
|
298
|
+
* const allPrices = await client.getLatestPrices();
|
|
299
|
+
*
|
|
300
|
+
* // Get WTI price only
|
|
301
|
+
* const wti = await client.getLatestPrices({ commodity: 'WTI_USD' });
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
async getLatestPrices(options) {
|
|
305
|
+
const params = {};
|
|
306
|
+
if (options?.commodity) {
|
|
307
|
+
params.by_code = options.commodity;
|
|
308
|
+
}
|
|
309
|
+
return this.request("/v1/prices/latest", params);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get historical prices for a time period
|
|
313
|
+
*
|
|
314
|
+
* @param options - Time period and filter options
|
|
315
|
+
* @returns Array of historical price objects
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* // Get past week of WTI prices
|
|
320
|
+
* const weekPrices = await client.getHistoricalPrices({
|
|
321
|
+
* period: 'past_week',
|
|
322
|
+
* commodity: 'WTI_USD'
|
|
323
|
+
* });
|
|
324
|
+
*
|
|
325
|
+
* // Get custom date range
|
|
326
|
+
* const customPrices = await client.getHistoricalPrices({
|
|
327
|
+
* startDate: '2024-01-01',
|
|
328
|
+
* endDate: '2024-12-31',
|
|
329
|
+
* commodity: 'BRENT_CRUDE_USD'
|
|
330
|
+
* });
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
async getHistoricalPrices(options) {
|
|
334
|
+
const params = {};
|
|
335
|
+
if (options?.period) {
|
|
336
|
+
params.period = options.period;
|
|
337
|
+
}
|
|
338
|
+
if (options?.commodity) {
|
|
339
|
+
params.by_code = options.commodity;
|
|
340
|
+
}
|
|
341
|
+
if (options?.startDate) {
|
|
342
|
+
params.start_date = options.startDate;
|
|
343
|
+
}
|
|
344
|
+
if (options?.endDate) {
|
|
345
|
+
params.end_date = options.endDate;
|
|
346
|
+
}
|
|
347
|
+
// PERFORMANCE FIX (December 24, 2025):
|
|
348
|
+
// Pass interval parameter to enable aggregated queries
|
|
349
|
+
// This reduces response times from 74s to <1s for year-long queries
|
|
350
|
+
// by returning 365 daily points instead of 600k+ raw points
|
|
351
|
+
if (options?.interval) {
|
|
352
|
+
params.interval = options.interval;
|
|
353
|
+
}
|
|
354
|
+
// Pagination parameters
|
|
355
|
+
if (options?.perPage !== undefined) {
|
|
356
|
+
params.per_page = options.perPage.toString();
|
|
357
|
+
}
|
|
358
|
+
if (options?.page !== undefined) {
|
|
359
|
+
params.page = options.page.toString();
|
|
360
|
+
}
|
|
361
|
+
// CRITICAL FIX (December 17, 2025):
|
|
362
|
+
// Use /v1/prices/past_year endpoint instead of /v1/prices
|
|
363
|
+
// The /v1/prices endpoint does NOT correctly handle start_date/end_date parameters
|
|
364
|
+
// This was the same bug that affected the Python SDK (fixed in v1.4.4)
|
|
365
|
+
// Issue: SDK was returning wrong dates for historical queries
|
|
366
|
+
// Root Cause: Backend has_scope :by_period not working on /v1/prices
|
|
367
|
+
// Solution: Use /v1/prices/past_year which uses direct WHERE clauses
|
|
368
|
+
return this.request("/v1/prices/past_year", params);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Paginate through historical prices automatically.
|
|
372
|
+
*
|
|
373
|
+
* Returns an async generator that yields pages of prices, fetching
|
|
374
|
+
* the next page only when needed. Avoids loading all data into memory.
|
|
375
|
+
*
|
|
376
|
+
* @param options - Same options as getHistoricalPrices, plus perPage (default: 100)
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* // Iterate through all pages
|
|
381
|
+
* for await (const page of client.paginateHistoricalPrices({
|
|
382
|
+
* commodity: 'BRENT_CRUDE_USD',
|
|
383
|
+
* startDate: '2024-01-01',
|
|
384
|
+
* endDate: '2024-12-31',
|
|
385
|
+
* perPage: 100,
|
|
386
|
+
* })) {
|
|
387
|
+
* console.log(`Got ${page.length} prices`);
|
|
388
|
+
* // Process each page...
|
|
389
|
+
* }
|
|
390
|
+
*
|
|
391
|
+
* // Or collect all prices
|
|
392
|
+
* const allPrices: Price[] = [];
|
|
393
|
+
* for await (const page of client.paginateHistoricalPrices({ commodity: 'WTI_USD' })) {
|
|
394
|
+
* allPrices.push(...page);
|
|
395
|
+
* }
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
async *paginateHistoricalPrices(options) {
|
|
399
|
+
const perPage = options?.perPage || 100;
|
|
400
|
+
let page = 1;
|
|
401
|
+
while (true) {
|
|
402
|
+
const results = await this.getHistoricalPrices({
|
|
403
|
+
...options,
|
|
404
|
+
page,
|
|
405
|
+
perPage,
|
|
406
|
+
});
|
|
407
|
+
if (results.length === 0)
|
|
408
|
+
break;
|
|
409
|
+
yield results;
|
|
410
|
+
if (results.length < perPage)
|
|
411
|
+
break;
|
|
412
|
+
page++;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get prices from your connected data sources (BYOS)
|
|
417
|
+
*
|
|
418
|
+
* Requires Data Connector feature enabled on your organization.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```typescript
|
|
422
|
+
* // Get all connected prices
|
|
423
|
+
* const prices = await client.getDataConnectorPrices();
|
|
424
|
+
*
|
|
425
|
+
* // Filter by fuel type
|
|
426
|
+
* const vlsfo = await client.getDataConnectorPrices({ fuelType: 'VLSFO' });
|
|
427
|
+
*
|
|
428
|
+
* // Filter by port
|
|
429
|
+
* const singapore = await client.getDataConnectorPrices({ port: 'SINGAPORE' });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
async getDataConnectorPrices(options = {}) {
|
|
433
|
+
const params = {};
|
|
434
|
+
if (options.fuelType)
|
|
435
|
+
params.fuel_type = options.fuelType;
|
|
436
|
+
if (options.port)
|
|
437
|
+
params.port = options.port;
|
|
438
|
+
if (options.region)
|
|
439
|
+
params.region = options.region;
|
|
440
|
+
if (options.since)
|
|
441
|
+
params.since = options.since;
|
|
442
|
+
const response = await this.request("/v1/prices/data-connector", params);
|
|
443
|
+
return response.prices;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get metadata for all supported commodities
|
|
447
|
+
*
|
|
448
|
+
* @returns Object containing array of commodities
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* const response = await client.getCommodities();
|
|
453
|
+
* console.log(response.commodities); // Array of commodity objects
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
async getCommodities() {
|
|
457
|
+
return this.request("/v1/commodities", {});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get all commodity categories with their commodities
|
|
461
|
+
*
|
|
462
|
+
* @returns Object with category keys mapped to category objects
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```typescript
|
|
466
|
+
* const categories = await client.getCommodityCategories();
|
|
467
|
+
* console.log(categories.oil.name); // "Oil"
|
|
468
|
+
* console.log(categories.oil.commodities.length); // 11
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
async getCommodityCategories() {
|
|
472
|
+
return this.request("/v1/commodities/categories", {});
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get metadata for a specific commodity by code
|
|
476
|
+
*
|
|
477
|
+
* @param code - Commodity code (e.g., "WTI_USD", "BRENT_CRUDE_USD")
|
|
478
|
+
* @returns Commodity metadata object
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* const commodity = await client.getCommodity('WTI_USD');
|
|
483
|
+
* console.log(commodity.name); // "WTI Crude Oil"
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
async getCommodity(code) {
|
|
487
|
+
return this.request(`/v1/commodities/${code}`, {});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
exports.OilPriceAPI = OilPriceAPI;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TimeoutError = exports.ValidationError = exports.ServerError = exports.NotFoundError = exports.RateLimitError = exports.AuthenticationError = exports.OilPriceAPIError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all Oil Price API errors
|
|
6
|
+
*/
|
|
7
|
+
class OilPriceAPIError extends Error {
|
|
8
|
+
constructor(message, statusCode, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "OilPriceAPIError";
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.code = code;
|
|
13
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
14
|
+
if (Error.captureStackTrace) {
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.OilPriceAPIError = OilPriceAPIError;
|
|
20
|
+
/**
|
|
21
|
+
* Thrown when API authentication fails (401)
|
|
22
|
+
*/
|
|
23
|
+
class AuthenticationError extends OilPriceAPIError {
|
|
24
|
+
constructor(message = "Invalid API key") {
|
|
25
|
+
super(message, 401, "AUTHENTICATION_ERROR");
|
|
26
|
+
this.name = "AuthenticationError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.AuthenticationError = AuthenticationError;
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when rate limit is exceeded (429)
|
|
32
|
+
*/
|
|
33
|
+
class RateLimitError extends OilPriceAPIError {
|
|
34
|
+
constructor(message = "Rate limit exceeded", retryAfter) {
|
|
35
|
+
super(message, 429, "RATE_LIMIT_ERROR");
|
|
36
|
+
this.name = "RateLimitError";
|
|
37
|
+
this.retryAfter = retryAfter;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.RateLimitError = RateLimitError;
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when requested resource is not found (404)
|
|
43
|
+
*/
|
|
44
|
+
class NotFoundError extends OilPriceAPIError {
|
|
45
|
+
constructor(message = "Resource not found") {
|
|
46
|
+
super(message, 404, "NOT_FOUND_ERROR");
|
|
47
|
+
this.name = "NotFoundError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.NotFoundError = NotFoundError;
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when server returns 5xx error
|
|
53
|
+
*/
|
|
54
|
+
class ServerError extends OilPriceAPIError {
|
|
55
|
+
constructor(message = "Internal server error", statusCode = 500) {
|
|
56
|
+
super(message, statusCode, "SERVER_ERROR");
|
|
57
|
+
this.name = "ServerError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.ServerError = ServerError;
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when SDK-side input validation fails
|
|
63
|
+
*/
|
|
64
|
+
class ValidationError extends OilPriceAPIError {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message, undefined, "VALIDATION_ERROR");
|
|
67
|
+
this.name = "ValidationError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.ValidationError = ValidationError;
|
|
71
|
+
/**
|
|
72
|
+
* Thrown when request exceeds timeout
|
|
73
|
+
*/
|
|
74
|
+
class TimeoutError extends OilPriceAPIError {
|
|
75
|
+
constructor(message = "Request timeout", timeout) {
|
|
76
|
+
super(`${message} (${timeout}ms)`, undefined, "TIMEOUT_ERROR");
|
|
77
|
+
this.name = "TimeoutError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.TimeoutError = TimeoutError;
|