oilpriceapi 0.8.2 → 0.9.1

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 (53) hide show
  1. package/README.md +201 -19
  2. package/dist/cjs/client.js +139 -19
  3. package/dist/cjs/index.js +17 -3
  4. package/dist/cjs/resources/analytics.js +99 -137
  5. package/dist/cjs/resources/bunker-fuels.js +37 -23
  6. package/dist/cjs/resources/data-sources.js +13 -12
  7. package/dist/cjs/resources/ei/frac-focus.js +16 -6
  8. package/dist/cjs/resources/ei/well-permits.js +18 -6
  9. package/dist/cjs/resources/forecasts.js +11 -5
  10. package/dist/cjs/resources/futures.js +244 -16
  11. package/dist/cjs/resources/indicators.js +79 -0
  12. package/dist/cjs/resources/raw.js +128 -0
  13. package/dist/cjs/resources/rig-counts.js +5 -2
  14. package/dist/cjs/resources/spreads.js +105 -0
  15. package/dist/cjs/resources/storage.js +5 -5
  16. package/dist/cjs/resources/streaming.js +350 -0
  17. package/dist/cjs/resources/webhooks.js +3 -14
  18. package/dist/cjs/version.js +1 -1
  19. package/dist/client.d.ts +97 -1
  20. package/dist/client.js +139 -19
  21. package/dist/index.d.ts +12 -3
  22. package/dist/index.js +5 -0
  23. package/dist/resources/analytics.d.ts +147 -214
  24. package/dist/resources/analytics.js +99 -137
  25. package/dist/resources/bunker-fuels.d.ts +35 -12
  26. package/dist/resources/bunker-fuels.js +37 -23
  27. package/dist/resources/data-sources.d.ts +31 -31
  28. package/dist/resources/data-sources.js +13 -12
  29. package/dist/resources/ei/frac-focus.d.ts +23 -9
  30. package/dist/resources/ei/frac-focus.js +16 -6
  31. package/dist/resources/ei/well-permits.d.ts +25 -9
  32. package/dist/resources/ei/well-permits.js +18 -6
  33. package/dist/resources/forecasts.d.ts +4 -1
  34. package/dist/resources/forecasts.js +11 -5
  35. package/dist/resources/futures.d.ts +287 -33
  36. package/dist/resources/futures.js +241 -15
  37. package/dist/resources/indicators.d.ts +170 -0
  38. package/dist/resources/indicators.js +75 -0
  39. package/dist/resources/raw.d.ts +94 -0
  40. package/dist/resources/raw.js +124 -0
  41. package/dist/resources/rig-counts.js +5 -2
  42. package/dist/resources/spreads.d.ts +121 -0
  43. package/dist/resources/spreads.js +101 -0
  44. package/dist/resources/storage.d.ts +5 -4
  45. package/dist/resources/storage.js +5 -5
  46. package/dist/resources/streaming.d.ts +272 -0
  47. package/dist/resources/streaming.js +342 -0
  48. package/dist/resources/webhooks.d.ts +37 -23
  49. package/dist/resources/webhooks.js +3 -14
  50. package/dist/types.d.ts +41 -0
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +7 -1
@@ -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;
@@ -117,27 +117,16 @@ class WebhooksResource {
117
117
  * ```
118
118
  */
119
119
  async create(params) {
120
- if (!params.name || typeof params.name !== "string") {
121
- throw new errors_js_1.ValidationError("Webhook name is required");
122
- }
123
120
  if (!params.url || !params.url.startsWith("https://")) {
124
121
  throw new errors_js_1.ValidationError("Webhook URL must use HTTPS protocol");
125
122
  }
126
123
  if (!params.events || !Array.isArray(params.events) || params.events.length === 0) {
127
124
  throw new errors_js_1.ValidationError("At least one event type is required");
128
125
  }
126
+ // The controller reads a flat (un-nested) body via params.permit(...).
129
127
  const response = await this.client["request"]("/v1/webhooks", {}, {
130
128
  method: "POST",
131
- body: {
132
- webhook: {
133
- name: params.name,
134
- url: params.url,
135
- events: params.events,
136
- enabled: params.enabled ?? true,
137
- secret: params.secret,
138
- metadata: params.metadata,
139
- },
140
- },
129
+ body: { ...params },
141
130
  });
142
131
  return "webhook" in response ? response.webhook : response;
143
132
  }
@@ -175,7 +164,7 @@ class WebhooksResource {
175
164
  }
176
165
  const response = await this.client["request"](`/v1/webhooks/${id}`, {}, {
177
166
  method: "PATCH",
178
- body: { webhook: params },
167
+ body: { ...params },
179
168
  });
180
169
  return "webhook" in response ? response.webhook : response;
181
170
  }
@@ -11,7 +11,7 @@ exports.buildUserAgent = buildUserAgent;
11
11
  * - X-Client-Version header
12
12
  * - Package.json (should match)
13
13
  */
14
- exports.SDK_VERSION = "0.8.2";
14
+ exports.SDK_VERSION = "0.9.1";
15
15
  /**
16
16
  * SDK identifier used in User-Agent and X-Api-Client headers
17
17
  */
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OilPriceAPIConfig, Price, LatestPricesOptions, HistoricalPricesOptions, Commodity, CommoditiesResponse, CategoriesResponse, DataConnectorPrice, DataConnectorOptions } from "./types.js";
1
+ import type { OilPriceAPIConfig, Price, LatestPricesOptions, HistoricalPricesOptions, Commodity, CommoditiesResponse, CategoriesResponse, DataConnectorPrice, DataConnectorOptions, DemoPricesResponse, DemoCommoditiesResponse } from "./types.js";
2
2
  import { DieselResource } from "./resources/diesel.js";
3
3
  import { AlertsResource } from "./resources/alerts.js";
4
4
  import { CommoditiesResource } from "./resources/commodities.js";
@@ -13,6 +13,26 @@ import { DrillingIntelligenceResource } from "./resources/drilling.js";
13
13
  import { EnergyIntelligenceResource } from "./resources/ei/index.js";
14
14
  import { WebhooksResource } from "./resources/webhooks.js";
15
15
  import { DataSourcesResource } from "./resources/data-sources.js";
16
+ import { SpreadsResource } from "./resources/spreads.js";
17
+ import { IndicatorsResource } from "./resources/indicators.js";
18
+ import { RawResource } from "./resources/raw.js";
19
+ import { StreamingResource } from "./resources/streaming.js";
20
+ /**
21
+ * Raw HTTP response wrapper.
22
+ *
23
+ * Returned by {@link OilPriceAPI.raw} accessors to expose the underlying
24
+ * HTTP status code and response headers alongside the parsed data.
25
+ *
26
+ * @typeParam T - The parsed response body type.
27
+ */
28
+ export interface APIResponse<T> {
29
+ /** Parsed response data (same shape the non-raw method would return). */
30
+ data: T;
31
+ /** HTTP status code (e.g., 200, 201). */
32
+ status: number;
33
+ /** Response headers. */
34
+ headers: Headers;
35
+ }
16
36
  /**
17
37
  * Official Node.js client for Oil Price API
18
38
  *
@@ -105,6 +125,28 @@ export declare class OilPriceAPI {
105
125
  * Data sources resource (BYOS - Bring Your Own Source)
106
126
  */
107
127
  readonly dataSources: DataSourcesResource;
128
+ /**
129
+ * Spreads resource (crack, basis, curve structure, margin, physical premium)
130
+ */
131
+ readonly spreads: SpreadsResource;
132
+ /**
133
+ * Indicators resource (fuel switching, price context, storage analytics,
134
+ * annotations, CFTC positioning, congressional trades)
135
+ */
136
+ readonly indicators: IndicatorsResource;
137
+ /**
138
+ * Raw-response accessor.
139
+ *
140
+ * Mirrors the top-level price/commodity methods but returns the underlying
141
+ * HTTP status and headers alongside the parsed data via {@link APIResponse}.
142
+ */
143
+ readonly raw: RawResource;
144
+ /**
145
+ * Real-time price streaming resource (WebSocket / ActionCable).
146
+ *
147
+ * Streaming requires a Reservoir Mastery (Professional+) plan.
148
+ */
149
+ readonly stream: StreamingResource;
108
150
  constructor(config?: OilPriceAPIConfig);
109
151
  /**
110
152
  * Log debug messages if debug mode is enabled
@@ -122,12 +164,29 @@ export declare class OilPriceAPI {
122
164
  * Determine if error is retryable
123
165
  */
124
166
  private isRetryable;
167
+ /**
168
+ * Shape a parsed JSON response body into the value returned to callers.
169
+ *
170
+ * Centralizes the response-structure handling so that both {@link request}
171
+ * and {@link requestRaw} return identical data. Handles the latest/historical
172
+ * envelope shapes as well as the generic `{ data }` fallback used by resource
173
+ * mutations, alerts, webhooks, etc.
174
+ */
175
+ private shapeResponseData;
125
176
  /**
126
177
  * Internal method to make HTTP requests with retry and timeout.
127
178
  * Supports all HTTP methods (GET, POST, PATCH, DELETE) with consistent
128
179
  * retry logic, timeout handling, and typed error responses.
129
180
  */
130
181
  private request;
182
+ /**
183
+ * Internal method identical to {@link request} but returns the underlying
184
+ * HTTP status and headers alongside the parsed data.
185
+ *
186
+ * Used by the public {@link raw} accessor to expose response metadata
187
+ * (issue #7) without changing the return shape of existing methods.
188
+ */
189
+ private requestRaw;
131
190
  /**
132
191
  * Get the latest prices for all commodities or a specific commodity
133
192
  *
@@ -252,4 +311,41 @@ export declare class OilPriceAPI {
252
311
  * ```
253
312
  */
254
313
  getCommodity(code: string): Promise<Commodity>;
314
+ /**
315
+ * Fetch live sample prices from the public, no-auth demo endpoint.
316
+ *
317
+ * Hits `GET /v1/demo/prices` (no API key required) and returns the parsed
318
+ * `{ prices, meta }` envelope. Useful for trying the client without
319
+ * credentials. Subject to the demo rate limit (~20 requests/hour).
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const demo = await client.getDemoPrices();
324
+ * const brent = demo.prices.find(p => p.code === 'BRENT_CRUDE_USD');
325
+ * console.log(brent?.price);
326
+ * ```
327
+ */
328
+ getDemoPrices(): Promise<DemoPricesResponse>;
329
+ /**
330
+ * Fetch the catalogue of commodities from the public, no-auth demo endpoint.
331
+ *
332
+ * Hits `GET /v1/demo/commodities` (no API key required) and returns the parsed
333
+ * `{ commodities, meta }` envelope, where `meta.free_commodities` lists the
334
+ * codes available on the free demo tier.
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * const demo = await client.getDemoCommodities();
339
+ * console.log(demo.meta.total, demo.meta.free_commodities);
340
+ * ```
341
+ */
342
+ getDemoCommodities(): Promise<DemoCommoditiesResponse>;
343
+ /**
344
+ * Minimal fetch for the no-auth demo endpoints.
345
+ *
346
+ * Unlike {@link request}, this does NOT run the latest/historical response
347
+ * shaping (which would strip the `meta` block) and does NOT require an API
348
+ * key — it returns the raw `data` envelope from `{ status, data }`.
349
+ */
350
+ private requestDemo;
255
351
  }