oilpriceapi 0.9.0 → 0.10.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 +78 -2
- package/dist/cjs/client.js +49 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/resources/futures.js +61 -24
- package/dist/cjs/resources/market-brief.js +9 -0
- package/dist/cjs/resources/streaming.js +3 -3
- package/dist/cjs/resources/subscriptions.js +217 -0
- package/dist/cjs/version.js +1 -1
- package/dist/client.d.ts +36 -1
- package/dist/client.js +50 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/resources/futures.d.ts +112 -35
- package/dist/resources/futures.js +60 -24
- package/dist/resources/market-brief.d.ts +72 -0
- package/dist/resources/market-brief.js +8 -0
- package/dist/resources/streaming.d.ts +2 -2
- package/dist/resources/streaming.js +3 -3
- package/dist/resources/subscriptions.d.ts +238 -0
- package/dist/resources/subscriptions.js +212 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ The official Node.js/TypeScript SDK for [OilPriceAPI](https://www.oilpriceapi.co
|
|
|
26
26
|
- 🔔 **NEW v0.5.0** - Price alerts with webhook notifications
|
|
27
27
|
- 📊 **NEW v0.7.0** - Futures, storage, rig counts, analytics, drilling intelligence, webhooks, and energy intelligence
|
|
28
28
|
- 🧰 **NEW** - Typed ICE Brent / Gasoil / WTI & gas/carbon futures helpers, `spreads`, `indicators`, and raw HTTP responses (`client.raw.*` with status + headers)
|
|
29
|
+
- 🤖 **NEW v0.10.0** - `getMarketBrief()` (multi-commodity structured + narrative summary) and agent `subscriptions` (persistent watches + event polling)
|
|
29
30
|
|
|
30
31
|
## Installation
|
|
31
32
|
|
|
@@ -645,8 +646,8 @@ logs.forEach((log) => {
|
|
|
645
646
|
|
|
646
647
|
Stream live price updates over a persistent WebSocket connection instead of
|
|
647
648
|
polling. Streaming uses the server's ActionCable `/cable` endpoint and the
|
|
648
|
-
`EnergyPricesChannel`, and is available on the **
|
|
649
|
-
(
|
|
649
|
+
`EnergyPricesChannel`, and is available on the **Professional plan
|
|
650
|
+
($99/mo) or higher**.
|
|
650
651
|
|
|
651
652
|
The `client.stream.prices()` method returns a subscription handle (an
|
|
652
653
|
`EventEmitter`). It performs the ActionCable handshake, answers server pings,
|
|
@@ -704,6 +705,81 @@ process.on("SIGINT", () => {
|
|
|
704
705
|
|
|
705
706
|
See [`examples/streaming.ts`](./examples/streaming.ts) for a complete runnable example.
|
|
706
707
|
|
|
708
|
+
### Market Brief (New in v0.10.0)
|
|
709
|
+
|
|
710
|
+
A single call returns a structured, multi-commodity market summary — latest
|
|
711
|
+
price, 24h change, freshness, and a 1-month forecast band per commodity — with
|
|
712
|
+
an optional natural-language narrative. Counts as one request against your quota.
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
import { OilPriceAPI } from "oilpriceapi";
|
|
716
|
+
|
|
717
|
+
const client = new OilPriceAPI({ apiKey: "your_api_key" });
|
|
718
|
+
|
|
719
|
+
const brief = await client.getMarketBrief(["BRENT_CRUDE_USD", "WTI_USD"]);
|
|
720
|
+
|
|
721
|
+
for (const c of brief.commodities) {
|
|
722
|
+
console.log(`${c.name}: $${c.price} (${c.change_24h_pct}% 24h)`);
|
|
723
|
+
if (c.forecast_1m) {
|
|
724
|
+
console.log(
|
|
725
|
+
` 1m forecast: ${c.forecast_1m.point} [${c.forecast_1m.low}–${c.forecast_1m.high}]`,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Include a natural-language narrative
|
|
731
|
+
const withNarrative = await client.getMarketBrief(["BRENT_CRUDE_USD"], {
|
|
732
|
+
narrative: true,
|
|
733
|
+
});
|
|
734
|
+
console.log(withNarrative.narrative);
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Agent Subscriptions / Watches (New in v0.10.0)
|
|
738
|
+
|
|
739
|
+
Persistent server-side "watches" evaluate a set of commodity codes on a recurring
|
|
740
|
+
interval and emit events you can poll for — ideal for autonomous agents that want
|
|
741
|
+
change notifications without holding an open connection. The friendly `interval`
|
|
742
|
+
("5m" / "15m" / "1h" / "daily", or a number of seconds) is mapped to the API's
|
|
743
|
+
`interval_seconds`. Polling for events does **not** consume your request quota.
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
import { OilPriceAPI } from "oilpriceapi";
|
|
747
|
+
|
|
748
|
+
const client = new OilPriceAPI({ apiKey: "your_api_key" });
|
|
749
|
+
|
|
750
|
+
// Create a watch (source defaults to "sdk-node"; pass `tool` for attribution)
|
|
751
|
+
const watch = await client.subscriptions.create({
|
|
752
|
+
name: "Crude desk",
|
|
753
|
+
codes: ["BRENT_CRUDE_USD", "WTI_USD"],
|
|
754
|
+
interval: "5m",
|
|
755
|
+
tool: "my-trading-bot",
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// List all watches
|
|
759
|
+
const watches = await client.subscriptions.list();
|
|
760
|
+
console.log(`You have ${watches.length} watch(es)`);
|
|
761
|
+
|
|
762
|
+
// Poll for new events using a cursor (does not burn quota)
|
|
763
|
+
let cursor = 0;
|
|
764
|
+
while (true) {
|
|
765
|
+
const {
|
|
766
|
+
events,
|
|
767
|
+
cursor: next,
|
|
768
|
+
has_more,
|
|
769
|
+
} = await client.subscriptions.events({
|
|
770
|
+
since: cursor,
|
|
771
|
+
});
|
|
772
|
+
for (const event of events) {
|
|
773
|
+
console.log(`event #${event.seq} on ${event.code} (${event.type})`);
|
|
774
|
+
}
|
|
775
|
+
cursor = next;
|
|
776
|
+
if (!has_more) break;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Remove a watch when you're done
|
|
780
|
+
await client.subscriptions.delete(watch.id);
|
|
781
|
+
```
|
|
782
|
+
|
|
707
783
|
### Advanced Configuration
|
|
708
784
|
|
|
709
785
|
```typescript
|
package/dist/cjs/client.js
CHANGED
|
@@ -21,6 +21,7 @@ const spreads_js_1 = require("./resources/spreads.js");
|
|
|
21
21
|
const indicators_js_1 = require("./resources/indicators.js");
|
|
22
22
|
const raw_js_1 = require("./resources/raw.js");
|
|
23
23
|
const streaming_js_1 = require("./resources/streaming.js");
|
|
24
|
+
const subscriptions_js_1 = require("./resources/subscriptions.js");
|
|
24
25
|
/**
|
|
25
26
|
* Official Node.js client for Oil Price API
|
|
26
27
|
*
|
|
@@ -80,6 +81,7 @@ class OilPriceAPI {
|
|
|
80
81
|
this.indicators = new indicators_js_1.IndicatorsResource(this);
|
|
81
82
|
this.raw = new raw_js_1.RawResource(this);
|
|
82
83
|
this.stream = new streaming_js_1.StreamingResource(this);
|
|
84
|
+
this.subscriptions = new subscriptions_js_1.SubscriptionsResource(this);
|
|
83
85
|
}
|
|
84
86
|
/**
|
|
85
87
|
* Log debug messages if debug mode is enabled
|
|
@@ -215,6 +217,14 @@ class OilPriceAPI {
|
|
|
215
217
|
if (this.appName) {
|
|
216
218
|
headers["X-App-Name"] = this.appName;
|
|
217
219
|
}
|
|
220
|
+
// Per-request headers (e.g. MCP attribution X-OPA-Source / X-OPA-Tool).
|
|
221
|
+
if (options?.headers) {
|
|
222
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
223
|
+
if (value !== undefined && value !== null) {
|
|
224
|
+
headers[key] = value;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
218
228
|
const fetchOptions = {
|
|
219
229
|
method: options?.method || "GET",
|
|
220
230
|
headers,
|
|
@@ -524,6 +534,45 @@ class OilPriceAPI {
|
|
|
524
534
|
async getCommodity(code) {
|
|
525
535
|
return this.request(`/v1/commodities/${code}`, {});
|
|
526
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Get a multi-commodity market brief (OilPriceAPI #3245 Phase 1a).
|
|
539
|
+
*
|
|
540
|
+
* Returns a structured summary (latest price, 24h change, freshness, and a
|
|
541
|
+
* 1-month forecast band) for each requested commodity, optionally with a
|
|
542
|
+
* natural-language narrative. Counts as a single request against your quota,
|
|
543
|
+
* like `/v1/prices/batch`. The per-tier cap on `codes` is enforced server-side.
|
|
544
|
+
*
|
|
545
|
+
* @param codes - Commodity codes (e.g. ["BRENT_CRUDE_USD", "WTI_USD"]). Shorthand
|
|
546
|
+
* codes like "WTI"/"BRENT" are accepted and resolved server-side.
|
|
547
|
+
* @param options - `{ narrative }` to request the natural-language summary.
|
|
548
|
+
* @returns The structured (and optional narrative) market brief.
|
|
549
|
+
*
|
|
550
|
+
* @throws {ValidationError} If `codes` is empty.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* ```typescript
|
|
554
|
+
* const brief = await client.getMarketBrief(['BRENT_CRUDE_USD', 'WTI_USD']);
|
|
555
|
+
* for (const c of brief.commodities) {
|
|
556
|
+
* console.log(`${c.name}: $${c.price} (${c.change_24h_pct}%)`);
|
|
557
|
+
* }
|
|
558
|
+
*
|
|
559
|
+
* // With narrative
|
|
560
|
+
* const withText = await client.getMarketBrief(['BRENT_CRUDE_USD'], { narrative: true });
|
|
561
|
+
* console.log(withText.narrative);
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
async getMarketBrief(codes, options) {
|
|
565
|
+
if (!Array.isArray(codes) || codes.length === 0) {
|
|
566
|
+
throw new errors_js_1.ValidationError("codes is required and must be a non-empty array of commodity codes");
|
|
567
|
+
}
|
|
568
|
+
const params = {
|
|
569
|
+
codes: codes.join(","),
|
|
570
|
+
};
|
|
571
|
+
if (options?.narrative) {
|
|
572
|
+
params.narrative = "true";
|
|
573
|
+
}
|
|
574
|
+
return this.request("/v1/market-brief", params);
|
|
575
|
+
}
|
|
527
576
|
/**
|
|
528
577
|
* Fetch live sample prices from the public, no-auth demo endpoint.
|
|
529
578
|
*
|
package/dist/cjs/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ENERGY_PRICES_CHANNEL = exports.PriceStreamSubscription = exports.StreamingResource = exports.DataSourcesResource = exports.WebhooksResource = exports.EIFracFocusResource = exports.EIWellPermitsResource = exports.EIForecastsResource = exports.EIDrillingProductivityResource = exports.EIOPECProductionResource = exports.EIOilInventoriesResource = exports.EIRigCountsResource = exports.EnergyIntelligenceResource = exports.DrillingIntelligenceResource = exports.DataQualityResource = exports.ForecastsResource = exports.AnalyticsResource = exports.BunkerFuelsResource = exports.RigCountsResource = exports.StorageResource = exports.FuturesResource = exports.CommoditiesResource = exports.AlertsResource = exports.DieselResource = exports.TimeoutError = exports.ValidationError = exports.ServerError = exports.NotFoundError = exports.RateLimitError = exports.AuthenticationError = exports.OilPriceAPIError = exports.RawResource = exports.IndicatorsResource = exports.SpreadsResource = exports.FuturesContractFamily = exports.FUTURES_FAMILY_SLUGS = exports.FUTURES_CONTRACTS = exports.SDK_NAME = exports.SDK_VERSION = exports.OilPriceAPI = void 0;
|
|
10
|
+
exports.ENERGY_PRICES_CHANNEL = exports.PriceStreamSubscription = exports.StreamingResource = exports.DataSourcesResource = exports.WebhooksResource = exports.EIFracFocusResource = exports.EIWellPermitsResource = exports.EIForecastsResource = exports.EIDrillingProductivityResource = exports.EIOPECProductionResource = exports.EIOilInventoriesResource = exports.EIRigCountsResource = exports.EnergyIntelligenceResource = exports.DrillingIntelligenceResource = exports.DataQualityResource = exports.ForecastsResource = exports.AnalyticsResource = exports.BunkerFuelsResource = exports.RigCountsResource = exports.StorageResource = exports.FuturesResource = exports.CommoditiesResource = exports.AlertsResource = exports.DieselResource = exports.TimeoutError = exports.ValidationError = exports.ServerError = exports.NotFoundError = exports.RateLimitError = exports.AuthenticationError = exports.OilPriceAPIError = exports.intervalToSeconds = exports.SubscriptionsResource = exports.RawResource = exports.IndicatorsResource = exports.SpreadsResource = exports.FuturesContractFamily = exports.FUTURES_FAMILY_SLUGS = exports.FUTURES_CONTRACTS = exports.SDK_NAME = exports.SDK_VERSION = exports.OilPriceAPI = void 0;
|
|
11
11
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
var client_js_1 = require("./client.js");
|
|
@@ -25,6 +25,9 @@ var indicators_js_1 = require("./resources/indicators.js");
|
|
|
25
25
|
Object.defineProperty(exports, "IndicatorsResource", { enumerable: true, get: function () { return indicators_js_1.IndicatorsResource; } });
|
|
26
26
|
var raw_js_1 = require("./resources/raw.js");
|
|
27
27
|
Object.defineProperty(exports, "RawResource", { enumerable: true, get: function () { return raw_js_1.RawResource; } });
|
|
28
|
+
var subscriptions_js_1 = require("./resources/subscriptions.js");
|
|
29
|
+
Object.defineProperty(exports, "SubscriptionsResource", { enumerable: true, get: function () { return subscriptions_js_1.SubscriptionsResource; } });
|
|
30
|
+
Object.defineProperty(exports, "intervalToSeconds", { enumerable: true, get: function () { return subscriptions_js_1.intervalToSeconds; } });
|
|
28
31
|
var errors_js_1 = require("./errors.js");
|
|
29
32
|
Object.defineProperty(exports, "OilPriceAPIError", { enumerable: true, get: function () { return errors_js_1.OilPriceAPIError; } });
|
|
30
33
|
Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return errors_js_1.AuthenticationError; } });
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.FuturesResource = exports.FuturesContractFamily = exports.FUTURES_FAMILY_SLUGS = exports.FUTURES_CONTRACTS = void 0;
|
|
10
|
+
exports.resolveFuturesFamilySlug = resolveFuturesFamilySlug;
|
|
10
11
|
const errors_js_1 = require("../errors.js");
|
|
11
12
|
/**
|
|
12
13
|
* Ergonomic contract codes for the most-requested futures families (issue #1).
|
|
@@ -48,15 +49,34 @@ exports.FUTURES_CONTRACTS = {
|
|
|
48
49
|
* path segment used by the typed family helpers.
|
|
49
50
|
*/
|
|
50
51
|
exports.FUTURES_FAMILY_SLUGS = {
|
|
51
|
-
[exports.FUTURES_CONTRACTS.BRENT]: "ice-brent",
|
|
52
|
-
[exports.FUTURES_CONTRACTS.WTI]: "ice-wti",
|
|
53
|
-
[exports.FUTURES_CONTRACTS.GASOIL]: "ice-gasoil",
|
|
54
|
-
|
|
55
|
-
[exports.FUTURES_CONTRACTS.
|
|
56
|
-
[exports.FUTURES_CONTRACTS.
|
|
57
|
-
[exports.FUTURES_CONTRACTS.
|
|
58
|
-
[exports.FUTURES_CONTRACTS.
|
|
52
|
+
[exports.FUTURES_CONTRACTS.BRENT]: "ice-brent", // BZ
|
|
53
|
+
[exports.FUTURES_CONTRACTS.WTI]: "ice-wti", // CL
|
|
54
|
+
[exports.FUTURES_CONTRACTS.GASOIL]: "ice-gasoil", // G
|
|
55
|
+
QS: "ice-gasoil", // ICE Gasoil also trades under the QS ticker prefix
|
|
56
|
+
[exports.FUTURES_CONTRACTS.NATURAL_GAS]: "natural-gas", // NG
|
|
57
|
+
[exports.FUTURES_CONTRACTS.TTF_GAS]: "ttf-gas", // TTF
|
|
58
|
+
[exports.FUTURES_CONTRACTS.LNG_JKM]: "lng-jkm", // JKM
|
|
59
|
+
[exports.FUTURES_CONTRACTS.EUA_CARBON]: "eua-carbon", // EUA
|
|
60
|
+
[exports.FUTURES_CONTRACTS.UK_CARBON]: "uk-carbon", // UKA
|
|
59
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a futures contract code (e.g. `"BZ"`, `"QS"`) or an already-valid
|
|
64
|
+
* family slug (e.g. `"ice-brent"`) to its `/v1/futures/{slug}` path segment.
|
|
65
|
+
*
|
|
66
|
+
* Matching is case-insensitive for codes. Returns `null` if the input maps to
|
|
67
|
+
* neither a known code nor a known family slug.
|
|
68
|
+
*/
|
|
69
|
+
function resolveFuturesFamilySlug(codeOrSlug) {
|
|
70
|
+
const trimmed = codeOrSlug.trim();
|
|
71
|
+
// Direct code match (case-insensitive — codes are upper-case).
|
|
72
|
+
const byCode = exports.FUTURES_FAMILY_SLUGS[trimmed.toUpperCase()];
|
|
73
|
+
if (byCode)
|
|
74
|
+
return byCode;
|
|
75
|
+
// Already a valid family slug?
|
|
76
|
+
const lower = trimmed.toLowerCase();
|
|
77
|
+
const isSlug = Object.values(exports.FUTURES_FAMILY_SLUGS).includes(lower);
|
|
78
|
+
return isSlug ? lower : null;
|
|
79
|
+
}
|
|
60
80
|
/**
|
|
61
81
|
* Typed helper for a single futures contract family (e.g., ICE Brent, Gasoil).
|
|
62
82
|
*
|
|
@@ -80,9 +100,12 @@ class FuturesContractFamily {
|
|
|
80
100
|
}
|
|
81
101
|
/**
|
|
82
102
|
* Get the latest price for this contract family.
|
|
103
|
+
*
|
|
104
|
+
* Latest is served from the bare slug path `GET /v1/futures/{slug}` —
|
|
105
|
+
* there is NO `/latest` suffix (that path 404s).
|
|
83
106
|
*/
|
|
84
107
|
async latest() {
|
|
85
|
-
return this.client["request"](`/v1/futures/${this.slug}
|
|
108
|
+
return this.client["request"](`/v1/futures/${this.slug}`, {});
|
|
86
109
|
}
|
|
87
110
|
/**
|
|
88
111
|
* Get historical prices for this contract family.
|
|
@@ -156,16 +179,13 @@ exports.FuturesContractFamily = FuturesContractFamily;
|
|
|
156
179
|
*
|
|
157
180
|
* const client = new OilPriceAPI({ apiKey: 'your_key' });
|
|
158
181
|
*
|
|
159
|
-
* // Get latest
|
|
160
|
-
* const latest = await client.futures.latest('CL
|
|
182
|
+
* // Get the latest curve by contract code (resolves to GET /v1/futures/ice-wti)
|
|
183
|
+
* const latest = await client.futures.latest('CL');
|
|
161
184
|
* console.log(`${latest.contract}: $${latest.price}`);
|
|
162
185
|
*
|
|
163
|
-
* //
|
|
164
|
-
* const
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* // Get futures curve
|
|
168
|
-
* const curve = await client.futures.curve('CL');
|
|
186
|
+
* // Typed family helpers are the most ergonomic option:
|
|
187
|
+
* const brent = await client.futures.brent().latest();
|
|
188
|
+
* const curve = await client.futures.brent().curve();
|
|
169
189
|
* curve.curve.forEach(point => {
|
|
170
190
|
* console.log(`${point.months_out}mo: $${point.price}`);
|
|
171
191
|
* });
|
|
@@ -176,25 +196,42 @@ class FuturesResource {
|
|
|
176
196
|
this.client = client;
|
|
177
197
|
}
|
|
178
198
|
/**
|
|
179
|
-
* Get latest
|
|
199
|
+
* Get the latest curve/quote for a futures contract family.
|
|
180
200
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
201
|
+
* Accepts an ergonomic contract code (e.g. `"BZ"`, `"CL"`, `"QS"`) or a
|
|
202
|
+
* family slug (e.g. `"ice-brent"`). The code is resolved to its family slug
|
|
203
|
+
* and the request is sent to `GET /v1/futures/{slug}` — the bare slug path,
|
|
204
|
+
* with NO `/latest` suffix (the suffixed path 404s).
|
|
183
205
|
*
|
|
184
|
-
*
|
|
206
|
+
* Supported codes: BZ (Brent), CL (WTI), G/QS (Gasoil), NG (Natural Gas),
|
|
207
|
+
* TTF, JKM, EUA, UKA. Slugs: ice-brent, ice-wti, ice-gasoil, natural-gas,
|
|
208
|
+
* ttf-gas, lng-jkm, eua-carbon, uk-carbon.
|
|
209
|
+
*
|
|
210
|
+
* @param contract - Contract code (e.g. "BZ") or family slug (e.g. "ice-brent").
|
|
211
|
+
* @returns Latest futures price/curve data
|
|
212
|
+
*
|
|
213
|
+
* @throws {ValidationError} If the code/slug is empty or unrecognized.
|
|
185
214
|
* @throws {OilPriceAPIError} If API request fails
|
|
186
215
|
*
|
|
187
216
|
* @example
|
|
188
217
|
* ```typescript
|
|
189
|
-
*
|
|
190
|
-
*
|
|
218
|
+
* import { FUTURES_CONTRACTS } from 'oilpriceapi';
|
|
219
|
+
* const price = await client.futures.latest(FUTURES_CONTRACTS.BRENT); // "BZ"
|
|
220
|
+
* const wti = await client.futures.latest('ice-wti');
|
|
191
221
|
* ```
|
|
192
222
|
*/
|
|
193
223
|
async latest(contract) {
|
|
194
224
|
if (!contract || typeof contract !== "string") {
|
|
195
225
|
throw new errors_js_1.ValidationError("Contract symbol must be a non-empty string");
|
|
196
226
|
}
|
|
197
|
-
|
|
227
|
+
const slug = resolveFuturesFamilySlug(contract);
|
|
228
|
+
if (!slug) {
|
|
229
|
+
throw new errors_js_1.ValidationError(`Unknown futures contract "${contract}". Use a contract code ` +
|
|
230
|
+
`(BZ, CL, G, QS, NG, TTF, JKM, EUA, UKA) or a family slug ` +
|
|
231
|
+
`(ice-brent, ice-wti, ice-gasoil, natural-gas, ttf-gas, lng-jkm, ` +
|
|
232
|
+
`eua-carbon, uk-carbon).`);
|
|
233
|
+
}
|
|
234
|
+
return this.client["request"](`/v1/futures/${slug}`, {});
|
|
198
235
|
}
|
|
199
236
|
/**
|
|
200
237
|
* Get historical prices for a futures contract
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Market Brief types (OilPriceAPI #3245 Phase 1a)
|
|
4
|
+
*
|
|
5
|
+
* A multi-commodity structured (+ optional narrative) market summary composed
|
|
6
|
+
* from existing price + forecast data. Served by `GET /v1/market-brief` and
|
|
7
|
+
* surfaced on the client as {@link OilPriceAPI.getMarketBrief}.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Real-time price streaming via the OilPriceAPI ActionCable endpoint
|
|
6
6
|
* (`wss://api.oilpriceapi.com/cable`).
|
|
7
7
|
*
|
|
8
|
-
* Streaming is a **
|
|
8
|
+
* Streaming is a **Professional plan ($99/mo) or higher** feature. Connections
|
|
9
9
|
* authenticate with your API key and subscribe to the `EnergyPricesChannel`,
|
|
10
10
|
* which pushes an initial `welcome` snapshot followed by live `price_update`
|
|
11
11
|
* and (for drilling-tier accounts) `rig_count_update` messages.
|
|
@@ -163,8 +163,8 @@ class PriceStreamSubscription extends node_events_1.EventEmitter {
|
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
if (transportType === "reject_subscription") {
|
|
166
|
-
this.emit("error", new Error("WebSocket subscription rejected. Streaming requires a
|
|
167
|
-
"(
|
|
166
|
+
this.emit("error", new Error("WebSocket subscription rejected. Streaming requires a Professional " +
|
|
167
|
+
"plan ($99/mo) or higher and a valid API key."));
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
if (transportType === "disconnect") {
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent Subscriptions ("Watches") Resource
|
|
4
|
+
*
|
|
5
|
+
* Persistent server-side "watches" that evaluate a set of commodity codes on a
|
|
6
|
+
* recurring interval and emit events an agent can poll for (OilPriceAPI #3245
|
|
7
|
+
* Phase 2). Designed for autonomous agents (MCP, schedulers, bots) that want
|
|
8
|
+
* change notifications without holding an open connection.
|
|
9
|
+
*
|
|
10
|
+
* The poll endpoint (`events`) does NOT consume the monthly request quota and
|
|
11
|
+
* has its own generous rate-limit lane, so agents can poll frequently.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SubscriptionsResource = void 0;
|
|
15
|
+
exports.intervalToSeconds = intervalToSeconds;
|
|
16
|
+
const errors_js_1 = require("../errors.js");
|
|
17
|
+
/** Default attribution source for watches created via this SDK. */
|
|
18
|
+
const DEFAULT_SOURCE = "sdk-node";
|
|
19
|
+
/** Named interval presets mapped to seconds. */
|
|
20
|
+
const INTERVAL_PRESETS = {
|
|
21
|
+
"5m": 300,
|
|
22
|
+
"15m": 900,
|
|
23
|
+
"1h": 3600,
|
|
24
|
+
hourly: 3600,
|
|
25
|
+
daily: 86400,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Convert a friendly interval ("5m" / "1h" / "daily" / 300) into seconds.
|
|
29
|
+
*
|
|
30
|
+
* Accepts:
|
|
31
|
+
* - presets: "5m", "15m", "1h", "hourly", "daily"
|
|
32
|
+
* - unit expressions: "<n>s", "<n>m", "<n>h", "<n>d"
|
|
33
|
+
* - a raw number of seconds
|
|
34
|
+
*
|
|
35
|
+
* @internal Exported for unit testing of the mapping.
|
|
36
|
+
*/
|
|
37
|
+
function intervalToSeconds(interval) {
|
|
38
|
+
if (interval === undefined) {
|
|
39
|
+
return INTERVAL_PRESETS["5m"];
|
|
40
|
+
}
|
|
41
|
+
if (typeof interval === "number") {
|
|
42
|
+
if (!Number.isFinite(interval) || interval <= 0) {
|
|
43
|
+
throw new errors_js_1.ValidationError("interval (seconds) must be a positive number");
|
|
44
|
+
}
|
|
45
|
+
return Math.floor(interval);
|
|
46
|
+
}
|
|
47
|
+
const key = interval.trim().toLowerCase();
|
|
48
|
+
if (key in INTERVAL_PRESETS) {
|
|
49
|
+
return INTERVAL_PRESETS[key];
|
|
50
|
+
}
|
|
51
|
+
// Unit expression: <number><unit> where unit ∈ s/m/h/d.
|
|
52
|
+
const match = /^(\d+)\s*(s|m|h|d)$/.exec(key);
|
|
53
|
+
if (match) {
|
|
54
|
+
const value = parseInt(match[1], 10);
|
|
55
|
+
const unit = match[2];
|
|
56
|
+
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
57
|
+
const seconds = value * multipliers[unit];
|
|
58
|
+
if (seconds <= 0) {
|
|
59
|
+
throw new errors_js_1.ValidationError("interval must be greater than zero");
|
|
60
|
+
}
|
|
61
|
+
return seconds;
|
|
62
|
+
}
|
|
63
|
+
throw new errors_js_1.ValidationError(`Invalid interval "${interval}". Use a preset ("5m", "15m", "1h", "daily"), ` +
|
|
64
|
+
`a unit expression ("30s", "10m", "2h", "1d"), or a number of seconds.`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Agent Subscriptions ("Watches") Resource
|
|
68
|
+
*
|
|
69
|
+
* Manage persistent, recurring watches over commodity codes and poll for the
|
|
70
|
+
* events they emit.
|
|
71
|
+
*
|
|
72
|
+
* **Example:**
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { OilPriceAPI } from 'oilpriceapi';
|
|
75
|
+
*
|
|
76
|
+
* const client = new OilPriceAPI({ apiKey: 'your_key' });
|
|
77
|
+
*
|
|
78
|
+
* // Create a watch that evaluates Brent + WTI every 5 minutes
|
|
79
|
+
* const watch = await client.subscriptions.create({
|
|
80
|
+
* name: 'Crude desk',
|
|
81
|
+
* codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
|
|
82
|
+
* interval: '5m',
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* // List all watches
|
|
86
|
+
* const watches = await client.subscriptions.list();
|
|
87
|
+
*
|
|
88
|
+
* // Poll for new events
|
|
89
|
+
* let cursor = 0;
|
|
90
|
+
* const { events, cursor: next } = await client.subscriptions.events({ since: cursor });
|
|
91
|
+
* cursor = next;
|
|
92
|
+
*
|
|
93
|
+
* // Remove a watch
|
|
94
|
+
* await client.subscriptions.delete(watch.id);
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
class SubscriptionsResource {
|
|
98
|
+
constructor(client) {
|
|
99
|
+
this.client = client;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* List all subscriptions/watches for the authenticated user.
|
|
103
|
+
*
|
|
104
|
+
* @returns Array of subscriptions, newest first.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const subscriptions = await client.subscriptions.list();
|
|
109
|
+
* console.log(`You have ${subscriptions.length} watches`);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
async list() {
|
|
113
|
+
const response = await this.client["request"]("/v1/subscriptions", {});
|
|
114
|
+
return Array.isArray(response) ? response : (response.subscriptions ?? []);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a new subscription/watch.
|
|
118
|
+
*
|
|
119
|
+
* Maps the friendly `interval` ("5m" / "1h" / "daily" / seconds) to the
|
|
120
|
+
* API's `interval_seconds`, and forwards optional attribution as
|
|
121
|
+
* `X-OPA-Source` / `X-OPA-Tool` headers (source defaults to `"sdk-node"`).
|
|
122
|
+
*
|
|
123
|
+
* @param params - Watch configuration. `codes` is required.
|
|
124
|
+
* @returns The created subscription.
|
|
125
|
+
*
|
|
126
|
+
* @throws {ValidationError} If `codes` is empty or `interval` is invalid.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const watch = await client.subscriptions.create({
|
|
131
|
+
* name: 'Crude desk',
|
|
132
|
+
* codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
|
|
133
|
+
* interval: '1h',
|
|
134
|
+
* tool: 'my-trading-bot',
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
async create(params) {
|
|
139
|
+
if (!params || !Array.isArray(params.codes) || params.codes.length === 0) {
|
|
140
|
+
throw new errors_js_1.ValidationError("codes is required and must be a non-empty array of commodity codes");
|
|
141
|
+
}
|
|
142
|
+
if (params.codes.some((c) => typeof c !== "string" || c.trim() === "")) {
|
|
143
|
+
throw new errors_js_1.ValidationError("every code must be a non-empty string");
|
|
144
|
+
}
|
|
145
|
+
const intervalSeconds = intervalToSeconds(params.interval);
|
|
146
|
+
const body = {
|
|
147
|
+
codes: params.codes,
|
|
148
|
+
interval_seconds: intervalSeconds,
|
|
149
|
+
};
|
|
150
|
+
if (params.name !== undefined) {
|
|
151
|
+
body.name = params.name;
|
|
152
|
+
}
|
|
153
|
+
if (params.deliverWebhook !== undefined) {
|
|
154
|
+
body.deliver_webhook = params.deliverWebhook;
|
|
155
|
+
}
|
|
156
|
+
const headers = {
|
|
157
|
+
"X-OPA-Source": params.source ?? DEFAULT_SOURCE,
|
|
158
|
+
};
|
|
159
|
+
if (params.tool !== undefined) {
|
|
160
|
+
headers["X-OPA-Tool"] = params.tool;
|
|
161
|
+
}
|
|
162
|
+
const response = await this.client["request"]("/v1/subscriptions", {}, { method: "POST", body, headers });
|
|
163
|
+
return "subscription" in response ? response.subscription : response;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Delete a subscription/watch.
|
|
167
|
+
*
|
|
168
|
+
* @param id - The subscription ID to delete.
|
|
169
|
+
*
|
|
170
|
+
* @throws {ValidationError} If `id` is not a non-empty string.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* await client.subscriptions.delete(watch.id);
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
async delete(id) {
|
|
178
|
+
if (!id || typeof id !== "string") {
|
|
179
|
+
throw new errors_js_1.ValidationError("Subscription ID must be a non-empty string");
|
|
180
|
+
}
|
|
181
|
+
await this.client["request"](`/v1/subscriptions/${id}`, {}, { method: "DELETE" });
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Poll for events emitted by your watches.
|
|
185
|
+
*
|
|
186
|
+
* Returns events with `seq` greater than the supplied cursor, ordered
|
|
187
|
+
* ascending. This endpoint does NOT consume the monthly request quota.
|
|
188
|
+
*
|
|
189
|
+
* @param options - Cursor (`since`), optional `watchId`, and `limit`.
|
|
190
|
+
* @returns The next cursor, a `has_more` flag, and the events.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* let cursor = 0;
|
|
195
|
+
* while (true) {
|
|
196
|
+
* const { events, cursor: next, has_more } = await client.subscriptions.events({ since: cursor });
|
|
197
|
+
* for (const ev of events) handle(ev);
|
|
198
|
+
* cursor = next;
|
|
199
|
+
* if (!has_more) break;
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
async events(options = {}) {
|
|
204
|
+
const params = {};
|
|
205
|
+
if (options.since !== undefined) {
|
|
206
|
+
params.since = String(options.since);
|
|
207
|
+
}
|
|
208
|
+
if (options.watchId !== undefined) {
|
|
209
|
+
params.watch_id = options.watchId;
|
|
210
|
+
}
|
|
211
|
+
if (options.limit !== undefined) {
|
|
212
|
+
params.limit = String(options.limit);
|
|
213
|
+
}
|
|
214
|
+
return this.client["request"]("/v1/subscriptions/events", params);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.SubscriptionsResource = SubscriptionsResource;
|
package/dist/cjs/version.js
CHANGED
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OilPriceAPIConfig, Price, LatestPricesOptions, HistoricalPricesOptions, Commodity, CommoditiesResponse, CategoriesResponse, DataConnectorPrice, DataConnectorOptions, DemoPricesResponse, DemoCommoditiesResponse } from "./types.js";
|
|
2
|
+
import type { MarketBrief, MarketBriefOptions } from "./resources/market-brief.js";
|
|
2
3
|
import { DieselResource } from "./resources/diesel.js";
|
|
3
4
|
import { AlertsResource } from "./resources/alerts.js";
|
|
4
5
|
import { CommoditiesResource } from "./resources/commodities.js";
|
|
@@ -17,6 +18,7 @@ import { SpreadsResource } from "./resources/spreads.js";
|
|
|
17
18
|
import { IndicatorsResource } from "./resources/indicators.js";
|
|
18
19
|
import { RawResource } from "./resources/raw.js";
|
|
19
20
|
import { StreamingResource } from "./resources/streaming.js";
|
|
21
|
+
import { SubscriptionsResource } from "./resources/subscriptions.js";
|
|
20
22
|
/**
|
|
21
23
|
* Raw HTTP response wrapper.
|
|
22
24
|
*
|
|
@@ -144,9 +146,14 @@ export declare class OilPriceAPI {
|
|
|
144
146
|
/**
|
|
145
147
|
* Real-time price streaming resource (WebSocket / ActionCable).
|
|
146
148
|
*
|
|
147
|
-
* Streaming requires a
|
|
149
|
+
* Streaming requires a Professional plan ($99/mo) or higher.
|
|
148
150
|
*/
|
|
149
151
|
readonly stream: StreamingResource;
|
|
152
|
+
/**
|
|
153
|
+
* Agent subscriptions ("watches") resource — persistent recurring watches
|
|
154
|
+
* over commodity codes plus an event poll endpoint (#3245 Phase 2).
|
|
155
|
+
*/
|
|
156
|
+
readonly subscriptions: SubscriptionsResource;
|
|
150
157
|
constructor(config?: OilPriceAPIConfig);
|
|
151
158
|
/**
|
|
152
159
|
* Log debug messages if debug mode is enabled
|
|
@@ -311,6 +318,34 @@ export declare class OilPriceAPI {
|
|
|
311
318
|
* ```
|
|
312
319
|
*/
|
|
313
320
|
getCommodity(code: string): Promise<Commodity>;
|
|
321
|
+
/**
|
|
322
|
+
* Get a multi-commodity market brief (OilPriceAPI #3245 Phase 1a).
|
|
323
|
+
*
|
|
324
|
+
* Returns a structured summary (latest price, 24h change, freshness, and a
|
|
325
|
+
* 1-month forecast band) for each requested commodity, optionally with a
|
|
326
|
+
* natural-language narrative. Counts as a single request against your quota,
|
|
327
|
+
* like `/v1/prices/batch`. The per-tier cap on `codes` is enforced server-side.
|
|
328
|
+
*
|
|
329
|
+
* @param codes - Commodity codes (e.g. ["BRENT_CRUDE_USD", "WTI_USD"]). Shorthand
|
|
330
|
+
* codes like "WTI"/"BRENT" are accepted and resolved server-side.
|
|
331
|
+
* @param options - `{ narrative }` to request the natural-language summary.
|
|
332
|
+
* @returns The structured (and optional narrative) market brief.
|
|
333
|
+
*
|
|
334
|
+
* @throws {ValidationError} If `codes` is empty.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* const brief = await client.getMarketBrief(['BRENT_CRUDE_USD', 'WTI_USD']);
|
|
339
|
+
* for (const c of brief.commodities) {
|
|
340
|
+
* console.log(`${c.name}: $${c.price} (${c.change_24h_pct}%)`);
|
|
341
|
+
* }
|
|
342
|
+
*
|
|
343
|
+
* // With narrative
|
|
344
|
+
* const withText = await client.getMarketBrief(['BRENT_CRUDE_USD'], { narrative: true });
|
|
345
|
+
* console.log(withText.narrative);
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
getMarketBrief(codes: string[], options?: MarketBriefOptions): Promise<MarketBrief>;
|
|
314
349
|
/**
|
|
315
350
|
* Fetch live sample prices from the public, no-auth demo endpoint.
|
|
316
351
|
*
|