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/dist/client.js
CHANGED
|
@@ -41,11 +41,11 @@ import { SDK_VERSION, SDK_NAME, buildUserAgent } from "./version.js";
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
export class OilPriceAPI {
|
|
44
|
-
constructor(config) {
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
constructor(config = {}) {
|
|
45
|
+
this.apiKey = config.apiKey || process.env.OILPRICEAPI_KEY || "";
|
|
46
|
+
if (!this.apiKey) {
|
|
47
|
+
throw new OilPriceAPIError("API key required. Set OILPRICEAPI_KEY env var or pass apiKey in config.");
|
|
47
48
|
}
|
|
48
|
-
this.apiKey = config.apiKey;
|
|
49
49
|
this.baseUrl = config.baseUrl || "https://api.oilpriceapi.com";
|
|
50
50
|
this.retries = config.retries !== undefined ? config.retries : 3;
|
|
51
51
|
this.retryDelay = config.retryDelay || 1000;
|
|
@@ -123,9 +123,11 @@ export class OilPriceAPI {
|
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
* Internal method to make HTTP requests with retry and timeout
|
|
126
|
+
* Internal method to make HTTP requests with retry and timeout.
|
|
127
|
+
* Supports all HTTP methods (GET, POST, PATCH, DELETE) with consistent
|
|
128
|
+
* retry logic, timeout handling, and typed error responses.
|
|
127
129
|
*/
|
|
128
|
-
async request(endpoint, params) {
|
|
130
|
+
async request(endpoint, params, options) {
|
|
129
131
|
// Build URL with query parameters
|
|
130
132
|
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
131
133
|
if (params) {
|
|
@@ -163,11 +165,15 @@ export class OilPriceAPI {
|
|
|
163
165
|
if (this.appName) {
|
|
164
166
|
headers["X-App-Name"] = this.appName;
|
|
165
167
|
}
|
|
166
|
-
const
|
|
167
|
-
method: "GET",
|
|
168
|
+
const fetchOptions = {
|
|
169
|
+
method: options?.method || "GET",
|
|
168
170
|
headers,
|
|
169
171
|
signal: controller.signal,
|
|
170
|
-
}
|
|
172
|
+
};
|
|
173
|
+
if (options?.body !== undefined) {
|
|
174
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
175
|
+
}
|
|
176
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
171
177
|
clearTimeout(timeoutId);
|
|
172
178
|
this.log(`Response: ${response.status} ${response.statusText}`);
|
|
173
179
|
// Handle error responses
|
|
@@ -177,8 +183,7 @@ export class OilPriceAPI {
|
|
|
177
183
|
// Try to parse JSON error response
|
|
178
184
|
try {
|
|
179
185
|
const errorJson = JSON.parse(errorBody);
|
|
180
|
-
errorMessage =
|
|
181
|
-
errorJson.message || errorJson.error || errorMessage;
|
|
186
|
+
errorMessage = errorJson.message || errorJson.error || errorMessage;
|
|
182
187
|
}
|
|
183
188
|
catch {
|
|
184
189
|
// Use default error message if response isn't JSON
|
|
@@ -209,8 +214,14 @@ export class OilPriceAPI {
|
|
|
209
214
|
throw new OilPriceAPIError(errorMessage, response.status, "HTTP_ERROR");
|
|
210
215
|
}
|
|
211
216
|
}
|
|
217
|
+
// Handle empty responses (e.g., 204 No Content from DELETE)
|
|
218
|
+
const responseText = await response.text();
|
|
219
|
+
if (!responseText) {
|
|
220
|
+
this.log("Empty response body");
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
212
223
|
// Parse successful response
|
|
213
|
-
const responseData =
|
|
224
|
+
const responseData = JSON.parse(responseText);
|
|
214
225
|
this.log("Response data received", {
|
|
215
226
|
status: responseData.status,
|
|
216
227
|
hasData: !!responseData.data,
|
|
@@ -230,9 +241,9 @@ export class OilPriceAPI {
|
|
|
230
241
|
return [responseData.data];
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
|
-
// Fallback - return data as-is
|
|
244
|
+
// Fallback - return data as-is (used by resource mutations, alerts, webhooks, etc.)
|
|
234
245
|
this.log("Returning data as-is");
|
|
235
|
-
return responseData.data;
|
|
246
|
+
return (responseData.data !== undefined ? responseData.data : responseData);
|
|
236
247
|
}
|
|
237
248
|
catch (error) {
|
|
238
249
|
// Handle abort (timeout)
|
|
@@ -353,6 +364,51 @@ export class OilPriceAPI {
|
|
|
353
364
|
// Solution: Use /v1/prices/past_year which uses direct WHERE clauses
|
|
354
365
|
return this.request("/v1/prices/past_year", params);
|
|
355
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Paginate through historical prices automatically.
|
|
369
|
+
*
|
|
370
|
+
* Returns an async generator that yields pages of prices, fetching
|
|
371
|
+
* the next page only when needed. Avoids loading all data into memory.
|
|
372
|
+
*
|
|
373
|
+
* @param options - Same options as getHistoricalPrices, plus perPage (default: 100)
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* // Iterate through all pages
|
|
378
|
+
* for await (const page of client.paginateHistoricalPrices({
|
|
379
|
+
* commodity: 'BRENT_CRUDE_USD',
|
|
380
|
+
* startDate: '2024-01-01',
|
|
381
|
+
* endDate: '2024-12-31',
|
|
382
|
+
* perPage: 100,
|
|
383
|
+
* })) {
|
|
384
|
+
* console.log(`Got ${page.length} prices`);
|
|
385
|
+
* // Process each page...
|
|
386
|
+
* }
|
|
387
|
+
*
|
|
388
|
+
* // Or collect all prices
|
|
389
|
+
* const allPrices: Price[] = [];
|
|
390
|
+
* for await (const page of client.paginateHistoricalPrices({ commodity: 'WTI_USD' })) {
|
|
391
|
+
* allPrices.push(...page);
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
async *paginateHistoricalPrices(options) {
|
|
396
|
+
const perPage = options?.perPage || 100;
|
|
397
|
+
let page = 1;
|
|
398
|
+
while (true) {
|
|
399
|
+
const results = await this.getHistoricalPrices({
|
|
400
|
+
...options,
|
|
401
|
+
page,
|
|
402
|
+
perPage,
|
|
403
|
+
});
|
|
404
|
+
if (results.length === 0)
|
|
405
|
+
break;
|
|
406
|
+
yield results;
|
|
407
|
+
if (results.length < perPage)
|
|
408
|
+
break;
|
|
409
|
+
page++;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
356
412
|
/**
|
|
357
413
|
* Get prices from your connected data sources (BYOS)
|
|
358
414
|
*
|
package/dist/errors.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export declare class NotFoundError extends OilPriceAPIError {
|
|
|
40
40
|
export declare class ServerError extends OilPriceAPIError {
|
|
41
41
|
constructor(message?: string, statusCode?: number);
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown when SDK-side input validation fails
|
|
45
|
+
*/
|
|
46
|
+
export declare class ValidationError extends OilPriceAPIError {
|
|
47
|
+
constructor(message: string);
|
|
48
|
+
}
|
|
43
49
|
/**
|
|
44
50
|
* Thrown when request exceeds timeout
|
|
45
51
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export class OilPriceAPIError extends Error {
|
|
5
5
|
constructor(message, statusCode, code) {
|
|
6
6
|
super(message);
|
|
7
|
-
this.name =
|
|
7
|
+
this.name = "OilPriceAPIError";
|
|
8
8
|
this.statusCode = statusCode;
|
|
9
9
|
this.code = code;
|
|
10
10
|
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
@@ -17,18 +17,18 @@ export class OilPriceAPIError extends Error {
|
|
|
17
17
|
* Thrown when API authentication fails (401)
|
|
18
18
|
*/
|
|
19
19
|
export class AuthenticationError extends OilPriceAPIError {
|
|
20
|
-
constructor(message =
|
|
21
|
-
super(message, 401,
|
|
22
|
-
this.name =
|
|
20
|
+
constructor(message = "Invalid API key") {
|
|
21
|
+
super(message, 401, "AUTHENTICATION_ERROR");
|
|
22
|
+
this.name = "AuthenticationError";
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Thrown when rate limit is exceeded (429)
|
|
27
27
|
*/
|
|
28
28
|
export class RateLimitError extends OilPriceAPIError {
|
|
29
|
-
constructor(message =
|
|
30
|
-
super(message, 429,
|
|
31
|
-
this.name =
|
|
29
|
+
constructor(message = "Rate limit exceeded", retryAfter) {
|
|
30
|
+
super(message, 429, "RATE_LIMIT_ERROR");
|
|
31
|
+
this.name = "RateLimitError";
|
|
32
32
|
this.retryAfter = retryAfter;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -36,26 +36,35 @@ export class RateLimitError extends OilPriceAPIError {
|
|
|
36
36
|
* Thrown when requested resource is not found (404)
|
|
37
37
|
*/
|
|
38
38
|
export class NotFoundError extends OilPriceAPIError {
|
|
39
|
-
constructor(message =
|
|
40
|
-
super(message, 404,
|
|
41
|
-
this.name =
|
|
39
|
+
constructor(message = "Resource not found") {
|
|
40
|
+
super(message, 404, "NOT_FOUND_ERROR");
|
|
41
|
+
this.name = "NotFoundError";
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Thrown when server returns 5xx error
|
|
46
46
|
*/
|
|
47
47
|
export class ServerError extends OilPriceAPIError {
|
|
48
|
-
constructor(message =
|
|
49
|
-
super(message, statusCode,
|
|
50
|
-
this.name =
|
|
48
|
+
constructor(message = "Internal server error", statusCode = 500) {
|
|
49
|
+
super(message, statusCode, "SERVER_ERROR");
|
|
50
|
+
this.name = "ServerError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Thrown when SDK-side input validation fails
|
|
55
|
+
*/
|
|
56
|
+
export class ValidationError extends OilPriceAPIError {
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message, undefined, "VALIDATION_ERROR");
|
|
59
|
+
this.name = "ValidationError";
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
63
|
* Thrown when request exceeds timeout
|
|
55
64
|
*/
|
|
56
65
|
export class TimeoutError extends OilPriceAPIError {
|
|
57
|
-
constructor(message =
|
|
58
|
-
super(`${message} (${timeout}ms)`, undefined,
|
|
59
|
-
this.name =
|
|
66
|
+
constructor(message = "Request timeout", timeout) {
|
|
67
|
+
super(`${message} (${timeout}ms)`, undefined, "TIMEOUT_ERROR");
|
|
68
|
+
this.name = "TimeoutError";
|
|
60
69
|
}
|
|
61
70
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,13 +15,13 @@ export type { StorageData, HistoricalStorageData, HistoricalStorageOptions, } fr
|
|
|
15
15
|
export type { RigCountData, HistoricalRigCountData, HistoricalRigCountOptions, RigCountTrend, RigCountSummary, } from "./resources/rig-counts.js";
|
|
16
16
|
export type { BunkerFuelPrice, PortBunkerPrices, PortPriceComparison, BunkerFuelSpreads, HistoricalBunkerPrice, HistoricalBunkerOptions, } from "./resources/bunker-fuels.js";
|
|
17
17
|
export type { PerformanceMetrics, PerformanceOptions, StatisticalAnalysis, CorrelationAnalysis, TrendAnalysis, SpreadAnalysis, ForecastPoint, PriceForecast as AnalyticsPriceForecast, } from "./resources/analytics.js";
|
|
18
|
-
export type { MonthlyForecast, ForecastAccuracy, ArchivedForecast
|
|
18
|
+
export type { MonthlyForecast, ForecastAccuracy, ArchivedForecast } from "./resources/forecasts.js";
|
|
19
19
|
export type { DataQualitySummary, DataQualityReportMeta, DataQualityReport, } from "./resources/data-quality.js";
|
|
20
20
|
export type { DrillingIntelligenceData, LatestDrillingData, DrillingSummary, DrillingTrend, FracSpreadData, WellPermitData, DUCWellData, CompletionData, WellsDrilledData, BasinDrillingData, } from "./resources/drilling.js";
|
|
21
21
|
export type { WellTimelineEvent, WellTimeline, RigCountRecord, RigCountByBasin, RigCountByState, HistoricalRigCount, OilInventoryRecord, OilInventorySummary, InventoryByProduct, HistoricalInventory, CushingInventory, OPECProductionRecord, TotalOPECProduction, ProductionByCountry, HistoricalProduction, TopProducer, DrillingProductivityRecord, DrillingProductivitySummary, DUCWellInventory, ProductivityByBasin, HistoricalProductivity, ProductivityTrend, ForecastRecord, ForecastSummary, PriceForecast, ProductionForecast, HistoricalForecast, ForecastComparison, WellPermitRecord, WellPermitSummary, PermitsByState, PermitsByOperator, PermitsByFormation, WellPermitSearchQuery, FracFocusRecord, FracFocusSummary, DisclosuresByState, DisclosuresByOperator, ChemicalUsage, WellChemical, FracFocusSearchQuery, } from "./resources/ei/index.js";
|
|
22
22
|
export type { WebhookEndpoint, CreateWebhookParams, UpdateWebhookParams, WebhookTestResponse as WebhookTestResult, WebhookEvent, } from "./resources/webhooks.js";
|
|
23
23
|
export type { DataSourceType, DataSourceStatus, DataSource, CreateDataSourceParams, UpdateDataSourceParams, DataSourceTestResponse, DataSourceLog, DataSourceHealth, CredentialRotationResponse, } from "./resources/data-sources.js";
|
|
24
|
-
export { OilPriceAPIError, AuthenticationError, RateLimitError, NotFoundError, ServerError, TimeoutError, } from "./errors.js";
|
|
24
|
+
export { OilPriceAPIError, AuthenticationError, RateLimitError, NotFoundError, ServerError, ValidationError, TimeoutError, } from "./errors.js";
|
|
25
25
|
export { DieselResource } from "./resources/diesel.js";
|
|
26
26
|
export { AlertsResource } from "./resources/alerts.js";
|
|
27
27
|
export { CommoditiesResource } from "./resources/commodities.js";
|
|
@@ -36,3 +36,17 @@ export { DrillingIntelligenceResource } from "./resources/drilling.js";
|
|
|
36
36
|
export { EnergyIntelligenceResource, EIRigCountsResource, EIOilInventoriesResource, EIOPECProductionResource, EIDrillingProductivityResource, EIForecastsResource, EIWellPermitsResource, EIFracFocusResource, } from "./resources/ei/index.js";
|
|
37
37
|
export { WebhooksResource } from "./resources/webhooks.js";
|
|
38
38
|
export { DataSourcesResource } from "./resources/data-sources.js";
|
|
39
|
+
/**
|
|
40
|
+
* Standalone webhook signature verification.
|
|
41
|
+
*
|
|
42
|
+
* Convenience function for verifying webhook signatures without
|
|
43
|
+
* instantiating a full client.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { verifyWebhookSignature } from 'oilpriceapi';
|
|
48
|
+
*
|
|
49
|
+
* const isValid = verifyWebhookSignature(rawBody, signatureHeader, secret);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
8
9
|
export { OilPriceAPI } from "./client.js";
|
|
9
10
|
export { SDK_VERSION, SDK_NAME } from "./version.js";
|
|
10
|
-
export { OilPriceAPIError, AuthenticationError, RateLimitError, NotFoundError, ServerError, TimeoutError, } from "./errors.js";
|
|
11
|
+
export { OilPriceAPIError, AuthenticationError, RateLimitError, NotFoundError, ServerError, ValidationError, TimeoutError, } from "./errors.js";
|
|
11
12
|
export { DieselResource } from "./resources/diesel.js";
|
|
12
13
|
export { AlertsResource } from "./resources/alerts.js";
|
|
13
14
|
export { CommoditiesResource } from "./resources/commodities.js";
|
|
@@ -22,3 +23,25 @@ export { DrillingIntelligenceResource } from "./resources/drilling.js";
|
|
|
22
23
|
export { EnergyIntelligenceResource, EIRigCountsResource, EIOilInventoriesResource, EIOPECProductionResource, EIDrillingProductivityResource, EIForecastsResource, EIWellPermitsResource, EIFracFocusResource, } from "./resources/ei/index.js";
|
|
23
24
|
export { WebhooksResource } from "./resources/webhooks.js";
|
|
24
25
|
export { DataSourcesResource } from "./resources/data-sources.js";
|
|
26
|
+
/**
|
|
27
|
+
* Standalone webhook signature verification.
|
|
28
|
+
*
|
|
29
|
+
* Convenience function for verifying webhook signatures without
|
|
30
|
+
* instantiating a full client.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { verifyWebhookSignature } from 'oilpriceapi';
|
|
35
|
+
*
|
|
36
|
+
* const isValid = verifyWebhookSignature(rawBody, signatureHeader, secret);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function verifyWebhookSignature(payload, signature, secret) {
|
|
40
|
+
const expectedSignature = "sha256=" + createHmac("sha256", secret).update(payload).digest("hex");
|
|
41
|
+
try {
|
|
42
|
+
return timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(signature));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/resources/alerts.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Manage price alert configurations for automated notifications.
|
|
5
5
|
*/
|
|
6
|
+
import { ValidationError } from "../errors.js";
|
|
6
7
|
/**
|
|
7
8
|
* Price Alerts Resource
|
|
8
9
|
*
|
|
@@ -100,7 +101,7 @@ export class AlertsResource {
|
|
|
100
101
|
*/
|
|
101
102
|
async get(id) {
|
|
102
103
|
if (!id || typeof id !== "string") {
|
|
103
|
-
throw new
|
|
104
|
+
throw new ValidationError("Alert ID must be a non-empty string");
|
|
104
105
|
}
|
|
105
106
|
const response = await this.client["request"](`/v1/alerts/${id}`, {});
|
|
106
107
|
// API returns object directly, but handle both formats for compatibility
|
|
@@ -146,16 +147,16 @@ export class AlertsResource {
|
|
|
146
147
|
async create(params) {
|
|
147
148
|
// Validate required fields
|
|
148
149
|
if (!params.name || typeof params.name !== "string") {
|
|
149
|
-
throw new
|
|
150
|
+
throw new ValidationError("Alert name is required and must be a string");
|
|
150
151
|
}
|
|
151
152
|
if (params.name.length < 1 || params.name.length > 100) {
|
|
152
|
-
throw new
|
|
153
|
+
throw new ValidationError("Alert name must be 1-100 characters");
|
|
153
154
|
}
|
|
154
155
|
if (!params.commodity_code || typeof params.commodity_code !== "string") {
|
|
155
|
-
throw new
|
|
156
|
+
throw new ValidationError("Commodity code is required and must be a string");
|
|
156
157
|
}
|
|
157
158
|
if (!params.condition_operator) {
|
|
158
|
-
throw new
|
|
159
|
+
throw new ValidationError("Condition operator is required");
|
|
159
160
|
}
|
|
160
161
|
const validOperators = [
|
|
161
162
|
"greater_than",
|
|
@@ -165,39 +166,34 @@ export class AlertsResource {
|
|
|
165
166
|
"less_than_or_equal",
|
|
166
167
|
];
|
|
167
168
|
if (!validOperators.includes(params.condition_operator)) {
|
|
168
|
-
throw new
|
|
169
|
+
throw new ValidationError(`Invalid operator. Must be one of: ${validOperators.join(", ")}`);
|
|
169
170
|
}
|
|
170
171
|
if (typeof params.condition_value !== "number") {
|
|
171
|
-
throw new
|
|
172
|
+
throw new ValidationError("Condition value must be a number");
|
|
172
173
|
}
|
|
173
174
|
if (params.condition_value <= 0 || params.condition_value > 1000000) {
|
|
174
|
-
throw new
|
|
175
|
+
throw new ValidationError("Condition value must be greater than 0 and less than or equal to 1,000,000");
|
|
175
176
|
}
|
|
176
177
|
// Validate optional fields
|
|
177
178
|
if (params.webhook_url !== undefined) {
|
|
178
179
|
if (typeof params.webhook_url !== "string") {
|
|
179
|
-
throw new
|
|
180
|
+
throw new ValidationError("Webhook URL must be a string");
|
|
180
181
|
}
|
|
181
182
|
if (params.webhook_url && !params.webhook_url.startsWith("https://")) {
|
|
182
|
-
throw new
|
|
183
|
+
throw new ValidationError("Webhook URL must use HTTPS protocol");
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
if (params.cooldown_minutes !== undefined) {
|
|
186
187
|
if (typeof params.cooldown_minutes !== "number") {
|
|
187
|
-
throw new
|
|
188
|
+
throw new ValidationError("Cooldown minutes must be a number");
|
|
188
189
|
}
|
|
189
190
|
if (params.cooldown_minutes < 0 || params.cooldown_minutes > 1440) {
|
|
190
|
-
throw new
|
|
191
|
+
throw new ValidationError("Cooldown minutes must be between 0 and 1440 (24 hours)");
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
|
-
const
|
|
194
|
-
const response = await fetch(url, {
|
|
194
|
+
const response = await this.client["request"]("/v1/alerts", {}, {
|
|
195
195
|
method: "POST",
|
|
196
|
-
|
|
197
|
-
Authorization: `Bearer ${this.client["apiKey"]}`,
|
|
198
|
-
"Content-Type": "application/json",
|
|
199
|
-
},
|
|
200
|
-
body: JSON.stringify({
|
|
196
|
+
body: {
|
|
201
197
|
price_alert: {
|
|
202
198
|
name: params.name,
|
|
203
199
|
commodity_code: params.commodity_code,
|
|
@@ -208,15 +204,9 @@ export class AlertsResource {
|
|
|
208
204
|
cooldown_minutes: params.cooldown_minutes ?? 60,
|
|
209
205
|
metadata: params.metadata,
|
|
210
206
|
},
|
|
211
|
-
}
|
|
207
|
+
},
|
|
212
208
|
});
|
|
213
|
-
|
|
214
|
-
const errorText = await response.text();
|
|
215
|
-
throw new Error(`Failed to create alert: ${response.status} ${errorText}`);
|
|
216
|
-
}
|
|
217
|
-
const data = (await response.json());
|
|
218
|
-
// API returns object directly, but handle both formats for compatibility
|
|
219
|
-
return "alert" in data ? data.alert : data;
|
|
209
|
+
return "alert" in response ? response.alert : response;
|
|
220
210
|
}
|
|
221
211
|
/**
|
|
222
212
|
* Update an existing price alert
|
|
@@ -251,14 +241,14 @@ export class AlertsResource {
|
|
|
251
241
|
*/
|
|
252
242
|
async update(id, params) {
|
|
253
243
|
if (!id || typeof id !== "string") {
|
|
254
|
-
throw new
|
|
244
|
+
throw new ValidationError("Alert ID must be a non-empty string");
|
|
255
245
|
}
|
|
256
246
|
// Validate fields if provided
|
|
257
247
|
if (params.name !== undefined) {
|
|
258
248
|
if (typeof params.name !== "string" ||
|
|
259
249
|
params.name.length < 1 ||
|
|
260
250
|
params.name.length > 100) {
|
|
261
|
-
throw new
|
|
251
|
+
throw new ValidationError("Alert name must be 1-100 characters");
|
|
262
252
|
}
|
|
263
253
|
}
|
|
264
254
|
if (params.condition_operator !== undefined) {
|
|
@@ -270,48 +260,35 @@ export class AlertsResource {
|
|
|
270
260
|
"less_than_or_equal",
|
|
271
261
|
];
|
|
272
262
|
if (!validOperators.includes(params.condition_operator)) {
|
|
273
|
-
throw new
|
|
263
|
+
throw new ValidationError(`Invalid operator. Must be one of: ${validOperators.join(", ")}`);
|
|
274
264
|
}
|
|
275
265
|
}
|
|
276
266
|
if (params.condition_value !== undefined) {
|
|
277
267
|
if (typeof params.condition_value !== "number") {
|
|
278
|
-
throw new
|
|
268
|
+
throw new ValidationError("Condition value must be a number");
|
|
279
269
|
}
|
|
280
270
|
if (params.condition_value <= 0 || params.condition_value > 1000000) {
|
|
281
|
-
throw new
|
|
271
|
+
throw new ValidationError("Condition value must be greater than 0 and less than or equal to 1,000,000");
|
|
282
272
|
}
|
|
283
273
|
}
|
|
284
274
|
if (params.webhook_url !== undefined && params.webhook_url !== null) {
|
|
285
275
|
if (typeof params.webhook_url !== "string" ||
|
|
286
276
|
!params.webhook_url.startsWith("https://")) {
|
|
287
|
-
throw new
|
|
277
|
+
throw new ValidationError("Webhook URL must be a valid HTTPS URL");
|
|
288
278
|
}
|
|
289
279
|
}
|
|
290
280
|
if (params.cooldown_minutes !== undefined) {
|
|
291
281
|
if (typeof params.cooldown_minutes !== "number" ||
|
|
292
282
|
params.cooldown_minutes < 0 ||
|
|
293
283
|
params.cooldown_minutes > 1440) {
|
|
294
|
-
throw new
|
|
284
|
+
throw new ValidationError("Cooldown minutes must be between 0 and 1440 (24 hours)");
|
|
295
285
|
}
|
|
296
286
|
}
|
|
297
|
-
const
|
|
298
|
-
const response = await fetch(url, {
|
|
287
|
+
const response = await this.client["request"](`/v1/alerts/${id}`, {}, {
|
|
299
288
|
method: "PATCH",
|
|
300
|
-
|
|
301
|
-
Authorization: `Bearer ${this.client["apiKey"]}`,
|
|
302
|
-
"Content-Type": "application/json",
|
|
303
|
-
},
|
|
304
|
-
body: JSON.stringify({
|
|
305
|
-
price_alert: params,
|
|
306
|
-
}),
|
|
289
|
+
body: { price_alert: params },
|
|
307
290
|
});
|
|
308
|
-
|
|
309
|
-
const errorText = await response.text();
|
|
310
|
-
throw new Error(`Failed to update alert: ${response.status} ${errorText}`);
|
|
311
|
-
}
|
|
312
|
-
const data = (await response.json());
|
|
313
|
-
// API returns object directly, but handle both formats for compatibility
|
|
314
|
-
return "alert" in data ? data.alert : data;
|
|
291
|
+
return "alert" in response ? response.alert : response;
|
|
315
292
|
}
|
|
316
293
|
/**
|
|
317
294
|
* Delete a price alert
|
|
@@ -331,20 +308,9 @@ export class AlertsResource {
|
|
|
331
308
|
*/
|
|
332
309
|
async delete(id) {
|
|
333
310
|
if (!id || typeof id !== "string") {
|
|
334
|
-
throw new
|
|
335
|
-
}
|
|
336
|
-
const url = `${this.client["baseUrl"]}/v1/alerts/${id}`;
|
|
337
|
-
const response = await fetch(url, {
|
|
338
|
-
method: "DELETE",
|
|
339
|
-
headers: {
|
|
340
|
-
Authorization: `Bearer ${this.client["apiKey"]}`,
|
|
341
|
-
"Content-Type": "application/json",
|
|
342
|
-
},
|
|
343
|
-
});
|
|
344
|
-
if (!response.ok) {
|
|
345
|
-
const errorText = await response.text();
|
|
346
|
-
throw new Error(`Failed to delete alert: ${response.status} ${errorText}`);
|
|
311
|
+
throw new ValidationError("Alert ID must be a non-empty string");
|
|
347
312
|
}
|
|
313
|
+
await this.client["request"](`/v1/alerts/${id}`, {}, { method: "DELETE" });
|
|
348
314
|
}
|
|
349
315
|
/**
|
|
350
316
|
* Test an alert
|
|
@@ -369,21 +335,9 @@ export class AlertsResource {
|
|
|
369
335
|
*/
|
|
370
336
|
async test(alertId) {
|
|
371
337
|
if (!alertId || typeof alertId !== "string") {
|
|
372
|
-
throw new
|
|
373
|
-
}
|
|
374
|
-
const url = `${this.client["baseUrl"]}/v1/alerts/${alertId}/test`;
|
|
375
|
-
const response = await fetch(url, {
|
|
376
|
-
method: "POST",
|
|
377
|
-
headers: {
|
|
378
|
-
Authorization: `Bearer ${this.client["apiKey"]}`,
|
|
379
|
-
"Content-Type": "application/json",
|
|
380
|
-
},
|
|
381
|
-
});
|
|
382
|
-
if (!response.ok) {
|
|
383
|
-
const errorText = await response.text();
|
|
384
|
-
throw new Error(`Failed to test alert: ${response.status} ${errorText}`);
|
|
338
|
+
throw new ValidationError("Alert ID must be a non-empty string");
|
|
385
339
|
}
|
|
386
|
-
return
|
|
340
|
+
return this.client["request"](`/v1/alerts/${alertId}/test`, {}, { method: "POST" });
|
|
387
341
|
}
|
|
388
342
|
/**
|
|
389
343
|
* Get available alert triggers
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Access advanced analytics including performance metrics, statistical analysis,
|
|
5
5
|
* correlations, trend detection, spreads, and forecasts.
|
|
6
6
|
*/
|
|
7
|
+
import { ValidationError } from "../errors.js";
|
|
7
8
|
/**
|
|
8
9
|
* Analytics Resource
|
|
9
10
|
*
|
|
@@ -83,7 +84,7 @@ export class AnalyticsResource {
|
|
|
83
84
|
*/
|
|
84
85
|
async statistics(commodity, days) {
|
|
85
86
|
if (!commodity || typeof commodity !== "string") {
|
|
86
|
-
throw new
|
|
87
|
+
throw new ValidationError("Commodity code must be a non-empty string");
|
|
87
88
|
}
|
|
88
89
|
const params = { commodity };
|
|
89
90
|
if (days)
|
|
@@ -114,10 +115,10 @@ export class AnalyticsResource {
|
|
|
114
115
|
*/
|
|
115
116
|
async correlation(commodity1, commodity2, days) {
|
|
116
117
|
if (!commodity1 || typeof commodity1 !== "string") {
|
|
117
|
-
throw new
|
|
118
|
+
throw new ValidationError("First commodity code must be a non-empty string");
|
|
118
119
|
}
|
|
119
120
|
if (!commodity2 || typeof commodity2 !== "string") {
|
|
120
|
-
throw new
|
|
121
|
+
throw new ValidationError("Second commodity code must be a non-empty string");
|
|
121
122
|
}
|
|
122
123
|
const params = {
|
|
123
124
|
commodity1,
|
|
@@ -149,7 +150,7 @@ export class AnalyticsResource {
|
|
|
149
150
|
*/
|
|
150
151
|
async trend(commodity, days) {
|
|
151
152
|
if (!commodity || typeof commodity !== "string") {
|
|
152
|
-
throw new
|
|
153
|
+
throw new ValidationError("Commodity code must be a non-empty string");
|
|
153
154
|
}
|
|
154
155
|
const params = { commodity };
|
|
155
156
|
if (days)
|
|
@@ -178,10 +179,10 @@ export class AnalyticsResource {
|
|
|
178
179
|
*/
|
|
179
180
|
async spread(commodity1, commodity2) {
|
|
180
181
|
if (!commodity1 || typeof commodity1 !== "string") {
|
|
181
|
-
throw new
|
|
182
|
+
throw new ValidationError("First commodity code must be a non-empty string");
|
|
182
183
|
}
|
|
183
184
|
if (!commodity2 || typeof commodity2 !== "string") {
|
|
184
|
-
throw new
|
|
185
|
+
throw new ValidationError("Second commodity code must be a non-empty string");
|
|
185
186
|
}
|
|
186
187
|
return this.client["request"]("/v1/analytics/spread", {
|
|
187
188
|
commodity1,
|
|
@@ -212,7 +213,7 @@ export class AnalyticsResource {
|
|
|
212
213
|
*/
|
|
213
214
|
async forecast(commodity) {
|
|
214
215
|
if (!commodity || typeof commodity !== "string") {
|
|
215
|
-
throw new
|
|
216
|
+
throw new ValidationError("Commodity code must be a non-empty string");
|
|
216
217
|
}
|
|
217
218
|
return this.client["request"]("/v1/analytics/forecast", {
|
|
218
219
|
commodity,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Access bunker fuel prices at major ports worldwide, including VLSFO, MGO,
|
|
5
5
|
* and IFO380. Compare prices across ports and track historical trends.
|
|
6
6
|
*/
|
|
7
|
+
import { ValidationError } from "../errors.js";
|
|
7
8
|
/**
|
|
8
9
|
* Bunker Fuels Resource
|
|
9
10
|
*
|
|
@@ -72,7 +73,7 @@ export class BunkerFuelsResource {
|
|
|
72
73
|
*/
|
|
73
74
|
async port(code) {
|
|
74
75
|
if (!code || typeof code !== "string") {
|
|
75
|
-
throw new
|
|
76
|
+
throw new ValidationError("Port code must be a non-empty string");
|
|
76
77
|
}
|
|
77
78
|
return this.client["request"](`/v1/bunker-fuels/ports/${code}`, {});
|
|
78
79
|
}
|
|
@@ -96,7 +97,7 @@ export class BunkerFuelsResource {
|
|
|
96
97
|
*/
|
|
97
98
|
async compare(ports) {
|
|
98
99
|
if (!Array.isArray(ports) || ports.length === 0) {
|
|
99
|
-
throw new
|
|
100
|
+
throw new ValidationError("Ports must be a non-empty array of port codes");
|
|
100
101
|
}
|
|
101
102
|
return this.client["request"]("/v1/bunker-fuels/compare", {
|
|
102
103
|
ports: ports.join(","),
|
|
@@ -147,10 +148,10 @@ export class BunkerFuelsResource {
|
|
|
147
148
|
*/
|
|
148
149
|
async historical(port, fuelType, options) {
|
|
149
150
|
if (!port || typeof port !== "string") {
|
|
150
|
-
throw new
|
|
151
|
+
throw new ValidationError("Port code must be a non-empty string");
|
|
151
152
|
}
|
|
152
153
|
if (!fuelType || typeof fuelType !== "string") {
|
|
153
|
-
throw new
|
|
154
|
+
throw new ValidationError("Fuel type must be a non-empty string");
|
|
154
155
|
}
|
|
155
156
|
const params = {
|
|
156
157
|
port,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Get metadata about supported commodities and categories.
|
|
5
5
|
*/
|
|
6
|
+
import { ValidationError } from "../errors.js";
|
|
6
7
|
/**
|
|
7
8
|
* Commodities Resource
|
|
8
9
|
*
|
|
@@ -75,7 +76,7 @@ export class CommoditiesResource {
|
|
|
75
76
|
*/
|
|
76
77
|
async get(code) {
|
|
77
78
|
if (!code || typeof code !== "string") {
|
|
78
|
-
throw new
|
|
79
|
+
throw new ValidationError("Commodity code must be a non-empty string");
|
|
79
80
|
}
|
|
80
81
|
return this.client["request"](`/v1/commodities/${code}`, {});
|
|
81
82
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Access data quality metrics, reports, and monitoring for commodity price data.
|
|
5
5
|
*/
|
|
6
|
+
import { ValidationError } from "../errors.js";
|
|
6
7
|
/**
|
|
7
8
|
* Data Quality Resource
|
|
8
9
|
*
|
|
@@ -132,7 +133,7 @@ export class DataQualityResource {
|
|
|
132
133
|
*/
|
|
133
134
|
async report(code) {
|
|
134
135
|
if (!code || typeof code !== "string") {
|
|
135
|
-
throw new
|
|
136
|
+
throw new ValidationError("Report code must be a non-empty string");
|
|
136
137
|
}
|
|
137
138
|
return this.client["request"](`/v1/data-quality/reports/${code}`, {});
|
|
138
139
|
}
|