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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Agent Subscriptions ("Watches") Resource
3
+ *
4
+ * Persistent server-side "watches" that evaluate a set of commodity codes on a
5
+ * recurring interval and emit events an agent can poll for (OilPriceAPI #3245
6
+ * Phase 2). Designed for autonomous agents (MCP, schedulers, bots) that want
7
+ * change notifications without holding an open connection.
8
+ *
9
+ * The poll endpoint (`events`) does NOT consume the monthly request quota and
10
+ * has its own generous rate-limit lane, so agents can poll frequently.
11
+ */
12
+ import type { OilPriceAPI } from "../client.js";
13
+ /**
14
+ * Lifecycle status of a subscription/watch.
15
+ */
16
+ export type SubscriptionStatus = "active" | "paused";
17
+ /**
18
+ * Attribution source recorded on a watch. Defaults to `"sdk-node"` when created
19
+ * via this SDK. The API canonicalizes unknown values to `"api"`.
20
+ */
21
+ export type SubscriptionSource = string;
22
+ /**
23
+ * A persistent agent subscription ("watch").
24
+ *
25
+ * Returned by {@link SubscriptionsResource.list} and
26
+ * {@link SubscriptionsResource.create}.
27
+ */
28
+ export interface Subscription {
29
+ /** Unique watch identifier (UUID). */
30
+ id: string;
31
+ /** User-friendly watch name. */
32
+ name: string | null;
33
+ /** Commodity codes this watch evaluates (e.g. ["BRENT_CRUDE_USD"]). */
34
+ codes: string[];
35
+ /** Evaluation cadence in seconds. */
36
+ interval_seconds: number;
37
+ /** Lifecycle status. */
38
+ status: SubscriptionStatus;
39
+ /** Whether matching events are also delivered via webhook. */
40
+ deliver_webhook: boolean;
41
+ /** Attribution source (e.g. "sdk-node", "mcp", "api"). */
42
+ source: string;
43
+ /** Attribution tool name, if any. */
44
+ tool_name: string | null;
45
+ /** ISO timestamp the watch was last evaluated, or null. */
46
+ last_evaluated_at: string | null;
47
+ /** ISO timestamp the watch is next scheduled to run, or null. */
48
+ next_run_at: string | null;
49
+ /** ISO timestamp when the watch was created. */
50
+ created_at: string;
51
+ }
52
+ /**
53
+ * A friendly interval expression accepted by {@link SubscriptionsResource.create}.
54
+ *
55
+ * Either a preset string ("5m", "15m", "1h", "daily") or an explicit number of
56
+ * seconds.
57
+ */
58
+ export type SubscriptionInterval = "5m" | "15m" | "1h" | "daily" | (string & {}) | number;
59
+ /**
60
+ * Parameters for creating a new subscription/watch.
61
+ */
62
+ export interface CreateSubscriptionParams {
63
+ /** Commodity codes to watch (e.g. ["BRENT_CRUDE_USD", "WTI_USD"]). Required. */
64
+ codes: string[];
65
+ /**
66
+ * Evaluation cadence. A friendly preset ("5m" / "15m" / "1h" / "daily"), a
67
+ * `<n>m` / `<n>h` / `<n>d` / `<n>s` expression, or a number of seconds.
68
+ * Defaults to "5m" when omitted.
69
+ */
70
+ interval?: SubscriptionInterval;
71
+ /** Optional friendly watch name. */
72
+ name?: string;
73
+ /** Whether to also deliver matching events via webhook. */
74
+ deliverWebhook?: boolean;
75
+ /**
76
+ * Attribution source → `X-OPA-Source` header. Defaults to `"sdk-node"`.
77
+ */
78
+ source?: string;
79
+ /** Attribution tool name → `X-OPA-Tool` header. */
80
+ tool?: string;
81
+ }
82
+ /**
83
+ * A single event emitted by a watch evaluation.
84
+ *
85
+ * The exact payload depends on the event type; common fields are surfaced here
86
+ * while the full server payload is preserved via the index signature.
87
+ */
88
+ export interface SubscriptionEvent {
89
+ /** Monotonic per-user sequence number; use as the `since` cursor. */
90
+ seq: number;
91
+ /** The watch that produced this event. */
92
+ watch_id: string;
93
+ /** Event type (e.g. "evaluated", "threshold_crossed"). */
94
+ type?: string;
95
+ /** Commodity code the event concerns, if applicable. */
96
+ code?: string;
97
+ /** ISO timestamp the event was emitted. */
98
+ created_at?: string;
99
+ /** Any additional server-provided fields. */
100
+ [key: string]: unknown;
101
+ }
102
+ /**
103
+ * Response from {@link SubscriptionsResource.events}.
104
+ */
105
+ export interface SubscriptionEventsResult {
106
+ /** The highest `seq` returned; pass as `since` on the next poll. */
107
+ cursor: number;
108
+ /** True if more events are available beyond this page. */
109
+ has_more: boolean;
110
+ /** Events with `seq > since`, ordered ascending by `seq`. */
111
+ events: SubscriptionEvent[];
112
+ }
113
+ /**
114
+ * Options for {@link SubscriptionsResource.events}.
115
+ */
116
+ export interface SubscriptionEventsOptions {
117
+ /** Return only events with `seq` greater than this cursor. Defaults to 0. */
118
+ since?: number;
119
+ /** Restrict to a single watch by id. */
120
+ watchId?: string;
121
+ /** Max events to return (1-500, server default 100). */
122
+ limit?: number;
123
+ }
124
+ /**
125
+ * Convert a friendly interval ("5m" / "1h" / "daily" / 300) into seconds.
126
+ *
127
+ * Accepts:
128
+ * - presets: "5m", "15m", "1h", "hourly", "daily"
129
+ * - unit expressions: "<n>s", "<n>m", "<n>h", "<n>d"
130
+ * - a raw number of seconds
131
+ *
132
+ * @internal Exported for unit testing of the mapping.
133
+ */
134
+ export declare function intervalToSeconds(interval: SubscriptionInterval | undefined): number;
135
+ /**
136
+ * Agent Subscriptions ("Watches") Resource
137
+ *
138
+ * Manage persistent, recurring watches over commodity codes and poll for the
139
+ * events they emit.
140
+ *
141
+ * **Example:**
142
+ * ```typescript
143
+ * import { OilPriceAPI } from 'oilpriceapi';
144
+ *
145
+ * const client = new OilPriceAPI({ apiKey: 'your_key' });
146
+ *
147
+ * // Create a watch that evaluates Brent + WTI every 5 minutes
148
+ * const watch = await client.subscriptions.create({
149
+ * name: 'Crude desk',
150
+ * codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
151
+ * interval: '5m',
152
+ * });
153
+ *
154
+ * // List all watches
155
+ * const watches = await client.subscriptions.list();
156
+ *
157
+ * // Poll for new events
158
+ * let cursor = 0;
159
+ * const { events, cursor: next } = await client.subscriptions.events({ since: cursor });
160
+ * cursor = next;
161
+ *
162
+ * // Remove a watch
163
+ * await client.subscriptions.delete(watch.id);
164
+ * ```
165
+ */
166
+ export declare class SubscriptionsResource {
167
+ private client;
168
+ constructor(client: OilPriceAPI);
169
+ /**
170
+ * List all subscriptions/watches for the authenticated user.
171
+ *
172
+ * @returns Array of subscriptions, newest first.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const subscriptions = await client.subscriptions.list();
177
+ * console.log(`You have ${subscriptions.length} watches`);
178
+ * ```
179
+ */
180
+ list(): Promise<Subscription[]>;
181
+ /**
182
+ * Create a new subscription/watch.
183
+ *
184
+ * Maps the friendly `interval` ("5m" / "1h" / "daily" / seconds) to the
185
+ * API's `interval_seconds`, and forwards optional attribution as
186
+ * `X-OPA-Source` / `X-OPA-Tool` headers (source defaults to `"sdk-node"`).
187
+ *
188
+ * @param params - Watch configuration. `codes` is required.
189
+ * @returns The created subscription.
190
+ *
191
+ * @throws {ValidationError} If `codes` is empty or `interval` is invalid.
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const watch = await client.subscriptions.create({
196
+ * name: 'Crude desk',
197
+ * codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
198
+ * interval: '1h',
199
+ * tool: 'my-trading-bot',
200
+ * });
201
+ * ```
202
+ */
203
+ create(params: CreateSubscriptionParams): Promise<Subscription>;
204
+ /**
205
+ * Delete a subscription/watch.
206
+ *
207
+ * @param id - The subscription ID to delete.
208
+ *
209
+ * @throws {ValidationError} If `id` is not a non-empty string.
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * await client.subscriptions.delete(watch.id);
214
+ * ```
215
+ */
216
+ delete(id: string): Promise<void>;
217
+ /**
218
+ * Poll for events emitted by your watches.
219
+ *
220
+ * Returns events with `seq` greater than the supplied cursor, ordered
221
+ * ascending. This endpoint does NOT consume the monthly request quota.
222
+ *
223
+ * @param options - Cursor (`since`), optional `watchId`, and `limit`.
224
+ * @returns The next cursor, a `has_more` flag, and the events.
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * let cursor = 0;
229
+ * while (true) {
230
+ * const { events, cursor: next, has_more } = await client.subscriptions.events({ since: cursor });
231
+ * for (const ev of events) handle(ev);
232
+ * cursor = next;
233
+ * if (!has_more) break;
234
+ * }
235
+ * ```
236
+ */
237
+ events(options?: SubscriptionEventsOptions): Promise<SubscriptionEventsResult>;
238
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Agent Subscriptions ("Watches") Resource
3
+ *
4
+ * Persistent server-side "watches" that evaluate a set of commodity codes on a
5
+ * recurring interval and emit events an agent can poll for (OilPriceAPI #3245
6
+ * Phase 2). Designed for autonomous agents (MCP, schedulers, bots) that want
7
+ * change notifications without holding an open connection.
8
+ *
9
+ * The poll endpoint (`events`) does NOT consume the monthly request quota and
10
+ * has its own generous rate-limit lane, so agents can poll frequently.
11
+ */
12
+ import { ValidationError } from "../errors.js";
13
+ /** Default attribution source for watches created via this SDK. */
14
+ const DEFAULT_SOURCE = "sdk-node";
15
+ /** Named interval presets mapped to seconds. */
16
+ const INTERVAL_PRESETS = {
17
+ "5m": 300,
18
+ "15m": 900,
19
+ "1h": 3600,
20
+ hourly: 3600,
21
+ daily: 86400,
22
+ };
23
+ /**
24
+ * Convert a friendly interval ("5m" / "1h" / "daily" / 300) into seconds.
25
+ *
26
+ * Accepts:
27
+ * - presets: "5m", "15m", "1h", "hourly", "daily"
28
+ * - unit expressions: "<n>s", "<n>m", "<n>h", "<n>d"
29
+ * - a raw number of seconds
30
+ *
31
+ * @internal Exported for unit testing of the mapping.
32
+ */
33
+ export function intervalToSeconds(interval) {
34
+ if (interval === undefined) {
35
+ return INTERVAL_PRESETS["5m"];
36
+ }
37
+ if (typeof interval === "number") {
38
+ if (!Number.isFinite(interval) || interval <= 0) {
39
+ throw new ValidationError("interval (seconds) must be a positive number");
40
+ }
41
+ return Math.floor(interval);
42
+ }
43
+ const key = interval.trim().toLowerCase();
44
+ if (key in INTERVAL_PRESETS) {
45
+ return INTERVAL_PRESETS[key];
46
+ }
47
+ // Unit expression: <number><unit> where unit ∈ s/m/h/d.
48
+ const match = /^(\d+)\s*(s|m|h|d)$/.exec(key);
49
+ if (match) {
50
+ const value = parseInt(match[1], 10);
51
+ const unit = match[2];
52
+ const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
53
+ const seconds = value * multipliers[unit];
54
+ if (seconds <= 0) {
55
+ throw new ValidationError("interval must be greater than zero");
56
+ }
57
+ return seconds;
58
+ }
59
+ throw new ValidationError(`Invalid interval "${interval}". Use a preset ("5m", "15m", "1h", "daily"), ` +
60
+ `a unit expression ("30s", "10m", "2h", "1d"), or a number of seconds.`);
61
+ }
62
+ /**
63
+ * Agent Subscriptions ("Watches") Resource
64
+ *
65
+ * Manage persistent, recurring watches over commodity codes and poll for the
66
+ * events they emit.
67
+ *
68
+ * **Example:**
69
+ * ```typescript
70
+ * import { OilPriceAPI } from 'oilpriceapi';
71
+ *
72
+ * const client = new OilPriceAPI({ apiKey: 'your_key' });
73
+ *
74
+ * // Create a watch that evaluates Brent + WTI every 5 minutes
75
+ * const watch = await client.subscriptions.create({
76
+ * name: 'Crude desk',
77
+ * codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
78
+ * interval: '5m',
79
+ * });
80
+ *
81
+ * // List all watches
82
+ * const watches = await client.subscriptions.list();
83
+ *
84
+ * // Poll for new events
85
+ * let cursor = 0;
86
+ * const { events, cursor: next } = await client.subscriptions.events({ since: cursor });
87
+ * cursor = next;
88
+ *
89
+ * // Remove a watch
90
+ * await client.subscriptions.delete(watch.id);
91
+ * ```
92
+ */
93
+ export class SubscriptionsResource {
94
+ constructor(client) {
95
+ this.client = client;
96
+ }
97
+ /**
98
+ * List all subscriptions/watches for the authenticated user.
99
+ *
100
+ * @returns Array of subscriptions, newest first.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const subscriptions = await client.subscriptions.list();
105
+ * console.log(`You have ${subscriptions.length} watches`);
106
+ * ```
107
+ */
108
+ async list() {
109
+ const response = await this.client["request"]("/v1/subscriptions", {});
110
+ return Array.isArray(response) ? response : (response.subscriptions ?? []);
111
+ }
112
+ /**
113
+ * Create a new subscription/watch.
114
+ *
115
+ * Maps the friendly `interval` ("5m" / "1h" / "daily" / seconds) to the
116
+ * API's `interval_seconds`, and forwards optional attribution as
117
+ * `X-OPA-Source` / `X-OPA-Tool` headers (source defaults to `"sdk-node"`).
118
+ *
119
+ * @param params - Watch configuration. `codes` is required.
120
+ * @returns The created subscription.
121
+ *
122
+ * @throws {ValidationError} If `codes` is empty or `interval` is invalid.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const watch = await client.subscriptions.create({
127
+ * name: 'Crude desk',
128
+ * codes: ['BRENT_CRUDE_USD', 'WTI_USD'],
129
+ * interval: '1h',
130
+ * tool: 'my-trading-bot',
131
+ * });
132
+ * ```
133
+ */
134
+ async create(params) {
135
+ if (!params || !Array.isArray(params.codes) || params.codes.length === 0) {
136
+ throw new ValidationError("codes is required and must be a non-empty array of commodity codes");
137
+ }
138
+ if (params.codes.some((c) => typeof c !== "string" || c.trim() === "")) {
139
+ throw new ValidationError("every code must be a non-empty string");
140
+ }
141
+ const intervalSeconds = intervalToSeconds(params.interval);
142
+ const body = {
143
+ codes: params.codes,
144
+ interval_seconds: intervalSeconds,
145
+ };
146
+ if (params.name !== undefined) {
147
+ body.name = params.name;
148
+ }
149
+ if (params.deliverWebhook !== undefined) {
150
+ body.deliver_webhook = params.deliverWebhook;
151
+ }
152
+ const headers = {
153
+ "X-OPA-Source": params.source ?? DEFAULT_SOURCE,
154
+ };
155
+ if (params.tool !== undefined) {
156
+ headers["X-OPA-Tool"] = params.tool;
157
+ }
158
+ const response = await this.client["request"]("/v1/subscriptions", {}, { method: "POST", body, headers });
159
+ return "subscription" in response ? response.subscription : response;
160
+ }
161
+ /**
162
+ * Delete a subscription/watch.
163
+ *
164
+ * @param id - The subscription ID to delete.
165
+ *
166
+ * @throws {ValidationError} If `id` is not a non-empty string.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * await client.subscriptions.delete(watch.id);
171
+ * ```
172
+ */
173
+ async delete(id) {
174
+ if (!id || typeof id !== "string") {
175
+ throw new ValidationError("Subscription ID must be a non-empty string");
176
+ }
177
+ await this.client["request"](`/v1/subscriptions/${id}`, {}, { method: "DELETE" });
178
+ }
179
+ /**
180
+ * Poll for events emitted by your watches.
181
+ *
182
+ * Returns events with `seq` greater than the supplied cursor, ordered
183
+ * ascending. This endpoint does NOT consume the monthly request quota.
184
+ *
185
+ * @param options - Cursor (`since`), optional `watchId`, and `limit`.
186
+ * @returns The next cursor, a `has_more` flag, and the events.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * let cursor = 0;
191
+ * while (true) {
192
+ * const { events, cursor: next, has_more } = await client.subscriptions.events({ since: cursor });
193
+ * for (const ev of events) handle(ev);
194
+ * cursor = next;
195
+ * if (!has_more) break;
196
+ * }
197
+ * ```
198
+ */
199
+ async events(options = {}) {
200
+ const params = {};
201
+ if (options.since !== undefined) {
202
+ params.since = String(options.since);
203
+ }
204
+ if (options.watchId !== undefined) {
205
+ params.watch_id = options.watchId;
206
+ }
207
+ if (options.limit !== undefined) {
208
+ params.limit = String(options.limit);
209
+ }
210
+ return this.client["request"]("/v1/subscriptions/events", params);
211
+ }
212
+ }
package/dist/version.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * - X-Client-Version header
8
8
  * - Package.json (should match)
9
9
  */
10
- export declare const SDK_VERSION = "0.9.0";
10
+ export declare const SDK_VERSION = "0.10.0";
11
11
  /**
12
12
  * SDK identifier used in User-Agent and X-Api-Client headers
13
13
  */
package/dist/version.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * - X-Client-Version header
8
8
  * - Package.json (should match)
9
9
  */
10
- export const SDK_VERSION = "0.9.0";
10
+ export const SDK_VERSION = "0.10.0";
11
11
  /**
12
12
  * SDK identifier used in User-Agent and X-Api-Client headers
13
13
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oilpriceapi",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",