@uplab/vendure-plugin-exchange-rates 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.
Files changed (94) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +369 -0
  3. package/dist/api/admin.resolver.d.ts +16 -0
  4. package/dist/api/admin.resolver.d.ts.map +1 -0
  5. package/dist/api/admin.resolver.js +66 -0
  6. package/dist/api/admin.resolver.js.map +1 -0
  7. package/dist/api/api-extensions.d.ts +6 -0
  8. package/dist/api/api-extensions.d.ts.map +1 -0
  9. package/dist/api/api-extensions.js +57 -0
  10. package/dist/api/api-extensions.js.map +1 -0
  11. package/dist/api/shop.resolver.d.ts +15 -0
  12. package/dist/api/shop.resolver.d.ts.map +1 -0
  13. package/dist/api/shop.resolver.js +63 -0
  14. package/dist/api/shop.resolver.js.map +1 -0
  15. package/dist/constants.d.ts +10 -0
  16. package/dist/constants.d.ts.map +1 -0
  17. package/dist/constants.js +13 -0
  18. package/dist/constants.js.map +1 -0
  19. package/dist/dashboard/format-rate.ts +8 -0
  20. package/dist/dashboard/graphql.ts +74 -0
  21. package/dist/dashboard/i18n/en.po +109 -0
  22. package/dist/dashboard/i18n/uk.po +109 -0
  23. package/dist/dashboard/index.tsx +7 -0
  24. package/dist/dashboard/routes/currency-exchange-rate-detail.tsx +194 -0
  25. package/dist/dashboard/routes/currency-exchange-rate-list.tsx +105 -0
  26. package/dist/derive-rates.d.ts +13 -0
  27. package/dist/derive-rates.d.ts.map +1 -0
  28. package/dist/derive-rates.js +40 -0
  29. package/dist/derive-rates.js.map +1 -0
  30. package/dist/effective-rate.d.ts +8 -0
  31. package/dist/effective-rate.d.ts.map +1 -0
  32. package/dist/effective-rate.js +13 -0
  33. package/dist/effective-rate.js.map +1 -0
  34. package/dist/entities/currency-exchange-rate.entity.d.ts +13 -0
  35. package/dist/entities/currency-exchange-rate.entity.d.ts.map +1 -0
  36. package/dist/entities/currency-exchange-rate.entity.js +55 -0
  37. package/dist/entities/currency-exchange-rate.entity.js.map +1 -0
  38. package/dist/events/currency-exchange-rate.event.d.ts +18 -0
  39. package/dist/events/currency-exchange-rate.event.d.ts.map +1 -0
  40. package/dist/events/currency-exchange-rate.event.js +18 -0
  41. package/dist/events/currency-exchange-rate.event.js.map +1 -0
  42. package/dist/exchange-rate-source-error.d.ts +16 -0
  43. package/dist/exchange-rate-source-error.d.ts.map +1 -0
  44. package/dist/exchange-rate-source-error.js +18 -0
  45. package/dist/exchange-rate-source-error.js.map +1 -0
  46. package/dist/exchange-rate-sync.task.d.ts +3 -0
  47. package/dist/exchange-rate-sync.task.d.ts.map +1 -0
  48. package/dist/exchange-rate-sync.task.js +18 -0
  49. package/dist/exchange-rate-sync.task.js.map +1 -0
  50. package/dist/exchange-rates.plugin.d.ts +24 -0
  51. package/dist/exchange-rates.plugin.d.ts.map +1 -0
  52. package/dist/exchange-rates.plugin.js +101 -0
  53. package/dist/exchange-rates.plugin.js.map +1 -0
  54. package/dist/index.d.ts +15 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +31 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/services/currency-exchange-rate-sync.service.d.ts +30 -0
  59. package/dist/services/currency-exchange-rate-sync.service.d.ts.map +1 -0
  60. package/dist/services/currency-exchange-rate-sync.service.js +145 -0
  61. package/dist/services/currency-exchange-rate-sync.service.js.map +1 -0
  62. package/dist/services/currency-exchange-rate.service.d.ts +30 -0
  63. package/dist/services/currency-exchange-rate.service.d.ts.map +1 -0
  64. package/dist/services/currency-exchange-rate.service.js +93 -0
  65. package/dist/services/currency-exchange-rate.service.js.map +1 -0
  66. package/dist/sources/ecb-exchange-rate-source.d.ts +20 -0
  67. package/dist/sources/ecb-exchange-rate-source.d.ts.map +1 -0
  68. package/dist/sources/ecb-exchange-rate-source.js +33 -0
  69. package/dist/sources/ecb-exchange-rate-source.js.map +1 -0
  70. package/dist/sources/fetch-body.d.ts +9 -0
  71. package/dist/sources/fetch-body.d.ts.map +1 -0
  72. package/dist/sources/fetch-body.js +59 -0
  73. package/dist/sources/fetch-body.js.map +1 -0
  74. package/dist/sources/frankfurter-exchange-rate-source.d.ts +23 -0
  75. package/dist/sources/frankfurter-exchange-rate-source.d.ts.map +1 -0
  76. package/dist/sources/frankfurter-exchange-rate-source.js +35 -0
  77. package/dist/sources/frankfurter-exchange-rate-source.js.map +1 -0
  78. package/dist/sources/monobank-exchange-rate-source.d.ts +29 -0
  79. package/dist/sources/monobank-exchange-rate-source.d.ts.map +1 -0
  80. package/dist/sources/monobank-exchange-rate-source.js +46 -0
  81. package/dist/sources/monobank-exchange-rate-source.js.map +1 -0
  82. package/dist/sources/nbu-exchange-rate-source.d.ts +20 -0
  83. package/dist/sources/nbu-exchange-rate-source.d.ts.map +1 -0
  84. package/dist/sources/nbu-exchange-rate-source.js +25 -0
  85. package/dist/sources/nbu-exchange-rate-source.js.map +1 -0
  86. package/dist/sources/static-exchange-rate-source.d.ts +20 -0
  87. package/dist/sources/static-exchange-rate-source.d.ts.map +1 -0
  88. package/dist/sources/static-exchange-rate-source.js +23 -0
  89. package/dist/sources/static-exchange-rate-source.js.map +1 -0
  90. package/dist/types.d.ts +64 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +3 -0
  93. package/dist/types.js.map +1 -0
  94. package/package.json +96 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @uplab/vendure-plugin-exchange-rates
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#30](https://github.com/Uplab/uplab-vendure-plugins/pull/30) [`d132726`](https://github.com/Uplab/uplab-vendure-plugins/commit/d132726b2fecf429684ae6e558db6d9b754c03b2) Thanks [@brmk](https://github.com/brmk)! - Initial release: exchange rates in your shop's base currency from a pluggable source (the ECB, Frankfurter, Monobank, the NBU, fixed rates or your own), cross rates derived automatically, refreshed on a schedule, served on the Shop API and overridable per currency in the dashboard.
package/README.md ADDED
@@ -0,0 +1,369 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Uplab/uplab-vendure-plugins/main/packages/vendure-plugin-exchange-rates/assets/icon.svg" alt="" width="96" height="96">
3
+ </p>
4
+
5
+ # @uplab/vendure-plugin-exchange-rates
6
+
7
+ [![npm](https://img.shields.io/npm/v/@uplab/vendure-plugin-exchange-rates.svg)](https://www.npmjs.com/package/@uplab/vendure-plugin-exchange-rates)
8
+ [![Vendure](https://img.shields.io/badge/Vendure-%5E3.7.0-17c9ff.svg)](https://www.vendure.io/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Uplab/uplab-vendure-plugins/blob/main/LICENSE)
10
+
11
+ Exchange rates in your shop's base currency, for a Vendure shop that prices in one currency and shows or
12
+ charges in others. Rates come from a **source you choose** — the European Central Bank, Frankfurter,
13
+ Monobank, the National Bank of Ukraine, fixed rates or your own — are re-based onto your currency, refreshed
14
+ on a schedule, served on the Shop API, and can be overridden per currency in the dashboard.
15
+
16
+ The plugin stores and serves rates; it does not touch Vendure prices or orders — your storefront, feed or
17
+ payment code converts with them. Vendure's own per-currency variant prices are unaffected.
18
+
19
+ ![Exchange rates in the dashboard](https://raw.githubusercontent.com/Uplab/uplab-vendure-plugins/main/packages/vendure-plugin-exchange-rates/assets/screenshot-list.png)
20
+
21
+ ## Contents
22
+
23
+ [Install](#install) · [Use cases](#use-cases) · [Sources](#sources) · [Base currency](#base-currency) ·
24
+ [Options](#options) · [Dashboard](#dashboard) · [GraphQL API](#graphql-api) ·
25
+ [In your own code](#in-your-own-code) · [Event](#event) · [Database](#database) · [Limitations](#limitations)
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install @uplab/vendure-plugin-exchange-rates
31
+ # or
32
+ pnpm add @uplab/vendure-plugin-exchange-rates
33
+ ```
34
+
35
+ ```ts
36
+ import { DefaultSchedulerPlugin, VendureConfig } from '@vendure/core';
37
+ import { EcbExchangeRateSource, ExchangeRatesPlugin } from '@uplab/vendure-plugin-exchange-rates';
38
+
39
+ export const config: VendureConfig = {
40
+ // ...
41
+ plugins: [
42
+ DefaultSchedulerPlugin.init(), // runs the scheduled refresh
43
+ ExchangeRatesPlugin.init({ source: new EcbExchangeRateSource() }),
44
+ ],
45
+ };
46
+ ```
47
+
48
+ Rates are stored in the **default channel's currency**; see [Base currency](#base-currency) to pin another.
49
+
50
+ `@vendure/core`, `@nestjs/*` and `typeorm` are peers. The dashboard pages need `@vendure/dashboard` (an
51
+ optional peer): its Vite plugin discovers the extension — restart the dashboard dev server after
52
+ installing.
53
+
54
+ **First deploy.** Generate and run a migration for the new `currency_exchange_rate` table. On the API
55
+ server's first boot the plugin fills the empty table from the source (startup waits for it, up to the
56
+ source's `timeout`); if the source is unreachable then, the table stays empty until the next scheduled
57
+ refresh. Every currency arrives **disabled** — enable the ones you sell in under _Settings → Currency
58
+ exchange rates_. Until you do, the storefront sees no rates and stays in your base currency.
59
+
60
+ ## Use cases
61
+
62
+ **Show prices in the visitor's currency.** The storefront reads the enabled rates once:
63
+
64
+ ```graphql
65
+ query {
66
+ currencyExchangeRates {
67
+ items {
68
+ code
69
+ rate # base-currency units per one unit, e.g. a USD shop: EUR → 1.12
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ```ts
76
+ type Rate = { code: string; rate: number };
77
+
78
+ /** `rates` = the query's items, [] until an admin enables a currency. */
79
+ export function convertPrice(priceInMinorUnits: number, rates: Rate[], code: string): number | undefined {
80
+ const rate = rates.find((r) => r.code === code)?.rate;
81
+ // Vendure prices are minor units: base minor units ÷ rate = target minor units. undefined → show the base.
82
+ return rate ? Math.round(priceInMinorUnits / rate) : undefined;
83
+ }
84
+ ```
85
+
86
+ This assumes both currencies have 1/100 minor units. When they differ (JPY has none, KWD and BHD have
87
+ 1/1000), multiply the result by 10^(target digits − base digits).
88
+
89
+ **Charge in a foreign currency.** A `PaymentMethodHandler` gets the service in `init` and converts the
90
+ amount it is asked to collect:
91
+
92
+ ```ts
93
+ import { CurrencyCode, LanguageCode, PaymentMethodHandler } from '@vendure/core';
94
+ import { CurrencyExchangeRateService } from '@uplab/vendure-plugin-exchange-rates';
95
+
96
+ let rates: CurrencyExchangeRateService;
97
+
98
+ export const eurCardHandler = new PaymentMethodHandler({
99
+ code: 'eur-card',
100
+ description: [{ languageCode: LanguageCode.en, value: 'Card, billed in EUR' }],
101
+ args: {},
102
+ init: (injector) => {
103
+ rates = injector.get(CurrencyExchangeRateService);
104
+ },
105
+ createPayment: async (ctx, _order, amount) => {
106
+ const rate = await rates.getRate(ctx, CurrencyCode.EUR, { requireEnabled: true });
107
+ const amountInCents = rate ? Math.round(amount / rate) : undefined; // undefined → bill in the base
108
+ // …call your payment provider with amountInCents
109
+ return { amount, state: 'Authorized', metadata: { amountInCents } };
110
+ },
111
+ settlePayment: () => ({ success: true }),
112
+ });
113
+ ```
114
+
115
+ **Price a product feed in another currency** (Google Merchant, Meta, …) even if the storefront does not
116
+ offer it: `getRate(ctx, CurrencyCode.EUR, { requireEnabled: false })`.
117
+
118
+ **Pin a rate.** Turn on _Use custom rate_ for a currency: the storefront and your code use your number
119
+ until you turn it off, while the fetched rate keeps updating underneath.
120
+
121
+ **Fixed rates only.** `source: new StaticExchangeRateSource({ base: CurrencyCode.USD, rates: { EUR: 1.12 } })`.
122
+ Keep the sync on, so a number you change in config is picked up on the next refresh; pin any currency in
123
+ the meantime with _Use custom rate_.
124
+
125
+ ## Sources
126
+
127
+ | Source | Base | Rates |
128
+ | ------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------- |
129
+ | `EcbExchangeRateSource` | EUR | The ECB's euro reference rates: ~30 major currencies, every working day around 16:00 CET. Free, no key. |
130
+ | `FrankfurterExchangeRateSource` | EUR, or `base` | [Frankfurter](https://frankfurter.dev): the same ECB rates as JSON. Free, no key, can be self-hosted (`apiUrl`). |
131
+ | `StaticExchangeRateSource` | yours | The numbers you pass: `{ base, rates }`. In e2e tests, set `source.config` before a sync — no network. |
132
+ | `MonobankExchangeRateSource` | UAH | [Ukrainian](#ukrainian-sources) bank rates, buy/sell/mid. |
133
+ | `NbuExchangeRateSource` | UAH | [Ukrainian](#ukrainian-sources) official rates, ~40 currencies. |
134
+
135
+ The network sources take `apiUrl` and `timeout` (default 10 s). Whatever a source returns, only Vendure
136
+ `CurrencyCode`s with a positive finite rate are stored — metals, the SDR and garbage are dropped. If the
137
+ source fails or returns nothing usable, the stored rates stay as they are.
138
+
139
+ **Cross rates.** A source that quotes against another base is re-based onto yours: with the ECB and a USD
140
+ shop, `GBP = (EUR per GBP) / (EUR per USD)`, and EUR itself is added. This needs the source to quote your
141
+ base currency — the ECB does not quote UAH, for instance, so a UAH shop uses a Ukrainian source. Rates are
142
+ stored per unit with 8 decimals, so a weak currency against a strong base keeps few significant digits
143
+ (LBP in USD: 0.00001117); a rate that does not fit decimal(19, 8) is dropped.
144
+
145
+ ### Ukrainian sources
146
+
147
+ - **`MonobankExchangeRateSource`** — Monobank's public rates. `side: 'buy'` (default) is what the bank pays
148
+ you for the currency — the lower rate, so a UAH price converted with it never comes up short; or
149
+ `'sell'`, `'mid'`. Rate-limited; cached 5 minutes.
150
+ - **`NbuExchangeRateSource`** — the National Bank of Ukraine's official rate, set once per business day.
151
+
152
+ Both quote against UAH, so they suit a UAH shop. They also work for a shop in a currency they quote (USD,
153
+ EUR, …), but the ECB is the more natural choice there.
154
+
155
+ ### Your own source
156
+
157
+ ```ts
158
+ import { CurrencyCode } from '@vendure/core';
159
+ import {
160
+ ExchangeRateSource,
161
+ ExchangeRateSourceError,
162
+ ExchangeRateSourceResult,
163
+ } from '@uplab/vendure-plugin-exchange-rates';
164
+
165
+ type Observation = { d: string } & Record<string, { v: string } | string>;
166
+
167
+ /** Rates of the Bank of Canada: CAD per one unit. */
168
+ export class BankOfCanadaExchangeRateSource implements ExchangeRateSource {
169
+ readonly name = 'bank-of-canada';
170
+
171
+ async fetchRates(): Promise<ExchangeRateSourceResult> {
172
+ const res = await fetch('https://www.bankofcanada.ca/valet/observations/group/FX_RATES_DAILY/json?recent=1', {
173
+ signal: AbortSignal.timeout(10_000), // the API server waits for the first fetch on boot
174
+ });
175
+ if (!res.ok) throw new ExchangeRateSourceError(`HTTP ${res.status}`, { source: this.name, status: res.status });
176
+ const body = (await res.json()) as { observations?: Observation[] };
177
+ if (!body.observations?.length) throw new ExchangeRateSourceError('No observations', { source: this.name });
178
+ // `recent=1` is the last value of every series, discontinued ones included: keep the newest day.
179
+ const latest = body.observations.reduce((a, b) => (a.d > b.d ? a : b));
180
+ const quotes = Object.entries(latest)
181
+ .filter(([series]) => /^FX[A-Z]{3}CAD$/.test(series))
182
+ .map(([series, value]) => ({
183
+ currencyCode: series.slice(2, 5) as CurrencyCode,
184
+ rate: Number((value as { v: string }).v),
185
+ }));
186
+ return { base: CurrencyCode.CAD, quotes };
187
+ }
188
+ }
189
+ ```
190
+
191
+ A source returns its `base` and quotes `{ currencyCode, rate }`, `rate` being **base units per one unit**;
192
+ the plugin re-bases them onto the shop's currency. Throw on failure — never return an empty list to mean
193
+ "failed" — and give the request a timeout, since the API server waits for the first fetch on boot. If the
194
+ source needs services, implement `init(injector)` (and `destroy()`); the plugin calls `init` before the
195
+ first fetch.
196
+
197
+ ### Combining sources
198
+
199
+ When a source rate-limits or is down, fall back to another — the first one that answers wins:
200
+
201
+ ```ts
202
+ import { RequestContext } from '@vendure/core';
203
+ import {
204
+ EcbExchangeRateSource,
205
+ ExchangeRateSource,
206
+ ExchangeRateSourceResult,
207
+ FrankfurterExchangeRateSource,
208
+ } from '@uplab/vendure-plugin-exchange-rates';
209
+
210
+ export class FallbackExchangeRateSource implements ExchangeRateSource {
211
+ readonly name = 'fallback';
212
+ constructor(private readonly sources: ExchangeRateSource[]) {}
213
+
214
+ async fetchRates(ctx: RequestContext): Promise<ExchangeRateSourceResult> {
215
+ let lastError: unknown;
216
+ for (const source of this.sources) {
217
+ try {
218
+ return await source.fetchRates(ctx);
219
+ } catch (e) {
220
+ lastError = e;
221
+ }
222
+ }
223
+ throw lastError;
224
+ }
225
+ }
226
+
227
+ export const source = new FallbackExchangeRateSource([
228
+ new EcbExchangeRateSource(),
229
+ new FrankfurterExchangeRateSource(),
230
+ ]);
231
+ ```
232
+
233
+ To add a currency a source does not quote, merge the quotes of several sources **in the same base** (a
234
+ `StaticExchangeRateSource` last); when two quotes share a code, the last one wins. Forward `init`/`destroy`
235
+ if the inner sources need them.
236
+
237
+ ## Base currency
238
+
239
+ Rates are stored and served in one base currency: `baseCurrency` if you set it, otherwise the default
240
+ channel's `defaultCurrencyCode`, read on every sync and lookup.
241
+
242
+ ```ts
243
+ ExchangeRatesPlugin.init({ source: new MonobankExchangeRateSource(), baseCurrency: CurrencyCode.UAH });
244
+ ```
245
+
246
+ When the base changes, the Shop API and `getRate` return nothing until a sync re-bases the table. The API
247
+ server does that on boot, so changing the option needs nothing more; after changing the default channel's
248
+ currency, run the task (see [Options](#options)) or restart. The sync moves every currency to the new
249
+ base, keeps whether it is enabled, **drops its custom rate** (it was in the old base) and deletes the
250
+ currencies the new base does not cover.
251
+
252
+ ## Options
253
+
254
+ | Option | Type | Default |
255
+ | -------------- | --------------------------------------------------------- | --------------------------------- |
256
+ | `source` | `ExchangeRateSource` — required | — |
257
+ | `baseCurrency` | `CurrencyCode` | the default channel's currency |
258
+ | `sync` | `{ schedule?: ScheduledTaskConfig['schedule'] } \| false` | `{ schedule: '40 2-23/3 * * *' }` |
259
+
260
+ `sync` registers the scheduled task `currency-exchange-rate-updater` (`SYNC_TASK_ID`, every 3 hours). It
261
+ needs a scheduler plugin such as `DefaultSchedulerPlugin`, and runs in the worker — so the worker needs the
262
+ plugin in its config too. `sync: false` leaves it out; you can still call `CurrencyExchangeRateSyncService.syncRates(ctx)`.
263
+
264
+ To refresh now, run it under _System → Scheduled tasks_ (or
265
+ `runScheduledTask(id: "currency-exchange-rate-updater")`); its last run there tells you the sync is alive.
266
+ A sync moves a row's `updatedAt` only when its rate changes. A currency the source stops quoting keeps its
267
+ last rate; if it is enabled, every sync logs a warning.
268
+
269
+ ## Dashboard
270
+
271
+ _Settings → Currency exchange rates_ lists every stored currency with its rate and base, whether it is
272
+ enabled and whether a custom rate is in use. A currency's page shows the fetched rate and lets you enable
273
+ it and set a custom rate.
274
+
275
+ ![Editing a currency](https://raw.githubusercontent.com/Uplab/uplab-vendure-plugins/main/packages/vendure-plugin-exchange-rates/assets/screenshot-detail.png)
276
+
277
+ Rates are shop settings: listing needs `ReadSettings`, editing `UpdateSettings` — no extra role setup.
278
+
279
+ ## GraphQL API
280
+
281
+ **Shop** (public)
282
+
283
+ ```graphql
284
+ currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList!
285
+ # items: { id, code, baseCurrency, rate, enabled, createdAt, updatedAt }
286
+ # enabled currencies in the current base only, whatever the filter
287
+ ```
288
+
289
+ `rate` is the custom rate while one is in use, otherwise the fetched one.
290
+
291
+ **Admin**
292
+
293
+ ```graphql
294
+ currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList! # ReadSettings
295
+ currencyExchangeRate(id: ID!): CurrencyExchangeRate # ReadSettings
296
+ updateCurrencyExchangeRate(input: { id, enabled, useCustomRate, customRate }): CurrencyExchangeRate! # UpdateSettings
297
+ ```
298
+
299
+ In the Admin API `rate` is always the fetched rate; `useCustomRate` and `customRate` are separate fields.
300
+ Turning on `useCustomRate` without a positive `customRate` is rejected.
301
+
302
+ ## In your own code
303
+
304
+ | Export | Use |
305
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
306
+ | `CurrencyExchangeRateService` | `getRate(ctx, code, { requireEnabled })` → base units per unit or `undefined`; `getBaseCurrency(ctx)`; `findAll`, `findOne`, `update` |
307
+ | `CurrencyExchangeRateSyncService` | `syncRates(ctx)` — refresh now |
308
+ | `effectiveRate(row)` | The rate to convert with for a row you loaded yourself, or `undefined` if it is not usable |
309
+
310
+ `requireEnabled` is your policy: `true` for anything the customer is billed in, `false` for internal uses.
311
+
312
+ ## Event
313
+
314
+ `CurrencyExchangeRateEvent` is published once the transaction has committed: `type: 'synced'` when a sync
315
+ changed anything, with every rate now stored in the base currency, or `'updated'` with the one rate an
316
+ admin saved. Use it to drop caches
317
+ that embed the rates:
318
+
319
+ ```ts
320
+ import { OnApplicationBootstrap } from '@nestjs/common';
321
+ import { EventBus, PluginCommonModule, VendurePlugin } from '@vendure/core';
322
+ import { CurrencyExchangeRateEvent } from '@uplab/vendure-plugin-exchange-rates';
323
+
324
+ @VendurePlugin({ imports: [PluginCommonModule] })
325
+ export class RatesCachePlugin implements OnApplicationBootstrap {
326
+ constructor(private readonly eventBus: EventBus) {}
327
+
328
+ onApplicationBootstrap() {
329
+ this.eventBus.ofType(CurrencyExchangeRateEvent).subscribe(() => {
330
+ // drop whatever caches the rates, e.g. a cached Shop API response
331
+ });
332
+ }
333
+ }
334
+ ```
335
+
336
+ ## Database
337
+
338
+ One table, `currency_exchange_rate`:
339
+
340
+ | Column | Type |
341
+ | --------------- | ------------------------------------------- |
342
+ | `code` | varchar, unique |
343
+ | `baseCurrency` | varchar — the currency `rate` is counted in |
344
+ | `rate` | decimal(19, 8) — the fetched one |
345
+ | `enabled` | boolean, default `false` |
346
+ | `useCustomRate` | boolean, default `false` |
347
+ | `customRate` | decimal(19, 8), nullable |
348
+
349
+ A sync updates `rate` and inserts new currencies, in one transaction; on a [base change](#base-currency) it
350
+ also rewrites `baseCurrency`, clears custom rates and deletes the currencies the new base does not cover.
351
+
352
+ ## Limitations
353
+
354
+ - **One base currency** — channels that sell in different currencies share the rates of the default
355
+ channel's (or `baseCurrency`).
356
+ - **Rates are global** — one table, not per channel: anyone with `UpdateSettings` in any channel can
357
+ change them.
358
+ - **Filtering and sorting on `rate`** use the fetched rate, while the returned `rate` is the effective one.
359
+ - **Dashboard 3.7.0–3.7.3 in dev mode** can render a blank page with two or more dashboard extensions
360
+ (for example together with `@uplab/vendure-plugin-unified-slug`). It is a Vendure bug, fixed in 3.7.4
361
+ ([vendure#5459](https://github.com/vendure-ecommerce/vendure/pull/5459)); production builds are fine.
362
+
363
+ ## Changelog
364
+
365
+ See [CHANGELOG.md](./CHANGELOG.md).
366
+
367
+ ## License
368
+
369
+ MIT
@@ -0,0 +1,16 @@
1
+ import { ID, ListQueryOptions, PaginatedList, RequestContext } from '@vendure/core';
2
+ import { CurrencyExchangeRate } from '../entities/currency-exchange-rate.entity';
3
+ import { CurrencyExchangeRateService } from '../services/currency-exchange-rate.service';
4
+ import { MutationUpdateCurrencyExchangeRateArgs } from '../types';
5
+ export declare class CurrencyExchangeRateAdminResolver {
6
+ private currencyExchangeRateService;
7
+ constructor(currencyExchangeRateService: CurrencyExchangeRateService);
8
+ currencyExchangeRates(ctx: RequestContext, args: {
9
+ options: ListQueryOptions<CurrencyExchangeRate>;
10
+ }): Promise<PaginatedList<CurrencyExchangeRate>>;
11
+ currencyExchangeRate(ctx: RequestContext, args: {
12
+ id: ID;
13
+ }): Promise<CurrencyExchangeRate | null>;
14
+ updateCurrencyExchangeRate(ctx: RequestContext, args: MutationUpdateCurrencyExchangeRateArgs): Promise<CurrencyExchangeRate>;
15
+ }
16
+ //# sourceMappingURL=admin.resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.resolver.d.ts","sourceRoot":"","sources":["../../src/api/admin.resolver.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,EAAE,EACF,gBAAgB,EAChB,aAAa,EAEb,cAAc,EAEf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,sCAAsC,EAAE,MAAM,UAAU,CAAC;AAElE,qBACa,iCAAiC;IAChC,OAAO,CAAC,2BAA2B;gBAA3B,2BAA2B,EAAE,2BAA2B;IAI5E,qBAAqB,CACZ,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE;QAAE,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;KAAE,GAChE,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAM/C,oBAAoB,CAAQ,GAAG,EAAE,cAAc,EAAU,IAAI,EAAE;QAAE,EAAE,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAOhH,0BAA0B,CACjB,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE,sCAAsC,GACnD,OAAO,CAAC,oBAAoB,CAAC;CAGjC"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CurrencyExchangeRateAdminResolver = void 0;
16
+ const graphql_1 = require("@nestjs/graphql");
17
+ const core_1 = require("@vendure/core");
18
+ const currency_exchange_rate_service_1 = require("../services/currency-exchange-rate.service");
19
+ let CurrencyExchangeRateAdminResolver = class CurrencyExchangeRateAdminResolver {
20
+ constructor(currencyExchangeRateService) {
21
+ this.currencyExchangeRateService = currencyExchangeRateService;
22
+ }
23
+ currencyExchangeRates(ctx, args) {
24
+ return this.currencyExchangeRateService.findAll(ctx, args.options);
25
+ }
26
+ currencyExchangeRate(ctx, args) {
27
+ return this.currencyExchangeRateService.findOne(ctx, args.id);
28
+ }
29
+ updateCurrencyExchangeRate(ctx, args) {
30
+ return this.currencyExchangeRateService.update(ctx, args.input);
31
+ }
32
+ };
33
+ exports.CurrencyExchangeRateAdminResolver = CurrencyExchangeRateAdminResolver;
34
+ __decorate([
35
+ (0, graphql_1.Query)(),
36
+ (0, core_1.Allow)(core_1.Permission.ReadSettings),
37
+ __param(0, (0, core_1.Ctx)()),
38
+ __param(1, (0, graphql_1.Args)()),
39
+ __metadata("design:type", Function),
40
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
41
+ __metadata("design:returntype", Promise)
42
+ ], CurrencyExchangeRateAdminResolver.prototype, "currencyExchangeRates", null);
43
+ __decorate([
44
+ (0, graphql_1.Query)(),
45
+ (0, core_1.Allow)(core_1.Permission.ReadSettings),
46
+ __param(0, (0, core_1.Ctx)()),
47
+ __param(1, (0, graphql_1.Args)()),
48
+ __metadata("design:type", Function),
49
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
50
+ __metadata("design:returntype", Promise)
51
+ ], CurrencyExchangeRateAdminResolver.prototype, "currencyExchangeRate", null);
52
+ __decorate([
53
+ (0, core_1.Transaction)(),
54
+ (0, graphql_1.Mutation)(),
55
+ (0, core_1.Allow)(core_1.Permission.UpdateSettings),
56
+ __param(0, (0, core_1.Ctx)()),
57
+ __param(1, (0, graphql_1.Args)()),
58
+ __metadata("design:type", Function),
59
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
60
+ __metadata("design:returntype", Promise)
61
+ ], CurrencyExchangeRateAdminResolver.prototype, "updateCurrencyExchangeRate", null);
62
+ exports.CurrencyExchangeRateAdminResolver = CurrencyExchangeRateAdminResolver = __decorate([
63
+ (0, graphql_1.Resolver)(),
64
+ __metadata("design:paramtypes", [currency_exchange_rate_service_1.CurrencyExchangeRateService])
65
+ ], CurrencyExchangeRateAdminResolver);
66
+ //# sourceMappingURL=admin.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.resolver.js","sourceRoot":"","sources":["../../src/api/admin.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAkE;AAClE,wCASuB;AAEvB,+FAAyF;AAIlF,IAAM,iCAAiC,GAAvC,MAAM,iCAAiC;IAC5C,YAAoB,2BAAwD;QAAxD,gCAA2B,GAA3B,2BAA2B,CAA6B;IAAG,CAAC;IAIhF,qBAAqB,CACZ,GAAmB,EAClB,IAAyD;QAEjE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IAID,oBAAoB,CAAQ,GAAmB,EAAU,IAAgB;QACvE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAKD,0BAA0B,CACjB,GAAmB,EAClB,IAA4C;QAEpD,OAAO,IAAI,CAAC,2BAA2B,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA3BY,8EAAiC;AAK5C;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,YAAY,CAAC;IAE5B,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;8EAI3B;AAID;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,YAAY,CAAC;IACT,WAAA,IAAA,UAAG,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAI,GAAE,CAAA;;qCAAvB,qBAAc;;6EAE9C;AAKD;IAHC,IAAA,kBAAW,GAAE;IACb,IAAA,kBAAQ,GAAE;IACV,IAAA,YAAK,EAAC,iBAAU,CAAC,cAAc,CAAC;IAE9B,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;mFAI3B;4CA1BU,iCAAiC;IAD7C,IAAA,kBAAQ,GAAE;qCAEwC,4DAA2B;GADjE,iCAAiC,CA2B7C"}
@@ -0,0 +1,6 @@
1
+ import gql from 'graphql-tag';
2
+ type Document = ReturnType<typeof gql>;
3
+ export declare const shopApiExtensions: Document;
4
+ export declare const adminApiExtensions: Document;
5
+ export {};
6
+ //# sourceMappingURL=api-extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-extensions.d.ts","sourceRoot":"","sources":["../../src/api/api-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AA0BvC,eAAO,MAAM,iBAAiB,EAAE,QAE/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,QAsBhC,CAAC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.adminApiExtensions = exports.shopApiExtensions = void 0;
7
+ const graphql_tag_1 = __importDefault(require("graphql-tag"));
8
+ const commonApiExtensions = (0, graphql_tag_1.default) `
9
+ type CurrencyExchangeRate implements Node {
10
+ id: ID!
11
+ createdAt: DateTime!
12
+ updatedAt: DateTime!
13
+ code: String!
14
+ "The currency rate is expressed in: rate = units of it per one unit of code."
15
+ baseCurrency: String!
16
+ rate: Float!
17
+ enabled: Boolean!
18
+ }
19
+
20
+ type CurrencyExchangeRateList implements PaginatedList {
21
+ items: [CurrencyExchangeRate!]!
22
+ totalItems: Int!
23
+ }
24
+
25
+ extend type Query {
26
+ currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList!
27
+ }
28
+
29
+ input CurrencyExchangeRateListOptions
30
+ `;
31
+ exports.shopApiExtensions = (0, graphql_tag_1.default) `
32
+ ${commonApiExtensions}
33
+ `;
34
+ exports.adminApiExtensions = (0, graphql_tag_1.default) `
35
+ ${commonApiExtensions}
36
+
37
+ extend type CurrencyExchangeRate {
38
+ useCustomRate: Boolean!
39
+ customRate: Float
40
+ }
41
+
42
+ extend type Query {
43
+ currencyExchangeRate(id: ID!): CurrencyExchangeRate
44
+ }
45
+
46
+ extend type Mutation {
47
+ updateCurrencyExchangeRate(input: UpdateCurrencyExchangeRateInput!): CurrencyExchangeRate!
48
+ }
49
+
50
+ input UpdateCurrencyExchangeRateInput {
51
+ id: ID!
52
+ enabled: Boolean!
53
+ useCustomRate: Boolean!
54
+ customRate: Float
55
+ }
56
+ `;
57
+ //# sourceMappingURL=api-extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-extensions.js","sourceRoot":"","sources":["../../src/api/api-extensions.ts"],"names":[],"mappings":";;;;;;AAAA,8DAA8B;AAI9B,MAAM,mBAAmB,GAAa,IAAA,qBAAG,EAAA;;;;;;;;;;;;;;;;;;;;;;CAsBxC,CAAC;AAEW,QAAA,iBAAiB,GAAa,IAAA,qBAAG,EAAA;IAC1C,mBAAmB;CACtB,CAAC;AAEW,QAAA,kBAAkB,GAAa,IAAA,qBAAG,EAAA;IAC3C,mBAAmB;;;;;;;;;;;;;;;;;;;;;CAqBtB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { ListQueryOptions, PaginatedList, RequestContext } from '@vendure/core';
2
+ import { CurrencyExchangeRate } from '../entities/currency-exchange-rate.entity';
3
+ import { CurrencyExchangeRateService } from '../services/currency-exchange-rate.service';
4
+ export declare class CurrencyExchangeRateShopResolver {
5
+ private currencyExchangeRateService;
6
+ constructor(currencyExchangeRateService: CurrencyExchangeRateService);
7
+ /** Only the currencies an admin enabled, in the current base: the storefront offers nothing else. */
8
+ currencyExchangeRates(ctx: RequestContext, args: {
9
+ options?: ListQueryOptions<CurrencyExchangeRate>;
10
+ }): Promise<PaginatedList<CurrencyExchangeRate>>;
11
+ }
12
+ export declare class CurrencyExchangeRateShopFieldResolver {
13
+ rate(currencyExchangeRate: CurrencyExchangeRate): number;
14
+ }
15
+ //# sourceMappingURL=shop.resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop.resolver.d.ts","sourceRoot":"","sources":["../../src/api/shop.resolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,gBAAgB,EAAE,aAAa,EAAc,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AAEzF,qBACa,gCAAgC;IAC/B,OAAO,CAAC,2BAA2B;gBAA3B,2BAA2B,EAAE,2BAA2B;IAE5E,qGAAqG;IAG/F,qBAAqB,CAClB,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;KAAE,GACjE,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;CAMhD;AAED,qBACa,qCAAqC;IAEhD,IAAI,CAAC,oBAAoB,EAAE,oBAAoB;CAIhD"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CurrencyExchangeRateShopFieldResolver = exports.CurrencyExchangeRateShopResolver = void 0;
16
+ const graphql_1 = require("@nestjs/graphql");
17
+ const core_1 = require("@vendure/core");
18
+ const currency_exchange_rate_entity_1 = require("../entities/currency-exchange-rate.entity");
19
+ const effective_rate_1 = require("../effective-rate");
20
+ const currency_exchange_rate_service_1 = require("../services/currency-exchange-rate.service");
21
+ let CurrencyExchangeRateShopResolver = class CurrencyExchangeRateShopResolver {
22
+ constructor(currencyExchangeRateService) {
23
+ this.currencyExchangeRateService = currencyExchangeRateService;
24
+ }
25
+ /** Only the currencies an admin enabled, in the current base: the storefront offers nothing else. */
26
+ async currencyExchangeRates(ctx, args) {
27
+ return this.currencyExchangeRateService.findAll(ctx, args.options, {
28
+ enabled: true,
29
+ baseCurrency: await this.currencyExchangeRateService.getBaseCurrency(ctx),
30
+ });
31
+ }
32
+ };
33
+ exports.CurrencyExchangeRateShopResolver = CurrencyExchangeRateShopResolver;
34
+ __decorate([
35
+ (0, graphql_1.Query)(),
36
+ (0, core_1.Allow)(core_1.Permission.Public),
37
+ __param(0, (0, core_1.Ctx)()),
38
+ __param(1, (0, graphql_1.Args)()),
39
+ __metadata("design:type", Function),
40
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
41
+ __metadata("design:returntype", Promise)
42
+ ], CurrencyExchangeRateShopResolver.prototype, "currencyExchangeRates", null);
43
+ exports.CurrencyExchangeRateShopResolver = CurrencyExchangeRateShopResolver = __decorate([
44
+ (0, graphql_1.Resolver)(),
45
+ __metadata("design:paramtypes", [currency_exchange_rate_service_1.CurrencyExchangeRateService])
46
+ ], CurrencyExchangeRateShopResolver);
47
+ let CurrencyExchangeRateShopFieldResolver = class CurrencyExchangeRateShopFieldResolver {
48
+ rate(currencyExchangeRate) {
49
+ // The Admin API forbids a custom rate that is on but empty, so this falls back only for old rows.
50
+ return (0, effective_rate_1.effectiveRate)(currencyExchangeRate) ?? currencyExchangeRate.rate;
51
+ }
52
+ };
53
+ exports.CurrencyExchangeRateShopFieldResolver = CurrencyExchangeRateShopFieldResolver;
54
+ __decorate([
55
+ (0, graphql_1.ResolveField)(),
56
+ __metadata("design:type", Function),
57
+ __metadata("design:paramtypes", [currency_exchange_rate_entity_1.CurrencyExchangeRate]),
58
+ __metadata("design:returntype", void 0)
59
+ ], CurrencyExchangeRateShopFieldResolver.prototype, "rate", null);
60
+ exports.CurrencyExchangeRateShopFieldResolver = CurrencyExchangeRateShopFieldResolver = __decorate([
61
+ (0, graphql_1.Resolver)('CurrencyExchangeRate')
62
+ ], CurrencyExchangeRateShopFieldResolver);
63
+ //# sourceMappingURL=shop.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop.resolver.js","sourceRoot":"","sources":["../../src/api/shop.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAsE;AACtE,wCAAwG;AACxG,6FAAiF;AACjF,sDAAkD;AAClD,+FAAyF;AAGlF,IAAM,gCAAgC,GAAtC,MAAM,gCAAgC;IAC3C,YAAoB,2BAAwD;QAAxD,gCAA2B,GAA3B,2BAA2B,CAA6B;IAAG,CAAC;IAEhF,qGAAqG;IAG/F,AAAN,KAAK,CAAC,qBAAqB,CAClB,GAAmB,EAClB,IAA0D;QAElE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE;YACjE,OAAO,EAAE,IAAI;YACb,YAAY,EAAE,MAAM,IAAI,CAAC,2BAA2B,CAAC,eAAe,CAAC,GAAG,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAfY,4EAAgC;AAMrC;IAFL,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,MAAM,CAAC;IAEtB,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;6EAO3B;2CAdU,gCAAgC;IAD5C,IAAA,kBAAQ,GAAE;qCAEwC,4DAA2B;GADjE,gCAAgC,CAe5C;AAGM,IAAM,qCAAqC,GAA3C,MAAM,qCAAqC;IAEhD,IAAI,CAAC,oBAA0C;QAC7C,kGAAkG;QAClG,OAAO,IAAA,8BAAa,EAAC,oBAAoB,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC;IAC1E,CAAC;CACF,CAAA;AANY,sFAAqC;AAEhD;IADC,IAAA,sBAAY,GAAE;;qCACY,oDAAoB;;iEAG9C;gDALU,qCAAqC;IADjD,IAAA,kBAAQ,EAAC,sBAAsB,CAAC;GACpB,qCAAqC,CAMjD"}
@@ -0,0 +1,10 @@
1
+ export declare const EXCHANGE_RATES_PLUGIN_OPTIONS: unique symbol;
2
+ export declare const loggerCtx = "ExchangeRatesPlugin";
3
+ /** Every 3 hours at minute 40. */
4
+ export declare const DEFAULT_SYNC_SCHEDULE = "40 2-23/3 * * *";
5
+ export declare const SYNC_TASK_ID = "currency-exchange-rate-updater";
6
+ /** How long a source request may take before it is aborted, in milliseconds. */
7
+ export declare const DEFAULT_SOURCE_TIMEOUT = 10000;
8
+ /** decimal(19, 8) holds 11 integer digits. */
9
+ export declare const MAX_RATE = 100000000000;
10
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,eAA0C,CAAC;AACrF,eAAO,MAAM,SAAS,wBAAwB,CAAC;AAE/C,kCAAkC;AAClC,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD,eAAO,MAAM,YAAY,mCAAmC,CAAC;AAE7D,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,8CAA8C;AAC9C,eAAO,MAAM,QAAQ,eAAO,CAAC"}