oilpriceapi 0.8.2 → 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.
- package/README.md +201 -19
- package/dist/cjs/client.js +139 -19
- package/dist/cjs/index.js +17 -3
- package/dist/cjs/resources/analytics.js +99 -137
- package/dist/cjs/resources/bunker-fuels.js +37 -23
- package/dist/cjs/resources/data-sources.js +13 -12
- package/dist/cjs/resources/ei/frac-focus.js +16 -6
- package/dist/cjs/resources/ei/well-permits.js +18 -6
- package/dist/cjs/resources/forecasts.js +11 -5
- package/dist/cjs/resources/futures.js +192 -1
- package/dist/cjs/resources/indicators.js +79 -0
- package/dist/cjs/resources/raw.js +128 -0
- package/dist/cjs/resources/rig-counts.js +5 -2
- package/dist/cjs/resources/spreads.js +105 -0
- package/dist/cjs/resources/storage.js +5 -5
- package/dist/cjs/resources/streaming.js +350 -0
- package/dist/cjs/resources/webhooks.js +3 -14
- package/dist/cjs/version.js +1 -1
- package/dist/client.d.ts +97 -1
- package/dist/client.js +139 -19
- package/dist/index.d.ts +12 -3
- package/dist/index.js +5 -0
- package/dist/resources/analytics.d.ts +147 -214
- package/dist/resources/analytics.js +99 -137
- package/dist/resources/bunker-fuels.d.ts +35 -12
- package/dist/resources/bunker-fuels.js +37 -23
- package/dist/resources/data-sources.d.ts +31 -31
- package/dist/resources/data-sources.js +13 -12
- package/dist/resources/ei/frac-focus.d.ts +23 -9
- package/dist/resources/ei/frac-focus.js +16 -6
- package/dist/resources/ei/well-permits.d.ts +25 -9
- package/dist/resources/ei/well-permits.js +18 -6
- package/dist/resources/forecasts.d.ts +4 -1
- package/dist/resources/forecasts.js +11 -5
- package/dist/resources/futures.d.ts +178 -1
- package/dist/resources/futures.js +190 -0
- package/dist/resources/indicators.d.ts +170 -0
- package/dist/resources/indicators.js +75 -0
- package/dist/resources/raw.d.ts +94 -0
- package/dist/resources/raw.js +124 -0
- package/dist/resources/rig-counts.js +5 -2
- package/dist/resources/spreads.d.ts +121 -0
- package/dist/resources/spreads.js +101 -0
- package/dist/resources/storage.d.ts +5 -4
- package/dist/resources/storage.js +5 -5
- package/dist/resources/streaming.d.ts +272 -0
- package/dist/resources/streaming.js +342 -0
- package/dist/resources/webhooks.d.ts +37 -23
- package/dist/resources/webhooks.js +3 -14
- package/dist/types.d.ts +41 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Streaming Resource
|
|
3
|
+
*
|
|
4
|
+
* Real-time price streaming via the OilPriceAPI ActionCable endpoint
|
|
5
|
+
* (`wss://api.oilpriceapi.com/cable`).
|
|
6
|
+
*
|
|
7
|
+
* Streaming is a **Reservoir Mastery (Professional+)** feature. Connections
|
|
8
|
+
* authenticate with your API key and subscribe to the `EnergyPricesChannel`,
|
|
9
|
+
* which pushes an initial `welcome` snapshot followed by live `price_update`
|
|
10
|
+
* and (for drilling-tier accounts) `rig_count_update` messages.
|
|
11
|
+
*
|
|
12
|
+
* The implementation speaks the raw ActionCable JSON subprotocol over the
|
|
13
|
+
* `ws` package: it performs the `welcome` -> `subscribe` ->
|
|
14
|
+
* `confirm_subscription` handshake, answers server `ping` frames, and
|
|
15
|
+
* surfaces decoded channel messages as typed events. Auto-reconnect with
|
|
16
|
+
* exponential backoff keeps the stream alive across transient network drops.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const sub = client.stream.prices({}, (update) => {
|
|
21
|
+
* console.log(update.prices.oil.wti?.original_price);
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* sub.on("rig_count_update", (m) => console.log(m.rig_count.region, m.rig_count.count));
|
|
25
|
+
* sub.on("error", (err) => console.error(err));
|
|
26
|
+
*
|
|
27
|
+
* // later
|
|
28
|
+
* sub.close();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { EventEmitter } from "node:events";
|
|
32
|
+
import WebSocket from "ws";
|
|
33
|
+
/**
|
|
34
|
+
* The ActionCable channel exposed by the OilPriceAPI server.
|
|
35
|
+
*
|
|
36
|
+
* Confirmed from `app/channels/energy_prices_channel.rb`
|
|
37
|
+
* (`class EnergyPricesChannel`).
|
|
38
|
+
*/
|
|
39
|
+
export const ENERGY_PRICES_CHANNEL = "EnergyPricesChannel";
|
|
40
|
+
/** Maps streamed slug <-> upstream code so `commodities` filtering accepts either. */
|
|
41
|
+
const COMMODITY_CODE_TO_SLUG = {
|
|
42
|
+
BRENT_CRUDE_USD: "brent",
|
|
43
|
+
WTI_USD: "wti",
|
|
44
|
+
NATURAL_GAS_GBP: "uk",
|
|
45
|
+
NATURAL_GAS_USD: "us",
|
|
46
|
+
DUTCH_TTF_EUR: "eu",
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Handle for an active price stream.
|
|
50
|
+
*
|
|
51
|
+
* Extends `EventEmitter`. Emitted events:
|
|
52
|
+
* - `"connected"` — transport connected and subscription confirmed
|
|
53
|
+
* - `"welcome"` — initial snapshot ({@link WelcomeMessage})
|
|
54
|
+
* - `"price_update"` — live price broadcast ({@link PriceUpdateMessage})
|
|
55
|
+
* - `"rig_count_update"` — drilling broadcast ({@link RigCountUpdateMessage})
|
|
56
|
+
* - `"message"` — every decoded channel message ({@link StreamMessage})
|
|
57
|
+
* - `"reconnecting"` — a reconnect attempt is scheduled (`{ attempt, delay }`)
|
|
58
|
+
* - `"disconnected"` — transport closed (`{ code, reason }`)
|
|
59
|
+
* - `"error"` — an `Error` (transport error, unauthorized, or retries exhausted)
|
|
60
|
+
* - `"close"` — the subscription was closed via {@link PriceStreamSubscription.close}
|
|
61
|
+
*/
|
|
62
|
+
export class PriceStreamSubscription extends EventEmitter {
|
|
63
|
+
/**
|
|
64
|
+
* @param url - The `wss://.../cable` endpoint.
|
|
65
|
+
* @param apiKey - API key sent as the ActionCable `Authorization: Token <key>` header.
|
|
66
|
+
* @param options - Reconnect + filter options.
|
|
67
|
+
* @param wsImpl - Injectable WebSocket constructor (used by tests to mock).
|
|
68
|
+
* @internal Construct via {@link StreamingResource.prices}.
|
|
69
|
+
*/
|
|
70
|
+
constructor(url, apiKey, options, wsImpl = WebSocket) {
|
|
71
|
+
super();
|
|
72
|
+
this.wsImpl = wsImpl;
|
|
73
|
+
this.ws = null;
|
|
74
|
+
this.closed = false;
|
|
75
|
+
this.subscribed = false;
|
|
76
|
+
this.reconnectAttempts = 0;
|
|
77
|
+
this.reconnectTimer = null;
|
|
78
|
+
this.url = url;
|
|
79
|
+
this.apiKey = apiKey;
|
|
80
|
+
this.options = {
|
|
81
|
+
autoReconnect: options.autoReconnect ?? true,
|
|
82
|
+
reconnectDelay: options.reconnectDelay ?? 1000,
|
|
83
|
+
maxReconnectDelay: options.maxReconnectDelay ?? 30000,
|
|
84
|
+
maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
|
|
85
|
+
commodities: options.commodities,
|
|
86
|
+
};
|
|
87
|
+
// The ActionCable subscription identifier. The channel takes no params;
|
|
88
|
+
// we additionally pass `api_key` in the identifier for parity with the
|
|
89
|
+
// documented native-WebSocket clients, while the server's
|
|
90
|
+
// ApplicationCable::Connection authenticates from the Authorization header.
|
|
91
|
+
this.identifier = JSON.stringify({
|
|
92
|
+
channel: ENERGY_PRICES_CHANNEL,
|
|
93
|
+
api_key: apiKey,
|
|
94
|
+
});
|
|
95
|
+
if (options.commodities && options.commodities.length > 0) {
|
|
96
|
+
this.commodityFilter = new Set(options.commodities.map((c) => COMMODITY_CODE_TO_SLUG[c] ?? c.toLowerCase()));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.commodityFilter = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Open the transport and begin the ActionCable handshake.
|
|
104
|
+
* @internal Called once by {@link StreamingResource.prices}.
|
|
105
|
+
*/
|
|
106
|
+
connect() {
|
|
107
|
+
if (this.closed)
|
|
108
|
+
return;
|
|
109
|
+
const ws = new this.wsImpl(this.url, {
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Token ${this.apiKey}`,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
this.ws = ws;
|
|
115
|
+
ws.on("open", () => {
|
|
116
|
+
// ActionCable sends a transport `welcome` frame first; we subscribe on
|
|
117
|
+
// open (the server tolerates an early subscribe and replies with
|
|
118
|
+
// confirm_subscription once the connection is established).
|
|
119
|
+
this.send({ command: "subscribe", identifier: this.identifier });
|
|
120
|
+
});
|
|
121
|
+
ws.on("message", (raw) => this.handleRaw(raw));
|
|
122
|
+
ws.on("error", (err) => {
|
|
123
|
+
this.emit("error", err);
|
|
124
|
+
});
|
|
125
|
+
ws.on("close", (code, reason) => {
|
|
126
|
+
this.ws = null;
|
|
127
|
+
this.subscribed = false;
|
|
128
|
+
this.emit("disconnected", { code, reason: reason?.toString() ?? "" });
|
|
129
|
+
if (!this.closed) {
|
|
130
|
+
this.scheduleReconnect();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
handleRaw(raw) {
|
|
135
|
+
let frame;
|
|
136
|
+
try {
|
|
137
|
+
frame = JSON.parse(raw.toString());
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Ignore malformed frames rather than crash the stream.
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// ActionCable transport-level frames carry a top-level `type`.
|
|
144
|
+
const transportType = frame["type"];
|
|
145
|
+
if (transportType === "ping") {
|
|
146
|
+
// Heartbeat — nothing to do; presence of frames keeps `ws` alive.
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (transportType === "welcome") {
|
|
150
|
+
// Transport handshake complete. (subscribe already sent on open.)
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (transportType === "confirm_subscription") {
|
|
154
|
+
this.subscribed = true;
|
|
155
|
+
this.reconnectAttempts = 0;
|
|
156
|
+
this.emit("connected");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (transportType === "reject_subscription") {
|
|
160
|
+
this.emit("error", new Error("WebSocket subscription rejected. Streaming requires a Reservoir Mastery " +
|
|
161
|
+
"(Professional+) plan and a valid API key."));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (transportType === "disconnect") {
|
|
165
|
+
// Server-initiated disconnect (e.g. auth failure). Let `close` drive reconnect.
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Channel message: payload lives under `message`.
|
|
169
|
+
const payload = frame["message"];
|
|
170
|
+
if (payload && typeof payload === "object") {
|
|
171
|
+
this.dispatch(payload);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
dispatch(message) {
|
|
175
|
+
this.emit("message", message);
|
|
176
|
+
switch (message.type) {
|
|
177
|
+
case "welcome":
|
|
178
|
+
this.emit("welcome", message);
|
|
179
|
+
break;
|
|
180
|
+
case "price_update": {
|
|
181
|
+
const update = message;
|
|
182
|
+
if (this.matchesFilter(update)) {
|
|
183
|
+
this.emit("price_update", update);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "rig_count_update":
|
|
188
|
+
this.emit("rig_count_update", message);
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
// Unknown message types are still emitted via `message` above.
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
matchesFilter(update) {
|
|
196
|
+
if (!this.commodityFilter)
|
|
197
|
+
return true;
|
|
198
|
+
const { oil, natural_gas } = update.prices ?? {};
|
|
199
|
+
const present = {
|
|
200
|
+
brent: oil?.brent,
|
|
201
|
+
wti: oil?.wti,
|
|
202
|
+
uk: natural_gas?.uk,
|
|
203
|
+
us: natural_gas?.us,
|
|
204
|
+
eu: natural_gas?.eu,
|
|
205
|
+
};
|
|
206
|
+
for (const slug of this.commodityFilter) {
|
|
207
|
+
if (present[slug])
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
scheduleReconnect() {
|
|
213
|
+
if (this.closed || !this.options.autoReconnect)
|
|
214
|
+
return;
|
|
215
|
+
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
|
|
216
|
+
this.emit("error", new Error(`WebSocket reconnect failed after ${this.reconnectAttempts} attempt(s); giving up.`));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const attempt = this.reconnectAttempts++;
|
|
220
|
+
const delay = Math.min(this.options.reconnectDelay * Math.pow(2, attempt), this.options.maxReconnectDelay);
|
|
221
|
+
this.emit("reconnecting", { attempt: attempt + 1, delay });
|
|
222
|
+
this.reconnectTimer = setTimeout(() => {
|
|
223
|
+
this.reconnectTimer = null;
|
|
224
|
+
this.connect();
|
|
225
|
+
}, delay);
|
|
226
|
+
}
|
|
227
|
+
send(payload) {
|
|
228
|
+
if (this.ws && this.ws.readyState === this.wsImpl.OPEN) {
|
|
229
|
+
this.ws.send(JSON.stringify(payload));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Whether the channel subscription has been confirmed by the server. */
|
|
233
|
+
get isSubscribed() {
|
|
234
|
+
return this.subscribed;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Cleanly tear down the stream: cancels any pending reconnect, unsubscribes
|
|
238
|
+
* from the channel, and closes the socket. Safe to call multiple times.
|
|
239
|
+
* Emits `"close"` once.
|
|
240
|
+
*/
|
|
241
|
+
close() {
|
|
242
|
+
if (this.closed)
|
|
243
|
+
return;
|
|
244
|
+
this.closed = true;
|
|
245
|
+
if (this.reconnectTimer) {
|
|
246
|
+
clearTimeout(this.reconnectTimer);
|
|
247
|
+
this.reconnectTimer = null;
|
|
248
|
+
}
|
|
249
|
+
if (this.ws) {
|
|
250
|
+
const ws = this.ws;
|
|
251
|
+
try {
|
|
252
|
+
if (ws.readyState === this.wsImpl.OPEN) {
|
|
253
|
+
ws.send(JSON.stringify({ command: "unsubscribe", identifier: this.identifier }));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// ignore — we're closing anyway
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
ws.close();
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// ignore
|
|
264
|
+
}
|
|
265
|
+
this.ws = null;
|
|
266
|
+
}
|
|
267
|
+
this.subscribed = false;
|
|
268
|
+
this.emit("close");
|
|
269
|
+
this.removeAllListeners();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Streaming resource — entry point for real-time price subscriptions.
|
|
274
|
+
*
|
|
275
|
+
* Accessed via `client.stream`.
|
|
276
|
+
*/
|
|
277
|
+
export class StreamingResource {
|
|
278
|
+
constructor(client,
|
|
279
|
+
/**
|
|
280
|
+
* Injectable WebSocket implementation. Defaults to the `ws` package;
|
|
281
|
+
* tests pass a mock constructor.
|
|
282
|
+
* @internal
|
|
283
|
+
*/
|
|
284
|
+
wsImpl = WebSocket) {
|
|
285
|
+
this.client = client;
|
|
286
|
+
this.wsImpl = wsImpl;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Derive the `wss://.../cable` endpoint from the client's REST base URL.
|
|
290
|
+
*
|
|
291
|
+
* `https://api.oilpriceapi.com` -> `wss://api.oilpriceapi.com/cable`
|
|
292
|
+
* `http://localhost:5000` -> `ws://localhost:5000/cable`
|
|
293
|
+
*/
|
|
294
|
+
cableUrl() {
|
|
295
|
+
const base = this.client["baseUrl"];
|
|
296
|
+
const wsBase = base
|
|
297
|
+
.replace(/^http:/, "ws:")
|
|
298
|
+
.replace(/^https:/, "wss:")
|
|
299
|
+
.replace(/\/$/, "");
|
|
300
|
+
return `${wsBase}/cable`;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Open a real-time price stream over the `EnergyPricesChannel`.
|
|
304
|
+
*
|
|
305
|
+
* Returns a {@link PriceStreamSubscription} handle (an `EventEmitter`) you
|
|
306
|
+
* can attach further listeners to and `.close()` when done. The optional
|
|
307
|
+
* `onUpdate` callback is a convenience wired to the `price_update` event.
|
|
308
|
+
*
|
|
309
|
+
* @param options - Filtering and reconnect options.
|
|
310
|
+
* @param onUpdate - Optional callback for each `price_update` message.
|
|
311
|
+
* @returns The subscription handle.
|
|
312
|
+
*
|
|
313
|
+
* @throws {OilPriceAPIError} If no API key is configured on the client.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* const client = new OilPriceAPI({ apiKey: process.env.OILPRICEAPI_KEY });
|
|
318
|
+
*
|
|
319
|
+
* const sub = client.stream.prices(
|
|
320
|
+
* { commodities: ["WTI_USD", "BRENT_CRUDE_USD"] },
|
|
321
|
+
* (update) => {
|
|
322
|
+
* const wti = update.prices.oil.wti;
|
|
323
|
+
* if (wti) console.log(`WTI ${wti.original_price} @ ${update.timestamp}`);
|
|
324
|
+
* },
|
|
325
|
+
* );
|
|
326
|
+
*
|
|
327
|
+
* sub.on("connected", () => console.log("streaming live"));
|
|
328
|
+
* sub.on("error", (err) => console.error("stream error:", err));
|
|
329
|
+
*
|
|
330
|
+
* process.on("SIGINT", () => sub.close());
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
prices(options = {}, onUpdate) {
|
|
334
|
+
const apiKey = this.client["apiKey"];
|
|
335
|
+
const sub = new PriceStreamSubscription(this.cableUrl(), apiKey, options, this.wsImpl);
|
|
336
|
+
if (onUpdate) {
|
|
337
|
+
sub.on("price_update", onUpdate);
|
|
338
|
+
}
|
|
339
|
+
sub.connect();
|
|
340
|
+
return sub;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -10,18 +10,16 @@ import type { OilPriceAPI } from "../client.js";
|
|
|
10
10
|
export interface WebhookEndpoint {
|
|
11
11
|
/** Unique webhook identifier */
|
|
12
12
|
id: string;
|
|
13
|
-
/** User-friendly webhook
|
|
14
|
-
|
|
13
|
+
/** User-friendly description of the webhook */
|
|
14
|
+
description?: string;
|
|
15
15
|
/** Webhook URL (must be HTTPS) */
|
|
16
16
|
url: string;
|
|
17
17
|
/** Event types to subscribe to */
|
|
18
18
|
events: string[];
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/** Optional secret for signature verification */
|
|
19
|
+
/** Lifecycle status (e.g. 'active', 'disabled') */
|
|
20
|
+
status?: string;
|
|
21
|
+
/** Optional secret for signature verification (generated server-side) */
|
|
22
22
|
secret?: string;
|
|
23
|
-
/** Optional metadata */
|
|
24
|
-
metadata?: Record<string, unknown>;
|
|
25
23
|
/** Number of successful deliveries */
|
|
26
24
|
successful_deliveries: number;
|
|
27
25
|
/** Number of failed deliveries */
|
|
@@ -37,37 +35,53 @@ export interface WebhookEndpoint {
|
|
|
37
35
|
}
|
|
38
36
|
/**
|
|
39
37
|
* Parameters for creating a webhook
|
|
38
|
+
*
|
|
39
|
+
* NOTE: The API permits a flat (un-nested) body with the fields below. Earlier
|
|
40
|
+
* SDK versions nested these under a `webhook` key and used `name`/`enabled`,
|
|
41
|
+
* which the controller dropped — the controller reads `description` and `status`.
|
|
40
42
|
*/
|
|
41
43
|
export interface CreateWebhookParams {
|
|
42
|
-
/** User-friendly webhook name */
|
|
43
|
-
name: string;
|
|
44
44
|
/** Webhook URL (must be HTTPS) */
|
|
45
45
|
url: string;
|
|
46
46
|
/** Event types to subscribe to */
|
|
47
47
|
events: string[];
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
|
|
48
|
+
/** User-friendly description */
|
|
49
|
+
description?: string;
|
|
50
|
+
/** Lifecycle status (e.g. 'active', 'disabled') */
|
|
51
|
+
status?: string;
|
|
52
|
+
/** Commodity codes to filter events to */
|
|
53
|
+
commodity_filters?: string[];
|
|
54
|
+
/** US state codes to filter events to */
|
|
55
|
+
state_filters?: string[];
|
|
56
|
+
/** Per-second delivery rate limit */
|
|
57
|
+
rate_limit_per_second?: number;
|
|
58
|
+
/** Delivery timeout in seconds */
|
|
59
|
+
timeout_seconds?: number;
|
|
60
|
+
/** Max delivery retry attempts */
|
|
61
|
+
max_retries?: number;
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
56
64
|
* Parameters for updating a webhook
|
|
57
65
|
*/
|
|
58
66
|
export interface UpdateWebhookParams {
|
|
59
|
-
/** User-friendly webhook name */
|
|
60
|
-
name?: string;
|
|
61
67
|
/** Webhook URL */
|
|
62
68
|
url?: string;
|
|
63
69
|
/** Event types to subscribe to */
|
|
64
70
|
events?: string[];
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
+
/** User-friendly description */
|
|
72
|
+
description?: string;
|
|
73
|
+
/** Lifecycle status (e.g. 'active', 'disabled') */
|
|
74
|
+
status?: string;
|
|
75
|
+
/** Commodity codes to filter events to */
|
|
76
|
+
commodity_filters?: string[];
|
|
77
|
+
/** US state codes to filter events to */
|
|
78
|
+
state_filters?: string[];
|
|
79
|
+
/** Per-second delivery rate limit */
|
|
80
|
+
rate_limit_per_second?: number;
|
|
81
|
+
/** Delivery timeout in seconds */
|
|
82
|
+
timeout_seconds?: number;
|
|
83
|
+
/** Max delivery retry attempts */
|
|
84
|
+
max_retries?: number;
|
|
71
85
|
}
|
|
72
86
|
/**
|
|
73
87
|
* Webhook test response
|
|
@@ -114,27 +114,16 @@ export class WebhooksResource {
|
|
|
114
114
|
* ```
|
|
115
115
|
*/
|
|
116
116
|
async create(params) {
|
|
117
|
-
if (!params.name || typeof params.name !== "string") {
|
|
118
|
-
throw new ValidationError("Webhook name is required");
|
|
119
|
-
}
|
|
120
117
|
if (!params.url || !params.url.startsWith("https://")) {
|
|
121
118
|
throw new ValidationError("Webhook URL must use HTTPS protocol");
|
|
122
119
|
}
|
|
123
120
|
if (!params.events || !Array.isArray(params.events) || params.events.length === 0) {
|
|
124
121
|
throw new ValidationError("At least one event type is required");
|
|
125
122
|
}
|
|
123
|
+
// The controller reads a flat (un-nested) body via params.permit(...).
|
|
126
124
|
const response = await this.client["request"]("/v1/webhooks", {}, {
|
|
127
125
|
method: "POST",
|
|
128
|
-
body: {
|
|
129
|
-
webhook: {
|
|
130
|
-
name: params.name,
|
|
131
|
-
url: params.url,
|
|
132
|
-
events: params.events,
|
|
133
|
-
enabled: params.enabled ?? true,
|
|
134
|
-
secret: params.secret,
|
|
135
|
-
metadata: params.metadata,
|
|
136
|
-
},
|
|
137
|
-
},
|
|
126
|
+
body: { ...params },
|
|
138
127
|
});
|
|
139
128
|
return "webhook" in response ? response.webhook : response;
|
|
140
129
|
}
|
|
@@ -172,7 +161,7 @@ export class WebhooksResource {
|
|
|
172
161
|
}
|
|
173
162
|
const response = await this.client["request"](`/v1/webhooks/${id}`, {}, {
|
|
174
163
|
method: "PATCH",
|
|
175
|
-
body: {
|
|
164
|
+
body: { ...params },
|
|
176
165
|
});
|
|
177
166
|
return "webhook" in response ? response.webhook : response;
|
|
178
167
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -240,6 +240,47 @@ export interface Commodity {
|
|
|
240
240
|
export interface CommoditiesResponse {
|
|
241
241
|
commodities: Commodity[];
|
|
242
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* A single price entry from the no-auth demo prices endpoint.
|
|
245
|
+
*/
|
|
246
|
+
export interface DemoPrice {
|
|
247
|
+
code: string;
|
|
248
|
+
name: string;
|
|
249
|
+
price: number;
|
|
250
|
+
currency: string;
|
|
251
|
+
updated_at: string;
|
|
252
|
+
change_24h?: number;
|
|
253
|
+
source?: string;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Parsed response from the no-auth `GET /v1/demo/prices` endpoint.
|
|
257
|
+
*/
|
|
258
|
+
export interface DemoPricesResponse {
|
|
259
|
+
prices: DemoPrice[];
|
|
260
|
+
meta: {
|
|
261
|
+
demo_mode?: boolean;
|
|
262
|
+
rate_limit?: string;
|
|
263
|
+
available_commodities?: number;
|
|
264
|
+
[key: string]: unknown;
|
|
265
|
+
};
|
|
266
|
+
examples?: unknown;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Parsed response from the no-auth `GET /v1/demo/commodities` endpoint.
|
|
270
|
+
*/
|
|
271
|
+
export interface DemoCommoditiesResponse {
|
|
272
|
+
/** Commodities grouped by category key. */
|
|
273
|
+
commodities: Record<string, Commodity[]>;
|
|
274
|
+
meta: {
|
|
275
|
+
/** Total number of demo-listed commodities. */
|
|
276
|
+
total: number;
|
|
277
|
+
/** Category keys present in the listing. */
|
|
278
|
+
categories: string[];
|
|
279
|
+
/** Commodity codes available on the free demo tier. */
|
|
280
|
+
free_commodities: string[];
|
|
281
|
+
[key: string]: unknown;
|
|
282
|
+
};
|
|
283
|
+
}
|
|
243
284
|
/**
|
|
244
285
|
* Category with its commodities
|
|
245
286
|
*/
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oilpriceapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Official Node.js SDK for Oil Price API - Real-time and historical oil & commodity prices",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"lint": "eslint src/",
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
|
+
"test:live": "vitest run --config vitest.live.config.ts",
|
|
30
31
|
"docs": "typedoc",
|
|
31
32
|
"prepublishOnly": "npm run build"
|
|
32
33
|
},
|
|
@@ -62,10 +63,15 @@
|
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@types/node": "^20.10.0",
|
|
66
|
+
"@types/ws": "^8.18.1",
|
|
65
67
|
"@vitest/coverage-v8": "^4.0.16",
|
|
66
68
|
"eslint": "^9.39.4",
|
|
69
|
+
"typedoc": "^0.27.9",
|
|
67
70
|
"typescript": "^5.3.0",
|
|
68
71
|
"typescript-eslint": "^8.57.2",
|
|
69
72
|
"vitest": "^4.0.16"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"ws": "^8.21.0"
|
|
70
76
|
}
|
|
71
77
|
}
|