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 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;
@@ -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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"}
@@ -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":"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"}
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
+ }