mongoose-currency-convert 0.1.5 → 0.2.5
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/README.md +272 -114
- package/dist/index.d.mts +107 -0
- package/dist/index.d.ts +103 -6
- package/dist/index.js +566 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +577 -0
- package/dist/index.mjs.map +1 -0
- package/dist/utils/cache.d.mts +15 -0
- package/dist/utils/cache.d.ts +6 -1
- package/dist/utils/cache.js +48 -32
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/cache.mjs +51 -0
- package/dist/utils/cache.mjs.map +1 -0
- package/dist/validate.d.mts +3 -0
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +216 -0
- package/dist/validate.js.map +1 -0
- package/dist/validate.mjs +214 -0
- package/dist/validate.mjs.map +1 -0
- package/package.json +34 -61
- package/dist/index.cjs +0 -126
- package/dist/types.d.ts +0 -31
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/utils/cache.cjs +0 -37
- package/dist/utils/helpers.d.ts +0 -5
- package/dist/utils/helpers.js +0 -142
- package/dist/utils/helpers.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
# mongoose-currency-convert
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/mongoose-currency-convert)
|
|
4
|
-
[](https://github.com/maku85/mongoose-currency-convert/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
A lightweight Mongoose plugin for automatic currency conversion at save time
|
|
7
|
+
A lightweight Mongoose plugin for automatic currency conversion at save and update time — flexible, extensible, and service-agnostic.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **Automatic currency conversion**
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
11
|
+
- **Automatic currency conversion** on `save`, `updateOne`, `updateMany`, and `findOneAndUpdate`
|
|
12
|
+
- **Parallel rate fetching** with optional concurrency limit
|
|
13
|
+
- **Same-currency short-circuit** — no external call when source and target currency are equal
|
|
14
|
+
- **Customizable exchange rate logic** via a user-provided `getRate` function
|
|
15
|
+
- **Support for nested paths** in documents
|
|
16
|
+
- **Pluggable rounding function** (default: round to 2 decimal places)
|
|
17
|
+
- **Optional in-memory cache** with active TTL eviction, or bring your own (Redis, Memcached, …)
|
|
18
|
+
- **Fallback rate** when `getRate` fails or returns an invalid value
|
|
19
|
+
- **Error handling and rollback** on conversion failure (`onError`, `rollbackOnError`)
|
|
20
|
+
- **Success callback** for audit logging and monitoring (`onSuccess`)
|
|
21
|
+
- **Rate bounds validation** to reject out-of-range rates from the provider
|
|
22
|
+
- **Per-operation skip** via `$locals` or query option
|
|
23
|
+
- **Full ISO 4217 currency code validation** (170+ codes), with public `isValidCurrencyCode` utility
|
|
24
|
+
- **ESM and CommonJS** compatible, fully typed
|
|
19
25
|
|
|
20
26
|
## Installation
|
|
21
27
|
|
|
@@ -25,7 +31,7 @@ npm install mongoose-currency-convert
|
|
|
25
31
|
pnpm add mongoose-currency-convert
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
##
|
|
34
|
+
## Quick start
|
|
29
35
|
|
|
30
36
|
```ts
|
|
31
37
|
import mongoose, { Schema } from 'mongoose';
|
|
@@ -37,7 +43,7 @@ const ProductSchema = new Schema({
|
|
|
37
43
|
currency: String,
|
|
38
44
|
date: Date,
|
|
39
45
|
},
|
|
40
|
-
|
|
46
|
+
priceEur: {
|
|
41
47
|
amount: Number,
|
|
42
48
|
currency: String,
|
|
43
49
|
date: Date,
|
|
@@ -47,68 +53,101 @@ const ProductSchema = new Schema({
|
|
|
47
53
|
ProductSchema.plugin(currencyConversionPlugin, {
|
|
48
54
|
fields: [
|
|
49
55
|
{
|
|
50
|
-
sourcePath: 'price.amount',
|
|
51
|
-
currencyPath: 'price.currency',
|
|
52
|
-
datePath: 'price.date',
|
|
53
|
-
targetPath: '
|
|
54
|
-
toCurrency: 'EUR',
|
|
56
|
+
sourcePath: 'price.amount', // path of the amount to convert
|
|
57
|
+
currencyPath: 'price.currency', // path of the source currency code
|
|
58
|
+
datePath: 'price.date', // (optional) path of the reference date
|
|
59
|
+
targetPath: 'priceEur', // where to write the converted result
|
|
60
|
+
toCurrency: 'EUR', // target currency code
|
|
55
61
|
},
|
|
56
62
|
],
|
|
57
63
|
getRate: async (from, to, date) => {
|
|
58
|
-
//
|
|
59
|
-
return 0.85; //
|
|
64
|
+
// Fetch the exchange rate from any service you prefer
|
|
65
|
+
return 0.85; // example: 1 USD = 0.85 EUR
|
|
60
66
|
},
|
|
61
|
-
// Optional: custom rounding function
|
|
62
|
-
round: (value) => Math.round(value * 100) / 100,
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
const Product = mongoose.model('Product', ProductSchema);
|
|
70
|
+
|
|
71
|
+
// Conversion happens automatically on save and updates
|
|
72
|
+
const p = await new Product({ price: { amount: 100, currency: 'USD' } }).save();
|
|
73
|
+
// p.priceEur → { amount: 85, currency: 'EUR', date: <Date> }
|
|
66
74
|
```
|
|
67
75
|
|
|
68
|
-
## Supported
|
|
76
|
+
## Supported operations
|
|
77
|
+
|
|
78
|
+
Currency conversion is applied automatically for:
|
|
79
|
+
|
|
80
|
+
| Operation | Notes |
|
|
81
|
+
|-----------|-------|
|
|
82
|
+
| `save` | Applies on initial insert and subsequent saves |
|
|
83
|
+
| `updateOne` | Handles both `$set` and plain update objects |
|
|
84
|
+
| `updateMany` | Same behaviour as `updateOne` |
|
|
85
|
+
| `findOneAndUpdate` | Handles both `$set` and plain update objects |
|
|
86
|
+
|
|
87
|
+
`$setOnInsert` fields inside upsert operations are also converted.
|
|
88
|
+
|
|
89
|
+
## Plugin options
|
|
90
|
+
|
|
91
|
+
All options are defined in the `CurrencyPluginOptions` interface and can be imported from `mongoose-currency-convert/types`.
|
|
92
|
+
|
|
93
|
+
### Required
|
|
94
|
+
|
|
95
|
+
| Option | Type | Description |
|
|
96
|
+
|--------|------|-------------|
|
|
97
|
+
| `fields` | `CurrencyFieldConfig[]` | Array of field mappings (see below) |
|
|
98
|
+
| `getRate` | `(from, to, date?) => Promise<number>` | Returns the exchange rate for a currency pair |
|
|
99
|
+
|
|
100
|
+
### `CurrencyFieldConfig`
|
|
101
|
+
|
|
102
|
+
| Property | Type | Required | Description |
|
|
103
|
+
|----------|------|----------|-------------|
|
|
104
|
+
| `sourcePath` | `string` | ✓ | Dot-notation path of the amount to convert |
|
|
105
|
+
| `currencyPath` | `string` | ✓ | Dot-notation path of the source currency code |
|
|
106
|
+
| `targetPath` | `string` | ✓ | Dot-notation path where the result is written |
|
|
107
|
+
| `toCurrency` | `string` | ✓ | ISO 4217 target currency code |
|
|
108
|
+
| `datePath` | `string` | | Dot-notation path of the reference date for the rate |
|
|
69
109
|
|
|
70
|
-
|
|
71
|
-
- `updateOne`
|
|
72
|
-
- `findOneAndUpdate`
|
|
110
|
+
The target path must point to a schema object with `amount`, `currency`, and `date` fields.
|
|
73
111
|
|
|
74
|
-
|
|
112
|
+
### Optional
|
|
75
113
|
|
|
76
|
-
|
|
114
|
+
| Option | Type | Default | Description |
|
|
115
|
+
|--------|------|---------|-------------|
|
|
116
|
+
| `round` | `(value: number) => number` | Round to 2 decimals | Custom rounding function |
|
|
117
|
+
| `cache` | `CurrencyRateCache<number>` | — | Cache for exchange rates |
|
|
118
|
+
| `allowedCurrencyCodes` | `string[]` | Full ISO 4217 list | Restrict accepted currency codes |
|
|
119
|
+
| `fallbackRate` | `number` (≥ 0) | — | Rate to use when `getRate` throws or returns an invalid value |
|
|
120
|
+
| `onError` | `(ctx: CurrencyPluginErrorContext) => void` | `console.error` | Called on rate fetch failure |
|
|
121
|
+
| `onSuccess` | `(ctx: CurrencyPluginSuccessContext) => void` | — | Called after each successful conversion |
|
|
122
|
+
| `rollbackOnError` | `boolean` | `false` | If `true`, clears already-converted fields when a field fails |
|
|
123
|
+
| `dateTransform` | `(date: Date) => Date` | — | Transform the conversion date before passing it to `getRate` |
|
|
124
|
+
| `concurrency` | `number` | `5` | Max parallel `getRate` calls per document |
|
|
125
|
+
| `rateValidation` | `{ min?: number; max?: number }` | — | Reject rates outside this range (throws, triggers `onError`/fallback). `min` defaults to `0` when not specified |
|
|
77
126
|
|
|
78
|
-
|
|
79
|
-
- `sourcePath`: Path to the source amount (supports nested and array paths).
|
|
80
|
-
- `currencyPath`: Path to the source currency.
|
|
81
|
-
- `datePath` (optional): Path to the date for conversion.
|
|
82
|
-
- `targetPath`: Path to write the converted value.
|
|
83
|
-
- `toCurrency`: Target currency code.
|
|
84
|
-
- `getRate(from: string, to: string, date: Date)`: Async function returning the exchange rate.
|
|
85
|
-
- `round(value: number)`: Optional rounding function.
|
|
86
|
-
- `allowedCurrencyCodes`: Optional array of allowed currency codes (ISO 4217).
|
|
87
|
-
- `onError(ctx)`: Optional callback called when a conversion error occurs. Receives an object with details.
|
|
88
|
-
- `fallbackRate`: Optional fallback rate if the conversion rate is invalid or missing.
|
|
89
|
-
- `rollbackOnError`: Optional boolean. If true, revokes all conversions made if there's an error.
|
|
90
|
-
- `cache`: Optional cache object for exchange rates (see `SimpleCache`).
|
|
91
|
-
- `dateTransform(date: Date)`: Optional function to transform the conversion date.
|
|
127
|
+
## Caching
|
|
92
128
|
|
|
93
|
-
|
|
129
|
+
### Built-in `SimpleCache`
|
|
94
130
|
|
|
95
|
-
|
|
131
|
+
An in-memory TTL cache with active eviction is included. The cache key is `{from}_{to}_{YYYY-MM-DD}`.
|
|
96
132
|
|
|
97
|
-
### Using the Internal SimpleCache
|
|
98
133
|
```ts
|
|
99
134
|
import { SimpleCache } from 'mongoose-currency-convert/cache';
|
|
100
135
|
|
|
101
|
-
const cache = new SimpleCache
|
|
136
|
+
const cache = new SimpleCache(60); // TTL in minutes, default 60
|
|
102
137
|
|
|
103
138
|
ProductSchema.plugin(currencyConversionPlugin, {
|
|
104
|
-
fields: [/*
|
|
105
|
-
getRate: async (from, to, date) => { /*
|
|
139
|
+
fields: [/* … */],
|
|
140
|
+
getRate: async (from, to, date) => { /* … */ },
|
|
106
141
|
cache,
|
|
107
142
|
});
|
|
143
|
+
|
|
144
|
+
// When shutting down, clear the sweep timer to avoid process hang
|
|
145
|
+
cache.destroy();
|
|
108
146
|
```
|
|
109
147
|
|
|
110
|
-
###
|
|
111
|
-
|
|
148
|
+
### Custom cache (e.g. Redis)
|
|
149
|
+
|
|
150
|
+
Implement the `CurrencyRateCache` interface:
|
|
112
151
|
|
|
113
152
|
```ts
|
|
114
153
|
import { createClient } from 'redis';
|
|
@@ -116,111 +155,230 @@ import type { CurrencyRateCache } from 'mongoose-currency-convert/types';
|
|
|
116
155
|
|
|
117
156
|
class RedisCache implements CurrencyRateCache<number> {
|
|
118
157
|
private client = createClient({ url: 'redis://localhost:6379' });
|
|
158
|
+
|
|
119
159
|
constructor() { this.client.connect(); }
|
|
120
160
|
|
|
121
161
|
async get(key: string): Promise<number | undefined> {
|
|
122
|
-
const
|
|
123
|
-
return
|
|
162
|
+
const v = await this.client.get(key);
|
|
163
|
+
return v != null ? Number(v) : undefined;
|
|
124
164
|
}
|
|
165
|
+
|
|
125
166
|
async set(key: string, value: number): Promise<void> {
|
|
126
|
-
await this.client.set(key, value
|
|
167
|
+
await this.client.set(key, String(value), { EX: 86400 });
|
|
127
168
|
}
|
|
128
169
|
}
|
|
129
170
|
|
|
130
|
-
const cache = new RedisCache();
|
|
131
|
-
|
|
132
171
|
ProductSchema.plugin(currencyConversionPlugin, {
|
|
133
|
-
fields: [/*
|
|
134
|
-
getRate: async (from, to, date) => { /*
|
|
135
|
-
cache,
|
|
172
|
+
fields: [/* … */],
|
|
173
|
+
getRate: async (from, to, date) => { /* … */ },
|
|
174
|
+
cache: new RedisCache(),
|
|
136
175
|
});
|
|
137
176
|
```
|
|
138
177
|
|
|
139
|
-
## Error
|
|
140
|
-
|
|
141
|
-
You can handle conversion errors using the `onError` callback:
|
|
178
|
+
## Error handling
|
|
142
179
|
|
|
143
180
|
```ts
|
|
144
181
|
ProductSchema.plugin(currencyConversionPlugin, {
|
|
145
|
-
fields: [/*
|
|
146
|
-
getRate:
|
|
182
|
+
fields: [/* … */],
|
|
183
|
+
getRate: myGetRate,
|
|
184
|
+
|
|
185
|
+
// Called when a conversion fails; receives details about the failure
|
|
147
186
|
onError: (ctx) => {
|
|
148
|
-
console.error(
|
|
187
|
+
console.error(`Conversion failed: ${ctx.fromCurrency} → ${ctx.toCurrency}`, ctx.error);
|
|
149
188
|
},
|
|
189
|
+
|
|
190
|
+
// Use a static rate when getRate fails or returns an invalid value
|
|
191
|
+
fallbackRate: 1,
|
|
192
|
+
|
|
193
|
+
// Undo already-converted fields if any field in the document fails
|
|
150
194
|
rollbackOnError: true,
|
|
151
195
|
});
|
|
152
196
|
```
|
|
153
197
|
|
|
154
|
-
|
|
198
|
+
The `CurrencyPluginErrorContext` object contains:
|
|
155
199
|
|
|
156
|
-
|
|
157
|
-
|
|
200
|
+
| Property | Type | Description |
|
|
201
|
+
|----------|------|-------------|
|
|
202
|
+
| `field` | `string` | `sourcePath` of the failing field |
|
|
203
|
+
| `fromCurrency` | `string` | Source currency code |
|
|
204
|
+
| `toCurrency` | `string` | Target currency code |
|
|
205
|
+
| `date` | `Date` | Conversion date used |
|
|
206
|
+
| `error` | `unknown` | The original error |
|
|
207
|
+
|
|
208
|
+
## Multiple fields
|
|
209
|
+
|
|
210
|
+
Multiple fields are fetched **in parallel** and written sequentially. Use `concurrency` to cap simultaneous `getRate` calls (useful with rate-limited providers):
|
|
158
211
|
|
|
159
212
|
```ts
|
|
160
|
-
|
|
213
|
+
ProductSchema.plugin(currencyConversionPlugin, {
|
|
214
|
+
fields: [
|
|
215
|
+
{ sourcePath: 'price.amount', currencyPath: 'price.currency', targetPath: 'priceEur', toCurrency: 'EUR' },
|
|
216
|
+
{ sourcePath: 'price.amount', currencyPath: 'price.currency', targetPath: 'priceGbp', toCurrency: 'GBP' },
|
|
217
|
+
],
|
|
218
|
+
getRate: myGetRate,
|
|
219
|
+
concurrency: 3, // at most 3 getRate calls at a time
|
|
220
|
+
});
|
|
161
221
|
```
|
|
162
222
|
|
|
163
|
-
##
|
|
223
|
+
## Success callback
|
|
164
224
|
|
|
165
|
-
|
|
225
|
+
Use `onSuccess` for audit logging, monitoring, or debugging in production:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
ProductSchema.plugin(currencyConversionPlugin, {
|
|
229
|
+
fields: [/* … */],
|
|
230
|
+
getRate: myGetRate,
|
|
231
|
+
onSuccess: (ctx) => {
|
|
232
|
+
console.log(
|
|
233
|
+
`Converted ${ctx.originalAmount} ${ctx.fromCurrency} → ${ctx.convertedAmount} ${ctx.toCurrency} at rate ${ctx.rate}`,
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The `CurrencyPluginSuccessContext` object contains:
|
|
240
|
+
|
|
241
|
+
| Property | Type | Description |
|
|
242
|
+
|----------|------|-------------|
|
|
243
|
+
| `field` | `string` | `sourcePath` of the converted field |
|
|
244
|
+
| `fromCurrency` | `string` | Source currency code |
|
|
245
|
+
| `toCurrency` | `string` | Target currency code |
|
|
246
|
+
| `originalAmount` | `number` | Amount before conversion |
|
|
247
|
+
| `convertedAmount` | `number` | Amount after conversion |
|
|
248
|
+
| `rate` | `number` | Exchange rate used |
|
|
249
|
+
| `date` | `Date` | Conversion date used |
|
|
250
|
+
| `usedFallback` | `boolean` | `true` when `fallbackRate` was used instead of the rate from `getRate` |
|
|
251
|
+
|
|
252
|
+
## Rate bounds validation
|
|
253
|
+
|
|
254
|
+
Protect against buggy providers that return nonsensical rates:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
ProductSchema.plugin(currencyConversionPlugin, {
|
|
258
|
+
fields: [/* … */],
|
|
259
|
+
getRate: myGetRate,
|
|
260
|
+
rateValidation: { min: 0.0001, max: 10000 },
|
|
261
|
+
onError: (ctx) => console.error('Rate out of bounds:', ctx.error),
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Rates outside the range are treated as errors and follow the same `onError` / `fallbackRate` / `rollbackOnError` flow.
|
|
266
|
+
|
|
267
|
+
## Skipping conversion
|
|
268
|
+
|
|
269
|
+
To skip conversion for a single operation (e.g. during data migration or seeding):
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
// On save — use $locals
|
|
273
|
+
const doc = new Product({ price: { amount: 100, currency: 'USD' } });
|
|
274
|
+
doc.$locals.skipCurrencyConversion = true;
|
|
275
|
+
await doc.save();
|
|
276
|
+
|
|
277
|
+
// On update queries — pass as a query option
|
|
278
|
+
await Product.updateOne(
|
|
279
|
+
{ _id: id },
|
|
280
|
+
{ $set: { 'price.amount': 200 } },
|
|
281
|
+
{ skipCurrencyConversion: true },
|
|
282
|
+
);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Currency code validation
|
|
286
|
+
|
|
287
|
+
`isValidCurrencyCode` is exported as a standalone utility, useful for validating user input in forms or API handlers:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { isValidCurrencyCode } from 'mongoose-currency-convert/validate';
|
|
291
|
+
|
|
292
|
+
isValidCurrencyCode('EUR'); // true
|
|
293
|
+
isValidCurrencyCode('XYZ'); // false
|
|
294
|
+
isValidCurrencyCode('usd'); // true — normalised to uppercase internally
|
|
295
|
+
|
|
296
|
+
// Restrict to a custom list
|
|
297
|
+
isValidCurrencyCode('EUR', ['USD', 'EUR', 'GBP']); // true
|
|
298
|
+
isValidCurrencyCode('JPY', ['USD', 'EUR', 'GBP']); // false
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## TypeScript
|
|
302
|
+
|
|
303
|
+
All types are exported:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import type {
|
|
307
|
+
CurrencyPluginOptions,
|
|
308
|
+
CurrencyFieldConfig,
|
|
309
|
+
CurrencyPluginErrorContext,
|
|
310
|
+
CurrencyPluginSuccessContext,
|
|
311
|
+
CurrencyRateCache,
|
|
312
|
+
GetRateFn,
|
|
313
|
+
} from 'mongoose-currency-convert/types';
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Extension plugins
|
|
317
|
+
|
|
318
|
+
Ready-made `getRate` providers:
|
|
166
319
|
|
|
167
320
|
| Package | Description |
|
|
168
321
|
|---------|-------------|
|
|
169
|
-
| `mongoose-currency-
|
|
170
|
-
|
|
171
|
-
###
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
getRate: createMyGetRate(),
|
|
191
|
-
cache,
|
|
192
|
-
});
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
> **Note:** The cache is managed by the base plugin. Extension plugins should not read or write to the cache directly; they only fetch rates from external services.
|
|
196
|
-
|
|
197
|
-
## Limitations & Known Issues
|
|
198
|
-
|
|
199
|
-
- Only `save`, `updateOne`, and `findOneAndUpdate` are supported for automatic conversion.
|
|
200
|
-
- Array paths are supported, but deep array updates (e.g. `items.$.price`) may require manual handling.
|
|
201
|
-
- Only `$set` and direct field updates are converted in update operations; other MongoDB operators (`$inc`, `$push`, etc.) are not automatically converted.
|
|
202
|
-
- The list of supported currency codes is static (ISO 4217).
|
|
203
|
-
- If you use custom cache, ensure it implements the required interface.
|
|
322
|
+
| [`mongoose-currency-convert-ecb`](https://www.npmjs.com/package/mongoose-currency-convert-ecb) | European Central Bank exchange rates |
|
|
323
|
+
|
|
324
|
+
### Creating your own provider
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import type { GetRateFn } from 'mongoose-currency-convert/types';
|
|
328
|
+
|
|
329
|
+
export function createMyProvider(): GetRateFn {
|
|
330
|
+
return async (from, to, date) => {
|
|
331
|
+
// fetch rate from your service
|
|
332
|
+
return rate;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
> The cache is managed by the base plugin. Providers only need to return a rate — they should not interact with the cache directly.
|
|
338
|
+
|
|
339
|
+
## Limitations
|
|
340
|
+
|
|
341
|
+
- Only `$set` and plain update objects are converted in update operations. Other MongoDB operators (`$inc`, `$push`, etc.) are not automatically converted.
|
|
342
|
+
- Deep array update paths (e.g. `items.$.price`) may require manual handling.
|
|
204
343
|
|
|
205
344
|
## Compatibility
|
|
206
345
|
|
|
207
|
-
- Node.js
|
|
208
|
-
- Mongoose
|
|
209
|
-
- TypeScript
|
|
346
|
+
- Node.js ≥ 18
|
|
347
|
+
- Mongoose ≥ 7
|
|
348
|
+
- TypeScript ≥ 5 (optional)
|
|
210
349
|
|
|
211
350
|
## Contributing
|
|
212
351
|
|
|
213
|
-
Contributions are welcome
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
352
|
+
Contributions are welcome. Please open an issue first for major changes.
|
|
353
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
354
|
+
|
|
355
|
+
## Releasing
|
|
356
|
+
|
|
357
|
+
Releases are cut manually using the `scripts/release.sh` script, which runs typecheck, tests, and build before bumping the version, pushing the tag, and publishing to npm.
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# patch release (0.2.4 → 0.2.5)
|
|
361
|
+
pnpm release
|
|
362
|
+
|
|
363
|
+
# minor release (0.2.4 → 0.3.0)
|
|
364
|
+
pnpm release:minor
|
|
365
|
+
|
|
366
|
+
# major release (0.2.4 → 1.0.0)
|
|
367
|
+
pnpm release:major
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Each command runs the following steps in order:
|
|
218
371
|
|
|
219
|
-
|
|
372
|
+
1. `pnpm typecheck`
|
|
373
|
+
2. `pnpm test`
|
|
374
|
+
3. `pnpm build`
|
|
375
|
+
4. `npm version <type>` — bumps `package.json` and creates a git tag
|
|
376
|
+
5. `git push --follow-tags`
|
|
377
|
+
6. `npm publish --access public`
|
|
220
378
|
|
|
221
379
|
## Changelog
|
|
222
380
|
|
|
223
|
-
See [CHANGELOG.md](CHANGELOG.md)
|
|
381
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
224
382
|
|
|
225
383
|
## License
|
|
226
384
|
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Schema } from 'mongoose';
|
|
2
|
+
|
|
3
|
+
interface CurrencyFieldConfig {
|
|
4
|
+
sourcePath: string;
|
|
5
|
+
currencyPath: string;
|
|
6
|
+
datePath?: string;
|
|
7
|
+
targetPath: string;
|
|
8
|
+
toCurrency: string;
|
|
9
|
+
}
|
|
10
|
+
interface CurrencyPluginOptions {
|
|
11
|
+
fields: CurrencyFieldConfig[];
|
|
12
|
+
getRate: (from: string, to: string, date?: Date) => Promise<number>;
|
|
13
|
+
round?: (value: number) => number;
|
|
14
|
+
cache?: CurrencyRateCache<number>;
|
|
15
|
+
allowedCurrencyCodes?: string[];
|
|
16
|
+
onError?: (ctx: CurrencyPluginErrorContext) => Promise<void> | void;
|
|
17
|
+
onSuccess?: (ctx: CurrencyPluginSuccessContext) => Promise<void> | void;
|
|
18
|
+
fallbackRate?: number;
|
|
19
|
+
rollbackOnError?: boolean;
|
|
20
|
+
dateTransform?: (date: Date) => Date;
|
|
21
|
+
/** Maximum number of concurrent `getRate` calls. Defaults to `5`. */
|
|
22
|
+
concurrency?: number;
|
|
23
|
+
rateValidation?: {
|
|
24
|
+
min?: number;
|
|
25
|
+
max?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface CurrencyPluginSuccessContext {
|
|
29
|
+
field: string;
|
|
30
|
+
fromCurrency: string;
|
|
31
|
+
toCurrency: string;
|
|
32
|
+
originalAmount: number;
|
|
33
|
+
convertedAmount: number;
|
|
34
|
+
rate: number;
|
|
35
|
+
date: Date;
|
|
36
|
+
/** `true` when `fallbackRate` was used instead of the rate returned by `getRate` */
|
|
37
|
+
usedFallback: boolean;
|
|
38
|
+
}
|
|
39
|
+
interface CurrencyPluginErrorContext {
|
|
40
|
+
field: string;
|
|
41
|
+
fromCurrency: string;
|
|
42
|
+
toCurrency: string;
|
|
43
|
+
date: Date;
|
|
44
|
+
error: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Interface for a cache backend used to store exchange rates.
|
|
48
|
+
*
|
|
49
|
+
* Both synchronous and asynchronous implementations are accepted — the plugin
|
|
50
|
+
* always uses `await` internally, so a sync method returning `T` directly works
|
|
51
|
+
* exactly like one returning `Promise<T>`.
|
|
52
|
+
*
|
|
53
|
+
* @example Sync implementation (e.g. in-memory Map):
|
|
54
|
+
* ```ts
|
|
55
|
+
* class MyCache implements CurrencyRateCache<number> {
|
|
56
|
+
* private store = new Map<string, number>();
|
|
57
|
+
* get(key: string) { return this.store.get(key); }
|
|
58
|
+
* set(key: string, value: number) { this.store.set(key, value); }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Async implementation (e.g. Redis):
|
|
63
|
+
* ```ts
|
|
64
|
+
* class RedisCache implements CurrencyRateCache<number> {
|
|
65
|
+
* async get(key: string) { const v = await redis.get(key); return v ? Number(v) : undefined; }
|
|
66
|
+
* async set(key: string, value: number) { await redis.set(key, String(value)); }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
interface CurrencyRateCache<T = unknown> {
|
|
71
|
+
get(key: string): Promise<T | undefined> | T | undefined;
|
|
72
|
+
set(key: string, value: T): Promise<void> | void;
|
|
73
|
+
delete?(key: string): Promise<void> | void;
|
|
74
|
+
clear?(): Promise<void> | void;
|
|
75
|
+
}
|
|
76
|
+
declare module "mongoose" {
|
|
77
|
+
interface QueryOptions {
|
|
78
|
+
/** Set to `true` to skip currency conversion for this query. */
|
|
79
|
+
skipCurrencyConversion?: boolean;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mongoose plugin that automatically converts currency fields on save and update operations.
|
|
85
|
+
*
|
|
86
|
+
* ## Error handling policy
|
|
87
|
+
*
|
|
88
|
+
* The plugin uses a three-tier strategy depending on when and where an error occurs:
|
|
89
|
+
*
|
|
90
|
+
* 1. **Initialization errors** (`throw`): Missing or invalid required options (`fields`, `getRate`)
|
|
91
|
+
* cause an immediate `Error` to be thrown when the plugin is registered. These are
|
|
92
|
+
* programmer errors and must be fixed before the application starts.
|
|
93
|
+
*
|
|
94
|
+
* 2. **Field validation warnings** (`console.warn` + skip): Invalid field configurations
|
|
95
|
+
* detected at conversion time (missing `targetPath`, invalid currency code, non-numeric
|
|
96
|
+
* `amount`, invalid date) are logged as warnings and the field is silently skipped.
|
|
97
|
+
* The document is still saved with the remaining conversions applied.
|
|
98
|
+
*
|
|
99
|
+
* 3. **Rate fetch errors** (`onError` callback or `console.error`): Errors thrown by `getRate`
|
|
100
|
+
* or invalid rates returned by it are passed to the `onError` callback if provided,
|
|
101
|
+
* otherwise logged via `console.error`. If `fallbackRate` is set it is used instead.
|
|
102
|
+
* If `rollbackOnError` is `true`, all previously converted fields in that document are
|
|
103
|
+
* reverted before the save continues.
|
|
104
|
+
*/
|
|
105
|
+
declare function currencyConversionPlugin(schema: Schema, options: CurrencyPluginOptions): void;
|
|
106
|
+
|
|
107
|
+
export { currencyConversionPlugin };
|