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.
Files changed (60) hide show
  1. package/README.md +43 -11
  2. package/dist/cjs/client.js +490 -0
  3. package/dist/cjs/errors.js +80 -0
  4. package/dist/cjs/index.js +82 -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 +226 -0
  8. package/dist/cjs/resources/bunker-fuels.js +196 -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 +297 -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 +155 -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 +124 -0
  22. package/dist/cjs/resources/forecasts.js +162 -0
  23. package/dist/cjs/resources/futures.js +233 -0
  24. package/dist/cjs/resources/rig-counts.js +161 -0
  25. package/dist/cjs/resources/storage.js +166 -0
  26. package/dist/cjs/resources/webhooks.js +294 -0
  27. package/dist/cjs/types.js +2 -0
  28. package/dist/cjs/version.js +24 -0
  29. package/dist/client.d.ts +33 -2
  30. package/dist/client.js +70 -14
  31. package/dist/errors.d.ts +6 -0
  32. package/dist/errors.js +25 -16
  33. package/dist/index.d.ts +16 -2
  34. package/dist/index.js +24 -1
  35. package/dist/resources/alerts.js +31 -77
  36. package/dist/resources/analytics.js +8 -7
  37. package/dist/resources/bunker-fuels.js +5 -4
  38. package/dist/resources/commodities.js +2 -1
  39. package/dist/resources/data-quality.js +2 -1
  40. package/dist/resources/data-sources.js +21 -77
  41. package/dist/resources/diesel.d.ts +1 -1
  42. package/dist/resources/diesel.js +9 -38
  43. package/dist/resources/drilling.js +2 -1
  44. package/dist/resources/ei/drilling-productivity.js +2 -1
  45. package/dist/resources/ei/forecasts.js +2 -1
  46. package/dist/resources/ei/frac-focus.js +4 -3
  47. package/dist/resources/ei/index.js +2 -1
  48. package/dist/resources/ei/oil-inventories.js +2 -1
  49. package/dist/resources/ei/opec-production.js +2 -1
  50. package/dist/resources/ei/rig-counts.js +2 -1
  51. package/dist/resources/ei/well-permits.js +2 -1
  52. package/dist/resources/forecasts.js +2 -1
  53. package/dist/resources/futures.js +9 -8
  54. package/dist/resources/storage.js +2 -1
  55. package/dist/resources/webhooks.d.ts +36 -0
  56. package/dist/resources/webhooks.js +60 -67
  57. package/dist/types.d.ts +2 -1
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +2 -2
  60. 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
- if (!config.apiKey) {
46
- throw new OilPriceAPIError("API key is required");
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 response = await fetch(url.toString(), {
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 = await response.json();
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 = 'OilPriceAPIError';
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 = 'Invalid API key') {
21
- super(message, 401, 'AUTHENTICATION_ERROR');
22
- this.name = 'AuthenticationError';
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 = 'Rate limit exceeded', retryAfter) {
30
- super(message, 429, 'RATE_LIMIT_ERROR');
31
- this.name = 'RateLimitError';
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 = 'Resource not found') {
40
- super(message, 404, 'NOT_FOUND_ERROR');
41
- this.name = 'NotFoundError';
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 = 'Internal server error', statusCode = 500) {
49
- super(message, statusCode, 'SERVER_ERROR');
50
- this.name = 'ServerError';
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 = 'Request timeout', timeout) {
58
- super(`${message} (${timeout}ms)`, undefined, 'TIMEOUT_ERROR');
59
- this.name = 'TimeoutError';
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, } from "./resources/forecasts.js";
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
+ }
@@ -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 Error("Alert ID must be a non-empty string");
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 Error("Alert name is required and must be a string");
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 Error("Alert name must be 1-100 characters");
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 Error("Commodity code is required and must be a string");
156
+ throw new ValidationError("Commodity code is required and must be a string");
156
157
  }
157
158
  if (!params.condition_operator) {
158
- throw new Error("Condition operator is required");
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 Error(`Invalid operator. Must be one of: ${validOperators.join(", ")}`);
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 Error("Condition value must be a number");
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 Error("Condition value must be greater than 0 and less than or equal to 1,000,000");
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 Error("Webhook URL must be a string");
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 Error("Webhook URL must use HTTPS protocol");
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 Error("Cooldown minutes must be a number");
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 Error("Cooldown minutes must be between 0 and 1440 (24 hours)");
191
+ throw new ValidationError("Cooldown minutes must be between 0 and 1440 (24 hours)");
191
192
  }
192
193
  }
193
- const url = `${this.client["baseUrl"]}/v1/alerts`;
194
- const response = await fetch(url, {
194
+ const response = await this.client["request"]("/v1/alerts", {}, {
195
195
  method: "POST",
196
- headers: {
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
- if (!response.ok) {
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 Error("Alert ID must be a non-empty string");
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 Error("Alert name must be 1-100 characters");
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 Error(`Invalid operator. Must be one of: ${validOperators.join(", ")}`);
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 Error("Condition value must be a number");
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 Error("Condition value must be greater than 0 and less than or equal to 1,000,000");
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 Error("Webhook URL must be a valid HTTPS URL");
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 Error("Cooldown minutes must be between 0 and 1440 (24 hours)");
284
+ throw new ValidationError("Cooldown minutes must be between 0 and 1440 (24 hours)");
295
285
  }
296
286
  }
297
- const url = `${this.client["baseUrl"]}/v1/alerts/${id}`;
298
- const response = await fetch(url, {
287
+ const response = await this.client["request"](`/v1/alerts/${id}`, {}, {
299
288
  method: "PATCH",
300
- headers: {
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
- if (!response.ok) {
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 Error("Alert ID must be a non-empty string");
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 Error("Alert ID must be a non-empty string");
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 response.json();
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 Error("Commodity code must be a non-empty string");
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 Error("First commodity code must be a non-empty string");
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 Error("Second commodity code must be a non-empty string");
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 Error("Commodity code must be a non-empty string");
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 Error("First commodity code must be a non-empty string");
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 Error("Second commodity code must be a non-empty string");
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 Error("Commodity code must be a non-empty string");
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 Error("Port code must be a non-empty string");
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 Error("Ports must be a non-empty array of port codes");
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 Error("Port code must be a non-empty string");
151
+ throw new ValidationError("Port code must be a non-empty string");
151
152
  }
152
153
  if (!fuelType || typeof fuelType !== "string") {
153
- throw new Error("Fuel type must be a non-empty string");
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 Error("Commodity code must be a non-empty string");
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 Error("Report code must be a non-empty string");
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
  }