oilpriceapi 0.7.0 → 0.9.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.
Files changed (81) hide show
  1. package/README.md +244 -30
  2. package/dist/cjs/client.js +610 -0
  3. package/dist/cjs/errors.js +80 -0
  4. package/dist/cjs/index.js +96 -0
  5. package/dist/cjs/package.json +1 -0
  6. package/dist/cjs/resources/alerts.js +387 -0
  7. package/dist/cjs/resources/analytics.js +188 -0
  8. package/dist/cjs/resources/bunker-fuels.js +210 -0
  9. package/dist/cjs/resources/commodities.js +115 -0
  10. package/dist/cjs/resources/data-quality.js +144 -0
  11. package/dist/cjs/resources/data-sources.js +298 -0
  12. package/dist/cjs/resources/diesel.js +119 -0
  13. package/dist/cjs/resources/drilling.js +269 -0
  14. package/dist/cjs/resources/ei/drilling-productivity.js +108 -0
  15. package/dist/cjs/resources/ei/forecasts.js +106 -0
  16. package/dist/cjs/resources/ei/frac-focus.js +165 -0
  17. package/dist/cjs/resources/ei/index.js +98 -0
  18. package/dist/cjs/resources/ei/oil-inventories.js +97 -0
  19. package/dist/cjs/resources/ei/opec-production.js +97 -0
  20. package/dist/cjs/resources/ei/rig-counts.js +93 -0
  21. package/dist/cjs/resources/ei/well-permits.js +136 -0
  22. package/dist/cjs/resources/forecasts.js +168 -0
  23. package/dist/cjs/resources/futures.js +424 -0
  24. package/dist/cjs/resources/indicators.js +79 -0
  25. package/dist/cjs/resources/raw.js +128 -0
  26. package/dist/cjs/resources/rig-counts.js +164 -0
  27. package/dist/cjs/resources/spreads.js +105 -0
  28. package/dist/cjs/resources/storage.js +166 -0
  29. package/dist/cjs/resources/streaming.js +350 -0
  30. package/dist/cjs/resources/webhooks.js +283 -0
  31. package/dist/cjs/types.js +2 -0
  32. package/dist/cjs/version.js +24 -0
  33. package/dist/client.d.ts +130 -3
  34. package/dist/client.js +206 -30
  35. package/dist/errors.d.ts +6 -0
  36. package/dist/errors.js +25 -16
  37. package/dist/index.d.ts +28 -5
  38. package/dist/index.js +29 -1
  39. package/dist/resources/alerts.js +31 -77
  40. package/dist/resources/analytics.d.ts +147 -214
  41. package/dist/resources/analytics.js +104 -141
  42. package/dist/resources/bunker-fuels.d.ts +35 -12
  43. package/dist/resources/bunker-fuels.js +41 -26
  44. package/dist/resources/commodities.js +2 -1
  45. package/dist/resources/data-quality.js +2 -1
  46. package/dist/resources/data-sources.d.ts +31 -31
  47. package/dist/resources/data-sources.js +30 -85
  48. package/dist/resources/diesel.d.ts +1 -1
  49. package/dist/resources/diesel.js +9 -38
  50. package/dist/resources/drilling.js +2 -1
  51. package/dist/resources/ei/drilling-productivity.js +2 -1
  52. package/dist/resources/ei/forecasts.js +2 -1
  53. package/dist/resources/ei/frac-focus.d.ts +23 -9
  54. package/dist/resources/ei/frac-focus.js +20 -9
  55. package/dist/resources/ei/index.js +2 -1
  56. package/dist/resources/ei/oil-inventories.js +2 -1
  57. package/dist/resources/ei/opec-production.js +2 -1
  58. package/dist/resources/ei/rig-counts.js +2 -1
  59. package/dist/resources/ei/well-permits.d.ts +25 -9
  60. package/dist/resources/ei/well-permits.js +20 -7
  61. package/dist/resources/forecasts.d.ts +4 -1
  62. package/dist/resources/forecasts.js +13 -6
  63. package/dist/resources/futures.d.ts +178 -1
  64. package/dist/resources/futures.js +199 -8
  65. package/dist/resources/indicators.d.ts +170 -0
  66. package/dist/resources/indicators.js +75 -0
  67. package/dist/resources/raw.d.ts +94 -0
  68. package/dist/resources/raw.js +124 -0
  69. package/dist/resources/rig-counts.js +5 -2
  70. package/dist/resources/spreads.d.ts +121 -0
  71. package/dist/resources/spreads.js +101 -0
  72. package/dist/resources/storage.d.ts +5 -4
  73. package/dist/resources/storage.js +7 -6
  74. package/dist/resources/streaming.d.ts +272 -0
  75. package/dist/resources/streaming.js +342 -0
  76. package/dist/resources/webhooks.d.ts +73 -23
  77. package/dist/resources/webhooks.js +59 -77
  78. package/dist/types.d.ts +43 -1
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +2 -2
  81. package/package.json +21 -6
@@ -0,0 +1,610 @@
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
+ const spreads_js_1 = require("./resources/spreads.js");
21
+ const indicators_js_1 = require("./resources/indicators.js");
22
+ const raw_js_1 = require("./resources/raw.js");
23
+ const streaming_js_1 = require("./resources/streaming.js");
24
+ /**
25
+ * Official Node.js client for Oil Price API
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { OilPriceAPI } from 'oilpriceapi';
30
+ *
31
+ * const client = new OilPriceAPI({
32
+ * apiKey: 'your_api_key_here',
33
+ * retries: 3,
34
+ * timeout: 30000
35
+ * });
36
+ *
37
+ * // Get latest prices
38
+ * const prices = await client.getLatestPrices();
39
+ *
40
+ * // Get WTI price only
41
+ * const wti = await client.getLatestPrices({ commodity: 'WTI_USD' });
42
+ *
43
+ * // Get historical data
44
+ * const historical = await client.getHistoricalPrices({
45
+ * period: 'past_week',
46
+ * commodity: 'BRENT_CRUDE_USD'
47
+ * });
48
+ * ```
49
+ */
50
+ class OilPriceAPI {
51
+ constructor(config = {}) {
52
+ this.apiKey = config.apiKey || process.env.OILPRICEAPI_KEY || "";
53
+ if (!this.apiKey) {
54
+ throw new errors_js_1.OilPriceAPIError("API key required. Set OILPRICEAPI_KEY env var or pass apiKey in config.");
55
+ }
56
+ this.baseUrl = config.baseUrl || "https://api.oilpriceapi.com";
57
+ this.retries = config.retries !== undefined ? config.retries : 3;
58
+ this.retryDelay = config.retryDelay || 1000;
59
+ this.retryStrategy = config.retryStrategy || "exponential";
60
+ this.timeout = config.timeout || 90000; // 90 seconds for slow historical queries
61
+ this.debug = config.debug || false;
62
+ this.appUrl = config.appUrl;
63
+ this.appName = config.appName;
64
+ // Initialize resources
65
+ this.diesel = new diesel_js_1.DieselResource(this);
66
+ this.alerts = new alerts_js_1.AlertsResource(this);
67
+ this.commodities = new commodities_js_1.CommoditiesResource(this);
68
+ this.futures = new futures_js_1.FuturesResource(this);
69
+ this.storage = new storage_js_1.StorageResource(this);
70
+ this.rigCounts = new rig_counts_js_1.RigCountsResource(this);
71
+ this.bunkerFuels = new bunker_fuels_js_1.BunkerFuelsResource(this);
72
+ this.analytics = new analytics_js_1.AnalyticsResource(this);
73
+ this.forecasts = new forecasts_js_1.ForecastsResource(this);
74
+ this.dataQuality = new data_quality_js_1.DataQualityResource(this);
75
+ this.drilling = new drilling_js_1.DrillingIntelligenceResource(this);
76
+ this.ei = new index_js_1.EnergyIntelligenceResource(this);
77
+ this.webhooks = new webhooks_js_1.WebhooksResource(this);
78
+ this.dataSources = new data_sources_js_1.DataSourcesResource(this);
79
+ this.spreads = new spreads_js_1.SpreadsResource(this);
80
+ this.indicators = new indicators_js_1.IndicatorsResource(this);
81
+ this.raw = new raw_js_1.RawResource(this);
82
+ this.stream = new streaming_js_1.StreamingResource(this);
83
+ }
84
+ /**
85
+ * Log debug messages if debug mode is enabled
86
+ */
87
+ log(message, data) {
88
+ if (this.debug) {
89
+ const timestamp = new Date().toISOString();
90
+ console.log(`[OilPriceAPI ${timestamp}] ${message}`, data || "");
91
+ }
92
+ }
93
+ /**
94
+ * Calculate delay for retry based on strategy
95
+ */
96
+ calculateRetryDelay(attempt) {
97
+ switch (this.retryStrategy) {
98
+ case "exponential":
99
+ return this.retryDelay * Math.pow(2, attempt);
100
+ case "linear":
101
+ return this.retryDelay * (attempt + 1);
102
+ case "fixed":
103
+ default:
104
+ return this.retryDelay;
105
+ }
106
+ }
107
+ /**
108
+ * Sleep for specified milliseconds
109
+ */
110
+ sleep(ms) {
111
+ return new Promise((resolve) => setTimeout(resolve, ms));
112
+ }
113
+ /**
114
+ * Determine if error is retryable
115
+ */
116
+ isRetryable(error) {
117
+ // Retry on network errors
118
+ if (error instanceof TypeError && error.message.includes("fetch")) {
119
+ return true;
120
+ }
121
+ // Retry on timeout errors
122
+ if (error instanceof errors_js_1.TimeoutError) {
123
+ return true;
124
+ }
125
+ // Retry on 5xx server errors
126
+ if (error instanceof errors_js_1.ServerError) {
127
+ return true;
128
+ }
129
+ // Retry on rate limit errors (with delay)
130
+ if (error instanceof errors_js_1.RateLimitError) {
131
+ return true;
132
+ }
133
+ // Don't retry on client errors (4xx except 429)
134
+ return false;
135
+ }
136
+ /**
137
+ * Shape a parsed JSON response body into the value returned to callers.
138
+ *
139
+ * Centralizes the response-structure handling so that both {@link request}
140
+ * and {@link requestRaw} return identical data. Handles the latest/historical
141
+ * envelope shapes as well as the generic `{ data }` fallback used by resource
142
+ * mutations, alerts, webhooks, etc.
143
+ */
144
+ shapeResponseData(responseData) {
145
+ // Handle different response structures
146
+ // Latest endpoint: { status, data: { price, ... } }
147
+ // Historical endpoint: { status, data: { prices: [...] } }
148
+ if (responseData.status === "success" && responseData.data) {
149
+ if (responseData.data.prices) {
150
+ // Historical endpoint - return prices array
151
+ this.log(`Returning ${responseData.data.prices.length} prices`);
152
+ return responseData.data.prices;
153
+ }
154
+ else if (responseData.data.price !== undefined) {
155
+ // Latest endpoint - wrap single price in array
156
+ this.log("Returning single price (wrapped in array)");
157
+ return [responseData.data];
158
+ }
159
+ }
160
+ // Fallback - return data as-is (used by resource mutations, alerts, webhooks, etc.)
161
+ this.log("Returning data as-is");
162
+ return (responseData.data !== undefined ? responseData.data : responseData);
163
+ }
164
+ /**
165
+ * Internal method to make HTTP requests with retry and timeout.
166
+ * Supports all HTTP methods (GET, POST, PATCH, DELETE) with consistent
167
+ * retry logic, timeout handling, and typed error responses.
168
+ */
169
+ async request(endpoint, params, options) {
170
+ const { data } = await this.requestRaw(endpoint, params, options);
171
+ return data;
172
+ }
173
+ /**
174
+ * Internal method identical to {@link request} but returns the underlying
175
+ * HTTP status and headers alongside the parsed data.
176
+ *
177
+ * Used by the public {@link raw} accessor to expose response metadata
178
+ * (issue #7) without changing the return shape of existing methods.
179
+ */
180
+ async requestRaw(endpoint, params, options) {
181
+ // Build URL with query parameters
182
+ const url = new URL(`${this.baseUrl}${endpoint}`);
183
+ if (params) {
184
+ Object.entries(params).forEach(([key, value]) => {
185
+ if (value !== undefined && value !== null) {
186
+ url.searchParams.append(key, value);
187
+ }
188
+ });
189
+ }
190
+ this.log(`Request: ${url.toString()}`);
191
+ let lastError = null;
192
+ // Retry loop
193
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
194
+ try {
195
+ // Add retry info to logs
196
+ if (attempt > 0) {
197
+ this.log(`Retry attempt ${attempt}/${this.retries}`);
198
+ }
199
+ // Create abort controller for timeout
200
+ const controller = new AbortController();
201
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
202
+ try {
203
+ // Build headers with optional telemetry
204
+ const headers = {
205
+ Authorization: `Bearer ${this.apiKey}`,
206
+ "Content-Type": "application/json",
207
+ "User-Agent": (0, version_js_1.buildUserAgent)(),
208
+ "X-SDK-Name": version_js_1.SDK_NAME,
209
+ "X-SDK-Version": version_js_1.SDK_VERSION,
210
+ };
211
+ // Add optional telemetry headers (10% bonus for appUrl!)
212
+ if (this.appUrl) {
213
+ headers["X-App-URL"] = this.appUrl;
214
+ }
215
+ if (this.appName) {
216
+ headers["X-App-Name"] = this.appName;
217
+ }
218
+ const fetchOptions = {
219
+ method: options?.method || "GET",
220
+ headers,
221
+ signal: controller.signal,
222
+ };
223
+ if (options?.body !== undefined) {
224
+ fetchOptions.body = JSON.stringify(options.body);
225
+ }
226
+ const response = await fetch(url.toString(), fetchOptions);
227
+ clearTimeout(timeoutId);
228
+ this.log(`Response: ${response.status} ${response.statusText}`);
229
+ // Handle error responses
230
+ if (!response.ok) {
231
+ const errorBody = await response.text();
232
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
233
+ // Try to parse JSON error response
234
+ try {
235
+ const errorJson = JSON.parse(errorBody);
236
+ errorMessage = errorJson.message || errorJson.error || errorMessage;
237
+ }
238
+ catch {
239
+ // Use default error message if response isn't JSON
240
+ }
241
+ this.log(`Error response: ${errorMessage}`);
242
+ // Throw specific error types based on status code
243
+ switch (response.status) {
244
+ case 401:
245
+ throw new errors_js_1.AuthenticationError(errorMessage);
246
+ case 404:
247
+ throw new errors_js_1.NotFoundError(errorMessage);
248
+ case 429:
249
+ const retryAfter = response.headers.get("Retry-After");
250
+ const rateLimitError = new errors_js_1.RateLimitError(errorMessage, retryAfter ? parseInt(retryAfter, 10) : undefined);
251
+ // If rate limited and we have retries left, wait and retry
252
+ if (attempt < this.retries && rateLimitError.retryAfter) {
253
+ this.log(`Rate limited. Waiting ${rateLimitError.retryAfter}s`);
254
+ await this.sleep(rateLimitError.retryAfter * 1000);
255
+ continue;
256
+ }
257
+ throw rateLimitError;
258
+ case 500:
259
+ case 502:
260
+ case 503:
261
+ case 504:
262
+ throw new errors_js_1.ServerError(errorMessage, response.status);
263
+ default:
264
+ throw new errors_js_1.OilPriceAPIError(errorMessage, response.status, "HTTP_ERROR");
265
+ }
266
+ }
267
+ // Handle empty responses (e.g., 204 No Content from DELETE)
268
+ const responseText = await response.text();
269
+ if (!responseText) {
270
+ this.log("Empty response body");
271
+ return {
272
+ data: {},
273
+ status: response.status,
274
+ headers: response.headers,
275
+ };
276
+ }
277
+ // Parse successful response
278
+ const responseData = JSON.parse(responseText);
279
+ this.log("Response data received", {
280
+ status: responseData.status,
281
+ hasData: !!responseData.data,
282
+ });
283
+ return {
284
+ data: this.shapeResponseData(responseData),
285
+ status: response.status,
286
+ headers: response.headers,
287
+ };
288
+ }
289
+ catch (error) {
290
+ // Handle abort (timeout)
291
+ if (error instanceof Error && error.name === "AbortError") {
292
+ throw new errors_js_1.TimeoutError("Request timeout", this.timeout);
293
+ }
294
+ throw error;
295
+ }
296
+ }
297
+ catch (error) {
298
+ lastError = error;
299
+ this.log(`Request failed: ${lastError.message}`, {
300
+ attempt,
301
+ retryable: this.isRetryable(lastError),
302
+ });
303
+ // Re-throw our custom errors if not retryable
304
+ if (error instanceof errors_js_1.OilPriceAPIError && !this.isRetryable(error)) {
305
+ throw error;
306
+ }
307
+ // If this was our last attempt, throw the error
308
+ if (attempt === this.retries) {
309
+ if (error instanceof errors_js_1.OilPriceAPIError) {
310
+ throw error;
311
+ }
312
+ // Wrap fetch errors (network issues, etc.)
313
+ if (error instanceof Error) {
314
+ throw new errors_js_1.OilPriceAPIError(`Request failed after ${this.retries + 1} attempts: ${error.message}`, undefined, "NETWORK_ERROR");
315
+ }
316
+ throw error;
317
+ }
318
+ // Calculate delay and retry
319
+ const delay = this.calculateRetryDelay(attempt);
320
+ this.log(`Waiting ${delay}ms before retry...`);
321
+ await this.sleep(delay);
322
+ }
323
+ }
324
+ // This should never be reached, but TypeScript wants it
325
+ throw lastError || new errors_js_1.OilPriceAPIError("Unknown error occurred");
326
+ }
327
+ /**
328
+ * Get the latest prices for all commodities or a specific commodity
329
+ *
330
+ * @param options - Optional filters
331
+ * @returns Array of price objects
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * // Get all latest prices
336
+ * const allPrices = await client.getLatestPrices();
337
+ *
338
+ * // Get WTI price only
339
+ * const wti = await client.getLatestPrices({ commodity: 'WTI_USD' });
340
+ * ```
341
+ */
342
+ async getLatestPrices(options) {
343
+ const params = {};
344
+ if (options?.commodity) {
345
+ params.by_code = options.commodity;
346
+ }
347
+ return this.request("/v1/prices/latest", params);
348
+ }
349
+ /**
350
+ * Get historical prices for a time period
351
+ *
352
+ * @param options - Time period and filter options
353
+ * @returns Array of historical price objects
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * // Get past week of WTI prices
358
+ * const weekPrices = await client.getHistoricalPrices({
359
+ * period: 'past_week',
360
+ * commodity: 'WTI_USD'
361
+ * });
362
+ *
363
+ * // Get custom date range
364
+ * const customPrices = await client.getHistoricalPrices({
365
+ * startDate: '2024-01-01',
366
+ * endDate: '2024-12-31',
367
+ * commodity: 'BRENT_CRUDE_USD'
368
+ * });
369
+ * ```
370
+ */
371
+ async getHistoricalPrices(options) {
372
+ const params = {};
373
+ if (options?.period) {
374
+ params.period = options.period;
375
+ }
376
+ if (options?.commodity) {
377
+ params.by_code = options.commodity;
378
+ }
379
+ if (options?.startDate) {
380
+ params.start_date = options.startDate;
381
+ }
382
+ if (options?.endDate) {
383
+ params.end_date = options.endDate;
384
+ }
385
+ // PERFORMANCE FIX (December 24, 2025):
386
+ // Pass interval parameter to enable aggregated queries
387
+ // This reduces response times from 74s to <1s for year-long queries
388
+ // by returning 365 daily points instead of 600k+ raw points
389
+ if (options?.interval) {
390
+ params.interval = options.interval;
391
+ }
392
+ // Pagination parameters
393
+ if (options?.perPage !== undefined) {
394
+ params.per_page = options.perPage.toString();
395
+ }
396
+ if (options?.page !== undefined) {
397
+ params.page = options.page.toString();
398
+ }
399
+ // CRITICAL FIX (December 17, 2025):
400
+ // Use /v1/prices/past_year endpoint instead of /v1/prices
401
+ // The /v1/prices endpoint does NOT correctly handle start_date/end_date parameters
402
+ // This was the same bug that affected the Python SDK (fixed in v1.4.4)
403
+ // Issue: SDK was returning wrong dates for historical queries
404
+ // Root Cause: Backend has_scope :by_period not working on /v1/prices
405
+ // Solution: Use /v1/prices/past_year which uses direct WHERE clauses
406
+ return this.request("/v1/prices/past_year", params);
407
+ }
408
+ /**
409
+ * Paginate through historical prices automatically.
410
+ *
411
+ * Returns an async generator that yields pages of prices, fetching
412
+ * the next page only when needed. Avoids loading all data into memory.
413
+ *
414
+ * @param options - Same options as getHistoricalPrices, plus perPage (default: 100)
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * // Iterate through all pages
419
+ * for await (const page of client.paginateHistoricalPrices({
420
+ * commodity: 'BRENT_CRUDE_USD',
421
+ * startDate: '2024-01-01',
422
+ * endDate: '2024-12-31',
423
+ * perPage: 100,
424
+ * })) {
425
+ * console.log(`Got ${page.length} prices`);
426
+ * // Process each page...
427
+ * }
428
+ *
429
+ * // Or collect all prices
430
+ * const allPrices: Price[] = [];
431
+ * for await (const page of client.paginateHistoricalPrices({ commodity: 'WTI_USD' })) {
432
+ * allPrices.push(...page);
433
+ * }
434
+ * ```
435
+ */
436
+ async *paginateHistoricalPrices(options) {
437
+ const perPage = options?.perPage || 100;
438
+ let page = 1;
439
+ while (true) {
440
+ const results = await this.getHistoricalPrices({
441
+ ...options,
442
+ page,
443
+ perPage,
444
+ });
445
+ if (results.length === 0)
446
+ break;
447
+ yield results;
448
+ if (results.length < perPage)
449
+ break;
450
+ page++;
451
+ }
452
+ }
453
+ /**
454
+ * Get prices from your connected data sources (BYOS)
455
+ *
456
+ * Requires Data Connector feature enabled on your organization.
457
+ *
458
+ * @example
459
+ * ```typescript
460
+ * // Get all connected prices
461
+ * const prices = await client.getDataConnectorPrices();
462
+ *
463
+ * // Filter by fuel type
464
+ * const vlsfo = await client.getDataConnectorPrices({ fuelType: 'VLSFO' });
465
+ *
466
+ * // Filter by port
467
+ * const singapore = await client.getDataConnectorPrices({ port: 'SINGAPORE' });
468
+ * ```
469
+ */
470
+ async getDataConnectorPrices(options = {}) {
471
+ const params = {};
472
+ if (options.fuelType)
473
+ params.fuel_type = options.fuelType;
474
+ if (options.port)
475
+ params.port = options.port;
476
+ if (options.region)
477
+ params.region = options.region;
478
+ if (options.since)
479
+ params.since = options.since;
480
+ const response = await this.request("/v1/prices/data-connector", params);
481
+ return response.prices;
482
+ }
483
+ /**
484
+ * Get metadata for all supported commodities
485
+ *
486
+ * @returns Object containing array of commodities
487
+ *
488
+ * @example
489
+ * ```typescript
490
+ * const response = await client.getCommodities();
491
+ * console.log(response.commodities); // Array of commodity objects
492
+ * ```
493
+ */
494
+ async getCommodities() {
495
+ return this.request("/v1/commodities", {});
496
+ }
497
+ /**
498
+ * Get all commodity categories with their commodities
499
+ *
500
+ * @returns Object with category keys mapped to category objects
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * const categories = await client.getCommodityCategories();
505
+ * console.log(categories.oil.name); // "Oil"
506
+ * console.log(categories.oil.commodities.length); // 11
507
+ * ```
508
+ */
509
+ async getCommodityCategories() {
510
+ return this.request("/v1/commodities/categories", {});
511
+ }
512
+ /**
513
+ * Get metadata for a specific commodity by code
514
+ *
515
+ * @param code - Commodity code (e.g., "WTI_USD", "BRENT_CRUDE_USD")
516
+ * @returns Commodity metadata object
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * const commodity = await client.getCommodity('WTI_USD');
521
+ * console.log(commodity.name); // "WTI Crude Oil"
522
+ * ```
523
+ */
524
+ async getCommodity(code) {
525
+ return this.request(`/v1/commodities/${code}`, {});
526
+ }
527
+ /**
528
+ * Fetch live sample prices from the public, no-auth demo endpoint.
529
+ *
530
+ * Hits `GET /v1/demo/prices` (no API key required) and returns the parsed
531
+ * `{ prices, meta }` envelope. Useful for trying the client without
532
+ * credentials. Subject to the demo rate limit (~20 requests/hour).
533
+ *
534
+ * @example
535
+ * ```typescript
536
+ * const demo = await client.getDemoPrices();
537
+ * const brent = demo.prices.find(p => p.code === 'BRENT_CRUDE_USD');
538
+ * console.log(brent?.price);
539
+ * ```
540
+ */
541
+ async getDemoPrices() {
542
+ return this.requestDemo("/v1/demo/prices");
543
+ }
544
+ /**
545
+ * Fetch the catalogue of commodities from the public, no-auth demo endpoint.
546
+ *
547
+ * Hits `GET /v1/demo/commodities` (no API key required) and returns the parsed
548
+ * `{ commodities, meta }` envelope, where `meta.free_commodities` lists the
549
+ * codes available on the free demo tier.
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * const demo = await client.getDemoCommodities();
554
+ * console.log(demo.meta.total, demo.meta.free_commodities);
555
+ * ```
556
+ */
557
+ async getDemoCommodities() {
558
+ return this.requestDemo("/v1/demo/commodities");
559
+ }
560
+ /**
561
+ * Minimal fetch for the no-auth demo endpoints.
562
+ *
563
+ * Unlike {@link request}, this does NOT run the latest/historical response
564
+ * shaping (which would strip the `meta` block) and does NOT require an API
565
+ * key — it returns the raw `data` envelope from `{ status, data }`.
566
+ */
567
+ async requestDemo(endpoint) {
568
+ const url = `${this.baseUrl}${endpoint}`;
569
+ this.log(`Demo request: ${url}`);
570
+ const controller = new AbortController();
571
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
572
+ try {
573
+ const response = await fetch(url, {
574
+ method: "GET",
575
+ headers: {
576
+ "Content-Type": "application/json",
577
+ "User-Agent": (0, version_js_1.buildUserAgent)(),
578
+ "X-SDK-Name": version_js_1.SDK_NAME,
579
+ "X-SDK-Version": version_js_1.SDK_VERSION,
580
+ },
581
+ signal: controller.signal,
582
+ });
583
+ if (!response.ok) {
584
+ const body = await response.text();
585
+ let message = `HTTP ${response.status}: ${response.statusText}`;
586
+ try {
587
+ const json = JSON.parse(body);
588
+ message = json.message || json.error || message;
589
+ }
590
+ catch {
591
+ // non-JSON error body
592
+ }
593
+ throw new errors_js_1.OilPriceAPIError(message, response.status, "HTTP_ERROR");
594
+ }
595
+ const parsed = JSON.parse(await response.text());
596
+ // Demo envelope is { status: "success", data: { ... } }.
597
+ return (parsed.data !== undefined ? parsed.data : parsed);
598
+ }
599
+ catch (error) {
600
+ if (error instanceof Error && error.name === "AbortError") {
601
+ throw new errors_js_1.TimeoutError("Request timeout", this.timeout);
602
+ }
603
+ throw error;
604
+ }
605
+ finally {
606
+ clearTimeout(timeoutId);
607
+ }
608
+ }
609
+ }
610
+ 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;