frankfurter-js 0.1.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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/index.cjs +186 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +126 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +185 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tin Sever
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frankfurter-js
|
|
2
|
+
|
|
3
|
+
A lightweight, production-ready TypeScript SDK for the [Frankfurter currency API](https://github.com/lineofflight/frankfurter).
|
|
4
|
+
|
|
5
|
+
It stays close to the HTTP API, uses `fetch`, ships type declarations, and works in modern Node.js and browsers.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install frankfurter-js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { FrankfurterClient } from "frankfurter-js";
|
|
17
|
+
|
|
18
|
+
const client = new FrankfurterClient();
|
|
19
|
+
|
|
20
|
+
const latest = await client.latest({
|
|
21
|
+
base: "USD",
|
|
22
|
+
quotes: ["EUR", "GBP"]
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The default base URL is `https://api.frankfurter.dev`. The client targets the OpenAPI server path at `/v2` internally.
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
### Create a client
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { FrankfurterClient, createFrankfurterClient } from "frankfurter-js";
|
|
34
|
+
|
|
35
|
+
const client = new FrankfurterClient({
|
|
36
|
+
timeout: 5_000
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const clientFromFactory = createFrankfurterClient();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `rates(query?, requestOptions?)`
|
|
43
|
+
|
|
44
|
+
Thin mapping to `GET /rates`.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const rates = await client.rates({
|
|
48
|
+
date: "2025-01-10",
|
|
49
|
+
base: "EUR",
|
|
50
|
+
quotes: ["USD", "CHF"],
|
|
51
|
+
providers: ["ECB"],
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `latest(options?, requestOptions?)`
|
|
56
|
+
|
|
57
|
+
Convenience method for the latest rates.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const latest = await client.latest({
|
|
61
|
+
base: "USD",
|
|
62
|
+
quotes: ["EUR", "GBP"]
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `historical(date, options?, requestOptions?)`
|
|
67
|
+
|
|
68
|
+
Convenience method for a specific date.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const historical = await client.historical("2025-01-10", {
|
|
72
|
+
base: "EUR",
|
|
73
|
+
quotes: "USD"
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `range(from, to?, options?, requestOptions?)`
|
|
78
|
+
|
|
79
|
+
Convenience method for date ranges.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const ranged = await client.range("2025-01-01", "2025-01-31", {
|
|
83
|
+
base: "EUR",
|
|
84
|
+
quotes: ["USD", "CHF"],
|
|
85
|
+
group: "month"
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `currencies(query?, requestOptions?)`
|
|
90
|
+
|
|
91
|
+
Maps to `GET /currencies`.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const currencies = await client.currencies();
|
|
95
|
+
const allCurrencies = await client.currencies({ scope: "all" });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `providers(requestOptions?)`
|
|
99
|
+
|
|
100
|
+
Maps to `GET /providers`.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const providers = await client.providers();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Self-Hosted Base URL
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { FrankfurterClient } from "frankfurter-js";
|
|
110
|
+
|
|
111
|
+
const client = new FrankfurterClient({
|
|
112
|
+
baseUrl: "https://rates.example.com"
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Custom Fetch
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { FrankfurterClient } from "frankfurter-js";
|
|
120
|
+
|
|
121
|
+
const client = new FrankfurterClient({
|
|
122
|
+
fetch: window.fetch.bind(window)
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Request Timeout and AbortSignal
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const client = new FrankfurterClient({
|
|
130
|
+
timeout: 5_000
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
|
|
135
|
+
const rates = await client.latest(
|
|
136
|
+
{ base: "EUR", quotes: ["USD"] },
|
|
137
|
+
{ signal: controller.signal }
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Error Handling
|
|
142
|
+
|
|
143
|
+
Non-2xx responses throw `FrankfurterError`.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { FrankfurterClient, FrankfurterError } from "frankfurter-js";
|
|
147
|
+
|
|
148
|
+
const client = new FrankfurterClient();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await client.historical("invalid-date");
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof FrankfurterError) {
|
|
154
|
+
console.error(error.status);
|
|
155
|
+
console.error(error.statusText);
|
|
156
|
+
console.error(error.url);
|
|
157
|
+
console.error(error.body);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## TypeScript
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import type { Currency, Provider, Rate } from "frankfurter-js";
|
|
166
|
+
import { FrankfurterClient } from "frankfurter-js";
|
|
167
|
+
|
|
168
|
+
const client = new FrankfurterClient();
|
|
169
|
+
|
|
170
|
+
const rates: Rate[] = await client.latest({ base: "EUR", quotes: ["USD"] });
|
|
171
|
+
const currencies: Currency[] = await client.currencies();
|
|
172
|
+
const providers: Provider[] = await client.providers();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Notes
|
|
176
|
+
|
|
177
|
+
- `quotes` and `providers` accept either a comma-separated string or a string array.
|
|
178
|
+
- Query serialization follows the OpenAPI contract and sends comma-separated values for list params.
|
|
179
|
+
- The SDK intentionally does not add retries, caching, or response reshaping.
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm install
|
|
185
|
+
npm run build
|
|
186
|
+
npm test
|
|
187
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
var FrankfurterError = class extends Error {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(`Frankfurter API request failed with ${options.status} ${options.statusText}`);
|
|
6
|
+
this.name = "FrankfurterError";
|
|
7
|
+
this.status = options.status;
|
|
8
|
+
this.statusText = options.statusText;
|
|
9
|
+
this.url = options.url;
|
|
10
|
+
this.body = options.body;
|
|
11
|
+
this.headers = options.headers;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/http.ts
|
|
16
|
+
const DEFAULT_BASE_URL = "https://api.frankfurter.dev";
|
|
17
|
+
const DEFAULT_API_PREFIX = "/v2";
|
|
18
|
+
function resolveClientOptions(options = {}) {
|
|
19
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
20
|
+
if (!fetchImpl) throw new Error("A fetch implementation is required. Pass one in the client options.");
|
|
21
|
+
return {
|
|
22
|
+
baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
|
|
23
|
+
fetch: fetchImpl,
|
|
24
|
+
timeout: options.timeout,
|
|
25
|
+
headers: options.headers
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function normalizeBaseUrl(baseUrl) {
|
|
29
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
30
|
+
const url = new URL(normalized);
|
|
31
|
+
if (url.pathname === DEFAULT_API_PREFIX || url.pathname.endsWith(`${DEFAULT_API_PREFIX}`)) return url.toString().replace(/\/+$/, "");
|
|
32
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}${DEFAULT_API_PREFIX}`;
|
|
33
|
+
return url.toString().replace(/\/+$/, "");
|
|
34
|
+
}
|
|
35
|
+
function createUrl(baseUrl, path, query) {
|
|
36
|
+
const url = new URL(path, `${baseUrl}/`);
|
|
37
|
+
if (query && Array.from(query.keys()).length > 0) url.search = query.toString();
|
|
38
|
+
return url.toString();
|
|
39
|
+
}
|
|
40
|
+
async function parseResponseBody(response) {
|
|
41
|
+
if (response.status === 204) return;
|
|
42
|
+
if ((response.headers.get("content-type") ?? "").includes("application/json")) return response.json();
|
|
43
|
+
const text = await response.text();
|
|
44
|
+
return text.length > 0 ? text : void 0;
|
|
45
|
+
}
|
|
46
|
+
async function requestJson(options, url, requestOptions = {}) {
|
|
47
|
+
const controller = createTimeoutController(options.timeout, requestOptions.signal);
|
|
48
|
+
const headers = mergeHeaders(options.headers, requestOptions.headers);
|
|
49
|
+
const init = {
|
|
50
|
+
method: "GET",
|
|
51
|
+
signal: controller.signal
|
|
52
|
+
};
|
|
53
|
+
if (headers) init.headers = headers;
|
|
54
|
+
try {
|
|
55
|
+
const response = await options.fetch(url, init);
|
|
56
|
+
const body = await parseResponseBody(response);
|
|
57
|
+
if (!response.ok) throw new FrankfurterError({
|
|
58
|
+
status: response.status,
|
|
59
|
+
statusText: response.statusText,
|
|
60
|
+
url,
|
|
61
|
+
body,
|
|
62
|
+
headers: response.headers
|
|
63
|
+
});
|
|
64
|
+
return body;
|
|
65
|
+
} finally {
|
|
66
|
+
controller.cleanup();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function mergeHeaders(defaultHeaders, requestHeaders) {
|
|
70
|
+
if (!defaultHeaders && !requestHeaders) return;
|
|
71
|
+
const headers = new Headers(defaultHeaders);
|
|
72
|
+
new Headers(requestHeaders).forEach((value, key) => {
|
|
73
|
+
headers.set(key, value);
|
|
74
|
+
});
|
|
75
|
+
return headers;
|
|
76
|
+
}
|
|
77
|
+
function createTimeoutController(timeout, signal) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
let timeoutId;
|
|
80
|
+
const abort = () => controller.abort(signal?.reason);
|
|
81
|
+
if (signal) if (signal.aborted) controller.abort(signal.reason);
|
|
82
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
83
|
+
if (typeof timeout === "number" && timeout > 0 && !controller.signal.aborted) timeoutId = setTimeout(() => {
|
|
84
|
+
controller.abort(/* @__PURE__ */ new Error(`Request timed out after ${timeout}ms`));
|
|
85
|
+
}, timeout);
|
|
86
|
+
return {
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
cleanup() {
|
|
89
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
90
|
+
if (signal) signal.removeEventListener("abort", abort);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/query.ts
|
|
96
|
+
function buildRatesQuery(query = {}) {
|
|
97
|
+
const searchParams = new URLSearchParams();
|
|
98
|
+
append(searchParams, "date", query.date);
|
|
99
|
+
append(searchParams, "from", query.from);
|
|
100
|
+
append(searchParams, "to", query.to);
|
|
101
|
+
append(searchParams, "base", query.base);
|
|
102
|
+
appendList(searchParams, "quotes", query.quotes);
|
|
103
|
+
appendList(searchParams, "providers", query.providers);
|
|
104
|
+
append(searchParams, "group", query.group);
|
|
105
|
+
return searchParams;
|
|
106
|
+
}
|
|
107
|
+
function buildCurrenciesQuery(query = {}) {
|
|
108
|
+
const searchParams = new URLSearchParams();
|
|
109
|
+
append(searchParams, "scope", query.scope);
|
|
110
|
+
return searchParams;
|
|
111
|
+
}
|
|
112
|
+
function append(searchParams, key, value) {
|
|
113
|
+
if (typeof value === "string" && value.length > 0) searchParams.set(key, value);
|
|
114
|
+
}
|
|
115
|
+
function appendList(searchParams, key, value) {
|
|
116
|
+
if (!value) return;
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
if (value.length > 0) searchParams.set(key, value.join(","));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const stringValue = value;
|
|
122
|
+
if (stringValue.length > 0) searchParams.set(key, stringValue);
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/client.ts
|
|
126
|
+
/**
|
|
127
|
+
* Thin API client for the Frankfurter currency API.
|
|
128
|
+
*/
|
|
129
|
+
var FrankfurterClient = class {
|
|
130
|
+
constructor(options = {}) {
|
|
131
|
+
this.options = resolveClientOptions(options);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fetch exchange rates using the raw `/rates` query shape from the API.
|
|
135
|
+
*/
|
|
136
|
+
async rates(query = {}, requestOptions = {}) {
|
|
137
|
+
const url = createUrl(this.options.baseUrl, "rates", buildRatesQuery(query));
|
|
138
|
+
return requestJson(this.options, url, requestOptions);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fetch the latest exchange rates.
|
|
142
|
+
*/
|
|
143
|
+
async latest(options = {}, requestOptions = {}) {
|
|
144
|
+
return this.rates(options, requestOptions);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Fetch exchange rates for a specific date.
|
|
148
|
+
*/
|
|
149
|
+
async historical(date, options = {}, requestOptions = {}) {
|
|
150
|
+
return this.rates({
|
|
151
|
+
...options,
|
|
152
|
+
date
|
|
153
|
+
}, requestOptions);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Fetch exchange rates across a date range.
|
|
157
|
+
*/
|
|
158
|
+
async range(from, to, options = {}, requestOptions = {}) {
|
|
159
|
+
return this.rates({
|
|
160
|
+
...options,
|
|
161
|
+
from,
|
|
162
|
+
...to ? { to } : {}
|
|
163
|
+
}, requestOptions);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetch available currencies.
|
|
167
|
+
*/
|
|
168
|
+
async currencies(query = {}, requestOptions = {}) {
|
|
169
|
+
const url = createUrl(this.options.baseUrl, "currencies", buildCurrenciesQuery(query));
|
|
170
|
+
return requestJson(this.options, url, requestOptions);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Fetch available data providers.
|
|
174
|
+
*/
|
|
175
|
+
async providers(requestOptions = {}) {
|
|
176
|
+
const url = createUrl(this.options.baseUrl, "providers");
|
|
177
|
+
return requestJson(this.options, url, requestOptions);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
function createFrankfurterClient(options = {}) {
|
|
181
|
+
return new FrankfurterClient(options);
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
exports.FrankfurterClient = FrankfurterClient;
|
|
185
|
+
exports.FrankfurterError = FrankfurterError;
|
|
186
|
+
exports.createFrankfurterClient = createFrankfurterClient;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type DateString = string;
|
|
3
|
+
type CurrencyCode = string;
|
|
4
|
+
type ProviderKey = string;
|
|
5
|
+
interface Rate {
|
|
6
|
+
date: DateString;
|
|
7
|
+
base: CurrencyCode;
|
|
8
|
+
quote: CurrencyCode;
|
|
9
|
+
rate: number;
|
|
10
|
+
}
|
|
11
|
+
interface Currency {
|
|
12
|
+
iso_code: CurrencyCode;
|
|
13
|
+
iso_numeric?: string | null;
|
|
14
|
+
name: string;
|
|
15
|
+
symbol?: string | null;
|
|
16
|
+
start_date?: DateString | null;
|
|
17
|
+
end_date?: DateString | null;
|
|
18
|
+
}
|
|
19
|
+
interface Provider {
|
|
20
|
+
key: ProviderKey;
|
|
21
|
+
name: string;
|
|
22
|
+
base: CurrencyCode;
|
|
23
|
+
start_date?: DateString | null;
|
|
24
|
+
end_date?: DateString | null;
|
|
25
|
+
currencies: CurrencyCode[];
|
|
26
|
+
}
|
|
27
|
+
interface ErrorResponseBody {
|
|
28
|
+
message?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
type Listable<T> = T | readonly T[];
|
|
32
|
+
type GroupBy = "week" | "month";
|
|
33
|
+
type CurrencyScope = "all";
|
|
34
|
+
interface RatesQuery {
|
|
35
|
+
date?: DateString;
|
|
36
|
+
from?: DateString;
|
|
37
|
+
to?: DateString;
|
|
38
|
+
base?: CurrencyCode;
|
|
39
|
+
quotes?: Listable<CurrencyCode>;
|
|
40
|
+
providers?: Listable<ProviderKey>;
|
|
41
|
+
group?: GroupBy;
|
|
42
|
+
}
|
|
43
|
+
interface LatestRatesOptions {
|
|
44
|
+
base?: CurrencyCode;
|
|
45
|
+
quotes?: Listable<CurrencyCode>;
|
|
46
|
+
providers?: Listable<ProviderKey>;
|
|
47
|
+
}
|
|
48
|
+
interface HistoricalRatesOptions {
|
|
49
|
+
base?: CurrencyCode;
|
|
50
|
+
quotes?: Listable<CurrencyCode>;
|
|
51
|
+
providers?: Listable<ProviderKey>;
|
|
52
|
+
}
|
|
53
|
+
interface RangeRatesOptions {
|
|
54
|
+
base?: CurrencyCode;
|
|
55
|
+
quotes?: Listable<CurrencyCode>;
|
|
56
|
+
providers?: Listable<ProviderKey>;
|
|
57
|
+
group?: GroupBy;
|
|
58
|
+
}
|
|
59
|
+
interface CurrenciesQuery {
|
|
60
|
+
scope?: CurrencyScope;
|
|
61
|
+
}
|
|
62
|
+
interface RequestOptions {
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
headers?: HeadersInit;
|
|
65
|
+
}
|
|
66
|
+
interface FrankfurterClientOptions {
|
|
67
|
+
baseUrl?: string;
|
|
68
|
+
fetch?: typeof fetch;
|
|
69
|
+
timeout?: number;
|
|
70
|
+
headers?: HeadersInit;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/client.d.ts
|
|
74
|
+
/**
|
|
75
|
+
* Thin API client for the Frankfurter currency API.
|
|
76
|
+
*/
|
|
77
|
+
declare class FrankfurterClient {
|
|
78
|
+
private readonly options;
|
|
79
|
+
constructor(options?: FrankfurterClientOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Fetch exchange rates using the raw `/rates` query shape from the API.
|
|
82
|
+
*/
|
|
83
|
+
rates(query?: RatesQuery, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Fetch the latest exchange rates.
|
|
86
|
+
*/
|
|
87
|
+
latest(options?: LatestRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Fetch exchange rates for a specific date.
|
|
90
|
+
*/
|
|
91
|
+
historical(date: string, options?: HistoricalRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Fetch exchange rates across a date range.
|
|
94
|
+
*/
|
|
95
|
+
range(from: string, to?: string, options?: RangeRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Fetch available currencies.
|
|
98
|
+
*/
|
|
99
|
+
currencies(query?: CurrenciesQuery, requestOptions?: RequestOptions): Promise<Currency[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Fetch available data providers.
|
|
102
|
+
*/
|
|
103
|
+
providers(requestOptions?: RequestOptions): Promise<Provider[]>;
|
|
104
|
+
}
|
|
105
|
+
declare function createFrankfurterClient(options?: FrankfurterClientOptions): FrankfurterClient;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/errors.d.ts
|
|
108
|
+
interface FrankfurterErrorOptions {
|
|
109
|
+
status: number;
|
|
110
|
+
statusText: string;
|
|
111
|
+
url: string;
|
|
112
|
+
body: unknown | undefined;
|
|
113
|
+
headers: Headers | undefined;
|
|
114
|
+
}
|
|
115
|
+
declare class FrankfurterError extends Error {
|
|
116
|
+
readonly name = "FrankfurterError";
|
|
117
|
+
readonly status: number;
|
|
118
|
+
readonly statusText: string;
|
|
119
|
+
readonly url: string;
|
|
120
|
+
readonly body: unknown | undefined;
|
|
121
|
+
readonly headers: Headers | undefined;
|
|
122
|
+
constructor(options: FrankfurterErrorOptions);
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { type CurrenciesQuery, type Currency, type CurrencyCode, type CurrencyScope, type DateString, type ErrorResponseBody, FrankfurterClient, type FrankfurterClientOptions, FrankfurterError, type GroupBy, type HistoricalRatesOptions, type LatestRatesOptions, type Listable, type Provider, type ProviderKey, type RangeRatesOptions, type Rate, type RatesQuery, type RequestOptions, createFrankfurterClient };
|
|
126
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/errors.ts"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type DateString = string;
|
|
3
|
+
type CurrencyCode = string;
|
|
4
|
+
type ProviderKey = string;
|
|
5
|
+
interface Rate {
|
|
6
|
+
date: DateString;
|
|
7
|
+
base: CurrencyCode;
|
|
8
|
+
quote: CurrencyCode;
|
|
9
|
+
rate: number;
|
|
10
|
+
}
|
|
11
|
+
interface Currency {
|
|
12
|
+
iso_code: CurrencyCode;
|
|
13
|
+
iso_numeric?: string | null;
|
|
14
|
+
name: string;
|
|
15
|
+
symbol?: string | null;
|
|
16
|
+
start_date?: DateString | null;
|
|
17
|
+
end_date?: DateString | null;
|
|
18
|
+
}
|
|
19
|
+
interface Provider {
|
|
20
|
+
key: ProviderKey;
|
|
21
|
+
name: string;
|
|
22
|
+
base: CurrencyCode;
|
|
23
|
+
start_date?: DateString | null;
|
|
24
|
+
end_date?: DateString | null;
|
|
25
|
+
currencies: CurrencyCode[];
|
|
26
|
+
}
|
|
27
|
+
interface ErrorResponseBody {
|
|
28
|
+
message?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
type Listable<T> = T | readonly T[];
|
|
32
|
+
type GroupBy = "week" | "month";
|
|
33
|
+
type CurrencyScope = "all";
|
|
34
|
+
interface RatesQuery {
|
|
35
|
+
date?: DateString;
|
|
36
|
+
from?: DateString;
|
|
37
|
+
to?: DateString;
|
|
38
|
+
base?: CurrencyCode;
|
|
39
|
+
quotes?: Listable<CurrencyCode>;
|
|
40
|
+
providers?: Listable<ProviderKey>;
|
|
41
|
+
group?: GroupBy;
|
|
42
|
+
}
|
|
43
|
+
interface LatestRatesOptions {
|
|
44
|
+
base?: CurrencyCode;
|
|
45
|
+
quotes?: Listable<CurrencyCode>;
|
|
46
|
+
providers?: Listable<ProviderKey>;
|
|
47
|
+
}
|
|
48
|
+
interface HistoricalRatesOptions {
|
|
49
|
+
base?: CurrencyCode;
|
|
50
|
+
quotes?: Listable<CurrencyCode>;
|
|
51
|
+
providers?: Listable<ProviderKey>;
|
|
52
|
+
}
|
|
53
|
+
interface RangeRatesOptions {
|
|
54
|
+
base?: CurrencyCode;
|
|
55
|
+
quotes?: Listable<CurrencyCode>;
|
|
56
|
+
providers?: Listable<ProviderKey>;
|
|
57
|
+
group?: GroupBy;
|
|
58
|
+
}
|
|
59
|
+
interface CurrenciesQuery {
|
|
60
|
+
scope?: CurrencyScope;
|
|
61
|
+
}
|
|
62
|
+
interface RequestOptions {
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
headers?: HeadersInit;
|
|
65
|
+
}
|
|
66
|
+
interface FrankfurterClientOptions {
|
|
67
|
+
baseUrl?: string;
|
|
68
|
+
fetch?: typeof fetch;
|
|
69
|
+
timeout?: number;
|
|
70
|
+
headers?: HeadersInit;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/client.d.ts
|
|
74
|
+
/**
|
|
75
|
+
* Thin API client for the Frankfurter currency API.
|
|
76
|
+
*/
|
|
77
|
+
declare class FrankfurterClient {
|
|
78
|
+
private readonly options;
|
|
79
|
+
constructor(options?: FrankfurterClientOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Fetch exchange rates using the raw `/rates` query shape from the API.
|
|
82
|
+
*/
|
|
83
|
+
rates(query?: RatesQuery, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Fetch the latest exchange rates.
|
|
86
|
+
*/
|
|
87
|
+
latest(options?: LatestRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Fetch exchange rates for a specific date.
|
|
90
|
+
*/
|
|
91
|
+
historical(date: string, options?: HistoricalRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Fetch exchange rates across a date range.
|
|
94
|
+
*/
|
|
95
|
+
range(from: string, to?: string, options?: RangeRatesOptions, requestOptions?: RequestOptions): Promise<Rate[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Fetch available currencies.
|
|
98
|
+
*/
|
|
99
|
+
currencies(query?: CurrenciesQuery, requestOptions?: RequestOptions): Promise<Currency[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Fetch available data providers.
|
|
102
|
+
*/
|
|
103
|
+
providers(requestOptions?: RequestOptions): Promise<Provider[]>;
|
|
104
|
+
}
|
|
105
|
+
declare function createFrankfurterClient(options?: FrankfurterClientOptions): FrankfurterClient;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/errors.d.ts
|
|
108
|
+
interface FrankfurterErrorOptions {
|
|
109
|
+
status: number;
|
|
110
|
+
statusText: string;
|
|
111
|
+
url: string;
|
|
112
|
+
body: unknown | undefined;
|
|
113
|
+
headers: Headers | undefined;
|
|
114
|
+
}
|
|
115
|
+
declare class FrankfurterError extends Error {
|
|
116
|
+
readonly name = "FrankfurterError";
|
|
117
|
+
readonly status: number;
|
|
118
|
+
readonly statusText: string;
|
|
119
|
+
readonly url: string;
|
|
120
|
+
readonly body: unknown | undefined;
|
|
121
|
+
readonly headers: Headers | undefined;
|
|
122
|
+
constructor(options: FrankfurterErrorOptions);
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { type CurrenciesQuery, type Currency, type CurrencyCode, type CurrencyScope, type DateString, type ErrorResponseBody, FrankfurterClient, type FrankfurterClientOptions, FrankfurterError, type GroupBy, type HistoricalRatesOptions, type LatestRatesOptions, type Listable, type Provider, type ProviderKey, type RangeRatesOptions, type Rate, type RatesQuery, type RequestOptions, createFrankfurterClient };
|
|
126
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/errors.ts"],"mappings":";KAAY,UAAA;AAAA,KACA,YAAA;AAAA,KACA,WAAA;AAAA,UAEK,IAAA;EACf,IAAA,EAAM,UAAA;EACN,IAAA,EAAM,YAAA;EACN,KAAA,EAAO,YAAA;EACP,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,QAAA,EAAU,YAAA;EACV,WAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA,GAAa,UAAA;EACb,QAAA,GAAW,UAAA;AAAA;AAAA,UAGI,QAAA;EACf,GAAA,EAAK,WAAA;EACL,IAAA;EACA,IAAA,EAAM,YAAA;EACN,UAAA,GAAa,UAAA;EACb,QAAA,GAAW,UAAA;EACX,UAAA,EAAY,YAAA;AAAA;AAAA,UAGG,iBAAA;EACf,OAAA;EAAA,CACC,GAAA;AAAA;AAAA,KAGS,QAAA,MAAc,CAAA,YAAa,CAAA;AAAA,KAC3B,OAAA;AAAA,KACA,aAAA;AAAA,UAEK,UAAA;EACf,IAAA,GAAO,UAAA;EACP,IAAA,GAAO,UAAA;EACP,EAAA,GAAK,UAAA;EACL,IAAA,GAAO,YAAA;EACP,MAAA,GAAS,QAAA,CAAS,YAAA;EAClB,SAAA,GAAY,QAAA,CAAS,WAAA;EACrB,KAAA,GAAQ,OAAA;AAAA;AAAA,UAGO,kBAAA;EACf,IAAA,GAAO,YAAA;EACP,MAAA,GAAS,QAAA,CAAS,YAAA;EAClB,SAAA,GAAY,QAAA,CAAS,WAAA;AAAA;AAAA,UAGN,sBAAA;EACf,IAAA,GAAO,YAAA;EACP,MAAA,GAAS,QAAA,CAAS,YAAA;EAClB,SAAA,GAAY,QAAA,CAAS,WAAA;AAAA;AAAA,UAGN,iBAAA;EACf,IAAA,GAAO,YAAA;EACP,MAAA,GAAS,QAAA,CAAS,YAAA;EAClB,SAAA,GAAY,QAAA,CAAS,WAAA;EACrB,KAAA,GAAQ,OAAA;AAAA;AAAA,UAGO,eAAA;EACf,KAAA,GAAQ,aAAA;AAAA;AAAA,UAGO,cAAA;EACf,MAAA,GAAS,WAAA;EACT,OAAA,GAAU,WAAA;AAAA;AAAA,UAGK,wBAAA;EACf,OAAA;EACA,KAAA,UAAe,KAAA;EACf,OAAA;EACA,OAAA,GAAU,WAAA;AAAA;;;AAhFZ;;;AAAA,cCkBa,iBAAA;EAAA,iBACM,OAAA;cAEL,OAAA,GAAS,wBAAA;EDpBC;;;EC2BhB,KAAA,CAAM,KAAA,GAAO,UAAA,EAAiB,cAAA,GAAgB,cAAA,GAAsB,OAAA,CAAQ,IAAA;ED1BxE;;;ECkCJ,MAAA,CAAO,OAAA,GAAS,kBAAA,EAAyB,cAAA,GAAgB,cAAA,GAAsB,OAAA,CAAQ,IAAA;EDlCxE;AAEvB;;ECuCQ,UAAA,CACJ,IAAA,UACA,OAAA,GAAS,sBAAA,EACT,cAAA,GAAgB,cAAA,GACf,OAAA,CAAQ,IAAA;ED1CL;;;ECiDA,KAAA,CACJ,IAAA,UACA,EAAA,WACA,OAAA,GAAS,iBAAA,EACT,cAAA,GAAgB,cAAA,GACf,OAAA,CAAQ,IAAA;EDpDQ;;;ECkEb,UAAA,CAAW,KAAA,GAAO,eAAA,EAAsB,cAAA,GAAgB,cAAA,GAAsB,OAAA,CAAQ,QAAA;EDnEtF;;;EC2EA,SAAA,CAAU,cAAA,GAAgB,cAAA,GAAsB,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAMhD,uBAAA,CAAwB,OAAA,GAAS,wBAAA,GAAgC,iBAAA;;;UCvFhE,uBAAA;EACf,MAAA;EACA,UAAA;EACA,GAAA;EACA,IAAA;EACA,OAAA,EAAS,OAAA;AAAA;AAAA,cAGE,gBAAA,SAAyB,KAAA;EAAA,SAC3B,IAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;EAAA,SACA,GAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA,EAAS,OAAA;cAEN,OAAA,EAAS,uBAAA;AAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
var FrankfurterError = class extends Error {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super(`Frankfurter API request failed with ${options.status} ${options.statusText}`);
|
|
5
|
+
this.name = "FrankfurterError";
|
|
6
|
+
this.status = options.status;
|
|
7
|
+
this.statusText = options.statusText;
|
|
8
|
+
this.url = options.url;
|
|
9
|
+
this.body = options.body;
|
|
10
|
+
this.headers = options.headers;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/http.ts
|
|
15
|
+
const DEFAULT_BASE_URL = "https://api.frankfurter.dev";
|
|
16
|
+
const DEFAULT_API_PREFIX = "/v2";
|
|
17
|
+
function resolveClientOptions(options = {}) {
|
|
18
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
19
|
+
if (!fetchImpl) throw new Error("A fetch implementation is required. Pass one in the client options.");
|
|
20
|
+
return {
|
|
21
|
+
baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
|
|
22
|
+
fetch: fetchImpl,
|
|
23
|
+
timeout: options.timeout,
|
|
24
|
+
headers: options.headers
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function normalizeBaseUrl(baseUrl) {
|
|
28
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
29
|
+
const url = new URL(normalized);
|
|
30
|
+
if (url.pathname === DEFAULT_API_PREFIX || url.pathname.endsWith(`${DEFAULT_API_PREFIX}`)) return url.toString().replace(/\/+$/, "");
|
|
31
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}${DEFAULT_API_PREFIX}`;
|
|
32
|
+
return url.toString().replace(/\/+$/, "");
|
|
33
|
+
}
|
|
34
|
+
function createUrl(baseUrl, path, query) {
|
|
35
|
+
const url = new URL(path, `${baseUrl}/`);
|
|
36
|
+
if (query && Array.from(query.keys()).length > 0) url.search = query.toString();
|
|
37
|
+
return url.toString();
|
|
38
|
+
}
|
|
39
|
+
async function parseResponseBody(response) {
|
|
40
|
+
if (response.status === 204) return;
|
|
41
|
+
if ((response.headers.get("content-type") ?? "").includes("application/json")) return response.json();
|
|
42
|
+
const text = await response.text();
|
|
43
|
+
return text.length > 0 ? text : void 0;
|
|
44
|
+
}
|
|
45
|
+
async function requestJson(options, url, requestOptions = {}) {
|
|
46
|
+
const controller = createTimeoutController(options.timeout, requestOptions.signal);
|
|
47
|
+
const headers = mergeHeaders(options.headers, requestOptions.headers);
|
|
48
|
+
const init = {
|
|
49
|
+
method: "GET",
|
|
50
|
+
signal: controller.signal
|
|
51
|
+
};
|
|
52
|
+
if (headers) init.headers = headers;
|
|
53
|
+
try {
|
|
54
|
+
const response = await options.fetch(url, init);
|
|
55
|
+
const body = await parseResponseBody(response);
|
|
56
|
+
if (!response.ok) throw new FrankfurterError({
|
|
57
|
+
status: response.status,
|
|
58
|
+
statusText: response.statusText,
|
|
59
|
+
url,
|
|
60
|
+
body,
|
|
61
|
+
headers: response.headers
|
|
62
|
+
});
|
|
63
|
+
return body;
|
|
64
|
+
} finally {
|
|
65
|
+
controller.cleanup();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function mergeHeaders(defaultHeaders, requestHeaders) {
|
|
69
|
+
if (!defaultHeaders && !requestHeaders) return;
|
|
70
|
+
const headers = new Headers(defaultHeaders);
|
|
71
|
+
new Headers(requestHeaders).forEach((value, key) => {
|
|
72
|
+
headers.set(key, value);
|
|
73
|
+
});
|
|
74
|
+
return headers;
|
|
75
|
+
}
|
|
76
|
+
function createTimeoutController(timeout, signal) {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
let timeoutId;
|
|
79
|
+
const abort = () => controller.abort(signal?.reason);
|
|
80
|
+
if (signal) if (signal.aborted) controller.abort(signal.reason);
|
|
81
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
82
|
+
if (typeof timeout === "number" && timeout > 0 && !controller.signal.aborted) timeoutId = setTimeout(() => {
|
|
83
|
+
controller.abort(/* @__PURE__ */ new Error(`Request timed out after ${timeout}ms`));
|
|
84
|
+
}, timeout);
|
|
85
|
+
return {
|
|
86
|
+
signal: controller.signal,
|
|
87
|
+
cleanup() {
|
|
88
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
89
|
+
if (signal) signal.removeEventListener("abort", abort);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/query.ts
|
|
95
|
+
function buildRatesQuery(query = {}) {
|
|
96
|
+
const searchParams = new URLSearchParams();
|
|
97
|
+
append(searchParams, "date", query.date);
|
|
98
|
+
append(searchParams, "from", query.from);
|
|
99
|
+
append(searchParams, "to", query.to);
|
|
100
|
+
append(searchParams, "base", query.base);
|
|
101
|
+
appendList(searchParams, "quotes", query.quotes);
|
|
102
|
+
appendList(searchParams, "providers", query.providers);
|
|
103
|
+
append(searchParams, "group", query.group);
|
|
104
|
+
return searchParams;
|
|
105
|
+
}
|
|
106
|
+
function buildCurrenciesQuery(query = {}) {
|
|
107
|
+
const searchParams = new URLSearchParams();
|
|
108
|
+
append(searchParams, "scope", query.scope);
|
|
109
|
+
return searchParams;
|
|
110
|
+
}
|
|
111
|
+
function append(searchParams, key, value) {
|
|
112
|
+
if (typeof value === "string" && value.length > 0) searchParams.set(key, value);
|
|
113
|
+
}
|
|
114
|
+
function appendList(searchParams, key, value) {
|
|
115
|
+
if (!value) return;
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
if (value.length > 0) searchParams.set(key, value.join(","));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const stringValue = value;
|
|
121
|
+
if (stringValue.length > 0) searchParams.set(key, stringValue);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/client.ts
|
|
125
|
+
/**
|
|
126
|
+
* Thin API client for the Frankfurter currency API.
|
|
127
|
+
*/
|
|
128
|
+
var FrankfurterClient = class {
|
|
129
|
+
constructor(options = {}) {
|
|
130
|
+
this.options = resolveClientOptions(options);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Fetch exchange rates using the raw `/rates` query shape from the API.
|
|
134
|
+
*/
|
|
135
|
+
async rates(query = {}, requestOptions = {}) {
|
|
136
|
+
const url = createUrl(this.options.baseUrl, "rates", buildRatesQuery(query));
|
|
137
|
+
return requestJson(this.options, url, requestOptions);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Fetch the latest exchange rates.
|
|
141
|
+
*/
|
|
142
|
+
async latest(options = {}, requestOptions = {}) {
|
|
143
|
+
return this.rates(options, requestOptions);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Fetch exchange rates for a specific date.
|
|
147
|
+
*/
|
|
148
|
+
async historical(date, options = {}, requestOptions = {}) {
|
|
149
|
+
return this.rates({
|
|
150
|
+
...options,
|
|
151
|
+
date
|
|
152
|
+
}, requestOptions);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Fetch exchange rates across a date range.
|
|
156
|
+
*/
|
|
157
|
+
async range(from, to, options = {}, requestOptions = {}) {
|
|
158
|
+
return this.rates({
|
|
159
|
+
...options,
|
|
160
|
+
from,
|
|
161
|
+
...to ? { to } : {}
|
|
162
|
+
}, requestOptions);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Fetch available currencies.
|
|
166
|
+
*/
|
|
167
|
+
async currencies(query = {}, requestOptions = {}) {
|
|
168
|
+
const url = createUrl(this.options.baseUrl, "currencies", buildCurrenciesQuery(query));
|
|
169
|
+
return requestJson(this.options, url, requestOptions);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Fetch available data providers.
|
|
173
|
+
*/
|
|
174
|
+
async providers(requestOptions = {}) {
|
|
175
|
+
const url = createUrl(this.options.baseUrl, "providers");
|
|
176
|
+
return requestJson(this.options, url, requestOptions);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
function createFrankfurterClient(options = {}) {
|
|
180
|
+
return new FrankfurterClient(options);
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { FrankfurterClient, FrankfurterError, createFrankfurterClient };
|
|
184
|
+
|
|
185
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors.ts","../src/http.ts","../src/query.ts","../src/client.ts"],"sourcesContent":["export interface FrankfurterErrorOptions {\n status: number;\n statusText: string;\n url: string;\n body: unknown | undefined;\n headers: Headers | undefined;\n}\n\nexport class FrankfurterError extends Error {\n readonly name = \"FrankfurterError\";\n readonly status: number;\n readonly statusText: string;\n readonly url: string;\n readonly body: unknown | undefined;\n readonly headers: Headers | undefined;\n\n constructor(options: FrankfurterErrorOptions) {\n super(`Frankfurter API request failed with ${options.status} ${options.statusText}`);\n this.status = options.status;\n this.statusText = options.statusText;\n this.url = options.url;\n this.body = options.body;\n this.headers = options.headers;\n }\n}\n","import { FrankfurterError } from \"./errors.js\";\nimport type { FrankfurterClientOptions, RequestOptions } from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.frankfurter.dev\";\nconst DEFAULT_API_PREFIX = \"/v2\";\n\nexport interface ResolvedClientOptions {\n baseUrl: string;\n fetch: typeof fetch;\n timeout: number | undefined;\n headers: HeadersInit | undefined;\n}\n\nexport function resolveClientOptions(options: FrankfurterClientOptions = {}): ResolvedClientOptions {\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n if (!fetchImpl) {\n throw new Error(\"A fetch implementation is required. Pass one in the client options.\");\n }\n\n return {\n baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),\n fetch: fetchImpl,\n timeout: options.timeout,\n headers: options.headers\n };\n}\n\nexport function normalizeBaseUrl(baseUrl: string): string {\n const normalized = baseUrl.replace(/\\/+$/, \"\");\n const url = new URL(normalized);\n\n if (url.pathname === DEFAULT_API_PREFIX || url.pathname.endsWith(`${DEFAULT_API_PREFIX}`)) {\n return url.toString().replace(/\\/+$/, \"\");\n }\n\n url.pathname = `${url.pathname.replace(/\\/+$/, \"\")}${DEFAULT_API_PREFIX}`;\n return url.toString().replace(/\\/+$/, \"\");\n}\n\nexport function createUrl(baseUrl: string, path: string, query?: URLSearchParams): string {\n const url = new URL(path, `${baseUrl}/`);\n\n if (query && Array.from(query.keys()).length > 0) {\n url.search = query.toString();\n }\n\n return url.toString();\n}\n\nexport async function parseResponseBody(response: Response): Promise<unknown> {\n if (response.status === 204) {\n return undefined;\n }\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n\n if (contentType.includes(\"application/json\")) {\n return response.json();\n }\n\n const text = await response.text();\n return text.length > 0 ? text : undefined;\n}\n\nexport async function requestJson<T>(\n options: ResolvedClientOptions,\n url: string,\n requestOptions: RequestOptions = {}\n): Promise<T> {\n const controller = createTimeoutController(options.timeout, requestOptions.signal);\n const headers = mergeHeaders(options.headers, requestOptions.headers);\n const init: RequestInit = {\n method: \"GET\",\n signal: controller.signal\n };\n\n if (headers) {\n init.headers = headers;\n }\n\n try {\n const response = await options.fetch(url, init);\n\n const body = await parseResponseBody(response);\n\n if (!response.ok) {\n throw new FrankfurterError({\n status: response.status,\n statusText: response.statusText,\n url,\n body,\n headers: response.headers\n });\n }\n\n return body as T;\n } finally {\n controller.cleanup();\n }\n}\n\nfunction mergeHeaders(defaultHeaders?: HeadersInit, requestHeaders?: HeadersInit): Headers | undefined {\n if (!defaultHeaders && !requestHeaders) {\n return undefined;\n }\n\n const headers = new Headers(defaultHeaders);\n new Headers(requestHeaders).forEach((value, key) => {\n headers.set(key, value);\n });\n\n return headers;\n}\n\nfunction createTimeoutController(timeout: number | undefined, signal?: AbortSignal) {\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const abort = () => controller.abort(signal?.reason);\n\n if (signal) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n } else {\n signal.addEventListener(\"abort\", abort, { once: true });\n }\n }\n\n if (typeof timeout === \"number\" && timeout > 0 && !controller.signal.aborted) {\n timeoutId = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeout}ms`));\n }, timeout);\n }\n\n return {\n signal: controller.signal,\n cleanup() {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n if (signal) {\n signal.removeEventListener(\"abort\", abort);\n }\n }\n };\n}\n","import type { CurrenciesQuery, Listable, RatesQuery } from \"./types.js\";\n\nexport function buildRatesQuery(query: RatesQuery = {}): URLSearchParams {\n const searchParams = new URLSearchParams();\n\n append(searchParams, \"date\", query.date);\n append(searchParams, \"from\", query.from);\n append(searchParams, \"to\", query.to);\n append(searchParams, \"base\", query.base);\n appendList(searchParams, \"quotes\", query.quotes);\n appendList(searchParams, \"providers\", query.providers);\n append(searchParams, \"group\", query.group);\n\n return searchParams;\n}\n\nexport function buildCurrenciesQuery(query: CurrenciesQuery = {}): URLSearchParams {\n const searchParams = new URLSearchParams();\n append(searchParams, \"scope\", query.scope);\n return searchParams;\n}\n\nfunction append(searchParams: URLSearchParams, key: string, value: string | undefined) {\n if (typeof value === \"string\" && value.length > 0) {\n searchParams.set(key, value);\n }\n}\n\nfunction appendList(searchParams: URLSearchParams, key: string, value: Listable<string> | undefined) {\n if (!value) {\n return;\n }\n\n if (Array.isArray(value)) {\n if (value.length > 0) {\n searchParams.set(key, value.join(\",\"));\n }\n return;\n }\n\n const stringValue = value as string;\n\n if (stringValue.length > 0) {\n searchParams.set(key, stringValue);\n }\n}\n","import { createUrl, requestJson, resolveClientOptions } from \"./http.js\";\nimport { buildCurrenciesQuery, buildRatesQuery } from \"./query.js\";\nimport type {\n CurrenciesQuery,\n Currency,\n FrankfurterClientOptions,\n HistoricalRatesOptions,\n LatestRatesOptions,\n Provider,\n RangeRatesOptions,\n Rate,\n RatesQuery,\n RequestOptions\n} from \"./types.js\";\n\n/**\n * Thin API client for the Frankfurter currency API.\n */\nexport class FrankfurterClient {\n private readonly options;\n\n constructor(options: FrankfurterClientOptions = {}) {\n this.options = resolveClientOptions(options);\n }\n\n /**\n * Fetch exchange rates using the raw `/rates` query shape from the API.\n */\n async rates(query: RatesQuery = {}, requestOptions: RequestOptions = {}): Promise<Rate[]> {\n const url = createUrl(this.options.baseUrl, \"rates\", buildRatesQuery(query));\n return requestJson<Rate[]>(this.options, url, requestOptions);\n }\n\n /**\n * Fetch the latest exchange rates.\n */\n async latest(options: LatestRatesOptions = {}, requestOptions: RequestOptions = {}): Promise<Rate[]> {\n return this.rates(options, requestOptions);\n }\n\n /**\n * Fetch exchange rates for a specific date.\n */\n async historical(\n date: string,\n options: HistoricalRatesOptions = {},\n requestOptions: RequestOptions = {}\n ): Promise<Rate[]> {\n return this.rates({ ...options, date }, requestOptions);\n }\n\n /**\n * Fetch exchange rates across a date range.\n */\n async range(\n from: string,\n to?: string,\n options: RangeRatesOptions = {},\n requestOptions: RequestOptions = {}\n ): Promise<Rate[]> {\n return this.rates(\n {\n ...options,\n from,\n ...(to ? { to } : {})\n },\n requestOptions\n );\n }\n\n /**\n * Fetch available currencies.\n */\n async currencies(query: CurrenciesQuery = {}, requestOptions: RequestOptions = {}): Promise<Currency[]> {\n const url = createUrl(this.options.baseUrl, \"currencies\", buildCurrenciesQuery(query));\n return requestJson<Currency[]>(this.options, url, requestOptions);\n }\n\n /**\n * Fetch available data providers.\n */\n async providers(requestOptions: RequestOptions = {}): Promise<Provider[]> {\n const url = createUrl(this.options.baseUrl, \"providers\");\n return requestJson<Provider[]>(this.options, url, requestOptions);\n }\n}\n\nexport function createFrankfurterClient(options: FrankfurterClientOptions = {}): FrankfurterClient {\n return new FrankfurterClient(options);\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frankfurter-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight TypeScript SDK for the Frankfurter currency API.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"import": "./dist/index.mjs",
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown src/index.ts --format esm,cjs --dts",
|
|
30
|
+
"dev": "tsdown src/index.ts --format esm,cjs --dts --watch",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"frankfurter",
|
|
37
|
+
"currency",
|
|
38
|
+
"exchange-rates",
|
|
39
|
+
"forex",
|
|
40
|
+
"sdk",
|
|
41
|
+
"typescript"
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^24.5.2",
|
|
45
|
+
"tsdown": "^0.21.4",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vitest": "^4.1.0"
|
|
48
|
+
}
|
|
49
|
+
}
|