@allratestoday/sdk 1.4.4 → 1.5.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/dist/index.js CHANGED
@@ -28,10 +28,13 @@ module.exports = __toCommonJS(index_exports);
28
28
  var DEFAULT_BASE_URL = "https://allratestoday.com";
29
29
  var DEFAULT_TIMEOUT = 1e4;
30
30
  var AllRatesTodayError = class extends Error {
31
- constructor(message, status) {
31
+ constructor(message, status, code, details, options) {
32
32
  super(message);
33
33
  this.name = "AllRatesTodayError";
34
34
  this.status = status;
35
+ this.code = code ?? (status !== void 0 ? "http" : "invalid_argument");
36
+ this.details = details;
37
+ if (options?.cause !== void 0) this.cause = options.cause;
35
38
  }
36
39
  };
37
40
  var AllRatesToday = class {
@@ -56,7 +59,9 @@ var AllRatesToday = class {
56
59
  const key = override || this.apiKey;
57
60
  if (!key) {
58
61
  throw new AllRatesTodayError(
59
- "API key is required. Register for free at https://allratestoday.com/register"
62
+ "API key is required. Register for free at https://allratestoday.com/register",
63
+ void 0,
64
+ "missing_key"
60
65
  );
61
66
  }
62
67
  return key;
@@ -73,24 +78,82 @@ var AllRatesToday = class {
73
78
  if (apiKey) {
74
79
  headers["Authorization"] = `Bearer ${apiKey}`;
75
80
  }
76
- const response = await fetch(url.toString(), {
77
- headers,
78
- signal: AbortSignal.timeout(this.timeout)
79
- });
81
+ let response;
82
+ try {
83
+ response = await fetch(url.toString(), {
84
+ headers,
85
+ signal: AbortSignal.timeout(this.timeout)
86
+ });
87
+ } catch (err) {
88
+ const name = err?.name;
89
+ if (name === "TimeoutError" || name === "AbortError") {
90
+ throw new AllRatesTodayError(`Request timed out after ${this.timeout} ms`, void 0, "timeout", void 0, { cause: err });
91
+ }
92
+ throw new AllRatesTodayError(
93
+ `Network error: ${err?.message ?? String(err)}`,
94
+ void 0,
95
+ "network",
96
+ void 0,
97
+ { cause: err }
98
+ );
99
+ }
80
100
  if (!response.ok) {
81
- const error = await response.json().catch(() => ({ error: response.statusText }));
101
+ const body = await response.json().catch(() => ({ error: response.statusText }));
102
+ const details = body && typeof body === "object" ? body : void 0;
82
103
  throw new AllRatesTodayError(
83
- error.error || `HTTP ${response.status}`,
84
- response.status
104
+ details?.error || `HTTP ${response.status}`,
105
+ response.status,
106
+ "http",
107
+ details
85
108
  );
86
109
  }
87
110
  return response.json();
88
111
  }
89
112
  formatDate(date) {
90
- if (date instanceof Date) {
91
- return date.toISOString().split("T")[0];
113
+ const str = date instanceof Date ? date.toISOString().split("T")[0] : date;
114
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(str) || Number.isNaN(Date.parse(`${str}T00:00:00Z`))) {
115
+ throw new AllRatesTodayError(`Invalid date: ${String(date)}. Use YYYY-MM-DD or a Date.`);
116
+ }
117
+ return str;
118
+ }
119
+ requireSymbols(symbols, method) {
120
+ if (!symbols?.length) {
121
+ throw new AllRatesTodayError(`${method}() needs symbols, e.g. { symbols: ['EUR'] }. Each symbol is one request.`);
92
122
  }
93
- return date;
123
+ return symbols;
124
+ }
125
+ /**
126
+ * Smallest history window that reaches back to `start`. Market history is
127
+ * served as windows ending today (7 days, 30 days, 1 year), so older dates
128
+ * are not available from the market-rate API.
129
+ */
130
+ windowFor(start) {
131
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
132
+ const days = Math.round((Date.parse(`${today}T00:00:00Z`) - Date.parse(`${start}T00:00:00Z`)) / 864e5);
133
+ if (days < 0) throw new AllRatesTodayError(`Date ${start} is in the future.`);
134
+ if (days <= 6) return "7d";
135
+ if (days <= 29) return "30d";
136
+ if (days <= 364) return "1y";
137
+ throw new AllRatesTodayError(
138
+ `Market-rate history covers the last 365 days; ${start} is older. For older dates use official central-bank rates: GET /api/v1/central-bank/{bank}/{date} (e.g. ECB from 1999).`
139
+ );
140
+ }
141
+ async history(source, target, period, key) {
142
+ const raw = await this.request(
143
+ "/api/historical-rates",
144
+ { source, target, period },
145
+ key
146
+ );
147
+ return (raw.data ?? []).slice().sort((a, b) => a.timestamp - b.timestamp);
148
+ }
149
+ /** Last point on or before `date` (YYYY-MM-DD). */
150
+ pointOn(points, date) {
151
+ let hit;
152
+ for (const p of points) {
153
+ if (p.date.slice(0, 10) <= date) hit = p;
154
+ else break;
155
+ }
156
+ return hit;
94
157
  }
95
158
  // -------------------------------------------------------------------------
96
159
  // Public API
@@ -128,89 +191,69 @@ var AllRatesToday = class {
128
191
  return { base: params.source, date, rates };
129
192
  }
130
193
  /**
131
- * Get exchange rates for a specific historical date.
194
+ * Get mid-market rates for a past date within the last 365 days.
132
195
  *
133
- * ```ts
134
- * // Using a string
135
- * const data = await client.forDate('2026-01-15');
196
+ * One history request per symbol. Billed per request by window: dates up to
197
+ * 29 days ago cost 1 call per symbol, older dates (up to 1 year) cost 12,
198
+ * because the 1-year window is weekly points. If a day has no point, the
199
+ * closest earlier one is used and reported in `rateDates`.
136
200
  *
137
- * // Using a Date object
138
- * const data = await client.forDate(new Date('2026-01-15'));
139
- *
140
- * // With options
141
- * const data = await client.forDate('2026-01-15', { base: 'EUR', symbols: ['USD', 'GBP'] });
201
+ * ```ts
202
+ * const data = await client.forDate('2026-10-01', { base: 'USD', symbols: ['EUR', 'GBP'] });
203
+ * // { base: 'USD', date: '2026-10-01', rates: { EUR: 0.85, GBP: 0.74 }, rateDates: { ... } }
142
204
  * ```
143
205
  */
144
- async forDate(date, options = {}) {
145
- const key = this.resolveKey(options.apiKey);
206
+ async forDate(date, options) {
207
+ const key = this.resolveKey(options?.apiKey);
146
208
  const dateStr = this.formatDate(date);
147
- const params = {
148
- source: options.base || "USD",
149
- from: dateStr,
150
- to: dateStr
151
- };
152
- if (options.symbols?.length) {
153
- params.target = options.symbols.join(",");
154
- }
155
- const raw = await this.request(
156
- "/api/historical-rates",
157
- params,
158
- key
159
- );
209
+ const symbols = this.requireSymbols(options?.symbols, "forDate");
210
+ const base = (options.base || "USD").toUpperCase();
211
+ const period = this.windowFor(dateStr);
160
212
  const rates = {};
161
- if (raw.rates?.length) {
162
- for (const r of raw.rates) {
163
- rates[raw.target] = r.rate;
213
+ const rateDates = {};
214
+ for (const sym of symbols) {
215
+ const target = sym.toUpperCase();
216
+ const point = this.pointOn(await this.history(base, target, period, key), dateStr);
217
+ if (point) {
218
+ rates[target] = point.rate;
219
+ rateDates[target] = point.date.slice(0, 10);
164
220
  }
165
221
  }
166
- if (raw.current) {
167
- rates[raw.target] = raw.current.rate;
168
- }
169
- return { base: params.source, date: dateStr, rates };
222
+ return { base, date: dateStr, rates, rateDates };
170
223
  }
171
224
  /**
172
- * Get exchange rate time series between two dates.
225
+ * Daily mid-market rates between two dates within the last 365 days.
173
226
  *
174
- * ```ts
175
- * const data = await client.timeSeries('2026-01-01', '2026-03-31', {
176
- * base: 'USD',
177
- * symbols: ['EUR', 'GBP'],
178
- * });
227
+ * One history request per symbol. Ranges starting up to 29 days ago return
228
+ * daily points (1 call per symbol); older starts use the 1-year window,
229
+ * which has weekly points and costs 12 calls per symbol.
179
230
  *
180
- * // Access rates by date
181
- * console.log(data.rates['2026-01-15']); // { EUR: 0.92, GBP: 0.78 }
231
+ * ```ts
232
+ * const data = await client.timeSeries('2026-10-01', '2026-10-10', { symbols: ['EUR', 'GBP'] });
233
+ * console.log(data.rates['2026-10-05']); // { EUR: 0.86, GBP: 0.75 }
182
234
  * ```
183
235
  */
184
- async timeSeries(startDate, endDate, options = {}) {
185
- const key = this.resolveKey(options.apiKey);
236
+ async timeSeries(startDate, endDate, options) {
237
+ const key = this.resolveKey(options?.apiKey);
186
238
  const start = this.formatDate(startDate);
187
239
  const end = this.formatDate(endDate);
188
- const source = options.base || "USD";
189
- const params = {
190
- source,
191
- from: start,
192
- to: end
193
- };
194
- if (options.symbols?.length) {
195
- params.target = options.symbols.join(",");
196
- }
197
- const raw = await this.request(
198
- "/api/historical-rates",
199
- params,
200
- key
201
- );
240
+ if (start > end) throw new AllRatesTodayError(`startDate ${start} is after endDate ${end}.`);
241
+ const symbols = this.requireSymbols(options?.symbols, "timeSeries");
242
+ const base = (options.base || "USD").toUpperCase();
243
+ const period = this.windowFor(start);
202
244
  const rates = {};
203
- if (raw.rates) {
204
- for (const point of raw.rates) {
205
- const dateKey = point.time.split("T")[0];
206
- if (!rates[dateKey]) rates[dateKey] = {};
207
- rates[dateKey][raw.target] = point.rate;
245
+ for (const sym of symbols) {
246
+ const target = sym.toUpperCase();
247
+ for (const point of await this.history(base, target, period, key)) {
248
+ const day = point.date.slice(0, 10);
249
+ if (day < start || day > end) continue;
250
+ (rates[day] ?? (rates[day] = {}))[target] = point.rate;
208
251
  }
209
252
  }
210
- return { base: source, startDate: start, endDate: end, rates };
253
+ return { base, startDate: start, endDate: end, rates };
211
254
  }
212
255
  /**
213
- * Get all supported currency symbols.
256
+ * Get all supported currency codes and names. **No API key needed.**
214
257
  *
215
258
  * ```ts
216
259
  * const { symbols } = await client.symbols();
@@ -222,8 +265,15 @@ var AllRatesToday = class {
222
265
  * ```
223
266
  */
224
267
  async symbols(options = {}) {
225
- const key = this.resolveKey(options.apiKey);
226
- return this.request("/api/v1/symbols", {}, key);
268
+ const raw = await this.request(
269
+ "/api/v1/symbols",
270
+ {},
271
+ options.apiKey
272
+ );
273
+ const currencies = raw.currencies ?? [];
274
+ const symbols = {};
275
+ for (const c of currencies) symbols[c.code] = c.name;
276
+ return { symbols, currencies, count: raw.count ?? currencies.length };
227
277
  }
228
278
  /**
229
279
  * Latest table of OFFICIAL rates published by a central bank or tax
@@ -236,12 +286,12 @@ var AllRatesToday = class {
236
286
  * const table = await client.officialRates('ecb');
237
287
  * console.log(table.rate_date, table.rates);
238
288
  *
239
- * // One pair — cross-computed inside the bank's table when not published directly
289
+ * // One pair — published as-is or the inverse of a published rate (`derived: true`)
240
290
  * const usd = await client.officialRates('ecb', { source: 'EUR', target: 'USD' });
241
291
  * console.log(`ECB ${usd.rate_date}: 1 EUR = ${usd.rate} USD`);
242
292
  * ```
243
293
  *
244
- * Dated tables and history need a free key: see `/api/v1/central-bank/{bank}/{date}`.
294
+ * Cross pairs and past dates need a key: use `officialRatesOn()`.
245
295
  */
246
296
  async officialRates(bank, options = {}) {
247
297
  const code = bank.trim().toLowerCase();
@@ -262,6 +312,39 @@ var AllRatesToday = class {
262
312
  options.apiKey
263
313
  );
264
314
  }
315
+ /**
316
+ * Official table (or one pair) as published on a given date. Needs an API
317
+ * key; 1 call. On weekends and holidays the last earlier publication is
318
+ * returned (`published_on_requested_date: false`). Unlike the keyless
319
+ * `officialRates()`, any pair inside the table can be cross-computed
320
+ * (`method: 'cross'`), e.g. USD/GBP from the ECB's EUR table.
321
+ *
322
+ * ```ts
323
+ * const r = await client.officialRatesOn('rbi', '2026-09-30', { source: 'USD', target: 'INR' });
324
+ * console.log(r.rate_date, r.rate); // 2026-09-30 95.9832
325
+ * ```
326
+ */
327
+ async officialRatesOn(bank, date, options = {}) {
328
+ const key = this.resolveKey(options.apiKey);
329
+ const code = bank.trim().toLowerCase();
330
+ if (!/^[a-z0-9_-]{2,20}$/.test(code)) {
331
+ throw new AllRatesTodayError(`Invalid source code: ${bank}. Call officialSources() for the list.`);
332
+ }
333
+ if (Boolean(options.source) !== Boolean(options.target)) {
334
+ throw new AllRatesTodayError("Provide both source and target, or neither.");
335
+ }
336
+ const dateStr = this.formatDate(date);
337
+ const params = {};
338
+ if (options.source && options.target) {
339
+ params.source = options.source.toUpperCase();
340
+ params.target = options.target.toUpperCase();
341
+ }
342
+ return this.request(
343
+ `/api/v1/central-bank/${encodeURIComponent(code)}/${dateStr}`,
344
+ params,
345
+ key
346
+ );
347
+ }
265
348
  /**
266
349
  * Every covered official source with its code, name, latest publication
267
350
  * date and whether it is behind schedule. **No API key needed.**
@@ -290,7 +373,20 @@ var AllRatesToday = class {
290
373
  const key = this.resolveKey(options.apiKey);
291
374
  const params = { source: from, target: to };
292
375
  if (amount !== void 0) params.amount = String(amount);
293
- return this.request("/api/v1/rates", params, key);
376
+ const raw = await this.request("/api/v1/rates", params, key);
377
+ if (Array.isArray(raw) || !("from" in raw)) {
378
+ const r = Array.isArray(raw) ? raw[0] : raw;
379
+ if (!r) throw new AllRatesTodayError(`No rate returned for ${from}/${to}`);
380
+ const qty = amount ?? 1;
381
+ return {
382
+ from: { currency: r.source, amount: qty },
383
+ to: { currency: r.target, amount: Number((qty * r.rate).toFixed(6)) },
384
+ rate: r.rate,
385
+ source: "interbank",
386
+ time: r.time
387
+ };
388
+ }
389
+ return raw;
294
390
  }
295
391
  /**
296
392
  * Get authenticated exchange rates with metadata.
@@ -302,10 +398,12 @@ var AllRatesToday = class {
302
398
  */
303
399
  async getRates(source, target, options = {}) {
304
400
  const key = this.resolveKey(options.apiKey);
305
- return this.request("/api/v1/rates", { source, target }, key);
401
+ const raw = await this.request("/api/v1/rates", { source, target }, key);
402
+ return Array.isArray(raw) ? raw : [raw];
306
403
  }
307
404
  /**
308
- * Get historical rates by period.
405
+ * Mid-market history for a preset window ending today: `1d` (hourly),
406
+ * `7d` / `30d` (daily) or `1y` (weekly). `1y` is billed as 12 calls.
309
407
  *
310
408
  * ```ts
311
409
  * const history = await client.getHistoricalRates('USD', 'EUR', '30d');
@@ -316,11 +414,17 @@ var AllRatesToday = class {
316
414
  */
317
415
  async getHistoricalRates(source, target, period = "7d", options = {}) {
318
416
  const key = this.resolveKey(options.apiKey);
319
- return this.request(
320
- "/api/historical-rates",
321
- { source, target, period },
322
- key
323
- );
417
+ const data = await this.history(source, target, period, key);
418
+ const rates = data.map((p) => ({ rate: p.rate, time: p.date }));
419
+ const last = rates[rates.length - 1];
420
+ return {
421
+ source: source.toUpperCase(),
422
+ target: target.toUpperCase(),
423
+ period,
424
+ current: last ?? { rate: 0, time: "" },
425
+ rates,
426
+ data
427
+ };
324
428
  }
325
429
  /**
326
430
  * Convert an amount between two currencies.
@@ -339,19 +443,21 @@ var AllRatesToday = class {
339
443
  const key = this.resolveKey(options.apiKey);
340
444
  if (options.date) {
341
445
  const dateStr = this.formatDate(options.date);
342
- const params = {
343
- source: from,
344
- target: to,
345
- from: dateStr,
346
- to: dateStr
446
+ const target = to.toUpperCase();
447
+ const day = await this.forDate(dateStr, { base: from, symbols: [target], apiKey: key });
448
+ const rate = day.rates[target];
449
+ if (rate === void 0) {
450
+ throw new AllRatesTodayError(`No ${from}/${to} rate available on or before ${dateStr}`);
451
+ }
452
+ return {
453
+ from,
454
+ to,
455
+ amount,
456
+ result: Number((amount * rate).toFixed(6)),
457
+ rate,
458
+ date: dateStr,
459
+ rateDate: day.rateDates[target]
347
460
  };
348
- const raw = await this.request(
349
- "/api/historical-rates",
350
- params,
351
- key
352
- );
353
- const rate = raw.current?.rate ?? raw.rates?.[0]?.rate ?? 0;
354
- return { from, to, amount, result: Number((amount * rate).toFixed(6)), rate, date: dateStr };
355
461
  }
356
462
  const data = await this.request(
357
463
  "/api/v1/rates",