@@ -7,18 +7,20 @@ import type {
77 Commodity ,
88 CommoditiesResponse ,
99 CategoriesResponse ,
10- } from './types.js' ;
10+ DataConnectorPrice ,
11+ DataConnectorOptions ,
12+ } from "./types.js" ;
1113import {
1214 OilPriceAPIError ,
1315 AuthenticationError ,
1416 RateLimitError ,
1517 NotFoundError ,
1618 ServerError ,
1719 TimeoutError ,
18- } from ' ./errors.js' ;
19- import { DieselResource } from ' ./resources/diesel.js' ;
20- import { AlertsResource } from ' ./resources/alerts.js' ;
21- import { SDK_VERSION , SDK_NAME , buildUserAgent } from ' ./version.js' ;
20+ } from " ./errors.js" ;
21+ import { DieselResource } from " ./resources/diesel.js" ;
22+ import { AlertsResource } from " ./resources/alerts.js" ;
23+ import { SDK_VERSION , SDK_NAME , buildUserAgent } from " ./version.js" ;
2224
2325/**
2426 * Official Node.js client for Oil Price API
@@ -69,14 +71,14 @@ export class OilPriceAPI {
6971
7072 constructor ( config : OilPriceAPIConfig ) {
7173 if ( ! config . apiKey ) {
72- throw new OilPriceAPIError ( ' API key is required' ) ;
74+ throw new OilPriceAPIError ( " API key is required" ) ;
7375 }
7476
7577 this . apiKey = config . apiKey ;
76- this . baseUrl = config . baseUrl || ' https://api.oilpriceapi.com' ;
78+ this . baseUrl = config . baseUrl || " https://api.oilpriceapi.com" ;
7779 this . retries = config . retries !== undefined ? config . retries : 3 ;
7880 this . retryDelay = config . retryDelay || 1000 ;
79- this . retryStrategy = config . retryStrategy || ' exponential' ;
81+ this . retryStrategy = config . retryStrategy || " exponential" ;
8082 this . timeout = config . timeout || 90000 ; // 90 seconds for slow historical queries
8183 this . debug = config . debug || false ;
8284 this . appUrl = config . appUrl ;
@@ -93,7 +95,7 @@ export class OilPriceAPI {
9395 private log ( message : string , data ?: any ) : void {
9496 if ( this . debug ) {
9597 const timestamp = new Date ( ) . toISOString ( ) ;
96- console . log ( `[OilPriceAPI ${ timestamp } ] ${ message } ` , data || '' ) ;
98+ console . log ( `[OilPriceAPI ${ timestamp } ] ${ message } ` , data || "" ) ;
9799 }
98100 }
99101
@@ -102,11 +104,11 @@ export class OilPriceAPI {
102104 */
103105 private calculateRetryDelay ( attempt : number ) : number {
104106 switch ( this . retryStrategy ) {
105- case ' exponential' :
107+ case " exponential" :
106108 return this . retryDelay * Math . pow ( 2 , attempt ) ;
107- case ' linear' :
109+ case " linear" :
108110 return this . retryDelay * ( attempt + 1 ) ;
109- case ' fixed' :
111+ case " fixed" :
110112 default :
111113 return this . retryDelay ;
112114 }
@@ -116,15 +118,15 @@ export class OilPriceAPI {
116118 * Sleep for specified milliseconds
117119 */
118120 private sleep ( ms : number ) : Promise < void > {
119- return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
121+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
120122 }
121123
122124 /**
123125 * Determine if error is retryable
124126 */
125127 private isRetryable ( error : any ) : boolean {
126128 // Retry on network errors
127- if ( error instanceof TypeError && error . message . includes ( ' fetch' ) ) {
129+ if ( error instanceof TypeError && error . message . includes ( " fetch" ) ) {
128130 return true ;
129131 }
130132
@@ -152,7 +154,7 @@ export class OilPriceAPI {
152154 */
153155 private async request < T > (
154156 endpoint : string ,
155- params ?: Record < string , string >
157+ params ?: Record < string , string > ,
156158 ) : Promise < T > {
157159 // Build URL with query parameters
158160 const url = new URL ( `${ this . baseUrl } ${ endpoint } ` ) ;
@@ -183,23 +185,23 @@ export class OilPriceAPI {
183185 try {
184186 // Build headers with optional telemetry
185187 const headers : Record < string , string > = {
186- ' Authorization' : `Bearer ${ this . apiKey } ` ,
187- ' Content-Type' : ' application/json' ,
188- ' User-Agent' : buildUserAgent ( ) ,
189- ' X-SDK-Name' : SDK_NAME ,
190- ' X-SDK-Version' : SDK_VERSION ,
188+ Authorization : `Bearer ${ this . apiKey } ` ,
189+ " Content-Type" : " application/json" ,
190+ " User-Agent" : buildUserAgent ( ) ,
191+ " X-SDK-Name" : SDK_NAME ,
192+ " X-SDK-Version" : SDK_VERSION ,
191193 } ;
192194
193195 // Add optional telemetry headers (10% bonus for appUrl!)
194196 if ( this . appUrl ) {
195- headers [ ' X-App-URL' ] = this . appUrl ;
197+ headers [ " X-App-URL" ] = this . appUrl ;
196198 }
197199 if ( this . appName ) {
198- headers [ ' X-App-Name' ] = this . appName ;
200+ headers [ " X-App-Name" ] = this . appName ;
199201 }
200202
201203 const response = await fetch ( url . toString ( ) , {
202- method : ' GET' ,
204+ method : " GET" ,
203205 headers,
204206 signal : controller . signal ,
205207 } ) ;
@@ -216,7 +218,8 @@ export class OilPriceAPI {
216218 // Try to parse JSON error response
217219 try {
218220 const errorJson = JSON . parse ( errorBody ) ;
219- errorMessage = errorJson . message || errorJson . error || errorMessage ;
221+ errorMessage =
222+ errorJson . message || errorJson . error || errorMessage ;
220223 } catch {
221224 // Use default error message if response isn't JSON
222225 }
@@ -230,15 +233,17 @@ export class OilPriceAPI {
230233 case 404 :
231234 throw new NotFoundError ( errorMessage ) ;
232235 case 429 :
233- const retryAfter = response . headers . get ( ' Retry-After' ) ;
236+ const retryAfter = response . headers . get ( " Retry-After" ) ;
234237 const rateLimitError = new RateLimitError (
235238 errorMessage ,
236- retryAfter ? parseInt ( retryAfter , 10 ) : undefined
239+ retryAfter ? parseInt ( retryAfter , 10 ) : undefined ,
237240 ) ;
238241
239242 // If rate limited and we have retries left, wait and retry
240243 if ( attempt < this . retries && rateLimitError . retryAfter ) {
241- this . log ( `Rate limited. Waiting ${ rateLimitError . retryAfter } s` ) ;
244+ this . log (
245+ `Rate limited. Waiting ${ rateLimitError . retryAfter } s` ,
246+ ) ;
242247 await this . sleep ( rateLimitError . retryAfter * 1000 ) ;
243248 continue ;
244249 }
@@ -253,51 +258,49 @@ export class OilPriceAPI {
253258 throw new OilPriceAPIError (
254259 errorMessage ,
255260 response . status ,
256- ' HTTP_ERROR'
261+ " HTTP_ERROR" ,
257262 ) ;
258263 }
259264 }
260265
261266 // Parse successful response
262267 const responseData : any = await response . json ( ) ;
263268
264- this . log ( ' Response data received' , {
269+ this . log ( " Response data received" , {
265270 status : responseData . status ,
266- hasData : ! ! responseData . data
271+ hasData : ! ! responseData . data ,
267272 } ) ;
268273
269274 // Handle different response structures
270275 // Latest endpoint: { status, data: { price, ... } }
271276 // Historical endpoint: { status, data: { prices: [...] } }
272- if ( responseData . status === ' success' && responseData . data ) {
277+ if ( responseData . status === " success" && responseData . data ) {
273278 if ( responseData . data . prices ) {
274279 // Historical endpoint - return prices array
275280 this . log ( `Returning ${ responseData . data . prices . length } prices` ) ;
276281 return responseData . data . prices as T ;
277282 } else if ( responseData . data . price !== undefined ) {
278283 // Latest endpoint - wrap single price in array
279- this . log ( ' Returning single price (wrapped in array)' ) ;
284+ this . log ( " Returning single price (wrapped in array)" ) ;
280285 return [ responseData . data ] as T ;
281286 }
282287 }
283288
284289 // Fallback - return data as-is
285- this . log ( ' Returning data as-is' ) ;
290+ this . log ( " Returning data as-is" ) ;
286291 return responseData . data as T ;
287-
288292 } catch ( error ) {
289293 // Handle abort (timeout)
290- if ( error instanceof Error && error . name === ' AbortError' ) {
291- throw new TimeoutError ( ' Request timeout' , this . timeout ) ;
294+ if ( error instanceof Error && error . name === " AbortError" ) {
295+ throw new TimeoutError ( " Request timeout" , this . timeout ) ;
292296 }
293297 throw error ;
294298 }
295-
296299 } catch ( error ) {
297300 lastError = error as Error ;
298301 this . log ( `Request failed: ${ lastError . message } ` , {
299302 attempt,
300- retryable : this . isRetryable ( lastError )
303+ retryable : this . isRetryable ( lastError ) ,
301304 } ) ;
302305
303306 // Re-throw our custom errors if not retryable
@@ -316,7 +319,7 @@ export class OilPriceAPI {
316319 throw new OilPriceAPIError (
317320 `Request failed after ${ this . retries + 1 } attempts: ${ error . message } ` ,
318321 undefined ,
319- ' NETWORK_ERROR'
322+ " NETWORK_ERROR" ,
320323 ) ;
321324 }
322325
@@ -331,7 +334,7 @@ export class OilPriceAPI {
331334 }
332335
333336 // This should never be reached, but TypeScript wants it
334- throw lastError || new OilPriceAPIError ( ' Unknown error occurred' ) ;
337+ throw lastError || new OilPriceAPIError ( " Unknown error occurred" ) ;
335338 }
336339
337340 /**
@@ -356,7 +359,7 @@ export class OilPriceAPI {
356359 params . by_code = options . commodity ;
357360 }
358361
359- return this . request < Price [ ] > ( ' /v1/prices/latest' , params ) ;
362+ return this . request < Price [ ] > ( " /v1/prices/latest" , params ) ;
360363 }
361364
362365 /**
@@ -382,7 +385,7 @@ export class OilPriceAPI {
382385 * ```
383386 */
384387 async getHistoricalPrices (
385- options ?: HistoricalPricesOptions
388+ options ?: HistoricalPricesOptions ,
386389 ) : Promise < Price [ ] > {
387390 const params : Record < string , string > = { } ;
388391
@@ -426,7 +429,41 @@ export class OilPriceAPI {
426429 // Issue: SDK was returning wrong dates for historical queries
427430 // Root Cause: Backend has_scope :by_period not working on /v1/prices
428431 // Solution: Use /v1/prices/past_year which uses direct WHERE clauses
429- return this . request < Price [ ] > ( '/v1/prices/past_year' , params ) ;
432+ return this . request < Price [ ] > ( "/v1/prices/past_year" , params ) ;
433+ }
434+
435+ /**
436+ * Get prices from your connected data sources (BYOS)
437+ *
438+ * Requires Data Connector feature enabled on your organization.
439+ *
440+ * @example
441+ * ```typescript
442+ * // Get all connected prices
443+ * const prices = await client.getDataConnectorPrices();
444+ *
445+ * // Filter by fuel type
446+ * const vlsfo = await client.getDataConnectorPrices({ fuelType: 'VLSFO' });
447+ *
448+ * // Filter by port
449+ * const singapore = await client.getDataConnectorPrices({ port: 'SINGAPORE' });
450+ * ```
451+ */
452+ async getDataConnectorPrices (
453+ options : DataConnectorOptions = { } ,
454+ ) : Promise < DataConnectorPrice [ ] > {
455+ const params : Record < string , string > = { } ;
456+
457+ if ( options . fuelType ) params . fuel_type = options . fuelType ;
458+ if ( options . port ) params . port = options . port ;
459+ if ( options . region ) params . region = options . region ;
460+ if ( options . since ) params . since = options . since ;
461+
462+ const response = await this . request < {
463+ prices : DataConnectorPrice [ ] ;
464+ } > ( "/v1/prices/data-connector" , params ) ;
465+
466+ return response . prices ;
430467 }
431468
432469 /**
@@ -441,7 +478,7 @@ export class OilPriceAPI {
441478 * ```
442479 */
443480 async getCommodities ( ) : Promise < CommoditiesResponse > {
444- return this . request < CommoditiesResponse > ( ' /v1/commodities' , { } ) ;
481+ return this . request < CommoditiesResponse > ( " /v1/commodities" , { } ) ;
445482 }
446483
447484 /**
@@ -457,7 +494,7 @@ export class OilPriceAPI {
457494 * ```
458495 */
459496 async getCommodityCategories ( ) : Promise < CategoriesResponse > {
460- return this . request < CategoriesResponse > ( ' /v1/commodities/categories' , { } ) ;
497+ return this . request < CategoriesResponse > ( " /v1/commodities/categories" , { } ) ;
461498 }
462499
463500 /**
0 commit comments