@unirate/astro 0.1.2
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 +101 -0
- package/components/Currency.astro +50 -0
- package/components/Rate.astro +24 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +114 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/index.d.ts +35 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +58 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/snapshot.d.ts +5 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +17 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vite-plugin.d.ts +19 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +33 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UniRate
|
|
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,101 @@
|
|
|
1
|
+
# @unirate/astro
|
|
2
|
+
|
|
3
|
+
Astro integration for [UniRate](https://unirateapi.com) — fetches a currency exchange rate snapshot at build time, exposes it as a Vite virtual module, and ships `<Currency />` and `<Rate />` components plus runtime conversion helpers.
|
|
4
|
+
|
|
5
|
+
- Build-time snapshot: rates resolve to constants in your static HTML; no client-side API key, no runtime fetch.
|
|
6
|
+
- Zero runtime dependencies (uses native `fetch`).
|
|
7
|
+
- Cross-pair conversion derived from a single base, so the snapshot stays small.
|
|
8
|
+
- Astro 4 + 5 compatible. Works with `static` and `server` output.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @unirate/astro
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
You'll need a free UniRate API key — grab one at <https://unirateapi.com>.
|
|
17
|
+
|
|
18
|
+
## Configure
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// astro.config.mjs
|
|
22
|
+
import { defineConfig } from "astro/config";
|
|
23
|
+
import unirate from "@unirate/astro";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
integrations: [
|
|
27
|
+
unirate({
|
|
28
|
+
apiKey: process.env.UNIRATE_API_KEY,
|
|
29
|
+
baseCurrency: "USD",
|
|
30
|
+
currencies: ["EUR", "GBP", "JPY", "CAD", "AUD"],
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If `apiKey` is omitted the integration reads `UNIRATE_API_KEY` from the environment.
|
|
37
|
+
|
|
38
|
+
### Options
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `apiKey` | `string` | `process.env.UNIRATE_API_KEY` | UniRate API key. |
|
|
43
|
+
| `baseCurrency` | `string` | `"USD"` | Base the build-time snapshot is keyed against. |
|
|
44
|
+
| `currencies` | `string[]` | _all UniRate currencies_ | Optional allowlist to shrink the bundled snapshot. |
|
|
45
|
+
| `apiBaseUrl` | `string` | `"https://api.unirateapi.com"` | API host. Override for self-hosted or testing. |
|
|
46
|
+
| `failOnFetchError` | `boolean` | `true` | Fail the build if the snapshot fetch fails. Set to `false` to ship an empty snapshot and let components fall back gracefully. |
|
|
47
|
+
|
|
48
|
+
## Use the components
|
|
49
|
+
|
|
50
|
+
```astro
|
|
51
|
+
---
|
|
52
|
+
import Currency from "@unirate/astro/components/Currency.astro";
|
|
53
|
+
import Rate from "@unirate/astro/components/Rate.astro";
|
|
54
|
+
---
|
|
55
|
+
<p>Subtotal: <Currency amount={100} from="USD" to="EUR" /></p>
|
|
56
|
+
<p>Today's USD/EUR rate: <Rate from="USD" to="EUR" /></p>
|
|
57
|
+
<p>Cross-pair (derived): <Rate from="EUR" to="GBP" precision={6} /></p>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`<Currency />` supports the same options as `Intl.NumberFormat`'s currency style:
|
|
61
|
+
|
|
62
|
+
```astro
|
|
63
|
+
<Currency amount={1234.5} from="USD" to="JPY" minimumFractionDigits={0} />
|
|
64
|
+
<Currency amount={100} from="USD" to="EUR" currencyDisplay="code" />
|
|
65
|
+
<Currency amount={100} from="USD" to="EUR" as="text" />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Pass `as="text"` to render the bare string with no wrapping `<span>` — useful inside `<title>` or table cells.
|
|
69
|
+
|
|
70
|
+
## Use the helpers directly
|
|
71
|
+
|
|
72
|
+
```astro
|
|
73
|
+
---
|
|
74
|
+
import { convertCurrency, getRate, formatCurrency, fetchedAt } from "@unirate/astro/runtime";
|
|
75
|
+
|
|
76
|
+
const eur = convertCurrency(100, "USD", "EUR");
|
|
77
|
+
const rate = getRate("USD", "EUR");
|
|
78
|
+
---
|
|
79
|
+
<p>{formatCurrency(eur, "EUR")}</p>
|
|
80
|
+
<p>Rate: {rate.toFixed(4)}</p>
|
|
81
|
+
<footer>Rates as of {fetchedAt}</footer>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The runtime entry also exports the raw `snapshot`, `base`, `rates`, and `fetchedAt` for advanced use cases (e.g. building your own table of all rates).
|
|
85
|
+
|
|
86
|
+
## How the build works
|
|
87
|
+
|
|
88
|
+
`astro:config:setup` fetches `/api/rates?from=<baseCurrency>[&to=...]` once, then registers a Vite plugin that resolves `virtual:unirate` to the snapshot as a frozen ES module. Components and helpers read from that virtual module, so by the time Astro renders pages every rate is already a constant in the bundle.
|
|
89
|
+
|
|
90
|
+
`astro:config:done` calls `injectTypes` to write the `virtual:unirate` ambient declaration into your `.astro/integrations/@unirate/astro/` dir — your tsconfig picks it up automatically with no extra setup.
|
|
91
|
+
|
|
92
|
+
Cross-pair rates are derived at render time: `rate(from → to) = rate(base → to) / rate(base → from)`. This means a snapshot keyed against USD can convert EUR → GBP without an extra fetch.
|
|
93
|
+
|
|
94
|
+
## Static vs. server output
|
|
95
|
+
|
|
96
|
+
- **Static (`output: "static"`, default).** Rates are baked into the rendered HTML. No client-side requests; no API key in the browser bundle.
|
|
97
|
+
- **Server (`output: "server"`).** Rates are still fetched once at build/start and frozen for the life of the process. Restart the server to refresh.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { convertCurrency, formatCurrency } from "@unirate/astro/runtime";
|
|
3
|
+
import type { FormatOptions } from "@unirate/astro/runtime";
|
|
4
|
+
|
|
5
|
+
export interface Props extends FormatOptions {
|
|
6
|
+
amount: number;
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
/**
|
|
10
|
+
* When true (default), wraps the output in `<span data-unirate>`. Set
|
|
11
|
+
* `as="text"` to render the bare string with no element — useful inside
|
|
12
|
+
* `<title>` or table cells where extra DOM is awkward.
|
|
13
|
+
*/
|
|
14
|
+
as?: "span" | "text";
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
amount,
|
|
20
|
+
from,
|
|
21
|
+
to,
|
|
22
|
+
as = "span",
|
|
23
|
+
class: className,
|
|
24
|
+
locale,
|
|
25
|
+
minimumFractionDigits,
|
|
26
|
+
maximumFractionDigits,
|
|
27
|
+
currencyDisplay,
|
|
28
|
+
} = Astro.props;
|
|
29
|
+
|
|
30
|
+
let rendered: string;
|
|
31
|
+
try {
|
|
32
|
+
const converted = convertCurrency(amount, from, to);
|
|
33
|
+
rendered = formatCurrency(converted, to, {
|
|
34
|
+
locale,
|
|
35
|
+
minimumFractionDigits,
|
|
36
|
+
maximumFractionDigits,
|
|
37
|
+
currencyDisplay,
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
rendered = formatCurrency(amount, from, {
|
|
41
|
+
locale,
|
|
42
|
+
minimumFractionDigits,
|
|
43
|
+
maximumFractionDigits,
|
|
44
|
+
currencyDisplay,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
---
|
|
48
|
+
{as === "text" ? rendered : (
|
|
49
|
+
<span data-unirate="currency" data-from={from.toUpperCase()} data-to={to.toUpperCase()} class={className}>{rendered}</span>
|
|
50
|
+
)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getRate } from "@unirate/astro/runtime";
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
/** Decimal places to render. Defaults to 4. */
|
|
8
|
+
precision?: number;
|
|
9
|
+
as?: "span" | "text";
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { from, to, precision = 4, as = "span", class: className } = Astro.props;
|
|
14
|
+
|
|
15
|
+
let rendered: string;
|
|
16
|
+
try {
|
|
17
|
+
rendered = getRate(from, to).toFixed(precision);
|
|
18
|
+
} catch {
|
|
19
|
+
rendered = "—";
|
|
20
|
+
}
|
|
21
|
+
---
|
|
22
|
+
{as === "text" ? rendered : (
|
|
23
|
+
<span data-unirate="rate" data-from={from.toUpperCase()} data-to={to.toUpperCase()} class={className}>{rendered}</span>
|
|
24
|
+
)}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class UniRateError extends Error {
|
|
2
|
+
readonly status?: number;
|
|
3
|
+
readonly body?: unknown;
|
|
4
|
+
constructor(message: string, status?: number, body?: unknown);
|
|
5
|
+
}
|
|
6
|
+
export interface UniRateClientOptions {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
fetch?: typeof fetch;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RatesResponse {
|
|
13
|
+
rates: Record<string, number>;
|
|
14
|
+
base: string;
|
|
15
|
+
fetchedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class UniRateClient {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(opts: UniRateClientOptions);
|
|
20
|
+
getRates(from: string, to?: string[]): Promise<RatesResponse>;
|
|
21
|
+
convert(amount: number, from: string, to: string): Promise<number>;
|
|
22
|
+
listCurrencies(): Promise<string[]>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAaA,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;gBACZ,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAM7D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD,qBAAa,aAAa;;gBAMZ,IAAI,EAAE,oBAAoB;IA4ChC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAmB7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMlE,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAO1C"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Tiny fetch wrapper around the UniRate REST API. Stays dependency-free so
|
|
2
|
+
// the integration adds no runtime weight to Astro builds; native fetch is
|
|
3
|
+
// available in Node 18+ (required by Astro 4) and at the edge.
|
|
4
|
+
//
|
|
5
|
+
// Only the free-tier endpoints used by the integration are exposed:
|
|
6
|
+
// GET /api/rates — current rates for one base
|
|
7
|
+
// GET /api/convert — single amount conversion
|
|
8
|
+
// GET /api/currencies — supported ISO codes
|
|
9
|
+
//
|
|
10
|
+
// `Accept: application/json` is set on every request because
|
|
11
|
+
// /api/currencies returns HTML 404 without it (see project CLAUDE.md
|
|
12
|
+
// "Key gotchas" #2).
|
|
13
|
+
export class UniRateError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
body;
|
|
16
|
+
constructor(message, status, body) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "UniRateError";
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.body = body;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const DEFAULT_BASE_URL = "https://api.unirateapi.com";
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
25
|
+
const toNum = (v) => {
|
|
26
|
+
const n = typeof v === "string" ? Number.parseFloat(v) : v;
|
|
27
|
+
if (!Number.isFinite(n))
|
|
28
|
+
throw new UniRateError(`Non-numeric rate value: ${String(v)}`);
|
|
29
|
+
return n;
|
|
30
|
+
};
|
|
31
|
+
export class UniRateClient {
|
|
32
|
+
#apiKey;
|
|
33
|
+
#baseUrl;
|
|
34
|
+
#fetch;
|
|
35
|
+
#timeoutMs;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
if (!opts.apiKey)
|
|
38
|
+
throw new UniRateError("apiKey is required");
|
|
39
|
+
this.#apiKey = opts.apiKey;
|
|
40
|
+
this.#baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
41
|
+
this.#fetch = opts.fetch ?? globalThis.fetch;
|
|
42
|
+
this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
43
|
+
if (typeof this.#fetch !== "function") {
|
|
44
|
+
throw new UniRateError("global fetch is unavailable; pass `fetch` explicitly");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async #get(path, params) {
|
|
48
|
+
const url = new URL(this.#baseUrl + path);
|
|
49
|
+
url.searchParams.set("api_key", this.#apiKey);
|
|
50
|
+
for (const [k, v] of Object.entries(params)) {
|
|
51
|
+
if (v !== undefined)
|
|
52
|
+
url.searchParams.set(k, String(v));
|
|
53
|
+
}
|
|
54
|
+
const ctrl = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => ctrl.abort(), this.#timeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
const res = await this.#fetch(url.toString(), {
|
|
58
|
+
headers: { Accept: "application/json" },
|
|
59
|
+
signal: ctrl.signal,
|
|
60
|
+
});
|
|
61
|
+
const text = await res.text();
|
|
62
|
+
let body;
|
|
63
|
+
try {
|
|
64
|
+
body = text ? JSON.parse(text) : null;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new UniRateError(`Non-JSON response from ${path}`, res.status, text);
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
const msg = (body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
|
71
|
+
? body.error
|
|
72
|
+
: `HTTP ${res.status} from ${path}`);
|
|
73
|
+
throw new UniRateError(msg, res.status, body);
|
|
74
|
+
}
|
|
75
|
+
return body;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async getRates(from, to) {
|
|
82
|
+
const params = { from };
|
|
83
|
+
if (to && to.length > 0)
|
|
84
|
+
params.to = to.join(",");
|
|
85
|
+
const raw = await this.#get("/api/rates", params);
|
|
86
|
+
const rates = {};
|
|
87
|
+
if (raw.rates) {
|
|
88
|
+
for (const [code, val] of Object.entries(raw.rates))
|
|
89
|
+
rates[code] = toNum(val);
|
|
90
|
+
}
|
|
91
|
+
else if (raw.rate !== undefined && to && to.length === 1) {
|
|
92
|
+
rates[to[0]] = toNum(raw.rate);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw new UniRateError("Malformed /api/rates response", undefined, raw);
|
|
96
|
+
}
|
|
97
|
+
rates[from] = 1;
|
|
98
|
+
return { rates, base: from, fetchedAt: new Date().toISOString() };
|
|
99
|
+
}
|
|
100
|
+
async convert(amount, from, to) {
|
|
101
|
+
const raw = await this.#get("/api/convert", { from, to, amount });
|
|
102
|
+
if (raw.result === undefined)
|
|
103
|
+
throw new UniRateError("Malformed /api/convert response", undefined, raw);
|
|
104
|
+
return toNum(raw.result);
|
|
105
|
+
}
|
|
106
|
+
async listCurrencies() {
|
|
107
|
+
const raw = await this.#get("/api/currencies", {});
|
|
108
|
+
if (!Array.isArray(raw.currencies)) {
|
|
109
|
+
throw new UniRateError("Malformed /api/currencies response", undefined, raw);
|
|
110
|
+
}
|
|
111
|
+
return raw.currencies;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AstroIntegration } from "astro";
|
|
2
|
+
import type { UniRateIntegrationOptions } from "./types.js";
|
|
3
|
+
export type { UniRateIntegrationOptions, RateSnapshot } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Astro integration that fetches a UniRate snapshot at `astro:config:setup`
|
|
6
|
+
* and exposes it to the build via a `virtual:unirate` Vite module. Pair
|
|
7
|
+
* with the bundled `<Currency />` and `<Rate />` components, or import
|
|
8
|
+
* `getRate` / `convertCurrency` from `@unirate/astro/runtime`.
|
|
9
|
+
*/
|
|
10
|
+
export default function unirate(options?: UniRateIntegrationOptions): AstroIntegration;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAG9C,OAAO,KAAK,EAAgB,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE1E,YAAY,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK1E;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,yBAA8B,GAAG,gBAAgB,CA2DzF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { fetchSnapshot, EMPTY_SNAPSHOT } from "./snapshot.js";
|
|
2
|
+
import { unirateVitePlugin } from "./vite-plugin.js";
|
|
3
|
+
const DEFAULT_BASE = "USD";
|
|
4
|
+
const DEFAULT_API = "https://api.unirateapi.com";
|
|
5
|
+
/**
|
|
6
|
+
* Astro integration that fetches a UniRate snapshot at `astro:config:setup`
|
|
7
|
+
* and exposes it to the build via a `virtual:unirate` Vite module. Pair
|
|
8
|
+
* with the bundled `<Currency />` and `<Rate />` components, or import
|
|
9
|
+
* `getRate` / `convertCurrency` from `@unirate/astro/runtime`.
|
|
10
|
+
*/
|
|
11
|
+
export default function unirate(options = {}) {
|
|
12
|
+
const apiKey = options.apiKey ?? process.env.UNIRATE_API_KEY;
|
|
13
|
+
const baseCurrency = (options.baseCurrency ?? DEFAULT_BASE).toUpperCase();
|
|
14
|
+
const apiBaseUrl = options.apiBaseUrl ?? DEFAULT_API;
|
|
15
|
+
const failOnFetchError = options.failOnFetchError ?? true;
|
|
16
|
+
const currencies = options.currencies?.map((c) => c.toUpperCase());
|
|
17
|
+
const virtualTypes = [
|
|
18
|
+
'declare module "virtual:unirate" {',
|
|
19
|
+
" interface RateSnapshot {",
|
|
20
|
+
" base: string;",
|
|
21
|
+
" rates: Record<string, number>;",
|
|
22
|
+
" fetchedAt: string | null;",
|
|
23
|
+
" }",
|
|
24
|
+
" const snapshot: RateSnapshot;",
|
|
25
|
+
" export default snapshot;",
|
|
26
|
+
" export const base: string;",
|
|
27
|
+
" export const rates: Record<string, number>;",
|
|
28
|
+
" export const fetchedAt: string | null;",
|
|
29
|
+
"}",
|
|
30
|
+
"",
|
|
31
|
+
].join("\n");
|
|
32
|
+
return {
|
|
33
|
+
name: "@unirate/astro",
|
|
34
|
+
hooks: {
|
|
35
|
+
"astro:config:done": ({ injectTypes }) => {
|
|
36
|
+
injectTypes({ filename: "types.d.ts", content: virtualTypes });
|
|
37
|
+
},
|
|
38
|
+
"astro:config:setup": async ({ updateConfig, logger }) => {
|
|
39
|
+
let snapshot;
|
|
40
|
+
if (!apiKey) {
|
|
41
|
+
const msg = "No UniRate API key provided. Pass `apiKey` to the integration or set UNIRATE_API_KEY.";
|
|
42
|
+
if (failOnFetchError)
|
|
43
|
+
throw new Error(msg);
|
|
44
|
+
logger.warn(msg + " Shipping empty rate snapshot.");
|
|
45
|
+
snapshot = EMPTY_SNAPSHOT(baseCurrency);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
try {
|
|
49
|
+
snapshot = await fetchSnapshot({ apiKey, baseCurrency, apiBaseUrl, currencies });
|
|
50
|
+
logger.info(`Fetched ${Object.keys(snapshot.rates).length} ${baseCurrency} rates from UniRate.`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const msg = `UniRate snapshot fetch failed: ${err.message}`;
|
|
54
|
+
if (failOnFetchError)
|
|
55
|
+
throw new Error(msg);
|
|
56
|
+
logger.warn(msg + " Shipping empty rate snapshot.");
|
|
57
|
+
snapshot = EMPTY_SNAPSHOT(baseCurrency);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
updateConfig({
|
|
61
|
+
vite: {
|
|
62
|
+
plugins: [unirateVitePlugin(snapshot)],
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAKrD,MAAM,YAAY,GAAG,KAAK,CAAC;AAC3B,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,UAAqC,EAAE;IACrE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC7D,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1E,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC;IACrD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,IAAI,CAAC;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAEnE,MAAM,YAAY,GAAG;QACnB,oCAAoC;QACpC,4BAA4B;QAC5B,mBAAmB;QACnB,oCAAoC;QACpC,+BAA+B;QAC/B,KAAK;QACL,iCAAiC;QACjC,4BAA4B;QAC5B,8BAA8B;QAC9B,+CAA+C;QAC/C,0CAA0C;QAC1C,GAAG;QACH,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE;YACL,mBAAmB,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE;gBACvC,WAAW,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,oBAAoB,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE;gBACvD,IAAI,QAAsB,CAAC;gBAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,GAAG,GACP,uFAAuF,CAAC;oBAC1F,IAAI,gBAAgB;wBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,gCAAgC,CAAC,CAAC;oBACpD,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;gBAC1C,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC;wBACH,QAAQ,GAAG,MAAM,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;wBACjF,MAAM,CAAC,IAAI,CACT,WAAW,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,YAAY,sBAAsB,CACpF,CAAC;oBACJ,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,GAAG,GAAG,kCAAmC,GAAa,CAAC,OAAO,EAAE,CAAC;wBACvE,IAAI,gBAAgB;4BAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;wBAC3C,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,gCAAgC,CAAC,CAAC;wBACpD,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;oBAC1C,CAAC;gBACH,CAAC;gBAED,YAAY,CAAC;oBACX,IAAI,EAAE;wBACJ,OAAO,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;qBACvC;iBACF,CAAC,CAAC;YACL,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RateSnapshot } from "../types.js";
|
|
2
|
+
import snapshot from "virtual:unirate";
|
|
3
|
+
export { snapshot };
|
|
4
|
+
export declare const base: string;
|
|
5
|
+
export declare const rates: Record<string, number>;
|
|
6
|
+
export declare const fetchedAt: string | null;
|
|
7
|
+
export declare class UnknownCurrencyError extends Error {
|
|
8
|
+
readonly currency: string;
|
|
9
|
+
constructor(currency: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Derive a `from → to` rate from a snapshot keyed against a single base.
|
|
13
|
+
* The math: rate(from→to) = rate(base→to) / rate(base→from).
|
|
14
|
+
* Throws if either code isn't present in the snapshot.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRate(from: string, to: string, snap?: RateSnapshot): number;
|
|
17
|
+
export declare function convertCurrency(amount: number, from: string, to: string, snap?: RateSnapshot): number;
|
|
18
|
+
export interface FormatOptions {
|
|
19
|
+
/**
|
|
20
|
+
* BCP-47 locale for `Intl.NumberFormat`. Defaults to "en-US".
|
|
21
|
+
* Pass `null` to skip Intl and emit a plain `<amount> <code>` string.
|
|
22
|
+
*/
|
|
23
|
+
locale?: string | null;
|
|
24
|
+
/** Minimum decimal places. Defaults to 2. */
|
|
25
|
+
minimumFractionDigits?: number;
|
|
26
|
+
/** Maximum decimal places. Defaults to `minimumFractionDigits`. */
|
|
27
|
+
maximumFractionDigits?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Intl currency display style. Defaults to "symbol".
|
|
30
|
+
* Set to "code" to render e.g. `EUR 92.50`.
|
|
31
|
+
*/
|
|
32
|
+
currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
|
|
33
|
+
}
|
|
34
|
+
export declare function formatCurrency(amount: number, currency: string, opts?: FormatOptions): string;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,eAAO,MAAM,IAAI,EAAE,MAAsB,CAAC;AAC1C,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAkB,CAAC;AAC5D,eAAO,MAAM,SAAS,EAAE,MAAM,GAAG,IAAyB,CAAC;AAE3D,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM;CAK7B;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,YAAuB,GAAG,MAAM,CASvF;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,YAAuB,GAC5B,MAAM,CAER;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,6CAA6C;IAC7C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,mEAAmE;IACnE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;CAC/D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAqBjG"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import snapshot from "virtual:unirate";
|
|
2
|
+
export { snapshot };
|
|
3
|
+
export const base = snapshot.base;
|
|
4
|
+
export const rates = snapshot.rates;
|
|
5
|
+
export const fetchedAt = snapshot.fetchedAt;
|
|
6
|
+
export class UnknownCurrencyError extends Error {
|
|
7
|
+
currency;
|
|
8
|
+
constructor(currency) {
|
|
9
|
+
super(`Unknown currency in snapshot: ${currency}`);
|
|
10
|
+
this.name = "UnknownCurrencyError";
|
|
11
|
+
this.currency = currency;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Derive a `from → to` rate from a snapshot keyed against a single base.
|
|
16
|
+
* The math: rate(from→to) = rate(base→to) / rate(base→from).
|
|
17
|
+
* Throws if either code isn't present in the snapshot.
|
|
18
|
+
*/
|
|
19
|
+
export function getRate(from, to, snap = snapshot) {
|
|
20
|
+
const f = from.toUpperCase();
|
|
21
|
+
const t = to.toUpperCase();
|
|
22
|
+
if (f === t)
|
|
23
|
+
return 1;
|
|
24
|
+
const rf = snap.rates[f];
|
|
25
|
+
const rt = snap.rates[t];
|
|
26
|
+
if (rf === undefined)
|
|
27
|
+
throw new UnknownCurrencyError(f);
|
|
28
|
+
if (rt === undefined)
|
|
29
|
+
throw new UnknownCurrencyError(t);
|
|
30
|
+
return rt / rf;
|
|
31
|
+
}
|
|
32
|
+
export function convertCurrency(amount, from, to, snap = snapshot) {
|
|
33
|
+
return amount * getRate(from, to, snap);
|
|
34
|
+
}
|
|
35
|
+
export function formatCurrency(amount, currency, opts = {}) {
|
|
36
|
+
const code = currency.toUpperCase();
|
|
37
|
+
const min = opts.minimumFractionDigits ?? 2;
|
|
38
|
+
const max = opts.maximumFractionDigits ?? min;
|
|
39
|
+
if (opts.locale === null) {
|
|
40
|
+
return `${amount.toFixed(max)} ${code}`;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return new Intl.NumberFormat(opts.locale ?? "en-US", {
|
|
44
|
+
style: "currency",
|
|
45
|
+
currency: code,
|
|
46
|
+
currencyDisplay: opts.currencyDisplay ?? "symbol",
|
|
47
|
+
minimumFractionDigits: min,
|
|
48
|
+
maximumFractionDigits: max,
|
|
49
|
+
}).format(amount);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Intl throws on malformed args (bad locale tag, etc.) — unknown ISO
|
|
53
|
+
// codes do NOT throw; Intl prefixes them as literal codes. Fall back
|
|
54
|
+
// to `<amount> <code>` so the build never breaks.
|
|
55
|
+
return `${amount.toFixed(max)} ${code}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,MAAM,CAAC,MAAM,IAAI,GAAW,QAAQ,CAAC,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,KAAK,GAA2B,QAAQ,CAAC,KAAK,CAAC;AAC5D,MAAM,CAAC,MAAM,SAAS,GAAkB,QAAQ,CAAC,SAAS,CAAC;AAE3D,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,QAAQ,CAAS;IAC1B,YAAY,QAAgB;QAC1B,KAAK,CAAC,iCAAiC,QAAQ,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,EAAU,EAAE,OAAqB,QAAQ;IAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3B,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,EAAE,KAAK,SAAS;QAAE,MAAM,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,EAAE,KAAK,SAAS;QAAE,MAAM,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,IAAY,EACZ,EAAU,EACV,OAAqB,QAAQ;IAE7B,OAAO,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC;AAsBD,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,QAAgB,EAAE,OAAsB,EAAE;IACvF,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,IAAI,GAAG,CAAC;IAC9C,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE;YACnD,KAAK,EAAE,UAAU;YACjB,QAAQ,EAAE,IAAI;YACd,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,QAAQ;YACjD,qBAAqB,EAAE,GAAG;YAC1B,qBAAqB,EAAE,GAAG;SAC3B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,qEAAqE;QACrE,kDAAkD;QAClD,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RateSnapshot, UniRateIntegrationOptions } from "./types.js";
|
|
2
|
+
declare const EMPTY_SNAPSHOT: (base: string) => RateSnapshot;
|
|
3
|
+
export declare function fetchSnapshot(opts: Required<Pick<UniRateIntegrationOptions, "apiKey" | "baseCurrency" | "apiBaseUrl">> & Pick<UniRateIntegrationOptions, "currencies">, fetchImpl?: typeof fetch): Promise<RateSnapshot>;
|
|
4
|
+
export { EMPTY_SNAPSHOT };
|
|
5
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE1E,QAAA,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,YAIrC,CAAC;AAEH,wBAAsB,aAAa,CACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,cAAc,GAAG,YAAY,CAAC,CAAC,GACvF,IAAI,CAAC,yBAAyB,EAAE,YAAY,CAAC,EAC/C,SAAS,GAAE,OAAO,KAAwB,GACzC,OAAO,CAAC,YAAY,CAAC,CAQvB;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/snapshot.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { UniRateClient } from "./client.js";
|
|
2
|
+
const EMPTY_SNAPSHOT = (base) => ({
|
|
3
|
+
base,
|
|
4
|
+
rates: { [base]: 1 },
|
|
5
|
+
fetchedAt: null,
|
|
6
|
+
});
|
|
7
|
+
export async function fetchSnapshot(opts, fetchImpl = globalThis.fetch) {
|
|
8
|
+
const client = new UniRateClient({
|
|
9
|
+
apiKey: opts.apiKey,
|
|
10
|
+
baseUrl: opts.apiBaseUrl,
|
|
11
|
+
fetch: fetchImpl,
|
|
12
|
+
});
|
|
13
|
+
const { rates, fetchedAt } = await client.getRates(opts.baseCurrency, opts.currencies);
|
|
14
|
+
return { base: opts.baseCurrency, rates, fetchedAt };
|
|
15
|
+
}
|
|
16
|
+
export { EMPTY_SNAPSHOT };
|
|
17
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,cAAc,GAAG,CAAC,IAAY,EAAgB,EAAE,CAAC,CAAC;IACtD,IAAI;IACJ,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;IACpB,SAAS,EAAE,IAAI;CAChB,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAC+C,EAC/C,YAA0B,UAAU,CAAC,KAAK;IAE1C,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,CAAC,UAAU;QACxB,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;IACH,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACvF,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACvD,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface UniRateIntegrationOptions {
|
|
2
|
+
/** UniRate API key. Required. Read from `UNIRATE_API_KEY` env var if omitted. */
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Base currency the rate snapshot is keyed against. All build-time rates
|
|
6
|
+
* are stored as `base → other`. Cross-pair conversions are derived at
|
|
7
|
+
* runtime, so picking a single base keeps the snapshot small.
|
|
8
|
+
*/
|
|
9
|
+
baseCurrency?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Quote currencies to pre-fetch at build time. If omitted, all currencies
|
|
12
|
+
* UniRate supports are fetched (one /api/rates call, no `to` param).
|
|
13
|
+
* Pass an explicit list to shrink the bundled snapshot for static sites
|
|
14
|
+
* that only care about a handful of pairs.
|
|
15
|
+
*/
|
|
16
|
+
currencies?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Override the API host. Useful for the test mock and self-hosted setups.
|
|
19
|
+
* Defaults to `https://api.unirateapi.com`.
|
|
20
|
+
*/
|
|
21
|
+
apiBaseUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* If `true`, build errors when the snapshot fetch fails (default).
|
|
24
|
+
* If `false`, the integration logs a warning and ships an empty snapshot
|
|
25
|
+
* — components fall back to displaying the source amount unchanged so
|
|
26
|
+
* the site still builds.
|
|
27
|
+
*/
|
|
28
|
+
failOnFetchError?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* If set, also include the snapshot's fetch timestamp in the virtual
|
|
31
|
+
* module so consumers can render a "rates as of …" footer.
|
|
32
|
+
* Defaults to `true`.
|
|
33
|
+
*/
|
|
34
|
+
includeFetchedAt?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface RateSnapshot {
|
|
37
|
+
base: string;
|
|
38
|
+
rates: Record<string, number>;
|
|
39
|
+
fetchedAt: string | null;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RateSnapshot } from "./types.js";
|
|
2
|
+
declare const VIRTUAL_ID = "virtual:unirate";
|
|
3
|
+
declare const RESOLVED_ID: string;
|
|
4
|
+
export interface VitePluginShape {
|
|
5
|
+
name: string;
|
|
6
|
+
resolveId(id: string): string | null;
|
|
7
|
+
load(id: string): string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Vite plugin that exposes the build-time UniRate snapshot as
|
|
11
|
+
* `import { snapshot, base, rates, fetchedAt } from "virtual:unirate"`.
|
|
12
|
+
*
|
|
13
|
+
* Returned as a plain object rather than imported from `vite` so unit tests
|
|
14
|
+
* can drive it without spinning a full Vite server. Astro accepts any value
|
|
15
|
+
* matching Vite's Plugin shape via `updateConfig({ vite: { plugins: [...] } })`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function unirateVitePlugin(snapshot: RateSnapshot): VitePluginShape;
|
|
18
|
+
export { VIRTUAL_ID, RESOLVED_ID };
|
|
19
|
+
//# sourceMappingURL=vite-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,QAAA,MAAM,UAAU,oBAAoB,CAAC;AACrC,QAAA,MAAM,WAAW,QAAoB,CAAC;AAEtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACrC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,eAAe,CAmBzE;AAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const VIRTUAL_ID = "virtual:unirate";
|
|
2
|
+
const RESOLVED_ID = "\0" + VIRTUAL_ID;
|
|
3
|
+
/**
|
|
4
|
+
* Vite plugin that exposes the build-time UniRate snapshot as
|
|
5
|
+
* `import { snapshot, base, rates, fetchedAt } from "virtual:unirate"`.
|
|
6
|
+
*
|
|
7
|
+
* Returned as a plain object rather than imported from `vite` so unit tests
|
|
8
|
+
* can drive it without spinning a full Vite server. Astro accepts any value
|
|
9
|
+
* matching Vite's Plugin shape via `updateConfig({ vite: { plugins: [...] } })`.
|
|
10
|
+
*/
|
|
11
|
+
export function unirateVitePlugin(snapshot) {
|
|
12
|
+
const payload = JSON.stringify(snapshot);
|
|
13
|
+
return {
|
|
14
|
+
name: "unirate:virtual-snapshot",
|
|
15
|
+
resolveId(id) {
|
|
16
|
+
return id === VIRTUAL_ID ? RESOLVED_ID : null;
|
|
17
|
+
},
|
|
18
|
+
load(id) {
|
|
19
|
+
if (id !== RESOLVED_ID)
|
|
20
|
+
return null;
|
|
21
|
+
return [
|
|
22
|
+
`const snapshot = Object.freeze(${payload});`,
|
|
23
|
+
`export default snapshot;`,
|
|
24
|
+
`export const snapshot_ = snapshot;`,
|
|
25
|
+
`export const base = snapshot.base;`,
|
|
26
|
+
`export const rates = snapshot.rates;`,
|
|
27
|
+
`export const fetchedAt = snapshot.fetchedAt;`,
|
|
28
|
+
].join("\n");
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export { VIRTUAL_ID, RESOLVED_ID };
|
|
33
|
+
//# sourceMappingURL=vite-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAG,iBAAiB,CAAC;AACrC,MAAM,WAAW,GAAG,IAAI,GAAG,UAAU,CAAC;AAQtC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAsB;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,SAAS,CAAC,EAAU;YAClB,OAAO,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,EAAU;YACb,IAAI,EAAE,KAAK,WAAW;gBAAE,OAAO,IAAI,CAAC;YACpC,OAAO;gBACL,kCAAkC,OAAO,IAAI;gBAC7C,0BAA0B;gBAC1B,oCAAoC;gBACpC,oCAAoC;gBACpC,sCAAsC;gBACtC,8CAA8C;aAC/C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unirate/astro",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Astro integration for UniRate — build-time currency exchange rate snapshots, <Currency /> and <Rate /> components, and runtime conversion helpers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "UniRate (https://unirateapi.com)",
|
|
8
|
+
"homepage": "https://github.com/UniRate-API/astro-unirate#readme",
|
|
9
|
+
"bugs": "https://github.com/UniRate-API/astro-unirate/issues",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/UniRate-API/astro-unirate.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"astro",
|
|
16
|
+
"astro-integration",
|
|
17
|
+
"astro-component",
|
|
18
|
+
"withastro",
|
|
19
|
+
"unirate",
|
|
20
|
+
"currency",
|
|
21
|
+
"exchange-rates",
|
|
22
|
+
"forex",
|
|
23
|
+
"money",
|
|
24
|
+
"fx"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"components",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./runtime": {
|
|
38
|
+
"types": "./dist/runtime/index.d.ts",
|
|
39
|
+
"import": "./dist/runtime/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./components/Currency.astro": "./components/Currency.astro",
|
|
42
|
+
"./components/Rate.astro": "./components/Rate.astro",
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.build.json && node ./scripts/copy-components.mjs",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"prepublishOnly": "npm run build"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"astro": ">=4.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.11.0",
|
|
57
|
+
"astro": "^5.0.0",
|
|
58
|
+
"typescript": "^5.4.0",
|
|
59
|
+
"vitest": "^2.1.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.17.0"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public",
|
|
66
|
+
"provenance": true
|
|
67
|
+
}
|
|
68
|
+
}
|