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 CHANGED
@@ -1,21 +1,27 @@
1
1
  # mongoose-currency-convert
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/mongoose-currency-convert.svg)](https://www.npmjs.com/package/mongoose-currency-convert)
4
- [![Release](https://github.com/maku85/mongoose-currency-convert/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/maku85/mongoose-currency-convert/actions/workflows/release.yml)
4
+ [![CI](https://github.com/maku85/mongoose-currency-convert/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/maku85/mongoose-currency-convert/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- A lightweight Mongoose plugin for automatic currency conversion at save time - flexible, extensible, and service-agnostic.
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** for specified fields when saving documents or updating them.
12
- - **Customizable exchange rate logic** via a user-provided function or extension plugin.
13
- - **Support for nested paths** in documents.
14
- - **Pluggable rounding function** (default: round to 2 decimals).
15
- - **Simple in-memory cache** for exchange rates (optional).
16
- - **Error handling and rollback** on conversion failure.
17
- - **Fully tested** with high code coverage.
18
- - **Compatible with ESM and CommonJS**.
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
- ## Usage
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
- priceConversion: {
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: 'priceConversion',
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
- // Implement your logic to fetch the exchange rate
59
- return 0.85; // Example: USD EUR
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 Mongoose Operations
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
- - `save`
71
- - `updateOne`
72
- - `findOneAndUpdate`
110
+ The target path must point to a schema object with `amount`, `currency`, and `date` fields.
73
111
 
74
- Currency conversion is automatically applied when saving or updating documents using these operations.
112
+ ### Optional
75
113
 
76
- ## Plugin Options
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
- - `fields`: Array of field mapping objects:
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
- ## Caching Exchange Rates
129
+ ### Built-in `SimpleCache`
94
130
 
95
- You can use the built-in in-memory cache (`SimpleCache` in `src/utils/cache.ts`) or provide your own cache implementation (e.g. backed by Redis, Memcached, etc.).
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<number>();
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
- ### Using an External Cache (e.g. Redis)
111
- You can implement the `CurrencyRateCache` interface to use any external service:
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 value = await this.client.get(key);
123
- return value ? Number(value) : undefined;
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.toString(), { EX: 86400 }); // 1 day expiry
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 Handling Example
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: async () => { throw new Error('rate error'); },
182
+ fields: [/* */],
183
+ getRate: myGetRate,
184
+
185
+ // Called when a conversion fails; receives details about the failure
147
186
  onError: (ctx) => {
148
- console.error('Conversion error:', ctx);
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
- ## TypeScript Support
198
+ The `CurrencyPluginErrorContext` object contains:
155
199
 
156
- - Fully typed, with exported types for plugin options and error context.
157
- - Example:
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
- import type { CurrencyPluginOptions, CurrencyPluginErrorContext } from 'mongoose-currency-convert/types';
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
- ## Extension Plugins (e.g. BCE)
223
+ ## Success callback
164
224
 
165
- You can use or create extension plugins that provide a ready-to-use `getRate` function for external services (e.g. European Central Bank, exchangerate.host, etc.).
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-converter-bce` | BCE provider for automatic exchange rate lookup. |
170
-
171
- ### How to Create Your Own Extension Plugin
172
-
173
- 1. Import the types from the base plugin:
174
- ```ts
175
- import type { GetRateFn } from 'mongoose-currency-convert';
176
- ```
177
- 2. Implement a factory function that returns a `getRate` function:
178
- ```ts
179
- export function createMyGetRate(): GetRateFn {
180
- return async function getRate(from, to, date) {
181
- // Fetch rate from your service
182
- // Return the rate
183
- };
184
- }
185
- ```
186
- 3. Use your plugin in the base plugin configuration:
187
- ```ts
188
- ProductSchema.plugin(currencyConversionPlugin, {
189
- fields: [/* ... */],
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 >= 18.x
208
- - Mongoose >= 7.x
209
- - TypeScript >= 5.x (optional)
346
+ - Node.js 18
347
+ - Mongoose 7
348
+ - TypeScript 5 (optional)
210
349
 
211
350
  ## Contributing
212
351
 
213
- Contributions are welcome! To contribute:
214
- - Fork the repository and create a new branch.
215
- - Submit a pull request with a clear description of your changes.
216
- - Follow the coding style and add tests for new features or bug fixes.
217
- - For major changes, open an issue first to discuss your idea.
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
- See [CONTRIBUTING.md](CONTRIBUTING.md) for more details (if available).
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) for a list of changes and release history.
381
+ See [CHANGELOG.md](CHANGELOG.md).
224
382
 
225
383
  ## License
226
384
 
@@ -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 };