@allratestoday/sdk 1.4.5 → 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/CHANGELOG.md +30 -0
- package/README.md +277 -412
- package/dist/index.d.mts +118 -33
- package/dist/index.d.ts +118 -33
- package/dist/index.js +204 -98
- package/dist/index.mjs +204 -98
- package/package.json +4 -3
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
168
|
+
* Get mid-market rates for a past date within the last 365 days.
|
|
106
169
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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
|
-
*
|
|
112
|
-
* const data = await client.forDate(
|
|
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
|
|
180
|
+
async forDate(date, options) {
|
|
181
|
+
const key = this.resolveKey(options?.apiKey);
|
|
120
182
|
const dateStr = this.formatDate(date);
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
199
|
+
* Daily mid-market rates between two dates within the last 365 days.
|
|
147
200
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
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
|
-
*
|
|
155
|
-
*
|
|
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
|
|
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
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
227
|
+
return { base, startDate: start, endDate: end, rates };
|
|
185
228
|
}
|
|
186
229
|
/**
|
|
187
|
-
* Get all supported currency
|
|
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
|
|
200
|
-
|
|
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 —
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
375
|
+
const raw = await this.request("/api/v1/rates", { source, target }, key);
|
|
376
|
+
return Array.isArray(raw) ? raw : [raw];
|
|
280
377
|
}
|
|
281
378
|
/**
|
|
282
|
-
*
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
to
|
|
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
|
-
"description": "Exchange
|
|
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
|
".": {
|