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,350 @@
1
+ "use strict";
2
+ /**
3
+ * WebSocket Streaming Resource
4
+ *
5
+ * Real-time price streaming via the OilPriceAPI ActionCable endpoint
6
+ * (`wss://api.oilpriceapi.com/cable`).
7
+ *
8
+ * Streaming is a **Reservoir Mastery (Professional+)** feature. Connections
9
+ * authenticate with your API key and subscribe to the `EnergyPricesChannel`,
10
+ * which pushes an initial `welcome` snapshot followed by live `price_update`
11
+ * and (for drilling-tier accounts) `rig_count_update` messages.
12
+ *
13
+ * The implementation speaks the raw ActionCable JSON subprotocol over the
14
+ * `ws` package: it performs the `welcome` -> `subscribe` ->
15
+ * `confirm_subscription` handshake, answers server `ping` frames, and
16
+ * surfaces decoded channel messages as typed events. Auto-reconnect with
17
+ * exponential backoff keeps the stream alive across transient network drops.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const sub = client.stream.prices({}, (update) => {
22
+ * console.log(update.prices.oil.wti?.original_price);
23
+ * });
24
+ *
25
+ * sub.on("rig_count_update", (m) => console.log(m.rig_count.region, m.rig_count.count));
26
+ * sub.on("error", (err) => console.error(err));
27
+ *
28
+ * // later
29
+ * sub.close();
30
+ * ```
31
+ */
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.StreamingResource = exports.PriceStreamSubscription = exports.ENERGY_PRICES_CHANNEL = void 0;
37
+ const node_events_1 = require("node:events");
38
+ const ws_1 = __importDefault(require("ws"));
39
+ /**
40
+ * The ActionCable channel exposed by the OilPriceAPI server.
41
+ *
42
+ * Confirmed from `app/channels/energy_prices_channel.rb`
43
+ * (`class EnergyPricesChannel`).
44
+ */
45
+ exports.ENERGY_PRICES_CHANNEL = "EnergyPricesChannel";
46
+ /** Maps streamed slug <-> upstream code so `commodities` filtering accepts either. */
47
+ const COMMODITY_CODE_TO_SLUG = {
48
+ BRENT_CRUDE_USD: "brent",
49
+ WTI_USD: "wti",
50
+ NATURAL_GAS_GBP: "uk",
51
+ NATURAL_GAS_USD: "us",
52
+ DUTCH_TTF_EUR: "eu",
53
+ };
54
+ /**
55
+ * Handle for an active price stream.
56
+ *
57
+ * Extends `EventEmitter`. Emitted events:
58
+ * - `"connected"` — transport connected and subscription confirmed
59
+ * - `"welcome"` — initial snapshot ({@link WelcomeMessage})
60
+ * - `"price_update"` — live price broadcast ({@link PriceUpdateMessage})
61
+ * - `"rig_count_update"` — drilling broadcast ({@link RigCountUpdateMessage})
62
+ * - `"message"` — every decoded channel message ({@link StreamMessage})
63
+ * - `"reconnecting"` — a reconnect attempt is scheduled (`{ attempt, delay }`)
64
+ * - `"disconnected"` — transport closed (`{ code, reason }`)
65
+ * - `"error"` — an `Error` (transport error, unauthorized, or retries exhausted)
66
+ * - `"close"` — the subscription was closed via {@link PriceStreamSubscription.close}
67
+ */
68
+ class PriceStreamSubscription extends node_events_1.EventEmitter {
69
+ /**
70
+ * @param url - The `wss://.../cable` endpoint.
71
+ * @param apiKey - API key sent as the ActionCable `Authorization: Token <key>` header.
72
+ * @param options - Reconnect + filter options.
73
+ * @param wsImpl - Injectable WebSocket constructor (used by tests to mock).
74
+ * @internal Construct via {@link StreamingResource.prices}.
75
+ */
76
+ constructor(url, apiKey, options, wsImpl = ws_1.default) {
77
+ super();
78
+ this.wsImpl = wsImpl;
79
+ this.ws = null;
80
+ this.closed = false;
81
+ this.subscribed = false;
82
+ this.reconnectAttempts = 0;
83
+ this.reconnectTimer = null;
84
+ this.url = url;
85
+ this.apiKey = apiKey;
86
+ this.options = {
87
+ autoReconnect: options.autoReconnect ?? true,
88
+ reconnectDelay: options.reconnectDelay ?? 1000,
89
+ maxReconnectDelay: options.maxReconnectDelay ?? 30000,
90
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
91
+ commodities: options.commodities,
92
+ };
93
+ // The ActionCable subscription identifier. The channel takes no params;
94
+ // we additionally pass `api_key` in the identifier for parity with the
95
+ // documented native-WebSocket clients, while the server's
96
+ // ApplicationCable::Connection authenticates from the Authorization header.
97
+ this.identifier = JSON.stringify({
98
+ channel: exports.ENERGY_PRICES_CHANNEL,
99
+ api_key: apiKey,
100
+ });
101
+ if (options.commodities && options.commodities.length > 0) {
102
+ this.commodityFilter = new Set(options.commodities.map((c) => COMMODITY_CODE_TO_SLUG[c] ?? c.toLowerCase()));
103
+ }
104
+ else {
105
+ this.commodityFilter = null;
106
+ }
107
+ }
108
+ /**
109
+ * Open the transport and begin the ActionCable handshake.
110
+ * @internal Called once by {@link StreamingResource.prices}.
111
+ */
112
+ connect() {
113
+ if (this.closed)
114
+ return;
115
+ const ws = new this.wsImpl(this.url, {
116
+ headers: {
117
+ Authorization: `Token ${this.apiKey}`,
118
+ },
119
+ });
120
+ this.ws = ws;
121
+ ws.on("open", () => {
122
+ // ActionCable sends a transport `welcome` frame first; we subscribe on
123
+ // open (the server tolerates an early subscribe and replies with
124
+ // confirm_subscription once the connection is established).
125
+ this.send({ command: "subscribe", identifier: this.identifier });
126
+ });
127
+ ws.on("message", (raw) => this.handleRaw(raw));
128
+ ws.on("error", (err) => {
129
+ this.emit("error", err);
130
+ });
131
+ ws.on("close", (code, reason) => {
132
+ this.ws = null;
133
+ this.subscribed = false;
134
+ this.emit("disconnected", { code, reason: reason?.toString() ?? "" });
135
+ if (!this.closed) {
136
+ this.scheduleReconnect();
137
+ }
138
+ });
139
+ }
140
+ handleRaw(raw) {
141
+ let frame;
142
+ try {
143
+ frame = JSON.parse(raw.toString());
144
+ }
145
+ catch {
146
+ // Ignore malformed frames rather than crash the stream.
147
+ return;
148
+ }
149
+ // ActionCable transport-level frames carry a top-level `type`.
150
+ const transportType = frame["type"];
151
+ if (transportType === "ping") {
152
+ // Heartbeat — nothing to do; presence of frames keeps `ws` alive.
153
+ return;
154
+ }
155
+ if (transportType === "welcome") {
156
+ // Transport handshake complete. (subscribe already sent on open.)
157
+ return;
158
+ }
159
+ if (transportType === "confirm_subscription") {
160
+ this.subscribed = true;
161
+ this.reconnectAttempts = 0;
162
+ this.emit("connected");
163
+ return;
164
+ }
165
+ if (transportType === "reject_subscription") {
166
+ this.emit("error", new Error("WebSocket subscription rejected. Streaming requires a Reservoir Mastery " +
167
+ "(Professional+) plan and a valid API key."));
168
+ return;
169
+ }
170
+ if (transportType === "disconnect") {
171
+ // Server-initiated disconnect (e.g. auth failure). Let `close` drive reconnect.
172
+ return;
173
+ }
174
+ // Channel message: payload lives under `message`.
175
+ const payload = frame["message"];
176
+ if (payload && typeof payload === "object") {
177
+ this.dispatch(payload);
178
+ }
179
+ }
180
+ dispatch(message) {
181
+ this.emit("message", message);
182
+ switch (message.type) {
183
+ case "welcome":
184
+ this.emit("welcome", message);
185
+ break;
186
+ case "price_update": {
187
+ const update = message;
188
+ if (this.matchesFilter(update)) {
189
+ this.emit("price_update", update);
190
+ }
191
+ break;
192
+ }
193
+ case "rig_count_update":
194
+ this.emit("rig_count_update", message);
195
+ break;
196
+ default:
197
+ // Unknown message types are still emitted via `message` above.
198
+ break;
199
+ }
200
+ }
201
+ matchesFilter(update) {
202
+ if (!this.commodityFilter)
203
+ return true;
204
+ const { oil, natural_gas } = update.prices ?? {};
205
+ const present = {
206
+ brent: oil?.brent,
207
+ wti: oil?.wti,
208
+ uk: natural_gas?.uk,
209
+ us: natural_gas?.us,
210
+ eu: natural_gas?.eu,
211
+ };
212
+ for (const slug of this.commodityFilter) {
213
+ if (present[slug])
214
+ return true;
215
+ }
216
+ return false;
217
+ }
218
+ scheduleReconnect() {
219
+ if (this.closed || !this.options.autoReconnect)
220
+ return;
221
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
222
+ this.emit("error", new Error(`WebSocket reconnect failed after ${this.reconnectAttempts} attempt(s); giving up.`));
223
+ return;
224
+ }
225
+ const attempt = this.reconnectAttempts++;
226
+ const delay = Math.min(this.options.reconnectDelay * Math.pow(2, attempt), this.options.maxReconnectDelay);
227
+ this.emit("reconnecting", { attempt: attempt + 1, delay });
228
+ this.reconnectTimer = setTimeout(() => {
229
+ this.reconnectTimer = null;
230
+ this.connect();
231
+ }, delay);
232
+ }
233
+ send(payload) {
234
+ if (this.ws && this.ws.readyState === this.wsImpl.OPEN) {
235
+ this.ws.send(JSON.stringify(payload));
236
+ }
237
+ }
238
+ /** Whether the channel subscription has been confirmed by the server. */
239
+ get isSubscribed() {
240
+ return this.subscribed;
241
+ }
242
+ /**
243
+ * Cleanly tear down the stream: cancels any pending reconnect, unsubscribes
244
+ * from the channel, and closes the socket. Safe to call multiple times.
245
+ * Emits `"close"` once.
246
+ */
247
+ close() {
248
+ if (this.closed)
249
+ return;
250
+ this.closed = true;
251
+ if (this.reconnectTimer) {
252
+ clearTimeout(this.reconnectTimer);
253
+ this.reconnectTimer = null;
254
+ }
255
+ if (this.ws) {
256
+ const ws = this.ws;
257
+ try {
258
+ if (ws.readyState === this.wsImpl.OPEN) {
259
+ ws.send(JSON.stringify({ command: "unsubscribe", identifier: this.identifier }));
260
+ }
261
+ }
262
+ catch {
263
+ // ignore — we're closing anyway
264
+ }
265
+ try {
266
+ ws.close();
267
+ }
268
+ catch {
269
+ // ignore
270
+ }
271
+ this.ws = null;
272
+ }
273
+ this.subscribed = false;
274
+ this.emit("close");
275
+ this.removeAllListeners();
276
+ }
277
+ }
278
+ exports.PriceStreamSubscription = PriceStreamSubscription;
279
+ /**
280
+ * Streaming resource — entry point for real-time price subscriptions.
281
+ *
282
+ * Accessed via `client.stream`.
283
+ */
284
+ class StreamingResource {
285
+ constructor(client,
286
+ /**
287
+ * Injectable WebSocket implementation. Defaults to the `ws` package;
288
+ * tests pass a mock constructor.
289
+ * @internal
290
+ */
291
+ wsImpl = ws_1.default) {
292
+ this.client = client;
293
+ this.wsImpl = wsImpl;
294
+ }
295
+ /**
296
+ * Derive the `wss://.../cable` endpoint from the client's REST base URL.
297
+ *
298
+ * `https://api.oilpriceapi.com` -> `wss://api.oilpriceapi.com/cable`
299
+ * `http://localhost:5000` -> `ws://localhost:5000/cable`
300
+ */
301
+ cableUrl() {
302
+ const base = this.client["baseUrl"];
303
+ const wsBase = base
304
+ .replace(/^http:/, "ws:")
305
+ .replace(/^https:/, "wss:")
306
+ .replace(/\/$/, "");
307
+ return `${wsBase}/cable`;
308
+ }
309
+ /**
310
+ * Open a real-time price stream over the `EnergyPricesChannel`.
311
+ *
312
+ * Returns a {@link PriceStreamSubscription} handle (an `EventEmitter`) you
313
+ * can attach further listeners to and `.close()` when done. The optional
314
+ * `onUpdate` callback is a convenience wired to the `price_update` event.
315
+ *
316
+ * @param options - Filtering and reconnect options.
317
+ * @param onUpdate - Optional callback for each `price_update` message.
318
+ * @returns The subscription handle.
319
+ *
320
+ * @throws {OilPriceAPIError} If no API key is configured on the client.
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * const client = new OilPriceAPI({ apiKey: process.env.OILPRICEAPI_KEY });
325
+ *
326
+ * const sub = client.stream.prices(
327
+ * { commodities: ["WTI_USD", "BRENT_CRUDE_USD"] },
328
+ * (update) => {
329
+ * const wti = update.prices.oil.wti;
330
+ * if (wti) console.log(`WTI ${wti.original_price} @ ${update.timestamp}`);
331
+ * },
332
+ * );
333
+ *
334
+ * sub.on("connected", () => console.log("streaming live"));
335
+ * sub.on("error", (err) => console.error("stream error:", err));
336
+ *
337
+ * process.on("SIGINT", () => sub.close());
338
+ * ```
339
+ */
340
+ prices(options = {}, onUpdate) {
341
+ const apiKey = this.client["apiKey"];
342
+ const sub = new PriceStreamSubscription(this.cableUrl(), apiKey, options, this.wsImpl);
343
+ if (onUpdate) {
344
+ sub.on("price_update", onUpdate);
345
+ }
346
+ sub.connect();
347
+ return sub;
348
+ }
349
+ }
350
+ exports.StreamingResource = StreamingResource;
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ /**
3
+ * Webhooks Resource
4
+ *
5
+ * Manage webhook endpoints for real-time event notifications.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.WebhooksResource = void 0;
9
+ const errors_js_1 = require("../errors.js");
10
+ const index_js_1 = require("../index.js");
11
+ /**
12
+ * Webhooks Resource
13
+ *
14
+ * Manage webhook endpoints for real-time notifications about price changes,
15
+ * alerts, and other events.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { OilPriceAPI } from 'oilpriceapi';
20
+ *
21
+ * const client = new OilPriceAPI({ apiKey: 'your_key' });
22
+ *
23
+ * // Create a webhook
24
+ * const webhook = await client.webhooks.create({
25
+ * name: 'Price Updates',
26
+ * url: 'https://myapp.com/webhooks/prices',
27
+ * events: ['price.updated', 'alert.triggered'],
28
+ * enabled: true
29
+ * });
30
+ *
31
+ * // Test the webhook
32
+ * const test = await client.webhooks.test(webhook.id);
33
+ * console.log(`Test result: ${test.success ? 'passed' : 'failed'}`);
34
+ *
35
+ * // List all webhooks
36
+ * const webhooks = await client.webhooks.list();
37
+ * webhooks.forEach(wh => {
38
+ * console.log(`${wh.name}: ${wh.successful_deliveries} successful`);
39
+ * });
40
+ *
41
+ * // Update webhook
42
+ * await client.webhooks.update(webhook.id, {
43
+ * enabled: false
44
+ * });
45
+ *
46
+ * // Delete webhook
47
+ * await client.webhooks.delete(webhook.id);
48
+ * ```
49
+ */
50
+ class WebhooksResource {
51
+ constructor(client) {
52
+ this.client = client;
53
+ }
54
+ /**
55
+ * List all webhook endpoints
56
+ *
57
+ * @returns Array of webhook endpoints
58
+ *
59
+ * @throws {OilPriceAPIError} If API request fails
60
+ * @throws {AuthenticationError} If API key is invalid
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const webhooks = await client.webhooks.list();
65
+ * webhooks.forEach(wh => {
66
+ * console.log(`${wh.name} (${wh.enabled ? 'enabled' : 'disabled'})`);
67
+ * console.log(` Events: ${wh.events.join(', ')}`);
68
+ * });
69
+ * ```
70
+ */
71
+ async list() {
72
+ const response = await this.client["request"]("/v1/webhooks", {});
73
+ return Array.isArray(response) ? response : response.webhooks;
74
+ }
75
+ /**
76
+ * Get a specific webhook endpoint
77
+ *
78
+ * @param id - Webhook ID
79
+ * @returns Webhook endpoint details
80
+ *
81
+ * @throws {NotFoundError} If webhook not found
82
+ * @throws {OilPriceAPIError} If API request fails
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const webhook = await client.webhooks.get('webhook-id');
87
+ * console.log(`${webhook.name}: ${webhook.url}`);
88
+ * console.log(`Success rate: ${webhook.successful_deliveries}/${webhook.successful_deliveries + webhook.failed_deliveries}`);
89
+ * ```
90
+ */
91
+ async get(id) {
92
+ if (!id || typeof id !== "string") {
93
+ throw new errors_js_1.ValidationError("Webhook ID must be a non-empty string");
94
+ }
95
+ const response = await this.client["request"](`/v1/webhooks/${id}`, {});
96
+ return "webhook" in response ? response.webhook : response;
97
+ }
98
+ /**
99
+ * Create a new webhook endpoint
100
+ *
101
+ * @param params - Webhook configuration
102
+ * @returns Created webhook endpoint
103
+ *
104
+ * @throws {OilPriceAPIError} If API request fails
105
+ * @throws {AuthenticationError} If API key is invalid
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const webhook = await client.webhooks.create({
110
+ * name: 'Production Alerts',
111
+ * url: 'https://api.myapp.com/webhooks',
112
+ * events: ['alert.triggered', 'price.updated'],
113
+ * secret: 'my-webhook-secret',
114
+ * enabled: true
115
+ * });
116
+ * console.log(`Webhook created: ${webhook.id}`);
117
+ * ```
118
+ */
119
+ async create(params) {
120
+ if (!params.url || !params.url.startsWith("https://")) {
121
+ throw new errors_js_1.ValidationError("Webhook URL must use HTTPS protocol");
122
+ }
123
+ if (!params.events || !Array.isArray(params.events) || params.events.length === 0) {
124
+ throw new errors_js_1.ValidationError("At least one event type is required");
125
+ }
126
+ // The controller reads a flat (un-nested) body via params.permit(...).
127
+ const response = await this.client["request"]("/v1/webhooks", {}, {
128
+ method: "POST",
129
+ body: { ...params },
130
+ });
131
+ return "webhook" in response ? response.webhook : response;
132
+ }
133
+ /**
134
+ * Update a webhook endpoint
135
+ *
136
+ * @param id - Webhook ID
137
+ * @param params - Fields to update
138
+ * @returns Updated webhook endpoint
139
+ *
140
+ * @throws {NotFoundError} If webhook not found
141
+ * @throws {OilPriceAPIError} If API request fails
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * // Disable a webhook
146
+ * await client.webhooks.update(webhookId, { enabled: false });
147
+ *
148
+ * // Change events
149
+ * await client.webhooks.update(webhookId, {
150
+ * events: ['alert.triggered']
151
+ * });
152
+ * ```
153
+ */
154
+ async update(id, params) {
155
+ if (!id || typeof id !== "string") {
156
+ throw new errors_js_1.ValidationError("Webhook ID must be a non-empty string");
157
+ }
158
+ if (params.url !== undefined && !params.url.startsWith("https://")) {
159
+ throw new errors_js_1.ValidationError("Webhook URL must use HTTPS protocol");
160
+ }
161
+ if (params.events !== undefined &&
162
+ (!Array.isArray(params.events) || params.events.length === 0)) {
163
+ throw new errors_js_1.ValidationError("Events must be a non-empty array");
164
+ }
165
+ const response = await this.client["request"](`/v1/webhooks/${id}`, {}, {
166
+ method: "PATCH",
167
+ body: { ...params },
168
+ });
169
+ return "webhook" in response ? response.webhook : response;
170
+ }
171
+ /**
172
+ * Delete a webhook endpoint
173
+ *
174
+ * @param id - Webhook ID
175
+ *
176
+ * @throws {NotFoundError} If webhook not found
177
+ * @throws {OilPriceAPIError} If API request fails
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * await client.webhooks.delete(webhookId);
182
+ * console.log('Webhook deleted');
183
+ * ```
184
+ */
185
+ async delete(id) {
186
+ if (!id || typeof id !== "string") {
187
+ throw new errors_js_1.ValidationError("Webhook ID must be a non-empty string");
188
+ }
189
+ await this.client["request"](`/v1/webhooks/${id}`, {}, { method: "DELETE" });
190
+ }
191
+ /**
192
+ * Test a webhook endpoint
193
+ *
194
+ * Sends a test payload to the webhook URL to verify it's reachable.
195
+ *
196
+ * @param id - Webhook ID
197
+ * @returns Test results
198
+ *
199
+ * @throws {NotFoundError} If webhook not found
200
+ * @throws {OilPriceAPIError} If API request fails
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const test = await client.webhooks.test(webhookId);
205
+ * console.log(`Test ${test.success ? 'passed' : 'failed'}`);
206
+ * console.log(`Response time: ${test.response_time_ms}ms`);
207
+ * if (!test.success) {
208
+ * console.log(`Error: ${test.error}`);
209
+ * }
210
+ * ```
211
+ */
212
+ async test(id) {
213
+ if (!id || typeof id !== "string") {
214
+ throw new errors_js_1.ValidationError("Webhook ID must be a non-empty string");
215
+ }
216
+ return this.client["request"](`/v1/webhooks/${id}/test`, {}, { method: "POST" });
217
+ }
218
+ /**
219
+ * Get webhook event history
220
+ *
221
+ * Returns recent delivery events for a webhook.
222
+ *
223
+ * @param id - Webhook ID
224
+ * @returns Array of webhook events
225
+ *
226
+ * @throws {NotFoundError} If webhook not found
227
+ * @throws {OilPriceAPIError} If API request fails
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const events = await client.webhooks.events(webhookId);
232
+ * events.forEach(event => {
233
+ * console.log(`${event.event_type}: ${event.status} (${event.attempts} attempts)`);
234
+ * });
235
+ * ```
236
+ */
237
+ async events(id) {
238
+ if (!id || typeof id !== "string") {
239
+ throw new errors_js_1.ValidationError("Webhook ID must be a non-empty string");
240
+ }
241
+ const response = await this.client["request"](`/v1/webhooks/${id}/events`, {});
242
+ return Array.isArray(response) ? response : response.events;
243
+ }
244
+ /**
245
+ * Verify a webhook signature.
246
+ *
247
+ * Validates that a webhook payload was sent by OilPriceAPI by checking
248
+ * the HMAC-SHA256 signature. Uses constant-time comparison to prevent
249
+ * timing attacks.
250
+ *
251
+ * @param payload - Raw request body (string or Buffer)
252
+ * @param signature - Value of the X-OilPriceAPI-Signature header (e.g., "sha256=abc123...")
253
+ * @param secret - Your webhook signing secret
254
+ * @returns true if signature is valid
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * import express from 'express';
259
+ * import { OilPriceAPI } from 'oilpriceapi';
260
+ *
261
+ * const app = express();
262
+ * const client = new OilPriceAPI({ apiKey: 'your_key' });
263
+ *
264
+ * // Use raw body parser for webhook routes
265
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
266
+ * const signature = req.headers['x-oilpriceapi-signature'] as string;
267
+ * const isValid = client.webhooks.verifySignature(req.body, signature, 'your_secret');
268
+ *
269
+ * if (!isValid) {
270
+ * return res.status(401).send('Invalid signature');
271
+ * }
272
+ *
273
+ * const event = JSON.parse(req.body.toString());
274
+ * console.log('Verified webhook:', event.type);
275
+ * res.sendStatus(200);
276
+ * });
277
+ * ```
278
+ */
279
+ verifySignature(payload, signature, secret) {
280
+ return (0, index_js_1.verifyWebhookSignature)(payload, signature, secret);
281
+ }
282
+ }
283
+ exports.WebhooksResource = WebhooksResource;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SDK_NAME = exports.SDK_VERSION = void 0;
4
+ exports.buildUserAgent = buildUserAgent;
5
+ /**
6
+ * SDK Version - centralized to ensure consistency across all headers
7
+ *
8
+ * This version must be updated when publishing a new release.
9
+ * It's used in:
10
+ * - User-Agent header: oilpriceapi-node/{version}
11
+ * - X-Client-Version header
12
+ * - Package.json (should match)
13
+ */
14
+ exports.SDK_VERSION = "0.9.0";
15
+ /**
16
+ * SDK identifier used in User-Agent and X-Api-Client headers
17
+ */
18
+ exports.SDK_NAME = "oilpriceapi-node";
19
+ /**
20
+ * Build the full User-Agent string
21
+ */
22
+ function buildUserAgent() {
23
+ return `${exports.SDK_NAME}/${exports.SDK_VERSION} node/${process.version}`;
24
+ }