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