[
  {
    "path": ".agents/skills/dinero-best-practices/SKILL.md",
    "content": "---\nname: dinero-best-practices\ndescription: >\n  Core best practices for the Dinero.js money library. Use when writing,\n  reviewing, or refactoring code that creates Dinero objects, performs\n  arithmetic on monetary values, or handles money in JavaScript/TypeScript.\n  Triggers on imports from 'dinero.js', monetary calculations, or price/cost\n  handling logic.\nlicense: MIT\nmetadata:\n  author: dinerojs\n  version: '1.0.0'\n---\n\n# Dinero.js Best Practices\n\nCore rules for working with [Dinero.js](https://v2.dinerojs.com), the JavaScript/TypeScript library for creating, calculating, and formatting money safely. Contains rules across 4 categories, prioritized by impact.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Creating Dinero objects from user input, API responses, or database values\n- Performing arithmetic on monetary values (adding, splitting, multiplying)\n- Choosing between `number` and `bigint` calculators\n- Importing from `dinero.js`, `dinero.js/currencies`, or `dinero.js/bigint`\n- Working with prices, costs, taxes, or any financial calculation\n\n## Rule Categories by Priority\n\n| Priority | Category        | Impact   | Prefix        |\n| -------- | --------------- | -------- | ------------- |\n| 1        | Object Creation | CRITICAL | `creation-`   |\n| 2        | Arithmetic      | CRITICAL | `arithmetic-` |\n| 3        | Precision       | HIGH     | `precision-`  |\n| 4        | Imports         | MEDIUM   | `imports-`    |\n\n## Quick Reference\n\n### 1. Object Creation (CRITICAL)\n\n- `creation-minor-units` - Always pass amounts as integers in minor currency units\n- `creation-from-floats` - Use a helper to convert float inputs to minor units\n- `creation-zero-exponent` - Currencies with exponent 0 (e.g., JPY) take major units directly\n\n### 2. Arithmetic (CRITICAL)\n\n- `arithmetic-immutability` - All operations return new objects; capture the return value\n- `arithmetic-allocate-not-divide` - Use `allocate` for splitting money, not manual division\n- `arithmetic-scaled-amounts` - Never multiply by a raw decimal; use scaled amounts\n- `arithmetic-percentages` - Calculate percentages with `allocate` or scaled `multiply`\n\n### 3. Precision (HIGH)\n\n- `precision-bigint` - Use `dinero.js/bigint` for amounts exceeding `Number.MAX_SAFE_INTEGER`\n- `precision-crypto` - Cryptocurrencies require bigint due to high exponents\n- `precision-trim-scale` - Use `trimScale` to remove trailing zeros after chained operations\n\n### 4. Imports (MEDIUM)\n\n- `imports-tree-shaking` - Import only what you use; standalone functions enable tree-shaking\n- `imports-bigint-currencies` - Match calculator type: use `dinero.js/bigint/currencies` with `dinero.js/bigint`\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/creation-minor-units.md\nrules/arithmetic-allocate-not-divide.md\n```\n\nEach rule file contains:\n\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/arithmetic-allocate-not-divide.md",
    "content": "---\ntitle: Use allocate for Splitting Money, Not Manual Division\nimpact: CRITICAL\nimpactDescription: prevents lost cents from rounding\ntags: arithmetic, allocate, division, remainder\n---\n\n## Use allocate for Splitting Money, Not Manual Division\n\nWhen splitting money between parties, use `allocate` instead of dividing manually. `allocate` distributes remainders so no money is lost.\n\n**Incorrect (manual division loses money):**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst total = dinero({ amount: 1003, currency: USD }); // $10.03\n\n// Splitting three ways: 1003 / 3 = 334.33... — where does the extra cent go?\nconst share = multiply(total, { amount: 1, scale: 0 }); // No good way to split evenly\n```\n\n**Correct (allocate distributes remainders):**\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst total = dinero({ amount: 1003, currency: USD }); // $10.03\n\nconst shares = allocate(total, [1, 1, 1]);\n// [$3.35, $3.34, $3.34] — extra cent goes to the first share\n```\n\nThe ratios in `allocate` are relative. `[1, 1, 1]` splits evenly. `[70, 20, 10]` splits 70%/20%/10%.\n\nReference: https://v2.dinerojs.com/api/mutations/allocate\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/arithmetic-immutability.md",
    "content": "---\ntitle: Capture Return Values — Dinero Objects Are Immutable\nimpact: CRITICAL\nimpactDescription: prevents silently lost calculations\ntags: arithmetic, immutability, pure-functions\n---\n\n## Capture Return Values — Dinero Objects Are Immutable\n\nAll Dinero.js operations are pure functions that return new objects. The original objects are never modified. Discarding the return value means losing your calculation.\n\n**Incorrect (discarding the return value):**\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1000, currency: USD });\nconst tax = dinero({ amount: 100, currency: USD });\n\nadd(price, tax); // Return value discarded — price is unchanged\n```\n\n**Correct (capturing the result):**\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1000, currency: USD });\nconst tax = dinero({ amount: 100, currency: USD });\n\nconst total = add(price, tax); // $11.00\n```\n\nThis applies to all operations: `add`, `subtract`, `multiply`, `allocate`, `convert`, `trimScale`, `transformScale`, and `normalizeScale`.\n\nReference: https://v2.dinerojs.com/core-concepts/mutations\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/arithmetic-percentages.md",
    "content": "---\ntitle: Calculate Percentages with allocate or Scaled multiply\nimpact: HIGH\nimpactDescription: prevents precision loss and thrown errors on percentage calculations\ntags: arithmetic, percentages, tax, discount\n---\n\n## Calculate Percentages with allocate or Scaled multiply\n\nThere are two safe ways to calculate percentages of a monetary value: `allocate` (for splitting into complementary parts) and `multiply` with a scaled amount (for extracting a percentage).\n\n**Incorrect (float percentage):**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst subtotal = dinero({ amount: 5000, currency: USD });\nconst tax = multiply(subtotal, 0.15); // Risky: may throw if result is non-integer\n```\n\n**Correct (allocate for complementary parts):**\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst subtotal = dinero({ amount: 5000, currency: USD });\nconst [tax, net] = allocate(subtotal, [15, 85]); // 15% tax, 85% net\n```\n\n**Correct (scaled multiply for a single percentage):**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst subtotal = dinero({ amount: 5000, currency: USD });\nconst tax = multiply(subtotal, { amount: 15, scale: 2 }); // 15/100 = 15%\n```\n\nUse `allocate` when you need both parts (e.g., tax and net) to guarantee they sum to the original. Use `multiply` when you only need one part.\n\nReference: https://v2.dinerojs.com/guides/calculating-percentages\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/arithmetic-scaled-amounts.md",
    "content": "---\ntitle: Never Multiply by a Raw Decimal — Use Scaled Amounts\nimpact: CRITICAL\nimpactDescription: prevents throws from non-integer intermediate results\ntags: arithmetic, multiply, scaled-amounts, decimals\n---\n\n## Never Multiply by a Raw Decimal — Use Scaled Amounts\n\nDinero.js uses integer arithmetic. Multiplying by a decimal that produces a non-integer result will throw. Use scaled amounts instead: `{ amount, scale }` represents `amount / (base ^ scale)`.\n\n**Incorrect (raw decimal multiplier):**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1001, currency: USD });\nmultiply(price, 0.5); // Throws: 1001 * 0.5 = 500.5 (not an integer)\n```\n\n**Correct (scaled amount):**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1001, currency: USD });\nmultiply(price, { amount: 5, scale: 1 }); // 5/10 = 0.5, result: amount 5005, scale 3\n```\n\nCommon scaled amounts:\n\n| Decimal | Scaled amount              |\n| ------- | -------------------------- |\n| 0.5     | `{ amount: 5, scale: 1 }`  |\n| 0.1     | `{ amount: 1, scale: 1 }`  |\n| 0.15    | `{ amount: 15, scale: 2 }` |\n| 1.5     | `{ amount: 15, scale: 1 }` |\n\nReference: https://v2.dinerojs.com/faq/can-i-multiply-by-a-decimal\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/creation-from-floats.md",
    "content": "---\ntitle: Convert Float Inputs to Minor Units with a Helper\nimpact: CRITICAL\nimpactDescription: prevents throws and precision bugs from float inputs\ntags: creation, floats, conversion, external-input\n---\n\n## Convert Float Inputs to Minor Units with a Helper\n\nWhen receiving float values from external sources (APIs, user input, databases), convert them to integer minor units before creating a Dinero object. Never pass floats directly.\n\n**Incorrect (passing a float directly):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst priceFromApi = 19.99;\nconst d = dinero({ amount: priceFromApi, currency: USD }); // Throws\n```\n\n**Correct (converting with a helper):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction dineroFromFloat({ amount: float, currency, scale }) {\n  const factor = currency.base ** (scale ?? currency.exponent);\n  const amount = Math.round(float * factor);\n\n  return dinero({ amount, currency, scale });\n}\n\nconst priceFromApi = 19.99;\nconst d = dineroFromFloat({ amount: priceFromApi, currency: USD }); // $19.99\n```\n\n`Math.round` is necessary to avoid floating-point artifacts (e.g., `19.99 * 100` evaluates to `1998.9999999999998` in JavaScript).\n\nReference: https://v2.dinerojs.com/guides/creating-from-floats\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/creation-minor-units.md",
    "content": "---\ntitle: Always Pass Amounts as Integers in Minor Currency Units\nimpact: CRITICAL\nimpactDescription: prevents silent off-by-100x errors\ntags: creation, amount, minor-units, cents\n---\n\n## Always Pass Amounts as Integers in Minor Currency Units\n\nDinero.js represents money in the smallest subdivision of a currency. For USD (exponent 2), the amount is in cents. Passing a major-unit value silently creates the wrong amount.\n\n**Incorrect (passing major units or floats):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// Wrong: 50 cents, not 50 dollars\nconst d = dinero({ amount: 50, currency: USD });\n\n// Wrong: throws on non-integer\nconst d = dinero({ amount: 19.99, currency: USD });\n```\n\n**Correct (minor units as integers):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD }); // $50.00\nconst d2 = dinero({ amount: 1999, currency: USD }); // $19.99\n```\n\nReference: https://v2.dinerojs.com/core-concepts/amount\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/creation-zero-exponent.md",
    "content": "---\ntitle: Currencies with Exponent 0 Take Major Units Directly\nimpact: HIGH\nimpactDescription: prevents wrong amounts for JPY, KRW, and similar currencies\ntags: creation, exponent, jpy, zero-decimal\n---\n\n## Currencies with Exponent 0 Take Major Units Directly\n\nCurrencies like JPY and KRW have no minor units (exponent 0). The amount you pass is in major units directly.\n\n**Incorrect (treating JPY like USD):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { JPY } from 'dinero.js/currencies';\n\n// Wrong: this is 500,000 yen, not 5,000 yen\nconst d = dinero({ amount: 500000, currency: JPY });\n```\n\n**Correct (major units for zero-exponent currencies):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { JPY } from 'dinero.js/currencies';\n\n// JPY has exponent 0, so 5000 means 5,000 yen\nconst d = dinero({ amount: 5000, currency: JPY });\n```\n\nCheck a currency's `exponent` property to determine the expected unit. USD has exponent 2 (cents), BHD has exponent 3 (fils), JPY has exponent 0 (yen).\n\nReference: https://v2.dinerojs.com/core-concepts/amount\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/imports-bigint-currencies.md",
    "content": "---\ntitle: Match Calculator Type — Use Matching Currency Imports\nimpact: HIGH\nimpactDescription: prevents TypeError from mixing number and bigint arithmetic\ntags: imports, bigint, currencies, type-mismatch\n---\n\n## Match Calculator Type — Use Matching Currency Imports\n\nCurrency definitions from `dinero.js/currencies` use `number` for `base` and `exponent`. Currency definitions from `dinero.js/bigint/currencies` use `bigint`. Mixing them throws a `TypeError`.\n\n**Incorrect (mixing number currencies with bigint calculator):**\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/currencies'; // number-typed\n\n// TypeError: Cannot mix BigInt and other types\nconst d = dinero({ amount: 500n, currency: USD });\n```\n\n**Correct (matching imports):**\n\n```js\n// Number calculator + number currencies\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\nconst d = dinero({ amount: 500, currency: USD });\n\n// Bigint calculator + bigint currencies\nimport { dinero } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\nconst d = dinero({ amount: 500n, currency: USD });\n```\n\nReference: https://v2.dinerojs.com/faq/why-cant-i-use-currencies-with-bigint\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/imports-tree-shaking.md",
    "content": "---\ntitle: Import Only What You Use — Standalone Functions Enable Tree-Shaking\nimpact: MEDIUM\nimpactDescription: reduces bundle size by excluding unused operations\ntags: imports, tree-shaking, bundle-size, functions\n---\n\n## Import Only What You Use — Standalone Functions Enable Tree-Shaking\n\nDinero.js exports standalone functions instead of methods on objects. This means bundlers can eliminate unused code. Import only the functions you need.\n\n**How it works:**\n\n```js\n// Only add, toDecimal, and dinero are included in your bundle\nimport { dinero, add, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst total = add(\n  dinero({ amount: 1000, currency: USD }),\n  dinero({ amount: 500, currency: USD }),\n);\n\ntoDecimal(total); // \"15.00\"\n```\n\nFunctions like `multiply`, `allocate`, `compare`, `greaterThan`, etc. are not shipped if you don't import them.\n\nNote: Dinero.js uses standalone functions, not methods. Write `add(d1, d2)`, not `d1.add(d2)`.\n\nReference: https://v2.dinerojs.com/faq/why-functions-instead-of-methods\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/precision-bigint.md",
    "content": "---\ntitle: Use bigint for Amounts Exceeding Number.MAX_SAFE_INTEGER\nimpact: HIGH\nimpactDescription: prevents silent precision loss on large monetary values\ntags: precision, bigint, large-amounts, safe-integer\n---\n\n## Use bigint for Amounts Exceeding Number.MAX_SAFE_INTEGER\n\nJavaScript `number` silently loses precision beyond `Number.MAX_SAFE_INTEGER` (9,007,199,254,740,991). For large monetary values, use the bigint entry point.\n\n**Incorrect (large amount with number calculator):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// 25800000000000000 exceeds safe integer range — silently wrong\nconst d = dinero({ amount: 25800000000000000, currency: USD });\n```\n\n**Correct (bigint calculator):**\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst d = dinero({ amount: 25800000000000000n, currency: USD });\n```\n\nNote: `dinero.js/bigint` uses its own currency definitions where `base` and `exponent` are bigint values. Always import currencies from `dinero.js/bigint/currencies` when using the bigint calculator.\n\nReference: https://v2.dinerojs.com/guides/precision-and-large-numbers\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/precision-crypto.md",
    "content": "---\ntitle: Cryptocurrencies Require bigint Due to High Exponents\nimpact: HIGH\nimpactDescription: prevents overflow on crypto amounts (e.g., 1 ETH = 10^18 wei)\ntags: precision, bigint, cryptocurrency, ethereum, bitcoin\n---\n\n## Cryptocurrencies Require bigint Due to High Exponents\n\nCryptocurrencies like ETH (exponent 18) and BTC (exponent 8) produce amounts that exceed the safe integer range even for small values. Always use the bigint calculator for crypto.\n\n**Incorrect (number calculator for ETH):**\n\n```js\nimport { dinero } from 'dinero.js';\n\nconst ETH = { code: 'ETH', base: 10, exponent: 18 };\n// 1 ETH = 1000000000000000000 wei — exceeds Number.MAX_SAFE_INTEGER\nconst d = dinero({ amount: 1000000000000000000, currency: ETH });\n```\n\n**Correct (bigint calculator):**\n\n```js\nimport { dinero } from 'dinero.js/bigint';\n\nconst ETH = { code: 'ETH', base: 10n, exponent: 18n };\nconst d = dinero({ amount: 1000000000000000000n, currency: ETH });\n```\n\nAvoid naming files after cryptocurrency ticker codes (e.g., `xbt.js`, `xmr.js`). Ad blockers may flag these file names as crypto mining scripts and block them from loading.\n\nReference: https://v2.dinerojs.com/guides/cryptocurrencies\n"
  },
  {
    "path": ".agents/skills/dinero-best-practices/rules/precision-trim-scale.md",
    "content": "---\ntitle: Use trimScale to Remove Trailing Zeros After Chained Operations\nimpact: MEDIUM\nimpactDescription: prevents unnecessary scale growth from chained operations\ntags: precision, scale, trim, chained-operations\n---\n\n## Use trimScale to Remove Trailing Zeros After Chained Operations\n\nDinero.js automatically promotes to the highest scale when combining objects with different scales. Over many operations, scale can grow unnecessarily. Use `trimScale` to drop trailing zeros.\n\n**Before trimming:**\n\n```js\nimport { dinero, add, trimScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 100, currency: USD });\nconst d2 = dinero({ amount: 2000000, currency: USD, scale: 6 });\n\nconst result = add(d1, d2); // amount: 3000000, scale: 6\n```\n\n**After trimming:**\n\n```js\nconst trimmed = trimScale(result); // amount: 300, scale: 2\n```\n\nThis is especially useful before serialization (storing or transporting) where compact representation matters.\n\nReference: https://v2.dinerojs.com/api/conversions/trim-scale\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/SKILL.md",
    "content": "---\nname: dinero-currency-patterns\ndescription: >\n  Currency handling patterns for the Dinero.js money library. Use when working\n  with multiple currencies, converting between currencies, defining custom\n  currencies, storing monetary values in databases, or integrating with payment\n  services like Stripe or PayPal. Triggers on currency conversion, database\n  schema design for money, or payment API integration with Dinero objects.\nlicense: MIT\nmetadata:\n  author: dinerojs\n  version: '1.0.0'\n---\n\n# Dinero.js Currency Patterns\n\nPatterns for handling currencies with [Dinero.js](https://v2.dinerojs.com): type safety, conversions, custom currencies, database storage, and payment service integration.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Converting between currencies with `convert`\n- Defining custom currencies (e.g., cryptocurrencies, loyalty points)\n- Looking up currencies dynamically from external input\n- Storing monetary values in a database\n- Integrating with payment services (Stripe, PayPal, Square)\n\n## Rule Categories by Priority\n\n| Priority | Category            | Impact | Prefix     |\n| -------- | ------------------- | ------ | ---------- |\n| 1        | Type Safety         | HIGH   | `types-`   |\n| 2        | Conversion          | HIGH   | `convert-` |\n| 3        | Storage             | HIGH   | `storage-` |\n| 4        | Payment Integration | MEDIUM | `payment-` |\n\n## Quick Reference\n\n### 1. Type Safety (HIGH)\n\n- `types-as-const` - Define custom currencies with `as const satisfies` for compile-time safety\n- `types-currency-mismatch` - TypeScript catches currency mismatches in operations at compile time\n- `types-lookup-validation` - Validate currency codes from external sources at runtime\n\n### 2. Conversion (HIGH)\n\n- `convert-scaled-rates` - Use scaled amounts for fractional exchange rates, not floats\n- `convert-reusable` - Build reusable converter functions with higher-order patterns\n\n### 3. Storage (HIGH)\n\n- `storage-database` - Store amount, currency code, and scale as separate columns\n- `storage-no-money-type` - Avoid PostgreSQL's `money` type for multi-currency applications\n\n### 4. Payment Integration (MEDIUM)\n\n- `payment-services` - Map Dinero objects to payment service formats with dedicated helpers\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/types-as-const.md\nrules/convert-scaled-rates.md\n```\n\nEach rule file contains:\n\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/convert-reusable.md",
    "content": "---\ntitle: Build Reusable Converter Functions\nimpact: MEDIUM\nimpactDescription: eliminates repeated rate passing across conversion call sites\ntags: convert, reusable, higher-order, pattern\n---\n\n## Build Reusable Converter Functions\n\nWhen converting many objects with the same rates, wrap `convert` in a higher-order function to avoid passing rates every time.\n\n**Correct (reusable converter):**\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nfunction createConverter(rates) {\n  return function converter(dineroObject, newCurrency) {\n    return convert(dineroObject, newCurrency, rates);\n  };\n}\n\nconst rates = { EUR: { amount: 89, scale: 2 } };\nconst convertWithRates = createConverter(rates);\n\nconst price = dinero({ amount: 500, currency: USD });\nconvertWithRates(price, EUR); // Dinero object in EUR\n```\n\nThis pattern works well when rates are fetched once per request or session and reused across multiple conversions.\n\nReference: https://v2.dinerojs.com/api/conversions/convert\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/convert-scaled-rates.md",
    "content": "---\ntitle: Use Scaled Amounts for Fractional Exchange Rates, Not Floats\nimpact: HIGH\nimpactDescription: prevents precision loss from floating-point exchange rates\ntags: convert, rates, scaled-amounts, exchange-rate\n---\n\n## Use Scaled Amounts for Fractional Exchange Rates, Not Floats\n\nWhen converting between currencies, fractional exchange rates should be passed as scaled amounts (`{ amount, scale }`) instead of floats. This avoids floating-point precision issues.\n\n**Incorrect (float rate):**\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst rates = { EUR: 0.89 }; // Float — imprecise\nconst d = dinero({ amount: 500, currency: USD });\nconvert(d, EUR, rates);\n```\n\n**Correct (scaled rate):**\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst rates = { EUR: { amount: 89, scale: 2 } }; // 89/100 = 0.89 — precise\nconst d = dinero({ amount: 500, currency: USD });\nconvert(d, EUR, rates); // Dinero object with amount 44500, scale 4\n```\n\nInteger rates can be passed directly without scaling:\n\n```js\nconst rates = { IQD: 1199 }; // 1 USD = 1199 IQD (integer, no scaling needed)\nconvert(d, IQD, rates);\n```\n\nReference: https://v2.dinerojs.com/api/conversions/convert\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/payment-services.md",
    "content": "---\ntitle: Map Dinero Objects to Payment Service Formats with Dedicated Helpers\nimpact: MEDIUM\nimpactDescription: prevents payment API errors from wrong amount/currency formats\ntags: payment, stripe, paypal, square, integration\n---\n\n## Map Dinero Objects to Payment Service Formats with Dedicated Helpers\n\nEach payment service expects a different money format. Build dedicated helper functions to convert Dinero objects to the right shape.\n\n**Stripe (minor units, lowercase currency):**\n\n```js\nimport { toSnapshot } from 'dinero.js';\n\nfunction toStripeMoney(dineroObject) {\n  const { amount, currency } = toSnapshot(dineroObject);\n\n  return {\n    amount,\n    currency: currency.code.toLowerCase(),\n  };\n}\n\n// { amount: 1999, currency: 'usd' }\n```\n\n**PayPal (decimal string, uppercase currency):**\n\n```js\nimport { toSnapshot, toDecimal } from 'dinero.js';\n\nfunction toPaypalMoney(dineroObject) {\n  const { currency } = toSnapshot(dineroObject);\n\n  return {\n    value: toDecimal(dineroObject),\n    currency_code: currency.code,\n  };\n}\n\n// { value: '19.99', currency_code: 'USD' }\n```\n\n**Square (BigInt amount, uppercase currency):**\n\n```js\nimport { toSnapshot } from 'dinero.js';\n\nfunction toSquareMoney(dineroObject) {\n  const { amount, currency } = toSnapshot(dineroObject);\n\n  return {\n    amount: BigInt(amount),\n    currency: currency.code,\n  };\n}\n\n// { amount: 1999n, currency: 'USD' }\n```\n\nKeep these helpers in a single module (e.g., `lib/money.js`) so format changes only need updating in one place.\n\nReference: https://v2.dinerojs.com/guides/integrating-with-payment-services\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/storage-database.md",
    "content": "---\ntitle: Store Amount, Currency Code, and Scale as Separate Columns\nimpact: HIGH\nimpactDescription: prevents data loss and enables correct restoration of Dinero objects\ntags: storage, database, schema, columns, restoration\n---\n\n## Store Amount, Currency Code, and Scale as Separate Columns\n\nStore each component of a Dinero object separately. This is portable across databases and preserves all information needed for reconstruction.\n\n**Correct (SQL schema):**\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  price_amount BIGINT NOT NULL,\n  price_currency VARCHAR(3) NOT NULL,\n  price_scale INTEGER NOT NULL DEFAULT 2\n);\n```\n\n**Correct (restoration from database row):**\n\n```js\nimport { dinero } from 'dinero.js';\nimport { getCurrency } from './currencies'; // Your validation helper\n\nfunction dineroFromRow(row) {\n  const currency = getCurrency(row.price_currency);\n\n  return dinero({\n    amount: row.price_amount,\n    currency,\n    scale: row.price_scale,\n  });\n}\n```\n\nAlways store the `scale`, not just the currency exponent. If a Dinero object was created with a custom scale (e.g., from a `multiply` with a scaled amount), restoring with just the exponent produces the wrong value.\n\nReference: https://v2.dinerojs.com/guides/storing-in-a-database\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/storage-no-money-type.md",
    "content": "---\ntitle: Avoid PostgreSQL's money Type for Multi-Currency Applications\nimpact: HIGH\nimpactDescription: prevents locale-dependent bugs and precision loss\ntags: storage, database, postgresql, money-type\n---\n\n## Avoid PostgreSQL's money Type for Multi-Currency Applications\n\nPostgreSQL's `money` type has no currency information, is locale-dependent, and has fixed 2-decimal precision. It fails for currencies with 0 decimals (JPY), 3 decimals (BHD), or multi-currency support.\n\n**Incorrect (PostgreSQL money type):**\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  price MONEY NOT NULL -- No currency info, locale-dependent, fixed precision\n);\n```\n\n**Correct (separate columns):**\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  price_amount BIGINT NOT NULL,\n  price_currency VARCHAR(3) NOT NULL,\n  price_scale INTEGER NOT NULL DEFAULT 2\n);\n```\n\nThe same advice applies to other database-specific money types. Use standard integer and string columns for maximum portability and control.\n\nReference: https://v2.dinerojs.com/guides/storing-in-a-database\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/types-as-const.md",
    "content": "---\ntitle: Define Custom Currencies with as const satisfies for Type Safety\nimpact: HIGH\nimpactDescription: enables compile-time currency mismatch detection\ntags: types, currency, typescript, as-const, custom-currency\n---\n\n## Define Custom Currencies with as const satisfies for Type Safety\n\nWhen defining custom currencies (e.g., cryptocurrencies, loyalty points), use `as const satisfies` to get a literal type for the `code` property. Without it, TypeScript infers `string`, losing compile-time currency mismatch detection.\n\n**Incorrect (code inferred as string):**\n\n```ts\nimport type { Currency } from 'dinero.js';\n\nconst BTC = { code: 'BTC', base: 10, exponent: 8 };\n// typeof BTC.code is string, not 'BTC'\n```\n\n**Correct (literal type with as const satisfies):**\n\n```ts\nimport type { Currency } from 'dinero.js';\n\nconst BTC = {\n  code: 'BTC',\n  base: 10,\n  exponent: 8,\n} as const satisfies Currency<number, 'BTC'>;\n// typeof BTC.code is 'BTC'\n```\n\nWith literal types, TypeScript catches mistakes like adding BTC and USD at compile time:\n\n```ts\nconst btcAmount = dinero({ amount: 100000000, currency: BTC });\nconst usdAmount = dinero({ amount: 500, currency: USD });\nadd(btcAmount, usdAmount); // Type error: 'USD' is not assignable to 'BTC'\n```\n\nAll built-in currencies from `dinero.js/currencies` already have literal types.\n\nReference: https://v2.dinerojs.com/guides/currency-type-safety\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/types-currency-mismatch.md",
    "content": "---\ntitle: TypeScript Catches Currency Mismatches at Compile Time\nimpact: HIGH\nimpactDescription: prevents adding, subtracting, or comparing different currencies\ntags: types, currency, mismatch, compile-time, typescript\n---\n\n## TypeScript Catches Currency Mismatches at Compile Time\n\nWhen using typed currencies, TypeScript prevents operations between different currencies. This catches bugs that would otherwise only fail at runtime.\n\n**Caught at compile time:**\n\n```ts\nimport { dinero, add, subtract, equal } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 500, currency: USD }); // Dinero<number, 'USD'>\nconst tax = dinero({ amount: 100, currency: EUR }); // Dinero<number, 'EUR'>\n\nadd(price, tax); // Type error: 'EUR' is not assignable to 'USD'\nsubtract(price, tax); // Type error\nequal(price, tax); // Type error\n```\n\n**Currency type preserved through operations:**\n\n```ts\nimport { dinero, multiply, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 500, currency: USD });\nconst doubled = multiply(price, 2); // Dinero<number, 'USD'> — preserved\nconst converted = convert(price, EUR, rates); // Dinero<number, 'EUR'> — changed\n\nadd(doubled, converted); // Type error: 'EUR' is not assignable to 'USD'\n```\n\nUnary operations (`multiply`, `allocate`, `trimScale`) preserve the currency type. `convert` changes it to the target currency.\n\nReference: https://v2.dinerojs.com/guides/currency-type-safety\n"
  },
  {
    "path": ".agents/skills/dinero-currency-patterns/rules/types-lookup-validation.md",
    "content": "---\ntitle: Validate Currency Codes from External Sources at Runtime\nimpact: HIGH\nimpactDescription: prevents undefined currency errors from invalid or unknown codes\ntags: types, validation, runtime, lookup, external-input\n---\n\n## Validate Currency Codes from External Sources at Runtime\n\nCurrency codes from APIs, databases, or user input may be invalid. Always validate before looking up a currency definition.\n\n**Incorrect (direct lookup without validation):**\n\n```ts\nimport * as currencies from 'dinero.js/currencies';\n\nfunction createPrice(amount: number, code: string) {\n  const currency = currencies[code]; // May be undefined\n  return dinero({ amount, currency }); // Runtime error\n}\n```\n\n**Correct (validate before lookup):**\n\n```ts\nimport { dinero } from 'dinero.js';\nimport * as currencies from 'dinero.js/currencies';\n\nfunction getCurrency(code: string) {\n  if (!(code in currencies)) {\n    throw new Error(`Unknown currency code: ${code}`);\n  }\n\n  return currencies[code as keyof typeof currencies];\n}\n\nfunction createPrice(amount: number, code: string) {\n  const currency = getCurrency(code);\n  return dinero({ amount, currency });\n}\n```\n\nCurrency codes may also change between Dinero.js versions, as the library tracks ISO 4217 amendments. Pin your package version if you need stability.\n\nReference: https://v2.dinerojs.com/faq/how-to-look-up-a-currency-by-code\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/SKILL.md",
    "content": "---\nname: dinero-formatting\ndescription: >\n  Formatting patterns for the Dinero.js money library. Use when displaying\n  monetary values to users, formatting prices with currency symbols, handling\n  locale-specific number formats, or working with non-decimal currencies.\n  Triggers on toDecimal, toUnits, toSnapshot calls, or Intl.NumberFormat\n  usage with Dinero objects.\nlicense: MIT\nmetadata:\n  author: dinerojs\n  version: '1.0.0'\n---\n\n# Dinero.js Formatting\n\nPatterns for formatting [Dinero.js](https://v2.dinerojs.com) monetary values for display. Covers currency symbols, locale-aware formatting, non-decimal currencies, and serialization.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Displaying prices, totals, or monetary values in a UI\n- Adding currency symbols or locale-specific formatting\n- Formatting non-decimal currencies (e.g., historical currencies with non-base-10 subdivisions)\n- Serializing Dinero objects for APIs, databases, or transport\n- Building reusable formatting utilities\n\n## Rule Categories by Priority\n\n| Priority | Category      | Impact   | Prefix           |\n| -------- | ------------- | -------- | ---------------- |\n| 1        | Display       | CRITICAL | `display-`       |\n| 2        | Locale        | HIGH     | `locale-`        |\n| 3        | Serialization | HIGH     | `serialization-` |\n| 4        | Non-Decimal   | MEDIUM   | `nondecimal-`    |\n\n## Quick Reference\n\n### 1. Display (CRITICAL)\n\n- `display-to-decimal` - Use `toDecimal` for display strings, not `toSnapshot`\n- `display-no-currency-symbols` - Dinero.js does not format currency symbols; compose with `Intl.NumberFormat`\n\n### 2. Locale (HIGH)\n\n- `locale-intl-formatter` - Build reusable formatters with `Intl.NumberFormat`\n- `locale-multilingual` - Create locale-parameterized formatters for multilingual sites\n\n### 3. Serialization (HIGH)\n\n- `serialization-snapshot` - Use `toSnapshot` for transport and storage, not display\n- `serialization-bigint-json` - BigInt Dinero objects require a custom JSON replacer\n\n### 4. Non-Decimal (MEDIUM)\n\n- `nondecimal-to-units` - Use `toUnits` for non-decimal currencies, not `toDecimal`\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/display-no-currency-symbols.md\nrules/locale-intl-formatter.md\n```\n\nEach rule file contains:\n\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/display-no-currency-symbols.md",
    "content": "---\ntitle: Dinero.js Does Not Format Currency Symbols — Compose with Intl.NumberFormat\nimpact: CRITICAL\nimpactDescription: prevents missing currency symbols in UI\ntags: display, currency-symbols, intl, numberformat\n---\n\n## Dinero.js Does Not Format Currency Symbols — Compose with Intl.NumberFormat\n\n`toDecimal` returns a plain decimal string like `\"19.99\"`, not `\"$19.99\"`. This is by design: currency formatting varies by locale (`$19.99` in en-US, `19,99 $US` in fr-CA, `19,99 $` in fr-FR). Use `toDecimal` with a transformer to add locale-aware formatting.\n\n**Incorrect (expecting currency symbols from toDecimal):**\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1999, currency: USD });\ntoDecimal(price); // \"19.99\" — no currency symbol\n```\n\n**Correct (composing with Intl.NumberFormat):**\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1999, currency: USD });\n\ntoDecimal(price, ({ value, currency }) => {\n  return Number(value).toLocaleString('en-US', {\n    style: 'currency',\n    currency: currency.code,\n  });\n}); // \"$19.99\"\n```\n\nReference: https://v2.dinerojs.com/faq/why-no-currency-formatting\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/display-to-decimal.md",
    "content": "---\ntitle: Use toDecimal for Display Strings, Not toSnapshot\nimpact: CRITICAL\nimpactDescription: prevents displaying raw minor-unit amounts to users\ntags: display, toDecimal, toSnapshot, formatting\n---\n\n## Use toDecimal for Display Strings, Not toSnapshot\n\n`toDecimal` returns a human-readable decimal string (e.g., `\"19.99\"`). `toSnapshot` returns the raw internal representation in minor units (e.g., `{ amount: 1999, ... }`). Use the right one for the right job.\n\n**Incorrect (using toSnapshot for display):**\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1999, currency: USD });\nconst { amount } = toSnapshot(price);\ndisplay.textContent = `$${amount}`; // Shows \"$1999\" — wrong\n```\n\n**Correct (using toDecimal for display):**\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1999, currency: USD });\ntoDecimal(price); // \"19.99\"\n```\n\n`toSnapshot` is for serialization (APIs, databases, transport). `toDecimal` is for rendering to users.\n\nReference: https://v2.dinerojs.com/core-concepts/formatting\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/locale-intl-formatter.md",
    "content": "---\ntitle: Build Reusable Formatters with Intl.NumberFormat\nimpact: HIGH\nimpactDescription: eliminates duplicated formatting logic across the codebase\ntags: locale, formatter, intl, reusable, higher-order\n---\n\n## Build Reusable Formatters with Intl.NumberFormat\n\nInstead of inlining `Intl.NumberFormat` at every call site, build a reusable formatter function.\n\n**Correct (reusable formatter):**\n\n```js\nimport { toDecimal } from 'dinero.js';\n\nfunction intlFormat(dineroObject, locale, options = {}) {\n  function transformer({ value, currency }) {\n    return Number(value).toLocaleString(locale, {\n      ...options,\n      style: 'currency',\n      currency: currency.code,\n    });\n  }\n\n  return toDecimal(dineroObject, transformer);\n}\n\nintlFormat(price, 'en-US'); // \"$19.99\"\nintlFormat(price, 'fr-FR'); // \"19,99 $US\"\nintlFormat(price, 'ja-JP'); // \"$19.99\"\n```\n\nYou can also create a higher-order function that bakes in the locale:\n\n```js\nfunction createFormatter(locale, options = {}) {\n  return function format(dineroObject) {\n    return intlFormat(dineroObject, locale, options);\n  };\n}\n\nconst formatUSD = createFormatter('en-US');\nformatUSD(price); // \"$19.99\"\n```\n\nReference: https://v2.dinerojs.com/guides/formatting-in-a-multilingual-site\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/locale-multilingual.md",
    "content": "---\ntitle: Parameterize Locale for Multilingual Sites\nimpact: HIGH\nimpactDescription: prevents hardcoded locale strings in formatting logic\ntags: locale, multilingual, i18n, internationalization\n---\n\n## Parameterize Locale for Multilingual Sites\n\nIn multilingual applications, pass the locale as a parameter rather than hardcoding it. This lets you format the same Dinero object differently depending on the user's language.\n\n**Incorrect (hardcoded locale):**\n\n```js\nfunction formatPrice(dineroObject) {\n  return toDecimal(dineroObject, ({ value, currency }) => {\n    return Number(value).toLocaleString('en-US', {\n      style: 'currency',\n      currency: currency.code,\n    });\n  });\n}\n```\n\n**Correct (locale as parameter):**\n\n```js\nfunction formatPrice(dineroObject, locale) {\n  return toDecimal(dineroObject, ({ value, currency }) => {\n    return Number(value).toLocaleString(locale, {\n      style: 'currency',\n      currency: currency.code,\n    });\n  });\n}\n\nformatPrice(price, 'en-US'); // \"$19.99\"\nformatPrice(price, 'de-DE'); // \"19,99 $\"\nformatPrice(price, 'ja-JP'); // \"$19.99\"\n```\n\nIn React, you can get the locale from your i18n context (e.g., `next-intl`, `react-intl`, `react-i18next`) and pass it to your formatter.\n\nReference: https://v2.dinerojs.com/guides/formatting-in-a-multilingual-site\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/nondecimal-to-units.md",
    "content": "---\ntitle: Use toUnits for Non-Decimal Currencies, Not toDecimal\nimpact: MEDIUM\nimpactDescription: prevents wrong output for currencies with non-base-10 subdivisions\ntags: nondecimal, toUnits, formatting, historical-currencies\n---\n\n## Use toUnits for Non-Decimal Currencies, Not toDecimal\n\n`toDecimal` assumes a decimal (base 10) currency. For currencies with non-decimal subdivisions (e.g., base 6, base 12, or multi-base like pre-decimal GBP), use `toUnits`.\n\n**Incorrect (toDecimal on non-decimal currency):**\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\n\nconst GRD = { code: 'GRD', base: 6, exponent: 1 };\nconst d = dinero({ amount: 9, currency: GRD });\n\ntoDecimal(d); // Throws or produces meaningless output\n```\n\n**Correct (toUnits with a custom transformer):**\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\n\nconst GRD = { code: 'GRD', base: 6, exponent: 1 };\nconst d = dinero({ amount: 9, currency: GRD });\nconst labels = ['drachma', 'obol'];\n\ntoUnits(d, ({ value }) =>\n  value\n    .filter((v) => v > 0)\n    .map((v, i) => `${v} ${labels[i]}`)\n    .join(', '),\n); // \"1 drachma, 3 obols\"\n```\n\n`toUnits` returns an array of unit values, one per subdivision level. It works with any base, including array bases like `[20, 12]` for pre-decimal GBP (pounds, shillings, pence).\n\nReference: https://v2.dinerojs.com/guides/formatting-non-decimal-currencies\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/serialization-bigint-json.md",
    "content": "---\ntitle: BigInt Dinero Objects Require a Custom JSON Replacer\nimpact: HIGH\nimpactDescription: prevents TypeError when serializing bigint Dinero objects\ntags: serialization, bigint, json, stringify, replacer\n---\n\n## BigInt Dinero Objects Require a Custom JSON Replacer\n\n`JSON.stringify` throws a `TypeError` on bigint values. When using the bigint calculator, provide a custom replacer.\n\n**Incorrect (stringify without replacer):**\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst price = dinero({ amount: 500n, currency: USD });\nJSON.stringify(toSnapshot(price)); // TypeError: Do not know how to serialize a BigInt\n```\n\n**Correct (with replacer):**\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst price = dinero({ amount: 500n, currency: USD });\n\nJSON.stringify(toSnapshot(price), (key, value) => {\n  if (typeof value === 'bigint') {\n    return String(value);\n  }\n  return value;\n});\n```\n\nWhen restoring, convert string values back to bigint:\n\n```js\nconst data = JSON.parse(json, (key, value) => {\n  if (typeof value === 'string' && /^\\d+$/.test(value)) {\n    return BigInt(value);\n  }\n  return value;\n});\nconst restored = dinero(data.price);\n```\n\nReference: https://v2.dinerojs.com/guides/transporting-and-restoring\n"
  },
  {
    "path": ".agents/skills/dinero-formatting/rules/serialization-snapshot.md",
    "content": "---\ntitle: Use toSnapshot for Transport and Storage, Not Display\nimpact: HIGH\nimpactDescription: prevents data loss from using display-oriented functions for serialization\ntags: serialization, toSnapshot, transport, storage, json\n---\n\n## Use toSnapshot for Transport and Storage, Not Display\n\n`toSnapshot` returns the full internal representation as a plain object, suitable for JSON serialization. Use it for APIs, databases, and transport. Use `toDecimal` for user-facing display.\n\n**Correct (serialization with toSnapshot):**\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1999, currency: USD });\nconst snapshot = toSnapshot(price);\n// { amount: 1999, currency: { code: 'USD', base: 10, exponent: 2 }, scale: 2 }\n\n// Send to API\nawait fetch('/api/products', {\n  method: 'POST',\n  body: JSON.stringify({ price: snapshot }),\n});\n```\n\n**Correct (restoring from snapshot):**\n\n```js\nimport { dinero } from 'dinero.js';\n\n// The snapshot can be passed directly to dinero()\nconst restored = dinero(data.price);\n```\n\nSnapshots are plain objects with no methods, making them safe for `JSON.stringify`, database columns, and cross-service communication.\n\nReference: https://v2.dinerojs.com/guides/transporting-and-restoring\n"
  },
  {
    "path": ".agents/skills/tsdown/SKILL.md",
    "content": "---\nname: tsdown\ndescription: Bundle TypeScript and JavaScript libraries with blazing-fast speed powered by Rolldown. Use when building libraries, generating type declarations, bundling for multiple formats, or migrating from tsup.\n---\n\n# tsdown - The Elegant Library Bundler\n\nBlazing-fast bundler for TypeScript/JavaScript libraries powered by Rolldown and Oxc.\n\n## When to Use\n\n- Building TypeScript/JavaScript libraries for npm\n- Generating TypeScript declaration files (.d.ts)\n- Bundling for multiple formats (ESM, CJS, IIFE, UMD)\n- Optimizing bundles with tree shaking and minification\n- Migrating from tsup with minimal changes\n- Building React, Vue, Solid, or Svelte component libraries\n\n## Quick Start\n\n```bash\n# Install\npnpm add -D tsdown\n\n# Basic usage\nnpx tsdown\n\n# With config file\nnpx tsdown --config tsdown.config.ts\n\n# Watch mode\nnpx tsdown --watch\n\n# Migrate from tsup\nnpx tsdown-migrate\n```\n\n## Basic Configuration\n\n```ts\nimport { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  entry: ['./src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n})\n```\n\n## Core References\n\n| Topic | Description | Reference |\n|-------|-------------|-----------|\n| Getting Started | Installation, first bundle, CLI basics | [guide-getting-started](references/guide-getting-started.md) |\n| Configuration File | Config file formats, multiple configs, workspace | [option-config-file](references/option-config-file.md) |\n| CLI Reference | All CLI commands and options | [reference-cli](references/reference-cli.md) |\n| Migrate from tsup | Migration guide and compatibility notes | [guide-migrate-from-tsup](references/guide-migrate-from-tsup.md) |\n| Plugins | Rolldown, Rollup, Unplugin support | [advanced-plugins](references/advanced-plugins.md) |\n| Hooks | Lifecycle hooks for custom logic | [advanced-hooks](references/advanced-hooks.md) |\n| Programmatic API | Build from Node.js scripts | [advanced-programmatic](references/advanced-programmatic.md) |\n| Rolldown Options | Pass options directly to Rolldown | [advanced-rolldown-options](references/advanced-rolldown-options.md) |\n| CI Environment | CI detection, `'ci-only'` / `'local-only'` values | [advanced-ci](references/advanced-ci.md) |\n\n## Build Options\n\n| Option | Usage | Reference |\n|--------|-------|-----------|\n| Entry points | `entry: ['src/*.ts', '!**/*.test.ts']` | [option-entry](references/option-entry.md) |\n| Output formats | `format: ['esm', 'cjs', 'iife', 'umd']` | [option-output-format](references/option-output-format.md) |\n| Output directory | `outDir: 'dist'`, `outExtensions` | [option-output-directory](references/option-output-directory.md) |\n| Type declarations | `dts: true`, `dts: { sourcemap, compilerOptions, vue }` | [option-dts](references/option-dts.md) |\n| Target environment | `target: 'es2020'`, `target: 'esnext'` | [option-target](references/option-target.md) |\n| Platform | `platform: 'node'`, `platform: 'browser'` | [option-platform](references/option-platform.md) |\n| Tree shaking | `treeshake: true`, custom options | [option-tree-shaking](references/option-tree-shaking.md) |\n| Minification | `minify: true`, `minify: 'dce-only'` | [option-minification](references/option-minification.md) |\n| Source maps | `sourcemap: true`, `'inline'`, `'hidden'` | [option-sourcemap](references/option-sourcemap.md) |\n| Watch mode | `watch: true`, watch options | [option-watch-mode](references/option-watch-mode.md) |\n| Cleaning | `clean: true`, clean patterns | [option-cleaning](references/option-cleaning.md) |\n| Log level | `logLevel: 'silent'`, `failOnWarn: 'ci-only'` | [option-log-level](references/option-log-level.md) |\n\n## Dependency Handling\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| External deps | `external: ['react', /^@myorg\\//]` | [option-dependencies](references/option-dependencies.md) |\n| Inline deps | `noExternal: ['dep-to-bundle']` | [option-dependencies](references/option-dependencies.md) |\n| Auto external | Automatic peer/dependency externalization | [option-dependencies](references/option-dependencies.md) |\n\n## Output Enhancement\n\n| Feature | Usage | Reference |\n|---------|-------|-----------|\n| Shims | `shims: true` - Add ESM/CJS compatibility | [option-shims](references/option-shims.md) |\n| CJS default | `cjsDefault: true` (default) / `false` | [option-cjs-default](references/option-cjs-default.md) |\n| Package exports | `exports: true` - Auto-generate exports field | [option-package-exports](references/option-package-exports.md) |\n| CSS handling | **[experimental]** Still in development | [option-css](references/option-css.md) |\n| Unbundle mode | `unbundle: true` - Preserve directory structure | [option-unbundle](references/option-unbundle.md) |\n| Package validation | `publint: true`, `attw: true` - Validate package | [option-lint](references/option-lint.md) |\n\n## Framework & Runtime Support\n\n| Framework | Guide | Reference |\n|-----------|-------|-----------|\n| React | JSX transform, Fast Refresh | [recipe-react](references/recipe-react.md) |\n| Vue | SFC support, JSX | [recipe-vue](references/recipe-vue.md) |\n| WASM | WebAssembly modules via `rolldown-plugin-wasm` | [recipe-wasm](references/recipe-wasm.md) |\n\n## Common Patterns\n\n### Basic Library Bundle\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n})\n```\n\n### Multiple Entry Points\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    utils: 'src/utils.ts',\n    cli: 'src/cli.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n### Browser Library (IIFE/UMD)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['iife'],\n  globalName: 'MyLib',\n  platform: 'browser',\n  minify: true,\n})\n```\n\n### React Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  external: ['react', 'react-dom'],\n  plugins: [\n    // React Fast Refresh support\n  ],\n})\n```\n\n### Preserve Directory Structure\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts', '!**/*.test.ts'],\n  unbundle: true, // Preserve file structure\n  format: ['esm'],\n  dts: true,\n})\n```\n\n### CI-Aware Configuration\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  failOnWarn: 'ci-only',\n  publint: 'ci-only',\n  attw: 'ci-only',\n})\n```\n\n### WASM Support\n\n```ts\nimport { wasm } from 'rolldown-plugin-wasm'\nimport { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [wasm()],\n})\n```\n\n### Advanced with Hooks\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  hooks: {\n    'build:before': async (context) => {\n      console.log('Building...')\n    },\n    'build:done': async (context) => {\n      console.log('Build complete!')\n    },\n  },\n})\n```\n\n## Configuration Features\n\n### Multiple Configs\n\nExport an array for multiple build configurations:\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    dts: true,\n  },\n  {\n    entry: ['src/cli.ts'],\n    format: ['esm'],\n    platform: 'node',\n  },\n])\n```\n\n### Conditional Config\n\nUse functions for dynamic configuration:\n\n```ts\nexport default defineConfig((options) => {\n  const isDev = options.watch\n  return {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    minify: !isDev,\n    sourcemap: isDev,\n  }\n})\n```\n\n### Workspace/Monorepo\n\nUse glob patterns to build multiple packages:\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n## CLI Quick Reference\n\n```bash\n# Basic commands\ntsdown                          # Build once\ntsdown --watch                  # Watch mode\ntsdown --config custom.ts       # Custom config\nnpx tsdown-migrate              # Migrate from tsup\n\n# Output options\ntsdown --format esm,cjs        # Multiple formats\ntsdown --outDir lib            # Custom output directory\ntsdown --minify                # Enable minification\ntsdown --dts                   # Generate declarations\n\n# Entry options\ntsdown src/index.ts            # Single entry\ntsdown src/*.ts                # Glob patterns\ntsdown src/a.ts src/b.ts       # Multiple entries\n\n# Development\ntsdown --watch                 # Watch mode\ntsdown --sourcemap             # Generate source maps\ntsdown --clean                 # Clean output directory\n```\n\n## Best Practices\n\n1. **Always generate type declarations** for TypeScript libraries:\n   ```ts\n   { dts: true }\n   ```\n\n2. **Externalize dependencies** to avoid bundling unnecessary code:\n   ```ts\n   { external: [/^react/, /^@myorg\\//] }\n   ```\n\n3. **Use tree shaking** for optimal bundle size:\n   ```ts\n   { treeshake: true }\n   ```\n\n4. **Enable minification** for production builds:\n   ```ts\n   { minify: true }\n   ```\n\n5. **Add shims** for better ESM/CJS compatibility:\n   ```ts\n   { shims: true }  // Adds __dirname, __filename, etc.\n   ```\n\n6. **Auto-generate package.json exports**:\n   ```ts\n   { exports: true }  // Creates proper exports field\n   ```\n\n7. **Use watch mode** during development:\n   ```bash\n   tsdown --watch\n   ```\n\n8. **Preserve structure** for utilities with many files:\n   ```ts\n   { unbundle: true }  // Keep directory structure\n   ```\n\n9. **Validate packages** in CI before publishing:\n   ```ts\n   { publint: 'ci-only', attw: 'ci-only' }\n   ```\n\n## Resources\n\n- Documentation: https://tsdown.dev\n- GitHub: https://github.com/rolldown/tsdown\n- Rolldown: https://rolldown.rs\n- Migration Guide: https://tsdown.dev/guide/migrate-from-tsup\n"
  },
  {
    "path": ".agents/skills/tsdown/references/advanced-ci.md",
    "content": "# CI Environment Support\n\nAutomatically detect CI environments and toggle features based on local vs CI builds.\n\n## Overview\n\ntsdown uses the [`is-in-ci`](https://www.npmjs.com/package/is-in-ci) package to detect CI environments. This covers GitHub Actions, GitLab CI, Jenkins, CircleCI, Travis CI, and more.\n\n## CI-Aware Values\n\nSeveral options accept CI-aware string values:\n\n| Value | Behavior |\n|-------|----------|\n| `true` | Always enabled |\n| `false` | Always disabled |\n| `'ci-only'` | Enabled only in CI, disabled locally |\n| `'local-only'` | Enabled only locally, disabled in CI |\n\n## Supported Options\n\nThese options accept CI-aware values:\n\n- `dts` - TypeScript declaration file generation\n- `publint` - Package lint validation\n- `attw` - \"Are the types wrong\" validation\n- `report` - Bundle size reporting\n- `exports` - Auto-generate `package.json` exports\n- `unused` - Unused dependency check\n- `devtools` - DevTools integration\n- `failOnWarn` - Fail on warnings (defaults to `'ci-only'`)\n\n## Usage\n\n### String Form\n\n```ts\nexport default defineConfig({\n  dts: 'local-only',        // Skip DTS in CI for faster builds\n  publint: 'ci-only',       // Only run publint in CI\n  failOnWarn: 'ci-only',    // Fail on warnings in CI only (default)\n})\n```\n\n### Object Form\n\nWhen an option takes a configuration object, set `enabled` to a CI-aware value:\n\n```ts\nexport default defineConfig({\n  publint: {\n    enabled: 'ci-only',\n    level: 'error',\n  },\n  attw: {\n    enabled: 'ci-only',\n    profile: 'node16',\n  },\n})\n```\n\n### Config Function\n\nThe config function receives a `ci` boolean in its context:\n\n```ts\nexport default defineConfig((_, { ci }) => ({\n  minify: ci,\n  sourcemap: !ci,\n}))\n```\n\n## Typical CI Configuration\n\n```ts\nexport default defineConfig({\n  entry: 'src/index.ts',\n  format: ['esm', 'cjs'],\n  dts: true,\n  failOnWarn: 'ci-only',\n  publint: 'ci-only',\n  attw: 'ci-only',\n})\n```\n\n## Related Options\n\n- [Package Validation](option-lint.md) - publint and attw configuration\n- [Log Level](option-log-level.md) - `failOnWarn` option details\n"
  },
  {
    "path": ".agents/skills/tsdown/references/advanced-hooks.md",
    "content": "# Lifecycle Hooks\n\nExtend the build process with lifecycle hooks.\n\n## Overview\n\nHooks provide a way to inject custom logic at specific stages of the build lifecycle. Inspired by [unbuild](https://github.com/unjs/unbuild).\n\n**Recommendation:** Use [plugins](advanced-plugins.md) for most extensions. Use hooks for simple custom tasks or Rolldown plugin injection.\n\n## Usage Patterns\n\n### Object Syntax\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  hooks: {\n    'build:prepare': async (context) => {\n      console.log('Build starting...')\n    },\n    'build:done': async (context) => {\n      console.log('Build complete!')\n    },\n  },\n})\n```\n\n### Function Syntax\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  hooks(hooks) {\n    hooks.hook('build:prepare', () => {\n      console.log('Preparing build...')\n    })\n\n    hooks.hook('build:before', (context) => {\n      console.log(`Building format: ${context.format}`)\n    })\n  },\n})\n```\n\n## Available Hooks\n\n### `build:prepare`\n\nCalled before the build process starts.\n\n**When:** Once per build session\n\n**Context:**\n```ts\n{\n  options: ResolvedConfig,\n  hooks: Hookable\n}\n```\n\n**Use cases:**\n- Setup tasks\n- Validation\n- Environment preparation\n\n**Example:**\n```ts\nhooks: {\n  'build:prepare': async (context) => {\n    console.log('Starting build for:', context.options.entry)\n    await cleanOldFiles()\n  },\n}\n```\n\n### `build:before`\n\nCalled before each Rolldown build.\n\n**When:** Once per format (ESM, CJS, etc.)\n\n**Context:**\n```ts\n{\n  options: ResolvedConfig,\n  buildOptions: BuildOptions,\n  hooks: Hookable\n}\n```\n\n**Use cases:**\n- Modify build options per format\n- Inject plugins dynamically\n- Format-specific setup\n\n**Example:**\n```ts\nhooks: {\n  'build:before': async (context) => {\n    console.log(`Building ${context.buildOptions.format} format...`)\n\n    // Add format-specific plugin\n    if (context.buildOptions.format === 'iife') {\n      context.buildOptions.plugins.push(browserPlugin())\n    }\n  },\n}\n```\n\n### `build:done`\n\nCalled after the build completes.\n\n**When:** Once per build session\n\n**Context:**\n```ts\n{\n  options: ResolvedConfig,\n  chunks: RolldownChunk[],\n  hooks: Hookable\n}\n```\n\n**Use cases:**\n- Post-processing\n- Asset copying\n- Notifications\n- Deployment\n\n**Example:**\n```ts\nhooks: {\n  'build:done': async (context) => {\n    console.log(`Built ${context.chunks.length} chunks`)\n\n    // Copy additional files\n    await copyAssets()\n\n    // Send notification\n    notifyBuildComplete()\n  },\n}\n```\n\n## Common Patterns\n\n### Build Notifications\n\n```ts\nexport default defineConfig({\n  hooks: {\n    'build:prepare': () => {\n      console.log('🚀 Starting build...')\n    },\n    'build:done': (context) => {\n      const size = context.chunks.reduce((sum, c) => sum + c.code.length, 0)\n      console.log(`✅ Build complete! Total size: ${size} bytes`)\n    },\n  },\n})\n```\n\n### Conditional Plugin Injection\n\n```ts\nexport default defineConfig({\n  hooks(hooks) {\n    hooks.hook('build:before', (context) => {\n      // Add minification only for production\n      if (process.env.NODE_ENV === 'production') {\n        context.buildOptions.plugins.push(minifyPlugin())\n      }\n    })\n  },\n})\n```\n\n### Custom File Copy\n\n```ts\nimport { copyFile } from 'fs/promises'\n\nexport default defineConfig({\n  hooks: {\n    'build:done': async (context) => {\n      // Copy README to dist\n      await copyFile('README.md', `${context.options.outDir}/README.md`)\n    },\n  },\n})\n```\n\n### Build Metrics\n\n```ts\nexport default defineConfig({\n  hooks: {\n    'build:prepare': (context) => {\n      context.startTime = Date.now()\n    },\n    'build:done': (context) => {\n      const duration = Date.now() - context.startTime\n      console.log(`Build took ${duration}ms`)\n\n      // Log chunk sizes\n      context.chunks.forEach((chunk) => {\n        console.log(`${chunk.fileName}: ${chunk.code.length} bytes`)\n      })\n    },\n  },\n})\n```\n\n### Format-Specific Logic\n\n```ts\nexport default defineConfig({\n  format: ['esm', 'cjs', 'iife'],\n  hooks: {\n    'build:before': (context) => {\n      const format = context.buildOptions.format\n\n      if (format === 'iife') {\n        // Browser-specific setup\n        context.buildOptions.globalName = 'MyLib'\n      } else if (format === 'cjs') {\n        // Node-specific setup\n        context.buildOptions.platform = 'node'\n      }\n    },\n  },\n})\n```\n\n### Deployment Hook\n\n```ts\nexport default defineConfig({\n  hooks: {\n    'build:done': async (context) => {\n      if (process.env.DEPLOY === 'true') {\n        console.log('Deploying to CDN...')\n        await deployToCDN(context.options.outDir)\n      }\n    },\n  },\n})\n```\n\n## Advanced Usage\n\n### Multiple Hooks\n\n```ts\nexport default defineConfig({\n  hooks(hooks) {\n    // Register multiple hooks\n    hooks.hook('build:prepare', setupEnvironment)\n    hooks.hook('build:prepare', validateConfig)\n\n    hooks.hook('build:before', injectPlugins)\n    hooks.hook('build:before', logFormat)\n\n    hooks.hook('build:done', generateManifest)\n    hooks.hook('build:done', notifyComplete)\n  },\n})\n```\n\n### Async Hooks\n\n```ts\nexport default defineConfig({\n  hooks: {\n    'build:prepare': async (context) => {\n      await fetchRemoteConfig()\n      await initializeDatabase()\n    },\n    'build:done': async (context) => {\n      await uploadToS3(context.chunks)\n      await invalidateCDN()\n    },\n  },\n})\n```\n\n### Error Handling\n\n```ts\nexport default defineConfig({\n  hooks: {\n    'build:done': async (context) => {\n      try {\n        await riskyOperation()\n      } catch (error) {\n        console.error('Hook failed:', error)\n        // Don't throw - allow build to complete\n      }\n    },\n  },\n})\n```\n\n## Hookable API\n\ntsdown uses [hookable](https://github.com/unjs/hookable) for hooks. Additional methods:\n\n```ts\nexport default defineConfig({\n  hooks(hooks) {\n    // Register hook\n    hooks.hook('build:done', handler)\n\n    // Register hook once\n    hooks.hookOnce('build:prepare', handler)\n\n    // Remove hook\n    hooks.removeHook('build:done', handler)\n\n    // Clear all hooks for event\n    hooks.removeHooks('build:done')\n\n    // Call hooks manually\n    await hooks.callHook('build:done', context)\n  },\n})\n```\n\n## Tips\n\n1. **Use plugins** for most extensions\n2. **Hooks for simple tasks** like notifications or file copying\n3. **Async hooks supported** for I/O operations\n4. **Don't throw errors** unless you want to fail the build\n5. **Context is mutable** in `build:before` for advanced use cases\n6. **Multiple hooks allowed** for the same event\n\n## Troubleshooting\n\n### Hook Not Called\n\n- Verify hook name is correct\n- Check hook is registered in config\n- Ensure async hooks are awaited\n\n### Build Fails in Hook\n\n- Add try/catch for error handling\n- Don't throw unless intentional\n- Log errors for debugging\n\n### Context Undefined\n\n- Check which hook you're using\n- Verify context properties available for that hook\n\n## Related\n\n- [Plugins](advanced-plugins.md) - Plugin system\n- [Rolldown Options](advanced-rolldown-options.md) - Build options\n- [Watch Mode](option-watch-mode.md) - Development workflow\n"
  },
  {
    "path": ".agents/skills/tsdown/references/advanced-plugins.md",
    "content": "# Plugins\n\nExtend tsdown with plugins from multiple ecosystems.\n\n## Overview\n\ntsdown, built on Rolldown, supports plugins from multiple ecosystems to extend and customize the bundling process.\n\n## Supported Ecosystems\n\n### 1. Rolldown Plugins\n\nNative plugins designed for Rolldown:\n\n```ts\nimport RolldownPlugin from 'rolldown-plugin-something'\n\nexport default defineConfig({\n  plugins: [RolldownPlugin()],\n})\n```\n\n**Compatibility:** ✅ Full support\n\n### 2. Unplugin\n\nUniversal plugins that work across bundlers:\n\n```ts\nimport UnpluginPlugin from 'unplugin-something'\n\nexport default defineConfig({\n  plugins: [UnpluginPlugin.rolldown()],\n})\n```\n\n**Compatibility:** ✅ Most unplugin-* plugins work\n\n**Examples:**\n- `unplugin-vue-components`\n- `unplugin-auto-import`\n- `unplugin-icons`\n\n### 3. Rollup Plugins\n\nMost Rollup plugins work with tsdown:\n\n```ts\nimport RollupPlugin from '@rollup/plugin-something'\n\nexport default defineConfig({\n  plugins: [RollupPlugin()],\n})\n```\n\n**Compatibility:** ✅ High compatibility\n\n**Type Issues:** May cause TypeScript errors - use type casting:\n\n```ts\nimport RollupPlugin from 'rollup-plugin-something'\n\nexport default defineConfig({\n  plugins: [\n    // @ts-expect-error Rollup plugin type mismatch\n    RollupPlugin(),\n    // Or cast to any\n    RollupPlugin() as any,\n  ],\n})\n```\n\n### 4. Vite Plugins\n\nSome Vite plugins may work:\n\n```ts\nimport VitePlugin from 'vite-plugin-something'\n\nexport default defineConfig({\n  plugins: [\n    // @ts-expect-error Vite plugin type mismatch\n    VitePlugin(),\n  ],\n})\n```\n\n**Compatibility:** ⚠️ Limited - only if not using Vite-specific APIs\n\n**Note:** Improved support planned for future releases.\n\n## Usage\n\n### Basic Plugin Usage\n\n```ts\nimport { defineConfig } from 'tsdown'\nimport SomePlugin from 'some-plugin'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [SomePlugin()],\n})\n```\n\n### Multiple Plugins\n\n```ts\nimport PluginA from 'plugin-a'\nimport PluginB from 'plugin-b'\nimport PluginC from 'plugin-c'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [\n    PluginA(),\n    PluginB({ option: true }),\n    PluginC(),\n  ],\n})\n```\n\n### Conditional Plugins\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  plugins: [\n    SomePlugin(),\n    options.watch && DevPlugin(),\n    !options.watch && ProdPlugin(),\n  ].filter(Boolean),\n}))\n```\n\n## Common Plugin Patterns\n\n### JSON Import\n\n```ts\nimport json from '@rollup/plugin-json'\n\nexport default defineConfig({\n  plugins: [json()],\n})\n```\n\n### Node Resolve\n\n```ts\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\n\nexport default defineConfig({\n  plugins: [nodeResolve()],\n})\n```\n\n### CommonJS\n\n```ts\nimport commonjs from '@rollup/plugin-commonjs'\n\nexport default defineConfig({\n  plugins: [commonjs()],\n})\n```\n\n### Replace\n\n```ts\nimport replace from '@rollup/plugin-replace'\n\nexport default defineConfig({\n  plugins: [\n    replace({\n      'process.env.NODE_ENV': JSON.stringify('production'),\n      __VERSION__: JSON.stringify('1.0.0'),\n    }),\n  ],\n})\n```\n\n### Auto Import\n\n```ts\nimport AutoImport from 'unplugin-auto-import/rolldown'\n\nexport default defineConfig({\n  plugins: [\n    AutoImport({\n      imports: ['vue', 'vue-router'],\n      dts: 'src/auto-imports.d.ts',\n    }),\n  ],\n})\n```\n\n### Vue Components\n\n```ts\nimport Components from 'unplugin-vue-components/rolldown'\n\nexport default defineConfig({\n  plugins: [\n    Components({\n      dts: 'src/components.d.ts',\n    }),\n  ],\n})\n```\n\n## Framework-Specific Plugins\n\n### React\n\n```ts\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  plugins: [\n    // @ts-expect-error Vite plugin\n    react(),\n  ],\n})\n```\n\n### Vue\n\n```ts\nimport vue from '@vitejs/plugin-vue'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [\n    // @ts-expect-error Vite plugin\n    vue(),\n  ],\n})\n```\n\n### Solid\n\n```ts\nimport solid from 'vite-plugin-solid'\n\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  plugins: [\n    // @ts-expect-error Vite plugin\n    solid(),\n  ],\n})\n```\n\n### Svelte\n\n```ts\nimport { svelte } from '@sveltejs/vite-plugin-svelte'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [\n    // @ts-expect-error Vite plugin\n    svelte(),\n  ],\n})\n```\n\n## Writing Custom Plugins\n\nFollow Rolldown's plugin development guide:\n\n### Basic Plugin Structure\n\n```ts\nimport type { Plugin } from 'rolldown'\n\nfunction myPlugin(): Plugin {\n  return {\n    name: 'my-plugin',\n\n    // Transform hook\n    transform(code, id) {\n      if (id.endsWith('.custom')) {\n        return {\n          code: transformCode(code),\n          map: null,\n        }\n      }\n    },\n\n    // Other hooks...\n  }\n}\n```\n\n### Using Custom Plugin\n\n```ts\nimport { myPlugin } from './my-plugin'\n\nexport default defineConfig({\n  plugins: [myPlugin()],\n})\n```\n\n## Plugin Configuration\n\n### Plugin-Specific Options\n\nRefer to each plugin's documentation for configuration options.\n\n### Plugin Order\n\nPlugins run in the order they're defined:\n\n```ts\nexport default defineConfig({\n  plugins: [\n    PluginA(),  // Runs first\n    PluginB(),  // Runs second\n    PluginC(),  // Runs last\n  ],\n})\n```\n\n## Troubleshooting\n\n### Type Errors with Rollup/Vite Plugins\n\nUse type casting:\n\n```ts\nplugins: [\n  // Option 1: @ts-expect-error\n  // @ts-expect-error Plugin type mismatch\n  SomePlugin(),\n\n  // Option 2: as any\n  SomePlugin() as any,\n]\n```\n\n### Plugin Not Working\n\n1. **Check compatibility** - Verify plugin supports your bundler\n2. **Read documentation** - Follow plugin's setup instructions\n3. **Check plugin order** - Some plugins depend on execution order\n4. **Enable debug mode** - Use `--debug` flag\n\n### Vite Plugin Fails\n\nVite plugins may rely on Vite-specific APIs:\n\n1. **Find Rollup equivalent** - Look for Rollup version of plugin\n2. **Use Unplugin version** - Check for `unplugin-*` alternative\n3. **Wait for support** - Vite plugin support improving\n\n## Resources\n\n- [Rolldown Plugin Development](https://rolldown.rs/guide/plugin-development)\n- [Unplugin Documentation](https://unplugin.unjs.io/)\n- [Rollup Plugins](https://github.com/rollup/plugins)\n- [Vite Plugins](https://vitejs.dev/plugins/)\n\n## Tips\n\n1. **Prefer Rolldown plugins** for best compatibility\n2. **Use Unplugin** for cross-bundler support\n3. **Cast types** for Rollup/Vite plugins\n4. **Test thoroughly** when using cross-ecosystem plugins\n5. **Check plugin docs** for specific configuration\n6. **Write custom plugins** for unique needs\n\n## Related\n\n- [Hooks](advanced-hooks.md) - Lifecycle hooks\n- [Rolldown Options](advanced-rolldown-options.md) - Advanced Rolldown config\n- [React Recipe](recipe-react.md) - React setup with plugins\n- [Vue Recipe](recipe-vue.md) - Vue setup with plugins\n"
  },
  {
    "path": ".agents/skills/tsdown/references/advanced-programmatic.md",
    "content": "# Programmatic Usage\n\nUse tsdown from JavaScript/TypeScript code.\n\n## Overview\n\ntsdown can be imported and used programmatically in your Node.js scripts, custom build tools, or automation workflows.\n\n## Basic Usage\n\n### Simple Build\n\n```ts\nimport { build } from 'tsdown'\n\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n### With Options\n\n```ts\nimport { build } from 'tsdown'\n\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outDir: 'dist',\n  dts: true,\n  minify: true,\n  sourcemap: true,\n  clean: true,\n})\n```\n\n## API Reference\n\n### build()\n\nMain function to run a build.\n\n```ts\nimport { build } from 'tsdown'\n\nawait build(options)\n```\n\n**Parameters:**\n- `options` - Build configuration object (same as config file)\n\n**Returns:**\n- `Promise<void>` - Resolves when build completes\n\n**Throws:**\n- Build errors if compilation fails\n\n## Configuration Object\n\nAll config file options are available:\n\n```ts\nimport { build, defineConfig } from 'tsdown'\n\nconst config = defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  minify: true,\n  sourcemap: true,\n  external: ['react', 'react-dom'],\n  plugins: [/* plugins */],\n  hooks: {\n    'build:done': async () => {\n      console.log('Build complete!')\n    },\n  },\n})\n\nawait build(config)\n```\n\nSee [Config Reference](option-config-file.md) for all options.\n\n## Common Patterns\n\n### Custom Build Script\n\n```ts\n// scripts/build.ts\nimport { build } from 'tsdown'\n\nasync function main() {\n  console.log('Building library...')\n\n  await build({\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    dts: true,\n    clean: true,\n  })\n\n  console.log('Build complete!')\n}\n\nmain().catch(console.error)\n```\n\nRun with:\n```bash\ntsx scripts/build.ts\n```\n\n### Multiple Builds\n\n```ts\nimport { build } from 'tsdown'\n\n// Build main library\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outDir: 'dist',\n  dts: true,\n})\n\n// Build CLI tool\nawait build({\n  entry: ['src/cli.ts'],\n  format: ['esm'],\n  outDir: 'dist/bin',\n  platform: 'node',\n  shims: true,\n})\n```\n\n### Conditional Build\n\n```ts\nimport { build } from 'tsdown'\n\nconst isDev = process.env.NODE_ENV === 'development'\n\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  minify: !isDev,\n  sourcemap: isDev,\n  clean: !isDev,\n})\n```\n\n### With Error Handling\n\n```ts\nimport { build } from 'tsdown'\n\ntry {\n  await build({\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    dts: true,\n  })\n  console.log('✅ Build successful')\n} catch (error) {\n  console.error('❌ Build failed:', error)\n  process.exit(1)\n}\n```\n\n### Automated Workflow\n\n```ts\nimport { build } from 'tsdown'\nimport { execSync } from 'child_process'\n\nasync function release() {\n  // Clean\n  console.log('Cleaning...')\n  execSync('rm -rf dist')\n\n  // Build\n  console.log('Building...')\n  await build({\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    dts: true,\n    minify: true,\n  })\n\n  // Test\n  console.log('Testing...')\n  execSync('npm test')\n\n  // Publish\n  console.log('Publishing...')\n  execSync('npm publish')\n}\n\nrelease().catch(console.error)\n```\n\n### Build with Post-Processing\n\n```ts\nimport { build } from 'tsdown'\nimport { copyFileSync } from 'fs'\n\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  hooks: {\n    'build:done': async () => {\n      // Copy additional files\n      copyFileSync('README.md', 'dist/README.md')\n      copyFileSync('LICENSE', 'dist/LICENSE')\n      console.log('Copied additional files')\n    },\n  },\n})\n```\n\n## Watch Mode\n\nUnfortunately, watch mode is not directly exposed in the programmatic API. Use the CLI for watch mode:\n\n```ts\n// Use CLI for watch mode\nimport { spawn } from 'child_process'\n\nspawn('tsdown', ['--watch'], {\n  stdio: 'inherit',\n  shell: true,\n})\n```\n\n## Integration Examples\n\n### With Task Runner\n\n```ts\n// gulpfile.js\nimport { build } from 'tsdown'\nimport gulp from 'gulp'\n\ngulp.task('build', async () => {\n  await build({\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    dts: true,\n  })\n})\n\ngulp.task('watch', () => {\n  return gulp.watch('src/**/*.ts', gulp.series('build'))\n})\n```\n\n### With Custom CLI\n\n```ts\n// scripts/cli.ts\nimport { build } from 'tsdown'\nimport { Command } from 'commander'\n\nconst program = new Command()\n\nprogram\n  .command('build')\n  .option('--prod', 'Production build')\n  .action(async (options) => {\n    await build({\n      entry: ['src/index.ts'],\n      format: ['esm', 'cjs'],\n      minify: options.prod,\n      sourcemap: !options.prod,\n    })\n  })\n\nprogram.parse()\n```\n\n### With CI/CD\n\n```ts\n// .github/scripts/build.ts\nimport { build } from 'tsdown'\n\nconst isCI = process.env.CI === 'true'\n\nawait build({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  minify: isCI,\n  clean: true,\n})\n\n// Upload to artifact storage\nif (isCI) {\n  // Upload dist/ to S3, etc.\n}\n```\n\n## TypeScript Support\n\n```ts\n// scripts/build.ts\nimport { build, type UserConfig } from 'tsdown'\n\nconst config: UserConfig = {\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n}\n\nawait build(config)\n```\n\n## Tips\n\n1. **Use TypeScript** for type safety\n2. **Handle errors** properly\n3. **Use hooks** for custom logic\n4. **Log progress** for visibility\n5. **Use CLI for watch** mode\n6. **Exit on error** in scripts\n\n## Troubleshooting\n\n### Import Errors\n\nEnsure tsdown is installed:\n```bash\npnpm add -D tsdown\n```\n\n### Type Errors\n\nImport types:\n```ts\nimport type { UserConfig } from 'tsdown'\n```\n\n### Build Fails Silently\n\nAdd error handling:\n```ts\ntry {\n  await build(config)\n} catch (error) {\n  console.error(error)\n  process.exit(1)\n}\n```\n\n### Options Not Working\n\nCheck spelling and types:\n```ts\n// ✅ Correct\n{ format: ['esm', 'cjs'] }\n\n// ❌ Wrong\n{ formats: ['esm', 'cjs'] }\n```\n\n## Related\n\n- [Config File](option-config-file.md) - Configuration options\n- [Hooks](advanced-hooks.md) - Lifecycle hooks\n- [CLI](reference-cli.md) - Command-line interface\n- [Plugins](advanced-plugins.md) - Plugin system\n"
  },
  {
    "path": ".agents/skills/tsdown/references/advanced-rolldown-options.md",
    "content": "# Customizing Rolldown Options\n\nPass options directly to the underlying Rolldown bundler.\n\n## Overview\n\ntsdown uses [Rolldown](https://rolldown.rs) as its core bundling engine. You can override Rolldown's input and output options directly for fine-grained control.\n\n**Warning:** You should be familiar with Rolldown's behavior before overriding options. Refer to the [Rolldown Config Options](https://rolldown.rs/options/input) documentation.\n\n## Input Options\n\n### Using an Object\n\n```ts\nexport default defineConfig({\n  inputOptions: {\n    cwd: './custom-directory',\n  },\n})\n```\n\n### Using a Function\n\nDynamically modify options based on the output format:\n\n```ts\nexport default defineConfig({\n  inputOptions(inputOptions, format) {\n    inputOptions.cwd = './custom-directory'\n    return inputOptions\n  },\n})\n```\n\n## Output Options\n\n### Using an Object\n\n```ts\nexport default defineConfig({\n  outputOptions: {\n    legalComments: 'inline',\n  },\n})\n```\n\n### Using a Function\n\n```ts\nexport default defineConfig({\n  outputOptions(outputOptions, format) {\n    if (format === 'esm') {\n      outputOptions.legalComments = 'inline'\n    }\n    return outputOptions\n  },\n})\n```\n\n## Common Use Cases\n\n### Preserve Legal Comments\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  outputOptions: {\n    legalComments: 'inline',\n  },\n})\n```\n\n### Custom Working Directory\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  inputOptions: {\n    cwd: './packages/my-lib',\n  },\n})\n```\n\n### Format-Specific Options\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outputOptions(outputOptions, format) {\n    if (format === 'esm') {\n      outputOptions.legalComments = 'inline'\n    }\n    return outputOptions\n  },\n})\n```\n\n## When to Use\n\n- When tsdown doesn't expose a specific Rolldown option\n- For format-specific Rolldown customizations\n- For advanced bundling scenarios\n\n## Tips\n\n1. **Read Rolldown docs** before overriding options\n2. **Use functions** for format-specific customization\n3. **Test thoroughly** when overriding defaults\n4. **Prefer tsdown options** when available (e.g., use `minify` instead of setting it via `outputOptions`)\n\n## Related\n\n- [Plugins](advanced-plugins.md) - Plugin system\n- [Hooks](advanced-hooks.md) - Lifecycle hooks\n- [Config File](option-config-file.md) - Configuration options\n"
  },
  {
    "path": ".agents/skills/tsdown/references/guide-getting-started.md",
    "content": "# Getting Started\n\nQuick guide to installing and using tsdown for the first time.\n\n## Installation\n\nInstall tsdown as a development dependency:\n\n```bash\npnpm add -D tsdown\n\n# Optionally install TypeScript if not using isolatedDeclarations\npnpm add -D typescript\n```\n\n**Requirements:**\n- Node.js 20.19 or higher\n- Experimental support for Deno and Bun\n\n## Quick Start Templates\n\nUse `create-tsdown` CLI for instant setup:\n\n```bash\npnpm create tsdown@latest\n```\n\nProvides templates for:\n- Pure TypeScript libraries\n- React component libraries\n- Vue component libraries\n- Ready-to-use configurations\n\n## First Bundle\n\n### 1. Create Source Files\n\n```ts\n// src/index.ts\nimport { hello } from './hello.ts'\nhello()\n\n// src/hello.ts\nexport function hello() {\n  console.log('Hello tsdown!')\n}\n```\n\n### 2. Create Config File\n\n```ts\n// tsdown.config.ts\nimport { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  entry: ['./src/index.ts'],\n})\n```\n\n### 3. Run Build\n\n```bash\n./node_modules/.bin/tsdown\n```\n\nOutput: `dist/index.mjs`\n\n### 4. Test Output\n\n```bash\nnode dist/index.mjs\n# Output: Hello tsdown!\n```\n\n## Add to npm Scripts\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"tsdown\"\n  }\n}\n```\n\nRun with:\n\n```bash\npnpm build\n```\n\n## CLI Commands\n\n```bash\n# Check version\ntsdown --version\n\n# View help\ntsdown --help\n\n# Build with watch mode\ntsdown --watch\n\n# Build with specific format\ntsdown --format esm,cjs\n\n# Generate type declarations\ntsdown --dts\n```\n\n## Basic Configurations\n\n### TypeScript Library (ESM + CJS)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n})\n```\n\n### Browser Library (IIFE)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['iife'],\n  globalName: 'MyLib',\n  platform: 'browser',\n  minify: true,\n})\n```\n\n### Multiple Entry Points\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    utils: 'src/utils.ts',\n    cli: 'src/cli.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n## Using Plugins\n\nAdd Rolldown, Rollup, or Unplugin plugins:\n\n```ts\nimport SomePlugin from 'some-plugin'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  plugins: [SomePlugin()],\n})\n```\n\n## Watch Mode\n\nEnable automatic rebuilds on file changes:\n\n```bash\ntsdown --watch\n# or\ntsdown -w\n```\n\n## Next Steps\n\n- Configure [entry points](option-entry.md) with glob patterns\n- Set up [multiple output formats](option-output-format.md)\n- Enable [type declaration generation](option-dts.md)\n- Explore [plugins](advanced-plugins.md) for extended functionality\n- Read [migration guide](guide-migrate-from-tsup.md) if coming from tsup\n"
  },
  {
    "path": ".agents/skills/tsdown/references/guide-migrate-from-tsup.md",
    "content": "# Migrate from tsup\n\nMigration guide for switching from tsup to tsdown.\n\n## Overview\n\ntsdown is built on Rolldown (Rust-based) vs tsup's esbuild, providing faster and more powerful bundling while maintaining compatibility.\n\n## Automatic Migration\n\n### Single Package\n\n```bash\nnpx tsdown-migrate\n```\n\n### Monorepo\n\n```bash\n# Using glob patterns\nnpx tsdown-migrate packages/*\n\n# Multiple directories\nnpx tsdown-migrate packages/foo packages/bar\n```\n\n### Migration Options\n\n- `[...dirs]` - Directories to migrate (supports globs)\n- `--dry-run` or `-d` - Preview changes without modifying files\n\n**Important:** Commit your changes before running migration.\n\n## Key Differences\n\n### Default Values\n\n| Option | tsup | tsdown |\n|--------|------|--------|\n| `format` | `['cjs']` | `['esm']` |\n| `clean` | `false` | `true` |\n| `dts` | `false` | Auto-enabled if `types`/`typings` in package.json |\n| `target` | Manual | Auto-read from `engines.node` in package.json |\n\n### New Features in tsdown\n\n#### Node Protocol Control\n\n```ts\nexport default defineConfig({\n  nodeProtocol: true,      // Add node: prefix (fs → node:fs)\n  nodeProtocol: 'strip',   // Remove node: prefix (node:fs → fs)\n  nodeProtocol: false,     // Keep as-is (default)\n})\n```\n\n#### Better Workspace Support\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',  // Build all packages\n})\n```\n\n## Migration Checklist\n\n1. **Backup your code** - Commit all changes\n2. **Run migration tool** - `npx tsdown-migrate`\n3. **Review changes** - Check modified config files\n4. **Update scripts** - Change `tsup` to `tsdown` in package.json\n5. **Test build** - Run `pnpm build` to verify\n6. **Adjust config** - Fine-tune based on your needs\n\n## Common Migration Patterns\n\n### Basic Library\n\n**Before (tsup):**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs', 'esm'],\n  dts: true,\n})\n```\n\n**After (tsdown):**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],  // ESM now default\n  dts: true,\n  clean: true,  // Now enabled by default\n})\n```\n\n### With Custom Target\n\n**Before (tsup):**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  target: 'es2020',\n})\n```\n\n**After (tsdown):**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  // target auto-reads from package.json engines.node\n  // Or override explicitly:\n  target: 'es2020',\n})\n```\n\n### CLI Scripts\n\n**Before (package.json):**\n```json\n{\n  \"scripts\": {\n    \"build\": \"tsup\",\n    \"dev\": \"tsup --watch\"\n  }\n}\n```\n\n**After (package.json):**\n```json\n{\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"dev\": \"tsdown --watch\"\n  }\n}\n```\n\n## Feature Compatibility\n\n### Supported tsup Features\n\nMost tsup features are supported:\n- ✅ Multiple entry points\n- ✅ Multiple formats (ESM, CJS, IIFE, UMD)\n- ✅ TypeScript declarations\n- ✅ Source maps\n- ✅ Minification\n- ✅ Watch mode\n- ✅ External dependencies\n- ✅ Tree shaking\n- ✅ Shims\n- ✅ Plugins (Rollup compatible)\n\n### Missing Features\n\nSome tsup features are not yet available. Check [GitHub issues](https://github.com/rolldown/tsdown/issues) for status and request features.\n\n## Troubleshooting\n\n### Build Fails After Migration\n\n1. **Check Node.js version** - Requires Node.js 20.19+\n2. **Install TypeScript** - Required for DTS generation\n3. **Review config changes** - Ensure format and options are correct\n4. **Check dependencies** - Verify all dependencies are installed\n\n### Different Output\n\n- **Format order** - tsdown defaults to ESM first\n- **Clean behavior** - tsdown cleans outDir by default\n- **Target** - tsdown auto-detects from package.json\n\n### Performance Issues\n\ntsdown should be faster than tsup. If not:\n1. Enable `isolatedDeclarations` for faster DTS generation\n2. Check for large dependencies being bundled\n3. Use `skipNodeModulesBundle` if needed\n\n## Getting Help\n\n- [GitHub Issues](https://github.com/rolldown/tsdown/issues) - Report bugs or request features\n- [Documentation](https://tsdown.dev) - Full documentation\n- [Migration Tool](https://github.com/rolldown/tsdown/tree/main/packages/tsdown-migrate) - Source code\n\n## Acknowledgements\n\ntsdown is heavily inspired by tsup and incorporates parts of its codebase. Thanks to [@egoist](https://github.com/egoist) and the tsup community.\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-cjs-default.md",
    "content": "# CJS Default Export\n\nControl how default exports are handled in CommonJS output.\n\n## Overview\n\nThe `cjsDefault` option improves compatibility when generating CommonJS modules. When enabled (default), modules with only a single default export use `module.exports = ...` instead of `exports.default = ...`.\n\n## Type\n\n```ts\ncjsDefault?: boolean  // default: true\n```\n\n## Basic Usage\n\n### Enabled (Default)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs'],\n  cjsDefault: true,  // default behavior\n})\n```\n\n### Disabled\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs'],\n  cjsDefault: false,\n})\n```\n\n## How It Works\n\n### With `cjsDefault: true` (Default)\n\nWhen your module has **only a single default export**, tsdown transforms:\n\n**Source:**\n```ts\n// src/index.ts\nexport default function greet() {\n  console.log('Hello, world!')\n}\n```\n\n**Generated CJS:**\n```js\n// dist/index.cjs\nfunction greet() {\n  console.log('Hello, world!')\n}\nmodule.exports = greet\n```\n\n**Generated Declaration:**\n```ts\n// dist/index.d.cts\ndeclare function greet(): void\nexport = greet\n```\n\nThis allows consumers to use `const greet = require('your-module')` directly.\n\n### With `cjsDefault: false`\n\nThe default export stays as `exports.default`:\n\n```js\n// dist/index.cjs\nfunction greet() {\n  console.log('Hello, world!')\n}\nexports.default = greet\n```\n\nConsumers need `require('your-module').default`.\n\n## When to Disable\n\n- When your module has both default and named exports\n- When you need consistent `exports.default` behavior\n- When consumers always use ESM imports\n\n## Tips\n\n1. **Leave enabled** for most libraries (default `true`)\n2. **Disable** if you have both default and named exports and need consistent behavior\n3. **Test CJS consumers** to verify compatibility\n\n## Related Options\n\n- [Output Format](option-output-format.md) - Module formats\n- [Shims](option-shims.md) - ESM/CJS compatibility\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-cleaning.md",
    "content": "# Output Directory Cleaning\n\nControl how the output directory is cleaned before builds.\n\n## Overview\n\nBy default, tsdown **cleans the output directory** before each build to remove stale files from previous builds.\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Clean enabled (default)\ntsdown\n\n# Disable cleaning\ntsdown --no-clean\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  clean: true,  // Default\n})\n```\n\n## Behavior\n\n### With Cleaning (Default)\n\nBefore each build:\n1. All files in `outDir` are removed\n2. Fresh build starts with empty directory\n3. Only current build outputs remain\n\n**Benefits:**\n- No stale files\n- Predictable output\n- Clean slate each build\n\n### Without Cleaning\n\nBuild outputs are added to existing files:\n\n```ts\nexport default defineConfig({\n  clean: false,\n})\n```\n\n**Use when:**\n- Multiple builds to same directory\n- Incremental builds\n- Preserving other files\n- Watch mode (faster rebuilds)\n\n## Common Patterns\n\n### Production Build\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  clean: true,  // Ensure clean output\n  minify: true,\n})\n```\n\n### Development Mode\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  clean: !options.watch,  // Don't clean in watch mode\n  sourcemap: options.watch,\n}))\n```\n\n### Multiple Builds\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    outDir: 'dist',\n    clean: true,  // Clean once\n  },\n  {\n    entry: ['src/cli.ts'],\n    outDir: 'dist',\n    clean: false,  // Don't clean, add to same dir\n  },\n])\n```\n\n### Monorepo Package\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  clean: true,  // Clean each package's dist\n})\n```\n\n### Preserve Static Files\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  clean: false,  // Keep manually added files\n  outDir: 'dist',\n})\n\n// Manually copy files first\n// Then run tsdown --no-clean\n```\n\n## Clean Patterns\n\n### Selective Cleaning\n\n```ts\nimport { rmSync } from 'fs'\n\nexport default defineConfig({\n  clean: false,  // Disable auto clean\n  hooks: {\n    'build:prepare': () => {\n      // Custom cleaning logic\n      rmSync('dist/*.js', { force: true })\n      // Keep other files\n    },\n  },\n})\n```\n\n### Clean Specific Directories\n\n```ts\nexport default defineConfig({\n  clean: false,\n  hooks: {\n    'build:prepare': async () => {\n      const { rm } = await import('fs/promises')\n      // Only clean specific subdirectories\n      await rm('dist/esm', { recursive: true, force: true })\n      await rm('dist/cjs', { recursive: true, force: true })\n      // Keep dist/types\n    },\n  },\n})\n```\n\n## Watch Mode Behavior\n\nIn watch mode, cleaning behavior is important:\n\n### Clean on First Build Only\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  watch: options.watch,\n  clean: !options.watch,  // Only clean initial build\n}))\n```\n\n**Result:**\n- First build: Clean\n- Subsequent rebuilds: Incremental\n\n### Always Clean\n\n```ts\nexport default defineConfig({\n  watch: true,\n  clean: true,  // Clean every rebuild\n})\n```\n\n**Trade-off:** Slower rebuilds, but always fresh output.\n\n## Tips\n\n1. **Leave enabled** for production builds\n2. **Disable in watch mode** for faster rebuilds\n3. **Use multiple configs** carefully with cleaning\n4. **Custom clean logic** via hooks if needed\n5. **Be cautious** - cleaning removes ALL files in outDir\n6. **Test cleaning** - ensure no important files are lost\n\n## Troubleshooting\n\n### Important Files Deleted\n\n- Don't put non-build files in outDir\n- Use separate directory for static files\n- Disable cleaning and manage manually\n\n### Stale Files in Output\n\n- Enable cleaning: `clean: true`\n- Or manually remove before build\n\n### Slow Rebuilds in Watch\n\n- Disable cleaning in watch mode\n- Use incremental builds\n\n## CLI Examples\n\n```bash\n# Default (clean enabled)\ntsdown\n\n# Disable cleaning\ntsdown --no-clean\n\n# Watch mode without cleaning\ntsdown --watch --no-clean\n\n# Multiple formats with cleaning\ntsdown --format esm,cjs --clean\n```\n\n## Examples\n\n### Safe Production Build\n\n```bash\n# Clean before build\nrm -rf dist\ntsdown --clean\n```\n\n### Incremental Development\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: true,\n  clean: false,  // Faster rebuilds\n  sourcemap: true,\n})\n```\n\n### Multi-Stage Build\n\n```ts\n// Stage 1: Clean and build main\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    outDir: 'dist',\n    clean: true,\n  },\n  {\n    entry: ['src/utils.ts'],\n    outDir: 'dist',\n    clean: false,  // Add to same directory\n  },\n])\n```\n\n## Related Options\n\n- [Output Directory](option-output-directory.md) - Configure outDir\n- [Watch Mode](option-watch-mode.md) - Development workflow\n- [Hooks](advanced-hooks.md) - Custom clean logic\n- [Entry](option-entry.md) - Entry points\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-config-file.md",
    "content": "# Configuration File\n\nCentralize and manage build settings with a configuration file.\n\n## Overview\n\ntsdown searches for config files automatically in the current directory and parent directories.\n\n## Supported File Names\n\ntsdown looks for these files (in order):\n- `tsdown.config.ts`\n- `tsdown.config.mts`\n- `tsdown.config.cts`\n- `tsdown.config.js`\n- `tsdown.config.mjs`\n- `tsdown.config.cjs`\n- `tsdown.config.json`\n- `tsdown.config`\n- `package.json` (in `tsdown` field)\n\n## Basic Configuration\n\n### TypeScript Config\n\n```ts\n// tsdown.config.ts\nimport { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n})\n```\n\n### JavaScript Config\n\n```js\n// tsdown.config.js\nexport default {\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n}\n```\n\n### JSON Config\n\n```json\n// tsdown.config.json\n{\n  \"entry\": [\"src/index.ts\"],\n  \"format\": [\"esm\", \"cjs\"],\n  \"dts\": true\n}\n```\n\n### Package.json Config\n\n```json\n// package.json\n{\n  \"name\": \"my-library\",\n  \"tsdown\": {\n    \"entry\": [\"src/index.ts\"],\n    \"format\": [\"esm\", \"cjs\"],\n    \"dts\": true\n  }\n}\n```\n\n## Multiple Configurations\n\nBuild multiple outputs with different settings:\n\n```ts\nexport default defineConfig([\n  {\n    entry: 'src/index.ts',\n    format: ['esm', 'cjs'],\n    platform: 'node',\n    dts: true,\n  },\n  {\n    entry: 'src/browser.ts',\n    format: ['iife'],\n    platform: 'browser',\n    globalName: 'MyLib',\n    minify: true,\n  },\n])\n```\n\nEach configuration runs as a separate build.\n\n## Dynamic Configuration\n\nUse a function for conditional config:\n\n```ts\nexport default defineConfig((options) => {\n  const isDev = options.watch\n\n  return {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    minify: !isDev,\n    sourcemap: isDev,\n    clean: !isDev,\n  }\n})\n```\n\nAvailable options:\n- `watch` - Whether watch mode is enabled\n- Other CLI flags passed to config\n\n## Config Loaders\n\nControl how TypeScript config files are loaded:\n\n### Auto Loader (Default)\n\nUses native TypeScript support if available, otherwise falls back to `unrun`:\n\n```bash\ntsdown # Uses auto loader\n```\n\n### Native Loader\n\nUses runtime's native TypeScript support (Node.js 23+, Bun, Deno):\n\n```bash\ntsdown --config-loader native\n```\n\n### Unrun Loader\n\nUses [unrun](https://gugustinette.github.io/unrun/) library for loading:\n\n```bash\ntsdown --config-loader unrun\n```\n\n**Tip:** Use `unrun` loader if you need to load TypeScript configs without file extensions in Node.js.\n\n## Custom Config Path\n\nSpecify a custom config file location:\n\n```bash\ntsdown --config ./configs/build.config.ts\n# or\ntsdown -c custom-config.ts\n```\n\n## Disable Config File\n\nIgnore config files and use CLI options only:\n\n```bash\ntsdown --no-config src/index.ts --format esm\n```\n\n## Extend Vite/Vitest Config (Experimental)\n\nReuse existing Vite or Vitest configurations:\n\n```bash\n# Extend vite.config.*\ntsdown --from-vite\n\n# Extend vitest.config.*\ntsdown --from-vite vitest\n```\n\n**Note:** Only specific options like `resolve` and `plugins` are reused. Test thoroughly as this feature is experimental.\n\n## Workspace / Monorepo\n\nBuild multiple packages with a single config:\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\nEach package directory matching the glob pattern will be built with the same configuration.\n\n## Common Patterns\n\n### Library with Multiple Builds\n\n```ts\nexport default defineConfig([\n  // Node.js build\n  {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    platform: 'node',\n    dts: true,\n  },\n  // Browser build\n  {\n    entry: ['src/browser.ts'],\n    format: ['iife'],\n    platform: 'browser',\n    globalName: 'MyLib',\n  },\n])\n```\n\n### Development vs Production\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  minify: !options.watch,\n  sourcemap: options.watch ? true : false,\n  clean: !options.watch,\n}))\n```\n\n### Monorepo Root Config\n\n```ts\n// Root tsdown.config.ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n  // Shared config for all packages\n})\n```\n\n### Per-Package Override\n\n```ts\n// packages/special/tsdown.config.ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'], // Override: only ESM\n  platform: 'browser', // Override: browser only\n})\n```\n\n## Config Precedence\n\nWhen multiple configs exist:\n\n1. CLI options (highest priority)\n2. Config file specified with `--config`\n3. Auto-discovered config files\n4. Package.json `tsdown` field\n5. Default values\n\n## Tips\n\n1. **Use TypeScript config** for type checking and autocomplete\n2. **Use defineConfig** helper for better DX\n3. **Export arrays** for multiple build configurations\n4. **Use functions** for dynamic/conditional configs\n5. **Keep configs simple** - prefer convention over configuration\n6. **Use workspace** for monorepo builds\n7. **Test experimental features** thoroughly before production use\n\n## Related Options\n\n- [Entry](option-entry.md) - Configure entry points\n- [Output Format](option-output-format.md) - Output formats\n- [Watch Mode](option-watch-mode.md) - Watch mode configuration\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-css.md",
    "content": "# CSS Support\n\n**Status: Experimental — still in active development.**\n\nCSS handling in tsdown is not yet stable. The API and capabilities may change significantly in future releases.\n\nAvoid relying on CSS-related options in production builds until the feature is marked as stable.\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-dependencies.md",
    "content": "# Dependencies\n\nControl how dependencies are bundled or externalized.\n\n## Overview\n\ntsdown intelligently handles dependencies to keep your library lightweight while ensuring all necessary code is included.\n\n## Default Behavior\n\n### Auto-Externalized\n\nThese are **NOT bundled** by default:\n\n- **`dependencies`** - Installed automatically with your package\n- **`peerDependencies`** - User must install manually\n\n### Conditionally Bundled\n\nThese are **bundled ONLY if imported**:\n\n- **`devDependencies`** - Only if actually used in source code\n- **Phantom dependencies** - In node_modules but not in package.json\n\n## Configuration Options\n\n### `external`\n\nMark dependencies as external (not bundled):\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  external: [\n    'react',              // Single package\n    'react-dom',\n    /^@myorg\\//,         // Regex pattern (all @myorg/* packages)\n    /^lodash/,           // All lodash packages\n  ],\n})\n```\n\n### `noExternal`\n\nForce dependencies to be bundled:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  noExternal: [\n    'some-package',      // Bundle this even if in dependencies\n    'vendor-lib',\n  ],\n})\n```\n\n### `skipNodeModulesBundle`\n\nSkip resolving and bundling ALL node_modules:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  skipNodeModulesBundle: true,\n})\n```\n\n**Result:** No dependencies from node_modules are parsed or bundled.\n\n## Common Patterns\n\n### React Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm', 'cjs'],\n  external: [\n    'react',\n    'react-dom',\n    /^react\\//,          // react/jsx-runtime, etc.\n  ],\n  dts: true,\n})\n```\n\n### Utility Library with Shared Deps\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  // Bundle lodash utilities\n  noExternal: ['lodash-es'],\n  dts: true,\n})\n```\n\n### Monorepo Package\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: [\n    /^@mycompany\\//,     // Don't bundle other workspace packages\n  ],\n  dts: true,\n})\n```\n\n### CLI Tool (Bundle Everything)\n\n```ts\nexport default defineConfig({\n  entry: ['src/cli.ts'],\n  format: ['esm'],\n  platform: 'node',\n  // Bundle all dependencies for standalone CLI\n  noExternal: [/.*/],\n  shims: true,\n})\n```\n\n### Library with Specific Externals\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: [\n    'vue',\n    '@vue/runtime-core',\n    '@vue/reactivity',\n  ],\n  dts: true,\n})\n```\n\n## Declaration Files\n\nDependency handling for `.d.ts` files follows the same rules as JavaScript.\n\n### Complex Type Resolution\n\nUse TypeScript resolver for complex third-party types:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  dts: {\n    resolver: 'tsc',     // Use TypeScript resolver instead of Oxc\n  },\n})\n```\n\n**When to use `tsc` resolver:**\n- Types in `@types/*` packages with non-standard naming (e.g., `@types/babel__generator`)\n- Complex type dependencies\n- Issues with default Oxc resolver\n\n**Trade-off:** `tsc` is slower but more compatible.\n\n## CLI Usage\n\n### External\n\n```bash\ntsdown --external react --external react-dom\ntsdown --external '/^@myorg\\/.*/'\n```\n\n### No External\n\n```bash\ntsdown --no-external some-package\n```\n\n## Examples by Use Case\n\n### Framework Component\n\n```ts\n// Don't bundle framework\nexport default defineConfig({\n  external: ['vue', 'react', 'solid-js', 'svelte'],\n})\n```\n\n### Standalone App\n\n```ts\n// Bundle everything\nexport default defineConfig({\n  noExternal: [/.*/],\n  skipNodeModulesBundle: false,\n})\n```\n\n### Shared Library\n\n```ts\n// Bundle only specific utils\nexport default defineConfig({\n  external: [/.*/],        // External by default\n  noExternal: ['tiny-utils'], // Except this one\n})\n```\n\n### Monorepo Package\n\n```ts\n// External workspace packages, bundle utilities\nexport default defineConfig({\n  external: [\n    /^@workspace\\//,     // Other workspace packages\n    'react',\n    'react-dom',\n  ],\n  noExternal: [\n    'lodash-es',         // Bundle utility libraries\n  ],\n})\n```\n\n## Troubleshooting\n\n### Dependency Bundled Unexpectedly\n\nCheck if it's in `devDependencies` and imported. Move to `dependencies`:\n\n```json\n{\n  \"dependencies\": {\n    \"should-be-external\": \"^1.0.0\"\n  }\n}\n```\n\nOr explicitly externalize:\n\n```ts\nexport default defineConfig({\n  external: ['should-be-external'],\n})\n```\n\n### Missing Dependency at Runtime\n\nEnsure it's in `dependencies` or `peerDependencies`:\n\n```json\n{\n  \"dependencies\": {\n    \"needed-package\": \"^1.0.0\"\n  }\n}\n```\n\nOr bundle it:\n\n```ts\nexport default defineConfig({\n  noExternal: ['needed-package'],\n})\n```\n\n### Type Resolution Errors\n\nUse TypeScript resolver for complex types:\n\n```ts\nexport default defineConfig({\n  dts: {\n    resolver: 'tsc',\n  },\n})\n```\n\n## Summary\n\n**Default behavior:**\n- `dependencies` & `peerDependencies` → External\n- `devDependencies` & phantom deps → Bundled if imported\n\n**Override:**\n- `external` → Force external\n- `noExternal` → Force bundled\n- `skipNodeModulesBundle` → Skip all node_modules\n\n**Declaration files:**\n- Same bundling logic as JavaScript\n- Use `resolver: 'tsc'` for complex types\n\n## Tips\n\n1. **Keep dependencies external** for libraries\n2. **Bundle everything** for standalone CLIs\n3. **Use regex patterns** for namespaced packages\n4. **Check bundle size** to verify external/bundled split\n5. **Test with fresh install** to catch missing dependencies\n6. **Use tsc resolver** only when needed (slower)\n\n## Related Options\n\n- [External](option-dependencies.md) - This page\n- [Platform](option-platform.md) - Runtime environment\n- [Output Format](option-output-format.md) - Module formats\n- [DTS](option-dts.md) - Type declarations\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-dts.md",
    "content": "# TypeScript Declaration Files\n\nGenerate `.d.ts` type declaration files for your library.\n\n## Overview\n\ntsdown uses [rolldown-plugin-dts](https://github.com/sxzz/rolldown-plugin-dts) to generate and bundle TypeScript declaration files.\n\n**Requirements:**\n- TypeScript must be installed in your project\n\n## Enabling DTS Generation\n\n### Auto-Enabled\n\nDTS generation is **automatically enabled** if `package.json` contains:\n- `types` field, or\n- `typings` field\n\n### Manual Enable\n\n#### CLI\n\n```bash\ntsdown --dts\n```\n\n#### Config File\n\n```ts\nexport default defineConfig({\n  dts: true,\n})\n```\n\n## Performance\n\n### With `isolatedDeclarations` (Recommended)\n\n**Extremely fast** - uses oxc-transform for generation.\n\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"isolatedDeclarations\": true\n  }\n}\n```\n\n### Without `isolatedDeclarations`\n\nFalls back to TypeScript compiler. Reliable but slower.\n\n## Declaration Maps\n\nMap `.d.ts` files back to original `.ts` sources (useful for monorepos).\n\n### Enable in tsconfig.json\n\n```json\n{\n  \"compilerOptions\": {\n    \"declarationMap\": true\n  }\n}\n```\n\n### Enable in tsdown Config\n\n```ts\nexport default defineConfig({\n  dts: {\n    sourcemap: true,\n  },\n})\n```\n\n## Advanced Options\n\n### Custom Compiler Options\n\nOverride TypeScript compiler options:\n\n```ts\nexport default defineConfig({\n  dts: {\n    compilerOptions: {\n      removeComments: false,\n    },\n  },\n})\n```\n\n## Build Process\n\n- **ESM format**: `.js` and `.d.ts` files generated in same build\n- **CJS format**: Separate build process for `.d.ts` files\n\n## Common Patterns\n\n### Basic Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\nOutput:\n- `dist/index.mjs`\n- `dist/index.cjs`\n- `dist/index.d.ts`\n\n### Multiple Entry Points\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    utils: 'src/utils.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\nOutput:\n- `dist/index.mjs`, `dist/index.cjs`, `dist/index.d.ts`\n- `dist/utils.mjs`, `dist/utils.cjs`, `dist/utils.d.ts`\n\n### With Monorepo Support\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: {\n    sourcemap: true, // Enable declaration maps\n  },\n})\n```\n\n### Fast Build (Isolated Declarations)\n\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"isolatedDeclarations\": true,\n    \"declaration\": true,\n    \"declarationMap\": true\n  }\n}\n```\n\n```ts\n// tsdown.config.ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true, // Will use fast oxc-transform\n})\n```\n\n## Troubleshooting\n\n### Missing Types\n\nEnsure TypeScript is installed:\n\n```bash\npnpm add -D typescript\n```\n\n### Slow Generation\n\nEnable `isolatedDeclarations` in `tsconfig.json` for faster builds.\n\n### Declaration Errors\n\nCheck that all exports have explicit types (required for `isolatedDeclarations`).\n\n### Report Issues\n\nFor DTS-specific issues, report to [rolldown-plugin-dts](https://github.com/sxzz/rolldown-plugin-dts/issues).\n\n### Vue Support\n\nEnable Vue component type generation (requires `vue-tsc`):\n\n```ts\nexport default defineConfig({\n  dts: {\n    vue: true,\n  },\n})\n```\n\n### Oxc Transform\n\nControl Oxc usage for declaration generation:\n\n```ts\nexport default defineConfig({\n  dts: {\n    oxc: true,  // Use oxc-transform (fast, requires isolatedDeclarations)\n  },\n})\n```\n\n### Custom TSConfig\n\nSpecify a different tsconfig for DTS generation:\n\n```ts\nexport default defineConfig({\n  dts: {\n    tsconfig: './tsconfig.build.json',\n  },\n})\n```\n\n## Available DTS Options\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `sourcemap` | `boolean` | Generate declaration source maps |\n| `compilerOptions` | `object` | Override TypeScript compiler options |\n| `vue` | `boolean` | Enable Vue type generation (requires vue-tsc) |\n| `oxc` | `boolean` | Use oxc-transform for fast generation |\n| `tsconfig` | `string` | Path to tsconfig file |\n| `resolver` | `'oxc' \\| 'tsc'` | Module resolver: `'oxc'` (default, fast) or `'tsc'` (more compatible) |\n| `cjsDefault` | `boolean` | CJS default export handling |\n| `sideEffects` | `boolean` | Preserve side effects in declarations |\n\n## Tips\n\n1. **Always enable DTS** for TypeScript libraries\n2. **Use isolatedDeclarations** for fast builds\n3. **Enable declaration maps** in monorepos\n4. **Ensure explicit types** for all exports\n5. **Install TypeScript** as dev dependency\n\n## Related Options\n\n- [Entry](option-entry.md) - Configure entry points\n- [Output Format](option-output-format.md) - Multiple output formats\n- [Target](option-target.md) - JavaScript version\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-entry.md",
    "content": "# Entry Points\n\nConfigure which files to bundle as entry points.\n\n## Overview\n\nEntry points are the starting files for the bundling process. Each entry point generates a separate bundle.\n\n## Usage Patterns\n\n### CLI\n\n```bash\n# Single entry\ntsdown src/index.ts\n\n# Multiple entries\ntsdown src/index.ts src/cli.ts\n\n# Glob patterns\ntsdown 'src/*.ts'\n```\n\n### Config File\n\n#### Single Entry\n\n```ts\nexport default defineConfig({\n  entry: 'src/index.ts',\n})\n```\n\n#### Multiple Entries (Array)\n\n```ts\nexport default defineConfig({\n  entry: ['src/entry1.ts', 'src/entry2.ts'],\n})\n```\n\n#### Named Entries (Object)\n\n```ts\nexport default defineConfig({\n  entry: {\n    main: 'src/index.ts',\n    utils: 'src/utils.ts',\n    cli: 'src/cli.ts',\n  },\n})\n```\n\nOutput files will match the keys:\n- `dist/main.mjs`\n- `dist/utils.mjs`\n- `dist/cli.mjs`\n\n## Glob Patterns\n\nMatch multiple files dynamically using glob patterns:\n\n### All TypeScript Files\n\n```ts\nexport default defineConfig({\n  entry: 'src/**/*.ts',\n})\n```\n\n### Exclude Test Files\n\n```ts\nexport default defineConfig({\n  entry: ['src/*.ts', '!src/*.test.ts'],\n})\n```\n\n### Object Entries with Glob Patterns\n\nUse glob wildcards (`*`) in both keys and values. The `*` in the key acts as a placeholder replaced with the matched file name (without extension):\n\n```ts\nexport default defineConfig({\n  entry: {\n    // Maps src/foo.ts → dist/lib/foo.js, src/bar.ts → dist/lib/bar.js\n    'lib/*': 'src/*.ts',\n  },\n})\n```\n\n#### Negation Patterns in Object Entries\n\nValues can be an array with negation patterns (`!`):\n\n```ts\nexport default defineConfig({\n  entry: {\n    'hooks/*': ['src/hooks/*.ts', '!src/hooks/index.ts'],\n  },\n})\n```\n\nMultiple positive and negation patterns:\n\n```ts\nexport default defineConfig({\n  entry: {\n    'utils/*': [\n      'src/utils/*.ts',\n      'src/utils/*.tsx',\n      '!src/utils/index.ts',\n      '!src/utils/internal.ts',\n    ],\n  },\n})\n```\n\n**Warning:** Multiple positive patterns in an array value must share the same base directory.\n\n### Mixed Entries\n\nMix strings, glob patterns, and object entries in an array:\n\n```ts\nexport default defineConfig({\n  entry: [\n    'src/*',\n    '!src/foo.ts',\n    { main: 'index.ts' },\n    { 'lib/*': ['src/*.ts', '!src/bar.ts'] },\n  ],\n})\n```\n\nObject entries take precedence when output names conflict.\n\n### Windows Compatibility\n\nUse forward slashes `/` instead of backslashes `\\` on Windows:\n\n```ts\n// ✅ Correct\nentry: 'src/utils/*.ts'\n\n// ❌ Wrong on Windows\nentry: 'src\\\\utils\\\\*.ts'\n```\n\n## Common Patterns\n\n### Library with Main Export\n\n```ts\nexport default defineConfig({\n  entry: 'src/index.ts',\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n### Library with Multiple Exports\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    client: 'src/client.ts',\n    server: 'src/server.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\n### CLI Tool\n\n```ts\nexport default defineConfig({\n  entry: {\n    cli: 'src/cli.ts',\n  },\n  format: ['esm'],\n  platform: 'node',\n})\n```\n\n### Preserve Directory Structure\n\nUse with `unbundle: true` to keep file structure:\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts', '!**/*.test.ts'],\n  unbundle: true,\n  format: ['esm'],\n  dts: true,\n})\n```\n\nThis will output files matching the source structure:\n- `src/index.ts` → `dist/index.mjs`\n- `src/utils/helper.ts` → `dist/utils/helper.mjs`\n\n## Tips\n\n1. **Use glob patterns** for multiple related files\n2. **Use object syntax** for custom output names\n3. **Exclude test files** with negation patterns `!**/*.test.ts`\n4. **Combine with unbundle** to preserve directory structure\n5. **Use named entries** for better control over output filenames\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-lint.md",
    "content": "# Package Validation (publint & attw)\n\nValidate your package configuration and type declarations before publishing.\n\n## Overview\n\ntsdown integrates with [publint](https://publint.dev/) and [Are the types wrong?](https://arethetypeswrong.github.io/) (attw) to catch common packaging issues. Both are optional dependencies.\n\n## Installation\n\n```bash\n# publint only\nnpm install -D publint\n\n# attw only\nnpm install -D @arethetypeswrong/core\n\n# both\nnpm install -D publint @arethetypeswrong/core\n```\n\n## publint\n\nChecks that `package.json` fields (`exports`, `main`, `module`, `types`) match your actual output files.\n\n### Enable\n\n```ts\nexport default defineConfig({\n  publint: true,\n})\n```\n\n### Configuration\n\n```ts\nexport default defineConfig({\n  publint: {\n    level: 'error', // 'warning' | 'error' | 'suggestion'\n  },\n})\n```\n\n### CLI\n\n```bash\ntsdown --publint\n```\n\n## attw (Are the types wrong?)\n\nVerifies TypeScript declarations are correct across different module resolution strategies (`node10`, `node16`, `bundler`).\n\n### Enable\n\n```ts\nexport default defineConfig({\n  attw: true,\n})\n```\n\n### Configuration\n\n```ts\nexport default defineConfig({\n  attw: {\n    profile: 'node16',   // 'strict' | 'node16' | 'esm-only'\n    level: 'error',       // 'warn' | 'error'\n    ignoreRules: ['false-cjs', 'cjs-resolves-to-esm'],\n  },\n})\n```\n\n### Profiles\n\n| Profile | Description |\n|---------|-------------|\n| `strict` | Requires all resolutions to pass (default) |\n| `node16` | Ignores `node10` resolution failures |\n| `esm-only` | Ignores `node10` and `node16-cjs` resolution failures |\n\n### Ignore Rules\n\nSuppress specific problem types with `ignoreRules`:\n\n| Rule | Description |\n|------|-------------|\n| `no-resolution` | Module could not be resolved |\n| `untyped-resolution` | Resolution succeeded but has no types |\n| `false-cjs` | Types indicate CJS but implementation is ESM |\n| `false-esm` | Types indicate ESM but implementation is CJS |\n| `cjs-resolves-to-esm` | CJS resolution points to an ESM module |\n| `fallback-condition` | A fallback/wildcard condition was used |\n| `cjs-only-exports-default` | CJS module only exports a default |\n| `named-exports` | Named exports mismatch between types and implementation |\n| `false-export-default` | Types declare a default export that doesn't exist |\n| `missing-export-equals` | Types are missing `export =` for CJS |\n| `unexpected-module-syntax` | File uses unexpected module syntax |\n| `internal-resolution-error` | Internal resolution error in type checking |\n\n### CLI\n\n```bash\ntsdown --attw\n```\n\n## CI Integration\n\nBoth tools support CI-aware options:\n\n```ts\nexport default defineConfig({\n  publint: 'ci-only',\n  attw: {\n    enabled: 'ci-only',\n    profile: 'node16',\n    level: 'error',\n  },\n})\n```\n\nBoth tools require a `package.json` in your project directory.\n\n## Related Options\n\n- [CI Environment](advanced-ci.md) - CI-aware option details\n- [Package Exports](option-package-exports.md) - Auto-generate exports field\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-log-level.md",
    "content": "# Log Level\n\nControl the verbosity of build output.\n\n## Overview\n\nThe `logLevel` option controls how much information tsdown displays during the build process.\n\n## Type\n\n```ts\nlogLevel?: 'silent' | 'error' | 'warn' | 'info'\n```\n\n**Default:** `'info'`\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Suppress all output\ntsdown --log-level silent\n\n# Only show errors\ntsdown --log-level error\n\n# Show warnings and errors\ntsdown --log-level warn\n\n# Show all info (default)\ntsdown --log-level info\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  logLevel: 'error',\n})\n```\n\n## Available Levels\n\n| Level | Shows | Use Case |\n|-------|-------|----------|\n| `silent` | Nothing | CI/CD pipelines, scripting |\n| `error` | Errors only | Minimal output |\n| `warn` | Warnings + errors | Standard CI/CD |\n| `info` | All messages | Development (default) |\n\n## Common Patterns\n\n### CI/CD Pipeline\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  logLevel: 'error',  // Only show errors in CI\n})\n```\n\n### Scripting\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  logLevel: 'silent',  // No output for automation\n})\n```\n\n## Fail on Warnings\n\nThe `failOnWarn` option controls whether warnings cause the build to exit with a non-zero code. Defaults to `'ci-only'` — warnings fail the build in CI but not locally.\n\n```ts\nexport default defineConfig({\n  failOnWarn: 'ci-only', // Default: fail on warnings only in CI\n  // failOnWarn: true,   // Always fail on warnings\n  // failOnWarn: false,  // Never fail on warnings\n})\n```\n\nSee [CI Environment](advanced-ci.md) for more about CI-aware options.\n\n## Related Options\n\n- [CI Environment](advanced-ci.md) - CI-aware option details\n- [CLI Reference](reference-cli.md) - All CLI options\n- [Config File](option-config-file.md) - Configuration setup\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-minification.md",
    "content": "# Minification\n\nCompress code to reduce bundle size.\n\n## Overview\n\nMinification removes unnecessary characters (whitespace, comments) and optimizes code for production, reducing bundle size and improving load times.\n\n**Note:** Uses [Oxc minifier](https://oxc.rs/docs/contribute/minifier) internally. The minifier is currently in alpha.\n\n## Type\n\n```ts\nminify?: boolean | 'dce-only' | MinifyOptions\n```\n\n- `true` — Enable full minification (whitespace removal, mangling, compression)\n- `false` — Disable minification (default)\n- `'dce-only'` — Only perform dead code elimination without full minification\n- `MinifyOptions` — Pass detailed options to the Oxc minifier\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Enable minification\ntsdown --minify\n\n# Disable minification\ntsdown --no-minify\n```\n\n**Note:** The CLI `--minify` flag is a boolean toggle. For `'dce-only'` mode or advanced options, use the config file.\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  minify: true,\n})\n```\n\n### DCE-Only Mode\n\nRemove dead code without full minification (keeps readable output):\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  minify: 'dce-only',\n})\n```\n\n## Example Output\n\n### Without Minification\n\n```js\n// dist/index.mjs\nconst x = 1\n\nfunction hello(x$1) {\n  console.log('Hello World')\n  console.log(x$1)\n}\n\nhello(x)\n```\n\n### With Minification\n\n```js\n// dist/index.mjs\nconst e=1;function t(e){console.log(`Hello World`),console.log(e)}t(e);\n```\n\n## Common Patterns\n\n### Production Build\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  minify: true,\n  clean: true,\n})\n```\n\n### Conditional Minification\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  minify: !options.watch,  // Only minify in production\n}))\n```\n\n### Browser Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['iife'],\n  platform: 'browser',\n  globalName: 'MyLib',\n  minify: true,\n})\n```\n\n### Multiple Builds\n\n```ts\nexport default defineConfig([\n  // Development build\n  {\n    entry: ['src/index.ts'],\n    format: ['esm'],\n    minify: false,\n    outDir: 'dist/dev',\n  },\n  // Production build\n  {\n    entry: ['src/index.ts'],\n    format: ['esm'],\n    minify: true,\n    outDir: 'dist/prod',\n  },\n])\n```\n\n## CLI Examples\n\n```bash\n# Production build with minification\ntsdown --minify --clean\n\n# Multiple formats with minification\ntsdown --format esm --format cjs --minify\n\n# Conditional minification (only when not watching)\ntsdown --minify  # Or omit --watch\n```\n\n## Tips\n\n1. **Use `minify: true`** for production builds\n2. **Use `'dce-only'`** to remove dead code while keeping output readable\n3. **Skip minification** during development for faster rebuilds\n4. **Combine with tree shaking** for best results\n5. **Test minified output** thoroughly (Oxc minifier is in alpha)\n\n## Troubleshooting\n\n### Minified Code Has Bugs\n\nOxc minifier is in alpha and may have issues:\n\n1. **Use DCE-only mode**: `minify: 'dce-only'`\n2. **Report bug** to [Oxc project](https://github.com/oxc-project/oxc/issues)\n3. **Disable minification**: `minify: false`\n\n### Unexpected Output\n\n- **Test unminified** first to isolate issue\n- **Check source maps** for debugging\n- **Verify target compatibility**\n\n## Related Options\n\n- [Tree Shaking](option-tree-shaking.md) - Remove unused code\n- [Target](option-target.md) - Syntax transformations\n- [Output Format](option-output-format.md) - Module formats\n- [Sourcemap](option-sourcemap.md) - Debug information\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-output-directory.md",
    "content": "# Output Directory\n\nConfigure the output directory for bundled files.\n\n## Overview\n\nBy default, tsdown outputs bundled files to the `dist` directory. You can customize this location using the `outDir` option.\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Default output to dist/\ntsdown\n\n# Custom output directory\ntsdown --out-dir build\ntsdown -d lib\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  outDir: 'build',\n})\n```\n\n## Common Patterns\n\n### Standard Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outDir: 'dist',  // Default\n  dts: true,\n})\n```\n\n**Output:**\n```\ndist/\n├── index.mjs\n├── index.cjs\n└── index.d.ts\n```\n\n### Separate Directories by Format\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    format: ['esm'],\n    outDir: 'dist/esm',\n  },\n  {\n    entry: ['src/index.ts'],\n    format: ['cjs'],\n    outDir: 'dist/cjs',\n  },\n])\n```\n\n**Output:**\n```\ndist/\n├── esm/\n│   └── index.js\n└── cjs/\n    └── index.js\n```\n\n### Monorepo Package\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  outDir: 'lib',  // Custom directory\n  clean: true,\n})\n```\n\n### Build to Root\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  outDir: '.',  // Output to project root (not recommended)\n  clean: false,  // Don't clean root!\n})\n```\n\n**Warning:** Be careful when outputting to root to avoid deleting important files.\n\n## Output Extensions\n\n### Custom Extensions\n\nUse `outExtensions` to control file extensions:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outDir: 'dist',\n  outExtensions({ format }) {\n    return {\n      js: format === 'esm' ? '.mjs' : '.cjs',\n    }\n  },\n})\n```\n\n### Default Extensions\n\n| Format | Default Extension | With `type: \"module\"` |\n|--------|-------------------|----------------------|\n| `esm` | `.mjs` | `.js` |\n| `cjs` | `.cjs` | `.js` |\n| `iife` | `.global.js` | `.global.js` |\n| `umd` | `.umd.js` | `.umd.js` |\n\n### ESM with .js Extension\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({ js: '.js' }),\n})\n```\n\nRequires `\"type\": \"module\"` in package.json.\n\n## File Naming\n\n### Entry Names\n\nControl output filenames based on entry names:\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    utils: 'src/utils.ts',\n  },\n  outDir: 'dist',\n})\n```\n\n**Output:**\n```\ndist/\n├── index.mjs\n└── utils.mjs\n```\n\n### Glob Entry\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts', '!**/*.test.ts'],\n  outDir: 'dist',\n  unbundle: true,  // Preserve structure\n})\n```\n\n**Output:**\n```\ndist/\n├── index.mjs\n├── utils/\n│   └── helper.mjs\n└── components/\n    └── button.mjs\n```\n\n## Multiple Builds\n\n### Same Output Directory\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    outDir: 'dist',\n    clean: true,  // Clean first\n  },\n  {\n    entry: ['src/cli.ts'],\n    outDir: 'dist',\n    clean: false,  // Don't clean again\n  },\n])\n```\n\n### Different Output Directories\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    outDir: 'dist/lib',\n  },\n  {\n    entry: ['src/cli.ts'],\n    format: ['esm'],\n    outDir: 'dist/bin',\n  },\n])\n```\n\n## CLI Examples\n\n```bash\n# Default\ntsdown\n\n# Custom directory\ntsdown --out-dir build\ntsdown -d lib\n\n# Nested directory\ntsdown --out-dir dist/lib\n\n# With other options\ntsdown --out-dir build --format esm,cjs --dts\n```\n\n## Tips\n\n1. **Use default `dist`** for standard projects\n2. **Be careful with root** - avoid `outDir: '.'`\n3. **Clean before build** - use `clean: true`\n4. **Consistent naming** - match your project conventions\n5. **Separate by format** if needed for clarity\n6. **Check .gitignore** - ensure output dir is ignored\n\n## Troubleshooting\n\n### Files Not in Expected Location\n\n- Check `outDir` config\n- Verify build completed successfully\n- Look for typos in path\n\n### Files Deleted Unexpectedly\n\n- Check if `clean: true`\n- Ensure outDir doesn't overlap with source\n- Don't use root as outDir\n\n### Permission Errors\n\n- Check write permissions\n- Ensure directory isn't locked\n- Try different location\n\n## Related Options\n\n- [Cleaning](option-cleaning.md) - Clean output directory\n- [Entry](option-entry.md) - Entry points\n- [Output Format](option-output-format.md) - Module formats\n- [Unbundle](option-unbundle.md) - Preserve structure\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-output-format.md",
    "content": "# Output Format\n\nConfigure the module format(s) for generated bundles.\n\n## Overview\n\ntsdown can generate bundles in multiple formats. Default is ESM.\n\n## Available Formats\n\n| Format | Description | Use Case |\n|--------|-------------|----------|\n| `esm` | ECMAScript Module (default) | Modern Node.js, browsers, Deno |\n| `cjs` | CommonJS | Legacy Node.js, require() |\n| `iife` | Immediately Invoked Function Expression | Browser `<script>` tags |\n| `umd` | Universal Module Definition | AMD, CommonJS, and globals |\n\n## Usage\n\n### CLI\n\n```bash\n# Single format\ntsdown --format esm\n\n# Multiple formats\ntsdown --format esm --format cjs\n\n# Or comma-separated\ntsdown --format esm,cjs\n```\n\n### Config File\n\n#### Single Format\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: 'esm',\n})\n```\n\n#### Multiple Formats\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n})\n```\n\n## Per-Format Configuration\n\nOverride options for specific formats:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: {\n    esm: {\n      target: ['es2015'],\n    },\n    cjs: {\n      target: ['node20'],\n    },\n  },\n})\n```\n\nThis allows different targets, platforms, or other settings per format.\n\n## Common Patterns\n\n### Modern Library (ESM + CJS)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n})\n```\n\nOutput:\n- `dist/index.mjs` (ESM)\n- `dist/index.cjs` (CJS)\n- `dist/index.d.ts` (Types)\n\n### Browser Library (IIFE)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['iife'],\n  globalName: 'MyLib',\n  platform: 'browser',\n  minify: true,\n})\n```\n\nOutput: `dist/index.global.js` (IIFE with global `MyLib`)\n\n### Universal Library (UMD)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['umd'],\n  globalName: 'MyLib',\n  platform: 'neutral',\n})\n```\n\nWorks with AMD, CommonJS, and browser globals.\n\n### Node.js Package (CJS + ESM)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'node',\n  dts: true,\n  shims: true, // Add __dirname, __filename for CJS compat\n})\n```\n\n### Framework Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm', 'cjs'],\n  external: ['react', 'react-dom'], // Don't bundle dependencies\n  dts: true,\n})\n```\n\n## Format-Specific Outputs\n\n### File Extensions\n\n| Format | Extension |\n|--------|-----------|\n| ESM | `.mjs` or `.js` (with `\"type\": \"module\"`) |\n| CJS | `.cjs` or `.js` (without `\"type\": \"module\"`) |\n| IIFE | `.global.js` |\n| UMD | `.umd.js` |\n\n### Customize Extensions\n\nUse `outExtensions` to override:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  outExtensions: ({ format }) => ({\n    js: format === 'esm' ? '.js' : '.cjs',\n  }),\n})\n```\n\n## Tips\n\n1. **Use ESM + CJS** for maximum compatibility\n2. **Use IIFE** for browser-only libraries\n3. **Use UMD** for universal compatibility (less common now)\n4. **Externalize dependencies** to avoid bundling framework code\n5. **Add shims** for CJS compatibility when using Node.js APIs\n6. **Set globalName** for IIFE/UMD formats\n\n## Related Options\n\n- [Target](option-target.md) - Set JavaScript version\n- [Platform](option-platform.md) - Set platform (node, browser, neutral)\n- [Shims](option-shims.md) - Add ESM/CJS compatibility\n- [Output Directory](option-output-directory.md) - Customize output paths\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-package-exports.md",
    "content": "# Auto-Generate Package Exports\n\nAutomatically generate package.json exports field from build output.\n\n## Overview\n\ntsdown can automatically infer and generate the `exports`, `main`, `module`, and `types` fields in your `package.json` based on your build outputs.\n\n**Status:** Experimental - review before publishing.\n\n## Basic Usage\n\n### CLI\n\n```bash\ntsdown --exports\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: true,\n})\n```\n\n## What Gets Generated\n\n### Single Entry\n\n**Config:**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: true,\n})\n```\n\n**Generated in package.json:**\n```json\n{\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  }\n}\n```\n\n### Multiple Entries\n\n**Config:**\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    utils: 'src/utils.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: true,\n})\n```\n\n**Generated in package.json:**\n```json\n{\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    },\n    \"./utils\": {\n      \"types\": \"./dist/utils.d.ts\",\n      \"import\": \"./dist/utils.mjs\",\n      \"require\": \"./dist/utils.cjs\"\n    }\n  }\n}\n```\n\n## Export All Files\n\nInclude all output files, not just entry points:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  exports: {\n    all: true,\n  },\n})\n```\n\n**Result:** All `.mjs`, `.cjs`, and `.d.ts` files will be added to exports.\n\n## Dev-Time Source Linking\n\n### Dev Exports\n\nLink to source files during development:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  exports: {\n    devExports: true,\n  },\n})\n```\n\n**Generated:**\n```json\n{\n  \"exports\": {\n    \".\": \"./src/index.ts\"  // Points to source\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"import\": \"./dist/index.mjs\",\n        \"require\": \"./dist/index.cjs\"\n      }\n    }\n  }\n}\n```\n\n**Note:** Supported by pnpm/yarn, not npm.\n\n### Conditional Dev Exports\n\nUse specific condition for dev exports:\n\n```ts\nexport default defineConfig({\n  exports: {\n    devExports: 'development',\n  },\n})\n```\n\n**Generated:**\n```json\n{\n  \"exports\": {\n    \".\": {\n      \"development\": \"./src/index.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  }\n}\n```\n\n**Use with TypeScript customConditions:**\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"customConditions\": [\"development\"]\n  }\n}\n```\n\n## Custom Exports\n\nAdd custom export mappings:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  exports: {\n    customExports(pkg, context) {\n      // Add custom export\n      pkg['./foo'] = './dist/foo.js'\n\n      // Add package.json export\n      pkg['./package.json'] = './package.json'\n\n      return pkg\n    },\n  },\n})\n```\n\n## Common Patterns\n\n### Complete Library Setup\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: true,\n  clean: true,\n})\n```\n\n### Multiple Exports with Dev Mode\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    client: 'src/client.ts',\n    server: 'src/server.ts',\n  },\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: {\n    all: false,  // Only entries\n    devExports: 'development',\n  },\n})\n```\n\n### Monorepo Package\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  exports: true,  // Generate for each package\n})\n```\n\n## Validation\n\n### Enable Publint\n\nValidate generated exports:\n\n```bash\ntsdown --exports --publint\n```\n\nOr in config:\n\n```ts\nexport default defineConfig({\n  exports: true,\n  publint: true,  // Validate exports\n})\n```\n\n## Tips\n\n1. **Review before publishing** - Check generated fields\n2. **Use with publint** - Validate exports field\n3. **Enable for libraries** - Especially with multiple exports\n4. **Use devExports** - Better DX during development\n5. **Test exports** - Verify imports work correctly\n\n## Troubleshooting\n\n### Exports Not Generated\n\n- Ensure `exports: true` is set\n- Check build completed successfully\n- Verify output files exist\n\n### Wrong Export Paths\n\n- Check `outDir` configuration\n- Verify entry names match expectations\n- Review `format` settings\n\n### Dev Exports Not Working\n\n- Only supported by pnpm/yarn\n- Check package manager\n- Use `publishConfig` for publishing\n\n### Types Not Exported\n\n- Enable `dts: true`\n- Ensure TypeScript is installed\n- Check `.d.ts` files are generated\n\n## CLI Examples\n\n```bash\n# Generate exports\ntsdown --exports\n\n# With publint validation\ntsdown --exports --publint\n\n# Export all files\ntsdown --exports\n\n# With dev exports\ntsdown --exports\n```\n\n## Related Options\n\n- [Entry](option-entry.md) - Configure entry points\n- [Output Format](option-output-format.md) - Module formats\n- [DTS](option-dts.md) - Type declarations\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-platform.md",
    "content": "# Platform\n\nTarget runtime environment for bundled code.\n\n## Overview\n\nPlatform determines the runtime environment and affects module resolution, built-in handling, and optimizations.\n\n## Available Platforms\n\n| Platform | Runtime | Built-ins | Use Case |\n|----------|---------|-----------|----------|\n| `node` | Node.js (default) | Resolved automatically | Server-side, CLIs, tooling |\n| `browser` | Web browsers | Warning if used | Front-end applications |\n| `neutral` | Platform-agnostic | No assumptions | Universal libraries |\n\n## Usage\n\n### CLI\n\n```bash\ntsdown --platform node     # Default\ntsdown --platform browser\ntsdown --platform neutral\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  platform: 'browser',\n})\n```\n\n## Platform Details\n\n### Node Platform\n\n**Default platform** for server-side and tooling.\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  platform: 'node',\n})\n```\n\n**Characteristics:**\n- Node.js built-ins (fs, path, etc.) resolved automatically\n- Optimized for Node.js runtime\n- Compatible with Deno and Bun\n- Default mainFields: `['main', 'module']`\n\n### Browser Platform\n\nFor web applications running in browsers.\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  platform: 'browser',\n  format: ['esm'],\n})\n```\n\n**Characteristics:**\n- Warnings if Node.js built-ins are used\n- May require polyfills for Node APIs\n- Optimized for browser environments\n- Default mainFields: `['browser', 'module', 'main']`\n\n### Neutral Platform\n\nPlatform-agnostic for universal libraries.\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  platform: 'neutral',\n  format: ['esm'],\n})\n```\n\n**Characteristics:**\n- No runtime assumptions\n- No automatic built-in resolution\n- Relies on `exports` field only\n- Default mainFields: `[]`\n- Full control over runtime behavior\n\n## CJS Format Limitation\n\n**CJS format always uses `node` platform** and cannot be changed.\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['cjs'],\n  platform: 'browser',  // Ignored for CJS\n})\n```\n\nSee [rolldown PR #4693](https://github.com/rolldown/rolldown/pull/4693#issuecomment-2912229545) for details.\n\n## Module Resolution\n\n### Main Fields\n\nDifferent platforms check different `package.json` fields:\n\n| Platform | mainFields | Priority |\n|----------|------------|----------|\n| `node` | `['main', 'module']` | main → module |\n| `browser` | `['browser', 'module', 'main']` | browser → module → main |\n| `neutral` | `[]` | Only `exports` field |\n\n### Neutral Platform Resolution\n\nWhen using `neutral`, packages without `exports` field may fail to resolve:\n\n```\nHelp: The \"main\" field here was ignored. Main fields must be configured\nexplicitly when using the \"neutral\" platform.\n```\n\n**Solution:** Configure mainFields explicitly:\n\n```ts\nexport default defineConfig({\n  platform: 'neutral',\n  inputOptions: {\n    resolve: {\n      mainFields: ['module', 'main'],\n    },\n  },\n})\n```\n\n## Common Patterns\n\n### Node.js CLI Tool\n\n```ts\nexport default defineConfig({\n  entry: ['src/cli.ts'],\n  format: ['esm'],\n  platform: 'node',\n  shims: true,\n})\n```\n\n### Browser Library (IIFE)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['iife'],\n  platform: 'browser',\n  globalName: 'MyLib',\n  minify: true,\n})\n```\n\n### Universal Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  platform: 'neutral',\n  inputOptions: {\n    resolve: {\n      mainFields: ['module', 'main'],\n    },\n  },\n})\n```\n\n### React Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm', 'cjs'],\n  platform: 'browser',\n  external: ['react', 'react-dom'],\n})\n```\n\n### Node.js + Browser Builds\n\n```ts\nexport default defineConfig([\n  {\n    entry: ['src/index.ts'],\n    format: ['esm', 'cjs'],\n    platform: 'node',\n  },\n  {\n    entry: ['src/browser.ts'],\n    format: ['esm'],\n    platform: 'browser',\n  },\n])\n```\n\n## Troubleshooting\n\n### Node Built-in Warnings (Browser)\n\nWhen using Node.js APIs in browser builds:\n\n```\nWarning: Module \"fs\" has been externalized for browser compatibility\n```\n\n**Solutions:**\n1. Use platform: 'node' if not browser-only\n2. Add polyfills for Node APIs\n3. Avoid Node.js built-ins in browser code\n4. Use platform: 'neutral' with careful dependency management\n\n### Module Resolution Issues (Neutral)\n\nWhen packages don't resolve with `neutral`:\n\n```ts\nexport default defineConfig({\n  platform: 'neutral',\n  inputOptions: {\n    resolve: {\n      mainFields: ['module', 'browser', 'main'],\n      conditions: ['import', 'require'],\n    },\n  },\n})\n```\n\n## Tips\n\n1. **Use `node`** for server-side and CLIs (default)\n2. **Use `browser`** for front-end applications\n3. **Use `neutral`** for universal libraries\n4. **Configure mainFields** when using neutral platform\n5. **CJS is always node** - use ESM for other platforms\n6. **Test in target environment** to verify compatibility\n\n## Related Options\n\n- [Output Format](option-output-format.md) - Module formats\n- [Target](option-target.md) - JavaScript version\n- [Shims](option-shims.md) - ESM/CJS compatibility\n- [Dependencies](option-dependencies.md) - External packages\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-shims.md",
    "content": "# Shims\n\nAdd compatibility between ESM and CommonJS module systems.\n\n## Overview\n\nShims provide small pieces of code that bridge the gap between CommonJS (CJS) and ECMAScript Modules (ESM), enabling cross-module-system compatibility.\n\n## What Shims Provide\n\n### ESM Output (when enabled)\n\nWith `shims: true`, adds CommonJS variables to ESM:\n\n- `__dirname` - Current directory path\n- `__filename` - Current file path\n\n### ESM Output (automatic)\n\nAlways added when using `require` in ESM on Node.js:\n\n- `require` function via `createRequire(import.meta.url)`\n\n### CJS Output (automatic)\n\nAlways added to CommonJS output:\n\n- `import.meta.url`\n- `import.meta.dirname`\n- `import.meta.filename`\n\n## Usage\n\n### CLI\n\n```bash\ntsdown --shims\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  shims: true,\n})\n```\n\n## Generated Code\n\n### ESM with Shims\n\n**Source:**\n```ts\nconsole.log(__dirname)\nconsole.log(__filename)\n```\n\n**Output (shims: true):**\n```js\nimport { fileURLToPath } from 'node:url'\nimport { dirname } from 'node:path'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\nconsole.log(__dirname)\nconsole.log(__filename)\n```\n\n### ESM with require\n\n**Source:**\n```ts\nconst mod = require('some-module')\n```\n\n**Output (automatic on Node.js):**\n```js\nimport { createRequire } from 'node:module'\nconst require = createRequire(import.meta.url)\n\nconst mod = require('some-module')\n```\n\n### CJS with import.meta\n\n**Source:**\n```ts\nconsole.log(import.meta.url)\nconsole.log(import.meta.dirname)\n```\n\n**Output (automatic):**\n```js\nconst import_meta = {\n  url: require('url').pathToFileURL(__filename).toString(),\n  dirname: __dirname,\n  filename: __filename\n}\n\nconsole.log(import_meta.url)\nconsole.log(import_meta.dirname)\n```\n\n## Common Patterns\n\n### Node.js CLI Tool\n\n```ts\nexport default defineConfig({\n  entry: ['src/cli.ts'],\n  format: ['esm'],\n  platform: 'node',\n  shims: true,  // Add __dirname, __filename\n})\n```\n\n### Dual Format Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'node',\n  shims: true,  // ESM gets __dirname/__filename\n                // CJS gets import.meta.* (automatic)\n})\n```\n\n### Server-Side Code\n\n```ts\nexport default defineConfig({\n  entry: ['src/server.ts'],\n  format: ['esm'],\n  platform: 'node',\n  shims: true,\n  external: [/.*/],  // External all deps\n})\n```\n\n### File System Operations\n\n```ts\n// Source code\nimport { readFileSync } from 'fs'\nimport { join } from 'path'\n\n// Read file relative to current module\nconst content = readFileSync(join(__dirname, 'data.json'), 'utf-8')\n```\n\n```ts\n// tsdown config\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  shims: true,  // Enables __dirname\n})\n```\n\n## When to Use Shims\n\n### Use `shims: true` when:\n\n- ✅ Building Node.js tools/CLIs\n- ✅ Code uses `__dirname` or `__filename`\n- ✅ Need file system operations relative to module\n- ✅ Migrating from CommonJS to ESM\n- ✅ Need cross-format compatibility\n\n### Don't need shims when:\n\n- ❌ Browser-only code\n- ❌ No file system operations\n- ❌ Using only `import.meta.url`\n- ❌ Pure ESM without CJS variables\n\n## Performance Impact\n\n### Runtime Overhead\n\nShims add minimal runtime overhead:\n\n```js\n// Added to output when shims enabled\nimport { fileURLToPath } from 'node:url'\nimport { dirname } from 'node:path'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n```\n\n### Tree Shaking\n\nIf `__dirname` or `__filename` are not used, they're automatically removed during bundling (no overhead).\n\n## Platform Considerations\n\n### Node.js Platform\n\n```ts\nexport default defineConfig({\n  platform: 'node',\n  format: ['esm'],\n  shims: true,  // Recommended for Node.js\n})\n```\n\n- `require` shim added automatically\n- `__dirname` and `__filename` available with `shims: true`\n\n### Browser Platform\n\n```ts\nexport default defineConfig({\n  platform: 'browser',\n  format: ['esm'],\n  shims: false,  // Not needed for browser\n})\n```\n\n- Shims not needed (no Node.js variables)\n- Will cause warnings if Node.js APIs used\n\n### Neutral Platform\n\n```ts\nexport default defineConfig({\n  platform: 'neutral',\n  format: ['esm'],\n  shims: false,  // Avoid platform-specific code\n})\n```\n\n- Avoid shims for maximum portability\n\n## CLI Examples\n\n```bash\n# Enable shims\ntsdown --shims\n\n# ESM with shims for Node.js\ntsdown --format esm --platform node --shims\n\n# Dual format with shims\ntsdown --format esm --format cjs --shims\n```\n\n## Troubleshooting\n\n### `__dirname is not defined`\n\nEnable shims:\n\n```ts\nexport default defineConfig({\n  shims: true,\n})\n```\n\n### `require is not defined` in ESM\n\nAutomatic on Node.js platform. If not working:\n\n```ts\nexport default defineConfig({\n  platform: 'node',  // Ensure Node.js platform\n})\n```\n\n### Import.meta not working in CJS\n\nAutomatic - no configuration needed. If still failing, check output format:\n\n```ts\nexport default defineConfig({\n  format: ['cjs'],  // Shims added automatically\n})\n```\n\n## Tips\n\n1. **Enable for Node.js tools** - Use `shims: true` for CLIs and servers\n2. **Skip for browsers** - Not needed for browser code\n3. **No overhead if unused** - Automatically tree-shaken\n4. **Automatic require shim** - No config needed for `require` in ESM\n5. **CJS shims automatic** - `import.meta.*` always available in CJS\n\n## Related Options\n\n- [Platform](option-platform.md) - Runtime environment\n- [Output Format](option-output-format.md) - Module formats\n- [Target](option-target.md) - Syntax transformations\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-sourcemap.md",
    "content": "# Source Maps\n\nGenerate source maps for debugging bundled code.\n\n## Overview\n\nSource maps map minified/bundled code back to original source files, making debugging significantly easier by showing original line numbers and variable names.\n\n## Basic Usage\n\n### CLI\n\n```bash\ntsdown --sourcemap\n\n# Or inline\ntsdown --sourcemap inline\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  sourcemap: true,\n})\n```\n\n## Source Map Types\n\n### External (default)\n\nGenerates separate `.map` files:\n\n```ts\nexport default defineConfig({\n  sourcemap: true,  // or 'external'\n})\n```\n\n**Output:**\n- `dist/index.mjs`\n- `dist/index.mjs.map`\n\n**Pros:**\n- Smaller bundle size\n- Can be excluded from production\n- Faster parsing\n\n### Inline\n\nEmbeds source maps in the bundle:\n\n```ts\nexport default defineConfig({\n  sourcemap: 'inline',\n})\n```\n\n**Output:**\n- `dist/index.mjs` (includes source map as data URL)\n\n**Pros:**\n- Single file deployment\n- Guaranteed to be available\n\n**Cons:**\n- Larger bundle size\n- Exposed in production\n\n### Hidden\n\nGenerates map files without reference comment:\n\n```ts\nexport default defineConfig({\n  sourcemap: 'hidden',\n})\n```\n\n**Output:**\n- `dist/index.mjs` (no `//# sourceMappingURL` comment)\n- `dist/index.mjs.map`\n\n**Use when:**\n- You want maps for error reporting tools\n- But don't want them exposed to users\n\n## Auto-Enable Scenarios\n\n### Declaration Maps\n\nIf `declarationMap` is enabled in `tsconfig.json`, source maps are automatically enabled:\n\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"declarationMap\": true\n  }\n}\n```\n\nThis also generates `.d.ts.map` files for TypeScript declarations.\n\n## Common Patterns\n\n### Development Build\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  sourcemap: options.watch,  // Only in dev\n  minify: !options.watch,\n}))\n```\n\n### Production with External Maps\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  sourcemap: true,  // External maps\n  minify: true,\n})\n```\n\nDeploy maps to separate error reporting service.\n\n### Always Inline (Development Tool)\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  sourcemap: 'inline',\n})\n```\n\n### Per-Format Source Maps\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: {\n    esm: {\n      sourcemap: true,\n    },\n    iife: {\n      sourcemap: 'inline',  // Inline for browser\n    },\n  },\n})\n```\n\n### TypeScript Library with Declaration Maps\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  sourcemap: true,\n  dts: {\n    sourcemap: true,  // Enable declaration maps\n  },\n})\n```\n\n**Output:**\n- `dist/index.mjs` + `dist/index.mjs.map`\n- `dist/index.cjs` + `dist/index.cjs.map`\n- `dist/index.d.ts` + `dist/index.d.ts.map`\n\n## Benefits\n\n### For Development\n\n- **Faster debugging** - See original code in debugger\n- **Better error messages** - Stack traces show original lines\n- **Easier breakpoints** - Set breakpoints on source code\n\n### For Production\n\n- **Error reporting** - Send accurate error locations to services\n- **Monitoring** - Track errors back to source\n- **Support** - Help users report issues accurately\n\n## Performance Impact\n\n| Type | Bundle Size | Parse Speed | Debugging |\n|------|-------------|-------------|-----------|\n| None | Smallest | Fastest | Hard |\n| External | Small | Fast | Easy |\n| Inline | Largest | Slower | Easy |\n| Hidden | Small | Fast | Tools only |\n\n## CLI Examples\n\n```bash\n# Enable source maps\ntsdown --sourcemap\n\n# Inline source maps\ntsdown --sourcemap inline\n\n# Hidden source maps\ntsdown --sourcemap hidden\n\n# Development with source maps\ntsdown --watch --sourcemap\n\n# Production with external maps\ntsdown --minify --sourcemap\n\n# No source maps\ntsdown --no-sourcemap\n```\n\n## Use Cases\n\n### Local Development\n\n```ts\nexport default defineConfig({\n  sourcemap: true,\n  minify: false,\n})\n```\n\n### Production Build\n\n```ts\nexport default defineConfig({\n  sourcemap: 'external',  // Upload to error service\n  minify: true,\n})\n```\n\n### Browser Library\n\n```ts\nexport default defineConfig({\n  format: ['iife'],\n  platform: 'browser',\n  sourcemap: 'inline',  // Self-contained\n  globalName: 'MyLib',\n})\n```\n\n### Node.js CLI Tool\n\n```ts\nexport default defineConfig({\n  format: ['esm'],\n  platform: 'node',\n  sourcemap: true,\n  shims: true,\n})\n```\n\n## Troubleshooting\n\n### Source Maps Not Working\n\n1. **Check output** - Verify `.map` files are generated\n2. **Check reference** - Look for `//# sourceMappingURL=` comment\n3. **Check paths** - Ensure relative paths are correct\n4. **Check tool** - Verify debugger/browser supports source maps\n\n### Large Bundle Size\n\nUse external source maps instead of inline:\n\n```ts\nexport default defineConfig({\n  sourcemap: true,  // Not 'inline'\n})\n```\n\n### Source Not Found\n\n- Ensure source files are accessible relative to map\n- Check `sourceRoot` in generated map\n- Verify paths in `sources` array\n\n## Tips\n\n1. **Use external maps** for production (smaller bundles)\n2. **Use inline maps** for single-file tools\n3. **Enable in development** for better DX\n4. **Upload to error services** for production debugging\n5. **Use hidden maps** when you want them for tools only\n6. **Enable declaration maps** for TypeScript libraries\n\n## Related Options\n\n- [Minification](option-minification.md) - Code compression\n- [DTS](option-dts.md) - TypeScript declarations\n- [Watch Mode](option-watch-mode.md) - Development workflow\n- [Target](option-target.md) - Syntax transformations\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-target.md",
    "content": "# Target Environment\n\nConfigure JavaScript syntax transformations for target environments.\n\n## Overview\n\nThe `target` option controls which JavaScript features are downleveled (transformed to older syntax) for compatibility.\n\n**Important:** Only affects syntax transformations, not runtime polyfills.\n\n## Default Behavior\n\ntsdown auto-reads from `package.json`:\n\n```json\n// package.json\n{\n  \"engines\": {\n    \"node\": \">=18.0.0\"\n  }\n}\n```\n\nAutomatically sets `target` to `node18.0.0`.\n\nIf no `engines.node` field exists, behaves as if `target: false` (no transformations).\n\n## Disabling Transformations\n\nSet to `false` to preserve modern syntax:\n\n```ts\nexport default defineConfig({\n  target: false,\n})\n```\n\n**Result:**\n- No JavaScript downleveling\n- Modern features preserved (optional chaining `?.`, nullish coalescing `??`, etc.)\n\n**Use when:**\n- Targeting modern environments\n- Handling transformations elsewhere\n- Building libraries for further processing\n\n## Setting Target\n\n### CLI\n\n```bash\n# Single target\ntsdown --target es2020\ntsdown --target node20\n\n# Multiple targets\ntsdown --target chrome100 --target node20.18\n\n# Disable\ntsdown --no-target\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  target: 'es2020',\n})\n```\n\n### Multiple Targets\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  target: ['chrome100', 'safari15', 'node18'],\n})\n```\n\n## Supported Targets\n\n### ECMAScript Versions\n\n- `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`, `es2021`, `es2022`, `es2023`, `esnext`\n\n### Browser Versions\n\n- `chrome100`, `safari18`, `firefox110`, `edge100`, etc.\n\n### Node.js Versions\n\n- `node16`, `node18`, `node20`, `node20.18`, etc.\n\n## Examples\n\n### Modern Browsers\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  target: ['chrome100', 'safari15', 'firefox100'],\n})\n```\n\n### Node.js Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  target: 'node18',\n})\n```\n\n### Legacy Support\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  target: 'es2015',  // Maximum compatibility\n})\n```\n\n### Per-Format Targets\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: {\n    esm: {\n      target: 'es2020',\n    },\n    cjs: {\n      target: 'node16',\n    },\n  },\n})\n```\n\n## Decorators\n\n### Legacy Decorators (Stage 2)\n\nEnable in `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true\n  }\n}\n```\n\n### Stage 3 Decorators\n\n**Not currently supported** by tsdown/Rolldown/Oxc.\n\nSee [oxc issue #9170](https://github.com/oxc-project/oxc/issues/9170).\n\n## Common Patterns\n\n### Universal Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  target: 'es2020',  // Wide compatibility\n})\n```\n\n### Modern-Only Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  target: false,  // No transformations\n})\n```\n\n### Browser Component\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm'],\n  target: ['chrome100', 'safari15', 'firefox100'],\n  platform: 'browser',\n})\n```\n\n## Tips\n\n1. **Let tsdown auto-detect** from package.json when possible\n2. **Use `false`** for modern-only builds\n3. **Specify multiple targets** for broader compatibility\n4. **Use legacy decorators** with `experimentalDecorators`\n6. **Test output** in target environments\n\n## Related Options\n\n- [Platform](option-platform.md) - Runtime environment\n- [Output Format](option-output-format.md) - Module formats\n- [Minification](option-minification.md) - Code optimization\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-tree-shaking.md",
    "content": "# Tree Shaking\n\nRemove unused code from bundles.\n\n## Overview\n\nTree shaking eliminates dead code (unused exports) from your final bundle, reducing size and improving performance.\n\n**Default:** Enabled\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Tree shaking enabled (default)\ntsdown\n\n# Disable tree shaking\ntsdown --no-treeshake\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  treeshake: true,  // Default\n})\n```\n\n## How It Works\n\n### With Tree Shaking\n\n**Source:**\n```ts\n// src/util.ts\nexport function unused() {\n  console.log(\"I'm unused\")\n}\n\nexport function hello(x: number) {\n  console.log('Hello World', x)\n}\n\n// src/index.ts\nimport { hello } from './util'\nhello(1)\n```\n\n**Output:**\n```js\n// dist/index.mjs\nfunction hello(x) {\n  console.log('Hello World', x)\n}\nhello(1)\n```\n\n`unused()` function is removed because it's never imported.\n\n### Without Tree Shaking\n\n**Output:**\n```js\n// dist/index.mjs\nfunction unused() {\n  console.log(\"I'm unused\")\n}\n\nfunction hello(x) {\n  console.log('Hello World', x)\n}\nhello(1)\n```\n\nAll code is included, even if unused.\n\n## Advanced Configuration\n\n### Enable (Default)\n\n```ts\nexport default defineConfig({\n  treeshake: true,\n})\n```\n\nUses Rolldown's default tree shaking.\n\n### Custom Options\n\n```ts\nexport default defineConfig({\n  treeshake: {\n    moduleSideEffects: false,\n    propertyReadSideEffects: false,\n    unknownGlobalSideEffects: false,\n  },\n})\n```\n\nSee [Rolldown docs](https://rolldown.rs/reference/config-options#treeshake) for all options.\n\n### Disable\n\n```ts\nexport default defineConfig({\n  treeshake: false,\n})\n```\n\n## Side Effects\n\n### Package.json sideEffects\n\nDeclare side effects in your package:\n\n```json\n{\n  \"sideEffects\": false\n}\n```\n\nOr specify files with side effects:\n\n```json\n{\n  \"sideEffects\": [\"*.css\", \"src/polyfills.ts\"]\n}\n```\n\n### Module Side Effects\n\n```ts\nexport default defineConfig({\n  treeshake: {\n    moduleSideEffects: (id) => {\n      // Preserve side effects for polyfills\n      return id.includes('polyfill')\n    },\n  },\n})\n```\n\n## Common Patterns\n\n### Production Build\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  treeshake: true,\n  minify: true,\n})\n```\n\n### Development Build\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  treeshake: !options.watch,  // Disable in dev\n}))\n```\n\n### Library with Side Effects\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  treeshake: {\n    moduleSideEffects: (id) => {\n      return (\n        id.includes('.css') ||\n        id.includes('polyfill') ||\n        id.includes('side-effect')\n      )\n    },\n  },\n})\n```\n\n### Utilities Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  treeshake: true,\n  dts: true,\n})\n```\n\nUsers can import only what they need:\n```ts\nimport { onlyWhatINeed } from 'my-utils'\n```\n\n## Benefits\n\n### Smaller Bundles\n\n- Only includes imported code\n- Removes unused functions, classes, variables\n- Reduces download size\n\n### Better Performance\n\n- Less code to parse\n- Faster execution\n- Improved loading times\n\n### Cleaner Output\n\n- No dead code in production\n- Easier to debug\n- Better maintainability\n\n## When to Disable\n\n### Debugging\n\nDuring development to see all code:\n\n```ts\nexport default defineConfig((options) => ({\n  treeshake: !options.watch,\n}))\n```\n\n### Side Effect Code\n\nCode with global side effects:\n\n```ts\n// This has side effects\nwindow.myGlobal = {}\n\nexport function setup() {\n  // ...\n}\n```\n\nDisable tree shaking or mark side effects:\n\n```json\n{\n  \"sideEffects\": true\n}\n```\n\n### Testing\n\nInclude all code for coverage:\n\n```ts\nexport default defineConfig({\n  treeshake: false,\n})\n```\n\n## Tips\n\n1. **Leave enabled** for production builds\n2. **Mark side effects** in package.json\n3. **Use with minification** for best results\n4. **Test tree shaking** - verify unused code is removed\n5. **Disable for debugging** if needed\n6. **Pure functions** are easier to tree shake\n\n## Troubleshooting\n\n### Code Still Included\n\n- Check for side effects\n- Verify imports are ES modules\n- Ensure code is actually unused\n- Check `sideEffects` in package.json\n\n### Missing Code at Runtime\n\n- Code has side effects but marked as none\n- Set `sideEffects: true` or list specific files\n\n### Unexpected Behavior\n\n- Module has side effects not declared\n- Try disabling tree shaking to isolate issue\n\n## Examples\n\n### Pure Utility Functions\n\n```ts\n// utils.ts - perfect for tree shaking\nexport function add(a, b) {\n  return a + b\n}\n\nexport function multiply(a, b) {\n  return a * b\n}\n\n// Only 'add' imported = only 'add' bundled\nimport { add } from './utils'\n```\n\n### With Side Effects\n\n```ts\n// polyfill.ts - has side effects\nif (!Array.prototype.at) {\n  Array.prototype.at = function(index) {\n    // polyfill implementation\n  }\n}\n\nexport {} // Need to export something\n```\n\n```json\n{\n  \"sideEffects\": [\"src/polyfill.ts\"]\n}\n```\n\n## Related Options\n\n- [Minification](option-minification.md) - Code compression\n- [Target](option-target.md) - Syntax transformations\n- [Dependencies](option-dependencies.md) - External packages\n- [Output Format](option-output-format.md) - Module formats\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-unbundle.md",
    "content": "# Unbundle Mode\n\nPreserve source directory structure in output.\n\n## Overview\n\nUnbundle mode (also called \"bundleless\" or \"transpile-only\") outputs files that mirror your source structure, rather than bundling everything into single files. Each source file is compiled individually with a one-to-one mapping.\n\n## Basic Usage\n\n### CLI\n\n```bash\ntsdown --unbundle\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts', '!**/*.test.ts'],\n  unbundle: true,\n})\n```\n\n## How It Works\n\n### Source Structure\n\n```\nsrc/\n├── index.ts\n├── utils/\n│   ├── helper.ts\n│   └── format.ts\n└── components/\n    └── button.ts\n```\n\n### With Unbundle\n\n**Config:**\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  unbundle: true,\n})\n```\n\n**Output:**\n```\ndist/\n├── index.mjs\n├── utils/\n│   ├── helper.mjs\n│   └── format.mjs\n└── components/\n    └── button.mjs\n```\n\nAll imported files are output individually, preserving structure.\n\n### Without Unbundle (Default)\n\n**Output:**\n```\ndist/\n└── index.mjs  (all code bundled together)\n```\n\n## When to Use\n\n### Use Unbundle When:\n\n✅ Building monorepo packages with shared utilities\n✅ Users need to import individual modules\n✅ Want clear source-to-output mapping\n✅ Library with many independent utilities\n✅ Debugging requires tracing specific files\n✅ Incremental builds for faster development\n\n### Use Standard Bundling When:\n\n❌ Single entry point application\n❌ Want to optimize bundle size\n❌ Need aggressive tree shaking\n❌ Creating IIFE/UMD bundles\n❌ Deploying to browsers directly\n\n## Common Patterns\n\n### Utility Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts', '!**/*.test.ts'],\n  format: ['esm', 'cjs'],\n  unbundle: true,\n  dts: true,\n})\n```\n\n**Benefits:**\n- Users import only what they need\n- Tree shaking still works at user's build\n- Clear module boundaries\n\n**Usage:**\n```ts\n// Users can import specific utilities\nimport { helper } from 'my-lib/utils/helper'\nimport { Button } from 'my-lib/components/button'\n```\n\n### Monorepo Shared Package\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  unbundle: true,\n  outDir: 'dist',\n})\n```\n\n### TypeScript Compilation Only\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts'],\n  format: ['esm'],\n  unbundle: true,\n  minify: false,\n  treeshake: false,\n  dts: true,\n})\n```\n\nPure TypeScript to JavaScript transformation.\n\n### Development Mode\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/**/*.ts'],\n  unbundle: options.watch,  // Unbundle in dev only\n  minify: !options.watch,\n}))\n```\n\nFast rebuilds during development, optimized for production.\n\n## With Entry Patterns\n\n### Include/Exclude\n\n```ts\nexport default defineConfig({\n  entry: [\n    'src/**/*.ts',\n    '!**/*.test.ts',\n    '!**/*.spec.ts',\n    '!**/fixtures/**',\n  ],\n  unbundle: true,\n})\n```\n\n### Multiple Entry Points\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    cli: 'src/cli.ts',\n  },\n  unbundle: true,\n})\n```\n\nBoth entry files and all imports preserved.\n\n## Output Control\n\n### Custom Extension\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts'],\n  unbundle: true,\n  outExtensions: () => ({ js: '.js' }),\n})\n```\n\n### Preserve Directory\n\n```ts\nexport default defineConfig({\n  entry: ['src/**/*.ts'],\n  unbundle: true,\n  outDir: 'lib',\n})\n```\n\n**Output:**\n```\nlib/\n├── index.js\n├── utils/\n│   └── helper.js\n└── components/\n    └── button.js\n```\n\n## Package.json Setup\n\n```json\n{\n  \"name\": \"my-library\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": \"./dist/index.js\",\n    \"./utils/*\": \"./dist/utils/*.js\",\n    \"./components/*\": \"./dist/components/*.js\"\n  },\n  \"files\": [\"dist\"]\n}\n```\n\nOr use `exports: true` to auto-generate.\n\n## Comparison\n\n| Feature | Bundled | Unbundled |\n|---------|---------|-----------|\n| Output files | Few | Many |\n| File size | Smaller | Larger |\n| Build speed | Slower | Faster |\n| Tree shaking | Build time | User's build |\n| Source mapping | Complex | Simple |\n| Module imports | Entry only | Any module |\n| Dev rebuilds | Slower | Faster |\n\n## Performance\n\n### Build Speed\n\nUnbundle is typically faster:\n- No bundling overhead\n- Parallel file processing\n- Incremental builds possible\n\n### Bundle Size\n\nUnbundle produces larger output:\n- Each file has its own overhead\n- No cross-module optimizations\n- User's bundler handles final optimization\n\n## Tips\n\n1. **Use with glob patterns** for multiple files\n2. **Enable in development** for faster rebuilds\n3. **Let users bundle** for production optimization\n4. **Preserve structure** for utilities/components\n5. **Combine with DTS** for type definitions\n6. **Use with monorepos** for shared code\n\n## Troubleshooting\n\n### Too Many Files\n\n- Adjust entry patterns\n- Exclude unnecessary files\n- Use specific entry points\n\n### Missing Files\n\n- Check entry patterns\n- Verify files are imported\n- Look for excluded patterns\n\n### Import Paths Wrong\n\n- Check relative paths\n- Verify output structure\n- Update package.json exports\n\n## CLI Examples\n\n```bash\n# Enable unbundle\ntsdown --unbundle\n\n# With specific entry\ntsdown src/**/*.ts --unbundle\n\n# With other options\ntsdown --unbundle --format esm --dts\n```\n\n## Related Options\n\n- [Entry](option-entry.md) - Entry patterns\n- [Output Directory](option-output-directory.md) - Output location\n- [Output Format](option-output-format.md) - Module formats\n- [DTS](option-dts.md) - Type declarations\n"
  },
  {
    "path": ".agents/skills/tsdown/references/option-watch-mode.md",
    "content": "# Watch Mode\n\nAutomatically rebuild when files change.\n\n## Overview\n\nWatch mode monitors your source files and rebuilds automatically on changes, streamlining the development workflow.\n\n## Basic Usage\n\n### CLI\n\n```bash\n# Watch all project files\ntsdown --watch\n\n# Or use short flag\ntsdown -w\n\n# Watch specific directory\ntsdown --watch ./src\n\n# Watch specific file\ntsdown --watch ./src/index.ts\n```\n\n### Config File\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: true,\n})\n```\n\n## Watch Options\n\n### Ignore Paths\n\nIgnore specific paths in watch mode:\n\n```bash\ntsdown --watch --ignore-watch test --ignore-watch '**/*.test.ts'\n```\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: {\n    exclude: ['test/**', '**/*.test.ts'],\n  },\n})\n```\n\n### On Success Command\n\nRun command after successful build:\n\n```bash\ntsdown --watch --on-success \"echo Build complete!\"\ntsdown --watch --on-success \"node dist/index.mjs\"\n```\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: true,\n  onSuccess: 'node dist/index.mjs',\n})\n```\n\n## Watch Behavior\n\n### Default Watch Targets\n\nBy default, tsdown watches:\n- All entry files\n- All imported files\n- Config file (triggers restart)\n\n### File Change Handling\n\n- **Source files** - Incremental rebuild\n- **Config file** - Full restart with cache clear\n- **Dependencies** - Rebuild if imported\n\n### Keyboard Shortcuts\n\nDuring watch mode:\n- `r` - Manual rebuild\n- `q` - Quit watch mode\n\n## Common Patterns\n\n### Development Mode\n\n```ts\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  watch: options.watch,\n  sourcemap: options.watch,\n  minify: !options.watch,\n}))\n```\n\n### With Post-Build Script\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: true,\n  onSuccess: 'npm run test',\n})\n```\n\n### Multiple Entry Points\n\n```ts\nexport default defineConfig({\n  entry: {\n    main: 'src/index.ts',\n    cli: 'src/cli.ts',\n  },\n  watch: true,\n  clean: false,  // Don't clean on each rebuild\n})\n```\n\n### Test Runner Integration\n\n```bash\n# Watch and run tests on change\ntsdown --watch --on-success \"vitest run\"\n\n# Watch and start dev server\ntsdown --watch --on-success \"node dist/server.mjs\"\n```\n\n### Monorepo Package\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  watch: true,\n  watch: {\n    exclude: ['**/test/**', '**/*.spec.ts'],\n  },\n})\n```\n\n## Advanced Configuration\n\n### Custom Watch Options\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  watch: {\n    include: ['src/**'],\n    exclude: ['**/*.test.ts', '**/fixtures/**'],\n    skipWrite: false,\n  },\n})\n```\n\n### Conditional Watch\n\n```ts\nexport default defineConfig((options) => {\n  const isDev = options.watch\n\n  return {\n    entry: ['src/index.ts'],\n    format: ['esm'],\n    dts: !isDev,        // Skip DTS in watch mode\n    sourcemap: isDev,\n    clean: !isDev,\n  }\n})\n```\n\n## CLI Examples\n\n```bash\n# Basic watch\ntsdown -w\n\n# Watch with source maps\ntsdown -w --sourcemap\n\n# Watch without cleaning\ntsdown -w --no-clean\n\n# Watch and run on success\ntsdown -w --on-success \"npm test\"\n\n# Watch specific format\ntsdown -w --format esm\n\n# Watch with minification\ntsdown -w --minify\n\n# Watch and ignore test files\ntsdown -w --ignore-watch '**/*.test.ts'\n```\n\n## Tips\n\n1. **Use watch mode** for active development\n2. **Skip DTS generation** in watch for faster rebuilds\n3. **Disable clean** to avoid unnecessary file operations\n4. **Use onSuccess** for post-build tasks\n5. **Ignore test files** to avoid unnecessary rebuilds\n6. **Use keyboard shortcuts** for manual control\n\n## Troubleshooting\n\n### Watch Not Detecting Changes\n\n- Check file is in entry or imported chain\n- Verify path is not in `exclude` patterns\n- Ensure file system supports watching\n\n### Too Many Rebuilds\n\nAdd ignore patterns:\n\n```ts\nexport default defineConfig({\n  watch: {\n    exclude: [\n      '**/node_modules/**',\n      '**/.git/**',\n      '**/dist/**',\n      '**/*.test.ts',\n    ],\n  },\n})\n```\n\n### Slow Rebuilds\n\n- Skip DTS in watch mode: `dts: !options.watch`\n- Disable minification: `minify: false`\n- Use smaller entry set during development\n\n### Config Changes Not Applied\n\nConfig file changes trigger full restart automatically.\n\n### Why Not Stub Mode?\n\ntsdown does not support stub mode. Watch mode is the recommended alternative for rapid development, providing instant rebuilds without the drawbacks of stub mode.\n\n## Related Options\n\n- [On Success](reference-cli.md#on-success-command) - Post-build commands\n- [Sourcemap](option-sourcemap.md) - Debug information\n- [Clean](option-cleaning.md) - Output directory cleaning\n"
  },
  {
    "path": ".agents/skills/tsdown/references/recipe-react.md",
    "content": "# React Support\n\nBuild React component libraries with tsdown.\n\n## Overview\n\ntsdown provides first-class support for React libraries. Rolldown natively supports JSX/TSX, so no additional plugins are required for basic React components.\n\n## Quick Start\n\n### Use Starter Template\n\n```bash\n# Basic React library\nnpx create-tsdown@latest -t react\n\n# With React Compiler\nnpx create-tsdown@latest -t react-compiler\n```\n\n## Basic Configuration\n\n### Minimal Setup\n\n```ts\n// tsdown.config.ts\nexport default defineConfig({\n  entry: ['./src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'neutral',\n  external: ['react', 'react-dom'],\n  dts: true,\n})\n```\n\n### Component Example\n\n```tsx\n// src/MyButton.tsx\nimport React from 'react'\n\ninterface MyButtonProps {\n  type?: 'primary' | 'secondary'\n  onClick?: () => void\n}\n\nexport const MyButton: React.FC<MyButtonProps> = ({ type = 'primary', onClick }) => {\n  return (\n    <button className={`btn btn-${type}`} onClick={onClick}>\n      Click me\n    </button>\n  )\n}\n```\n\n```ts\n// src/index.ts\nexport { MyButton } from './MyButton'\n```\n\n## JSX Transform\n\n### Automatic (Default)\n\nModern JSX transform (React 17+):\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  // Automatic JSX is default\n})\n```\n\n**Characteristics:**\n- No `import React` needed\n- Smaller bundle size\n- React 17+ required\n\n### Classic\n\nLegacy JSX transform:\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  inputOptions: {\n    transform: {\n      jsx: 'react',  // Classic transform\n    },\n  },\n})\n```\n\n**Characteristics:**\n- Requires `import React from 'react'`\n- Compatible with older React versions\n\n## React Compiler\n\nReact Compiler automatically optimizes React code at build time.\n\n### Install Dependencies\n\n```bash\npnpm add -D @rollup/plugin-babel babel-plugin-react-compiler\n```\n\n### Configure\n\n```ts\nimport pluginBabel from '@rollup/plugin-babel'\n\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  format: ['esm', 'cjs'],\n  external: ['react', 'react-dom'],\n  plugins: [\n    pluginBabel({\n      babelHelpers: 'bundled',\n      parserOpts: {\n        sourceType: 'module',\n        plugins: ['jsx', 'typescript'],\n      },\n      plugins: ['babel-plugin-react-compiler'],\n      extensions: ['.js', '.jsx', '.ts', '.tsx'],\n    }),\n  ],\n  dts: true,\n})\n```\n\n## Common Patterns\n\n### Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'neutral',\n  external: [\n    'react',\n    'react-dom',\n    /^react\\//,  // react/jsx-runtime, etc.\n  ],\n  dts: true,\n  clean: true,\n})\n```\n\n### Multiple Components\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    Button: 'src/Button.tsx',\n    Input: 'src/Input.tsx',\n    Modal: 'src/Modal.tsx',\n  },\n  format: ['esm', 'cjs'],\n  external: ['react', 'react-dom'],\n  dts: true,\n})\n```\n\n### Hooks Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'neutral',\n  external: ['react'],  // Only React needed\n  dts: true,\n  treeshake: true,\n})\n```\n\n### Monorepo React Packages\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: [\n    'react',\n    'react-dom',\n    /^@mycompany\\//,  // Other workspace packages\n  ],\n  dts: true,\n})\n```\n\n## TypeScript Configuration\n\n### Recommended tsconfig.json\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"jsx\": \"react-jsx\",  // or \"react\" for classic\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"strict\": true,\n    \"isolatedDeclarations\": true,  // Fast DTS generation\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"]\n}\n```\n\n## Package.json Configuration\n\n```json\n{\n  \"name\": \"my-react-library\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"files\": [\"dist\"],\n  \"peerDependencies\": {\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.0.0\",\n    \"@types/react-dom\": \"^18.0.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"tsdown\": \"^0.9.0\",\n    \"typescript\": \"^5.0.0\"\n  }\n}\n```\n\n## Advanced Patterns\n\n### With Fast Refresh (Development)\n\n```ts\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig((options) => ({\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  external: ['react', 'react-dom'],\n  plugins: options.watch\n    ? [\n        // @ts-expect-error Vite plugin\n        react({ fastRefresh: true }),\n      ]\n    : [],\n}))\n```\n\n## Tips\n\n1. **Always externalize React** - Don't bundle React/ReactDOM\n2. **Use automatic JSX** - Smaller bundles with React 17+\n3. **Enable DTS generation** - TypeScript support essential\n4. **Use platform: 'neutral'** - For maximum compatibility\n5. **Add peer dependencies** - Let users provide React\n6. **Enable tree shaking** - Reduce bundle size\n7. **Use React Compiler** - Better runtime performance\n\n## Troubleshooting\n\n### React Hook Errors\n\nEnsure React is externalized:\n\n```ts\nexternal: ['react', 'react-dom', /^react\\//]\n```\n\n### Type Errors with JSX\n\nCheck `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\"  // or \"react\"\n  }\n}\n```\n\n### Duplicate React\n\nAdd to external patterns:\n\n```ts\nexternal: [\n  'react',\n  'react-dom',\n  'react/jsx-runtime',\n  'react/jsx-dev-runtime',\n]\n```\n\n## Related\n\n- [Plugins](advanced-plugins.md) - Extend functionality\n- [Dependencies](option-dependencies.md) - External packages\n- [DTS](option-dts.md) - Type declarations\n- [Vue Recipe](recipe-vue.md) - Vue component libraries\n"
  },
  {
    "path": ".agents/skills/tsdown/references/recipe-vue.md",
    "content": "# Vue Support\n\nBuild Vue component libraries with tsdown.\n\n## Overview\n\ntsdown provides first-class support for Vue libraries through integration with `unplugin-vue` and `rolldown-plugin-dts` for type generation.\n\n## Quick Start\n\n### Use Starter Template\n\n```bash\nnpx create-tsdown@latest -t vue\n```\n\n## Basic Configuration\n\n### Install Dependencies\n\n```bash\npnpm add -D unplugin-vue vue-tsc\n```\n\n### Minimal Setup\n\n```ts\n// tsdown.config.ts\nimport { defineConfig } from 'tsdown'\nimport Vue from 'unplugin-vue/rolldown'\n\nexport default defineConfig({\n  entry: ['./src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'neutral',\n  external: ['vue'],\n  plugins: [\n    Vue({ isProduction: true }),\n  ],\n  dts: {\n    vue: true,  // Enable Vue type generation\n  },\n})\n```\n\n## How It Works\n\n### unplugin-vue\n\nCompiles `.vue` single-file components:\n- Transforms template to render functions\n- Handles scoped styles\n- Processes script setup\n\n### vue-tsc\n\nGenerates TypeScript declarations:\n- Type-checks Vue components\n- Creates `.d.ts` files\n- Preserves component props types\n- Exports component types\n\n## Component Example\n\n### Single File Component\n\n```vue\n<!-- src/Button.vue -->\n<script setup lang=\"ts\">\ninterface Props {\n  type?: 'primary' | 'secondary'\n  disabled?: boolean\n}\n\ndefineProps<Props>()\ndefineEmits<{\n  click: []\n}>()\n</script>\n\n<template>\n  <button\n    :class=\"['btn', `btn-${type}`]\"\n    :disabled=\"disabled\"\n    @click=\"$emit('click')\"\n  >\n    <slot />\n  </button>\n</template>\n\n<style scoped>\n.btn {\n  padding: 8px 16px;\n  border-radius: 4px;\n}\n\n.btn-primary {\n  background: blue;\n  color: white;\n}\n</style>\n```\n\n### Export Components\n\n```ts\n// src/index.ts\nexport { default as Button } from './Button.vue'\nexport { default as Input } from './Input.vue'\nexport { default as Modal } from './Modal.vue'\n\n// Re-export types\nexport type { ButtonProps } from './Button.vue'\n```\n\n## Common Patterns\n\n### Component Library\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  platform: 'neutral',\n  external: ['vue'],\n  plugins: [\n    Vue({\n      isProduction: true,\n      style: {\n        trim: true,\n      },\n    }),\n  ],\n  dts: {\n    vue: true,\n  },\n  clean: true,\n})\n```\n\n### Multiple Components\n\n```ts\nexport default defineConfig({\n  entry: {\n    index: 'src/index.ts',\n    Button: 'src/Button.vue',\n    Input: 'src/Input.vue',\n    Modal: 'src/Modal.vue',\n  },\n  format: ['esm', 'cjs'],\n  external: ['vue'],\n  plugins: [Vue({ isProduction: true })],\n  dts: { vue: true },\n})\n```\n\n### With Composition Utilities\n\n```ts\n// src/composables/useCounter.ts\nimport { ref } from 'vue'\n\nexport function useCounter(initial = 0) {\n  const count = ref(initial)\n  const increment = () => count.value++\n  const decrement = () => count.value--\n  return { count, increment, decrement }\n}\n```\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: ['vue'],\n  plugins: [Vue({ isProduction: true })],\n  dts: { vue: true },\n})\n```\n\n### TypeScript Configuration\n\n```json\n// tsconfig.json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"jsx\": \"preserve\",\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"strict\": true,\n    \"isolatedDeclarations\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n```\n\n### Package.json Configuration\n\n```json\n{\n  \"name\": \"my-vue-library\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    },\n  },\n  \"files\": [\"dist\"],\n  \"peerDependencies\": {\n    \"vue\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"tsdown\": \"^0.9.0\",\n    \"typescript\": \"^5.0.0\",\n    \"unplugin-vue\": \"^5.0.0\",\n    \"vue\": \"^3.4.0\",\n    \"vue-tsc\": \"^2.0.0\"\n  }\n}\n```\n\n## Advanced Patterns\n\n### With Vite Plugins\n\nSome Vite Vue plugins may work:\n\n```ts\nimport Vue from 'unplugin-vue/rolldown'\nimport Components from 'unplugin-vue-components/rolldown'\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  external: ['vue'],\n  plugins: [\n    Vue({ isProduction: true }),\n    Components({\n      dts: 'src/components.d.ts',\n    }),\n  ],\n  dts: { vue: true },\n})\n```\n\n### JSX Support\n\n```ts\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: ['vue'],\n  plugins: [\n    Vue({\n      isProduction: true,\n      script: {\n        propsDestructure: true,\n      },\n    }),\n  ],\n  inputOptions: {\n    transform: {\n      jsx: 'automatic',\n      jsxImportSource: 'vue',\n    },\n  },\n  dts: { vue: true },\n})\n```\n\n### Monorepo Vue Packages\n\n```ts\nexport default defineConfig({\n  workspace: 'packages/*',\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  external: ['vue', /^@mycompany\\//],\n  plugins: [Vue({ isProduction: true })],\n  dts: { vue: true },\n})\n```\n\n## Plugin Options\n\n### unplugin-vue Options\n\n```ts\nVue({\n  isProduction: true,\n  script: {\n    defineModel: true,\n    propsDestructure: true,\n  },\n  style: {\n    trim: true,\n  },\n  template: {\n    compilerOptions: {\n      isCustomElement: (tag) => tag.startsWith('custom-'),\n    },\n  },\n})\n```\n\n## Tips\n\n1. **Always externalize Vue** - Don't bundle Vue itself\n2. **Enable vue: true in dts** - For proper type generation\n3. **Use platform: 'neutral'** - Maximum compatibility\n4. **Install vue-tsc** - Required for type generation\n5. **Set isProduction: true** - Optimize for production\n6. **Add peer dependency** - Vue as peer dependency\n\n## Troubleshooting\n\n### Type Generation Fails\n\nEnsure vue-tsc is installed:\n```bash\npnpm add -D vue-tsc\n```\n\nEnable in config:\n```ts\ndts: { vue: true }\n```\n\n### Component Types Missing\n\nCheck TypeScript config:\n```json\n{\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\",\n    \"moduleResolution\": \"bundler\"\n  }\n}\n```\n\n### Vue Not Externalized\n\nAdd to external:\n```ts\nexternal: ['vue']\n```\n\n### SFC Compilation Errors\n\nCheck unplugin-vue version:\n```bash\npnpm add -D unplugin-vue@latest\n```\n\n## Related\n\n- [Plugins](advanced-plugins.md) - Plugin system\n- [Dependencies](option-dependencies.md) - External packages\n- [DTS](option-dts.md) - Type declarations\n- [React Recipe](recipe-react.md) - React component libraries\n"
  },
  {
    "path": ".agents/skills/tsdown/references/recipe-wasm.md",
    "content": "# WASM Support\n\nBundle WebAssembly modules in your TypeScript/JavaScript project.\n\n## Overview\n\ntsdown supports WASM through [`rolldown-plugin-wasm`](https://github.com/sxzz/rolldown-plugin-wasm), enabling direct `.wasm` imports with synchronous and asynchronous instantiation.\n\n## Setup\n\n### Install\n\n```bash\npnpm add -D rolldown-plugin-wasm\n```\n\n### Configure\n\n```ts\nimport { wasm } from 'rolldown-plugin-wasm'\nimport { defineConfig } from 'tsdown'\n\nexport default defineConfig({\n  entry: ['./src/index.ts'],\n  plugins: [wasm()],\n})\n```\n\n### TypeScript Support\n\nAdd type declarations to `tsconfig.json`:\n\n```jsonc\n{\n  \"compilerOptions\": {\n    \"types\": [\"rolldown-plugin-wasm/types\"]\n  }\n}\n```\n\n## Importing WASM Modules\n\n### Direct Import\n\n```ts\nimport { add } from './add.wasm'\nadd(1, 2)\n```\n\n### Async Init\n\nUse `?init` query for async initialization:\n\n```ts\nimport init from './add.wasm?init'\nconst instance = await init(imports) // imports optional\ninstance.exports.add(1, 2)\n```\n\n### Sync Init\n\nUse `?init&sync` query for synchronous initialization:\n\n```ts\nimport initSync from './add.wasm?init&sync'\nconst instance = initSync(imports) // imports optional\ninstance.exports.add(1, 2)\n```\n\n## wasm-bindgen Support\n\n### Target `bundler` (Recommended)\n\n```ts\nimport { add } from 'some-pkg'\nadd(1, 2)\n```\n\n### Target `web` (Node.js)\n\n```ts\nimport { readFile } from 'node:fs/promises'\nimport init, { add } from 'some-pkg'\nimport wasmUrl from 'some-pkg/add_bg.wasm?url'\n\nawait init({\n  module_or_path: readFile(new URL(wasmUrl, import.meta.url)),\n})\nadd(1, 2)\n```\n\n### Target `web` (Browser)\n\n```ts\nimport init, { add } from 'some-pkg/add.js'\nimport wasmUrl from 'some-pkg/add_bg.wasm?url'\n\nawait init({ module_or_path: wasmUrl })\nadd(1, 2)\n```\n\n`nodejs` and `no-modules` wasm-bindgen targets are not supported.\n\n## Plugin Options\n\n```ts\nwasm({\n  maxFileSize: 14 * 1024, // Max size for inline (default: 14KB)\n  fileName: '[hash][extname]', // Output file name pattern\n  publicPath: '',         // Prefix for non-inlined file paths\n  targetEnv: 'auto',      // 'auto' | 'auto-inline' | 'browser' | 'node'\n})\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `maxFileSize` | `14 * 1024` | Max file size for inlining. Set to `0` to always copy. |\n| `fileName` | `'[hash][extname]'` | Pattern for emitted WASM files |\n| `publicPath` | — | Prefix for non-inlined WASM file paths |\n| `targetEnv` | `'auto'` | `'auto'` detects at runtime; `'browser'` omits Node builtins; `'node'` omits fetch |\n\n## Related Options\n\n- [Plugins](advanced-plugins.md) - Plugin system overview\n- [Platform](option-platform.md) - Target platform configuration\n"
  },
  {
    "path": ".agents/skills/tsdown/references/reference-cli.md",
    "content": "# CLI Reference\n\nComplete reference for tsdown command-line interface.\n\n## Overview\n\nAll CLI flags can also be set in the config file. CLI flags override config file options.\n\n## Flag Patterns\n\nCLI flag mapping rules:\n- `--foo` sets `foo: true`\n- `--no-foo` sets `foo: false`\n- `--foo.bar` sets `foo: { bar: true }`\n- `--format esm --format cjs` sets `format: ['esm', 'cjs']`\n\nCLI flags support both camelCase and kebab-case. For example, `--outDir` and `--out-dir` are equivalent.\n\n## Basic Commands\n\n### Build\n\n```bash\n# Build with default config\ntsdown\n\n# Build specific files\ntsdown src/index.ts src/cli.ts\n\n# Build with watch mode\ntsdown --watch\n```\n\n## Configuration\n\n### `--config, -c <filename>`\n\nSpecify custom config file:\n\n```bash\ntsdown --config build.config.ts\ntsdown -c custom-config.js\n```\n\n### `--no-config`\n\nDisable config file loading:\n\n```bash\ntsdown --no-config src/index.ts\n```\n\n### `--config-loader <loader>`\n\nChoose config loader (`auto`, `native`, `unrun`):\n\n```bash\ntsdown --config-loader unrun\n```\n\n### `--tsconfig <file>`\n\nSpecify TypeScript config file:\n\n```bash\ntsdown --tsconfig tsconfig.build.json\n```\n\n## Entry Points\n\n### `[...files]`\n\nSpecify entry files as arguments:\n\n```bash\ntsdown src/index.ts src/utils.ts\n```\n\n## Output Options\n\n### `--format <format>`\n\nOutput format (`esm`, `cjs`, `iife`, `umd`):\n\n```bash\ntsdown --format esm\ntsdown --format esm --format cjs\n```\n\n### `--out-dir, -d <dir>`\n\nOutput directory:\n\n```bash\ntsdown --out-dir lib\ntsdown -d dist\n```\n\n### `--dts`\n\nGenerate TypeScript declarations:\n\n```bash\ntsdown --dts\n```\n\n### `--clean`\n\nClean output directory before build:\n\n```bash\ntsdown --clean\n```\n\n## Build Options\n\n### `--target <target>`\n\nJavaScript target version:\n\n```bash\ntsdown --target es2020\ntsdown --target node18\ntsdown --target chrome100\ntsdown --no-target  # Disable transformations\n```\n\n### `--platform <platform>`\n\nTarget platform (`node`, `browser`, `neutral`):\n\n```bash\ntsdown --platform node\ntsdown --platform browser\n```\n\n### `--minify`\n\nEnable minification:\n\n```bash\ntsdown --minify\ntsdown --no-minify\n```\n\n### `--sourcemap`\n\nGenerate source maps:\n\n```bash\ntsdown --sourcemap\ntsdown --sourcemap inline\n```\n\n### `--treeshake`\n\nEnable/disable tree shaking:\n\n```bash\ntsdown --treeshake\ntsdown --no-treeshake\n```\n\n## Dependencies\n\n### `--external <module>`\n\nMark module as external (not bundled):\n\n```bash\ntsdown --external react --external react-dom\n```\n\n### `--shims`\n\nAdd ESM/CJS compatibility shims:\n\n```bash\ntsdown --shims\n```\n\n## Development\n\n### `--watch, -w [path]`\n\nEnable watch mode:\n\n```bash\ntsdown --watch\ntsdown -w\ntsdown --watch src  # Watch specific directory\n```\n\n### `--ignore-watch <path>`\n\nIgnore paths in watch mode:\n\n```bash\ntsdown --watch --ignore-watch test\n```\n\n### `--on-success <command>`\n\nRun command after successful build:\n\n```bash\ntsdown --watch --on-success \"echo Build complete!\"\n```\n\n## Environment Variables\n\n### `--env.* <value>`\n\nSet compile-time environment variables:\n\n```bash\ntsdown --env.NODE_ENV=production --env.API_URL=https://api.example.com\n```\n\nAccess as `import.meta.env.*` or `process.env.*`.\n\n### `--env-file <file>`\n\nLoad environment variables from file:\n\n```bash\ntsdown --env-file .env.production\n```\n\n### `--env-prefix <prefix>`\n\nFilter environment variables by prefix (default: `TSDOWN_`):\n\n```bash\ntsdown --env-file .env --env-prefix APP_ --env-prefix TSDOWN_\n```\n\n## Assets\n\n### `--copy <dir>`\n\nCopy directory to output:\n\n```bash\ntsdown --copy public\ntsdown --copy assets --copy static\n```\n\n## Package Management\n\n### `--exports`\n\nAuto-generate package.json exports field:\n\n```bash\ntsdown --exports\n```\n\n### `--publint`\n\nEnable package validation:\n\n```bash\ntsdown --publint\n```\n\n### `--attw`\n\nEnable \"Are the types wrong\" validation:\n\n```bash\ntsdown --attw\n```\n\n### `--unused`\n\nCheck for unused dependencies:\n\n```bash\ntsdown --unused\n```\n\n## Logging\n\n### `--log-level <level>`\n\nSet logging verbosity (`silent`, `error`, `warn`, `info`):\n\n```bash\ntsdown --log-level error\ntsdown --log-level warn\n```\n\n### `--report` / `--no-report`\n\nEnable/disable build report:\n\n```bash\ntsdown --no-report  # Disable size report\ntsdown --report     # Enable (default)\n```\n\n### `--debug [feat]`\n\nShow debug logs:\n\n```bash\ntsdown --debug\ntsdown --debug rolldown  # Debug specific feature\n```\n\n## Integration\n\n### `--from-vite [vitest]`\n\nExtend Vite or Vitest config:\n\n```bash\ntsdown --from-vite         # Use vite.config.*\ntsdown --from-vite vitest  # Use vitest.config.*\n```\n\n## Common Usage Patterns\n\n### Basic Build\n\n```bash\ntsdown\n```\n\n### Library (ESM + CJS + Types)\n\n```bash\ntsdown --format esm --format cjs --dts --clean\n```\n\n### Production Build\n\n```bash\ntsdown --minify --clean --no-report\n```\n\n### Development (Watch)\n\n```bash\ntsdown --watch --sourcemap\n```\n\n### Browser Bundle (IIFE)\n\n```bash\ntsdown --format iife --platform browser --minify\n```\n\n### Node.js CLI Tool\n\n```bash\ntsdown --format esm --platform node --shims\n```\n\n### Monorepo Package\n\n```bash\ntsdown --clean --dts --exports --publint\n```\n\n### With Environment Variables\n\n```bash\ntsdown --env-file .env.production --env.BUILD_TIME=$(date +%s)\n```\n\n### Copy Assets\n\n```bash\ntsdown --copy public --copy assets --clean\n```\n\n## Tips\n\n1. **Use config file** for complex setups\n2. **CLI flags override** config file options\n3. **Chain multiple formats** for multi-target builds\n4. **Use --clean** to avoid stale files\n5. **Enable --dts** for TypeScript libraries\n6. **Use --watch** during development\n7. **Add --on-success** for post-build tasks\n8. **Use --exports** to auto-generate package.json fields\n\n## Related Documentation\n\n- [Config File](option-config-file.md) - Configuration file options\n- [Entry](option-entry.md) - Entry point configuration\n- [Output Format](option-output-format.md) - Format options\n- [Watch Mode](option-watch-mode.md) - Watch mode details\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/AGENTS.md",
    "content": "# React Composition Patterns\n\n**Version 1.0.0**  \nEngineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React codebases using composition. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComposition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.\n\n---\n\n## Table of Contents\n\n1. [Component Architecture](#1-component-architecture) — **HIGH**\n   - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation)\n   - 1.2 [Use Compound Components](#12-use-compound-components)\n2. [State Management](#2-state-management) — **MEDIUM**\n   - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui)\n   - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection)\n   - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components)\n3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM**\n   - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants)\n   - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props)\n4. [React 19 APIs](#4-react-19-apis) — **MEDIUM**\n   - 4.1 [React 19 API Changes](#41-react-19-api-changes)\n\n---\n\n## 1. Component Architecture\n\n**Impact: HIGH**\n\nFundamental patterns for structuring components to avoid prop\nproliferation and enable flexible composition.\n\n### 1.1 Avoid Boolean Prop Proliferation\n\n**Impact: CRITICAL (prevents unmaintainable component variants)**\n\nDon't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize\n\ncomponent behavior. Each boolean doubles possible states and creates\n\nunmaintainable conditional logic. Use composition instead.\n\n**Incorrect: boolean props create exponential complexity**\n\n```tsx\nfunction Composer({\n  onSubmit,\n  isThread,\n  channelId,\n  isDMThread,\n  dmId,\n  isEditing,\n  isForwarding,\n}: Props) {\n  return (\n    <form>\n      <Header />\n      <Input />\n      {isDMThread ? (\n        <AlsoSendToDMField id={dmId} />\n      ) : isThread ? (\n        <AlsoSendToChannelField id={channelId} />\n      ) : null}\n      {isEditing ? (\n        <EditActions />\n      ) : isForwarding ? (\n        <ForwardActions />\n      ) : (\n        <DefaultActions />\n      )}\n      <Footer onSubmit={onSubmit} />\n    </form>\n  )\n}\n```\n\n**Correct: composition eliminates conditionals**\n\n```tsx\n// Channel composer\nfunction ChannelComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Attachments />\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Thread composer - adds \"also send to channel\" field\nfunction ThreadComposer({ channelId }: { channelId: string }) {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <AlsoSendToChannelField id={channelId} />\n      <Composer.Footer>\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Edit composer - different footer actions\nfunction EditComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.CancelEdit />\n        <Composer.SaveEdit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n```\n\nEach variant is explicit about what it renders. We can share internals without\n\nsharing a single monolithic parent.\n\n### 1.2 Use Compound Components\n\n**Impact: HIGH (enables flexible composition without prop drilling)**\n\nStructure complex components as compound components with a shared context. Each\n\nsubcomponent accesses shared state via context, not props. Consumers compose the\n\npieces they need.\n\n**Incorrect: monolithic component with render props**\n\n```tsx\nfunction Composer({\n  renderHeader,\n  renderFooter,\n  renderActions,\n  showAttachments,\n  showFormatting,\n  showEmojis,\n}: Props) {\n  return (\n    <form>\n      {renderHeader?.()}\n      <Input />\n      {showAttachments && <Attachments />}\n      {renderFooter ? (\n        renderFooter()\n      ) : (\n        <Footer>\n          {showFormatting && <Formatting />}\n          {showEmojis && <Emojis />}\n          {renderActions?.()}\n        </Footer>\n      )}\n    </form>\n  )\n}\n```\n\n**Correct: compound components with shared context**\n\n```tsx\nconst ComposerContext = createContext<ComposerContextValue | null>(null)\n\nfunction ComposerProvider({ children, state, actions, meta }: ProviderProps) {\n  return (\n    <ComposerContext value={{ state, actions, meta }}>\n      {children}\n    </ComposerContext>\n  )\n}\n\nfunction ComposerFrame({ children }: { children: React.ReactNode }) {\n  return <form>{children}</form>\n}\n\nfunction ComposerInput() {\n  const {\n    state,\n    actions: { update },\n    meta: { inputRef },\n  } = use(ComposerContext)\n  return (\n    <TextInput\n      ref={inputRef}\n      value={state.input}\n      onChangeText={(text) => update((s) => ({ ...s, input: text }))}\n    />\n  )\n}\n\nfunction ComposerSubmit() {\n  const {\n    actions: { submit },\n  } = use(ComposerContext)\n  return <Button onPress={submit}>Send</Button>\n}\n\n// Export as compound component\nconst Composer = {\n  Provider: ComposerProvider,\n  Frame: ComposerFrame,\n  Input: ComposerInput,\n  Submit: ComposerSubmit,\n  Header: ComposerHeader,\n  Footer: ComposerFooter,\n  Attachments: ComposerAttachments,\n  Formatting: ComposerFormatting,\n  Emojis: ComposerEmojis,\n}\n```\n\n**Usage:**\n\n```tsx\n<Composer.Provider state={state} actions={actions} meta={meta}>\n  <Composer.Frame>\n    <Composer.Header />\n    <Composer.Input />\n    <Composer.Footer>\n      <Composer.Formatting />\n      <Composer.Submit />\n    </Composer.Footer>\n  </Composer.Frame>\n</Composer.Provider>\n```\n\nConsumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.\n\n---\n\n## 2. State Management\n\n**Impact: MEDIUM**\n\nPatterns for lifting state and managing shared context across\ncomposed components.\n\n### 2.1 Decouple State Management from UI\n\n**Impact: MEDIUM (enables swapping state implementations without changing UI)**\n\nThe provider component should be the only place that knows how state is managed.\n\nUI components consume the context interface—they don't know if state comes from\n\nuseState, Zustand, or a server sync.\n\n**Incorrect: UI coupled to state implementation**\n\n```tsx\nfunction ChannelComposer({ channelId }: { channelId: string }) {\n  // UI component knows about global state implementation\n  const state = useGlobalChannelState(channelId)\n  const { submit, updateInput } = useChannelSync(channelId)\n\n  return (\n    <Composer.Frame>\n      <Composer.Input\n        value={state.input}\n        onChange={(text) => sync.updateInput(text)}\n      />\n      <Composer.Submit onPress={() => sync.submit()} />\n    </Composer.Frame>\n  )\n}\n```\n\n**Correct: state management isolated in provider**\n\n```tsx\n// Provider handles all state management details\nfunction ChannelProvider({\n  channelId,\n  children,\n}: {\n  channelId: string\n  children: React.ReactNode\n}) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n  const inputRef = useRef(null)\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update, submit }}\n      meta={{ inputRef }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\n// UI component only knows about the context interface\nfunction ChannelComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Usage\nfunction Channel({ channelId }: { channelId: string }) {\n  return (\n    <ChannelProvider channelId={channelId}>\n      <ChannelComposer />\n    </ChannelProvider>\n  )\n}\n```\n\n**Different providers, same UI:**\n\n```tsx\n// Local state for ephemeral forms\nfunction ForwardMessageProvider({ children }) {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update: setState, submit: forwardMessage }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\n// Global synced state for channels\nfunction ChannelProvider({ channelId, children }) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n\n  return (\n    <Composer.Provider state={state} actions={{ update, submit }}>\n      {children}\n    </Composer.Provider>\n  )\n}\n```\n\nThe same `Composer.Input` component works with both providers because it only\n\ndepends on the context interface, not the implementation.\n\n### 2.2 Define Generic Context Interfaces for Dependency Injection\n\n**Impact: HIGH (enables dependency-injectable state across use-cases)**\n\nDefine a **generic interface** for your component context with three parts:\n\n`state`, `actions`, and `meta`. This interface is a contract that any provider\n\ncan implement—enabling the same UI components to work with completely different\n\nstate implementations.\n\n**Core principle:** Lift state, compose internals, make state\n\ndependency-injectable.\n\n**Incorrect: UI coupled to specific state implementation**\n\n```tsx\nfunction ComposerInput() {\n  // Tightly coupled to a specific hook\n  const { input, setInput } = useChannelComposerState()\n  return <TextInput value={input} onChangeText={setInput} />\n}\n```\n\n**Correct: generic interface enables dependency injection**\n\n```tsx\n// Define a GENERIC interface that any provider can implement\ninterface ComposerState {\n  input: string\n  attachments: Attachment[]\n  isSubmitting: boolean\n}\n\ninterface ComposerActions {\n  update: (updater: (state: ComposerState) => ComposerState) => void\n  submit: () => void\n}\n\ninterface ComposerMeta {\n  inputRef: React.RefObject<TextInput>\n}\n\ninterface ComposerContextValue {\n  state: ComposerState\n  actions: ComposerActions\n  meta: ComposerMeta\n}\n\nconst ComposerContext = createContext<ComposerContextValue | null>(null)\n```\n\n**UI components consume the interface, not the implementation:**\n\n```tsx\nfunction ComposerInput() {\n  const {\n    state,\n    actions: { update },\n    meta,\n  } = use(ComposerContext)\n\n  // This component works with ANY provider that implements the interface\n  return (\n    <TextInput\n      ref={meta.inputRef}\n      value={state.input}\n      onChangeText={(text) => update((s) => ({ ...s, input: text }))}\n    />\n  )\n}\n```\n\n**Different providers implement the same interface:**\n\n```tsx\n// Provider A: Local state for ephemeral forms\nfunction ForwardMessageProvider({ children }: { children: React.ReactNode }) {\n  const [state, setState] = useState(initialState)\n  const inputRef = useRef(null)\n  const submit = useForwardMessage()\n\n  return (\n    <ComposerContext\n      value={{\n        state,\n        actions: { update: setState, submit },\n        meta: { inputRef },\n      }}\n    >\n      {children}\n    </ComposerContext>\n  )\n}\n\n// Provider B: Global synced state for channels\nfunction ChannelProvider({ channelId, children }: Props) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n  const inputRef = useRef(null)\n\n  return (\n    <ComposerContext\n      value={{\n        state,\n        actions: { update, submit },\n        meta: { inputRef },\n      }}\n    >\n      {children}\n    </ComposerContext>\n  )\n}\n```\n\n**The same composed UI works with both:**\n\n```tsx\n// Works with ForwardMessageProvider (local state)\n<ForwardMessageProvider>\n  <Composer.Frame>\n    <Composer.Input />\n    <Composer.Submit />\n  </Composer.Frame>\n</ForwardMessageProvider>\n\n// Works with ChannelProvider (global synced state)\n<ChannelProvider channelId=\"abc\">\n  <Composer.Frame>\n    <Composer.Input />\n    <Composer.Submit />\n  </Composer.Frame>\n</ChannelProvider>\n```\n\n**Custom UI outside the component can access state and actions:**\n\n```tsx\nfunction ForwardMessageDialog() {\n  return (\n    <ForwardMessageProvider>\n      <Dialog>\n        {/* The composer UI */}\n        <Composer.Frame>\n          <Composer.Input placeholder=\"Add a message, if you'd like.\" />\n          <Composer.Footer>\n            <Composer.Formatting />\n            <Composer.Emojis />\n          </Composer.Footer>\n        </Composer.Frame>\n\n        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}\n        <MessagePreview />\n\n        {/* Actions at the bottom of the dialog */}\n        <DialogActions>\n          <CancelButton />\n          <ForwardButton />\n        </DialogActions>\n      </Dialog>\n    </ForwardMessageProvider>\n  )\n}\n\n// This button lives OUTSIDE Composer.Frame but can still submit based on its context!\nfunction ForwardButton() {\n  const {\n    actions: { submit },\n  } = use(ComposerContext)\n  return <Button onPress={submit}>Forward</Button>\n}\n\n// This preview lives OUTSIDE Composer.Frame but can read composer's state!\nfunction MessagePreview() {\n  const { state } = use(ComposerContext)\n  return <Preview message={state.input} attachments={state.attachments} />\n}\n```\n\nThe provider boundary is what matters—not the visual nesting. Components that\n\nneed shared state don't have to be inside the `Composer.Frame`. They just need\n\nto be within the provider.\n\nThe `ForwardButton` and `MessagePreview` are not visually inside the composer\n\nbox, but they can still access its state and actions. This is the power of\n\nlifting state into providers.\n\nThe UI is reusable bits you compose together. The state is dependency-injected\n\nby the provider. Swap the provider, keep the UI.\n\n### 2.3 Lift State into Provider Components\n\n**Impact: HIGH (enables state sharing outside component boundaries)**\n\nMove state management into dedicated provider components. This allows sibling\n\ncomponents outside the main UI to access and modify state without prop drilling\n\nor awkward refs.\n\n**Incorrect: state trapped inside component**\n\n```tsx\nfunction ForwardMessageComposer() {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n\n  return (\n    <Composer.Frame>\n      <Composer.Input />\n      <Composer.Footer />\n    </Composer.Frame>\n  )\n}\n\n// Problem: How does this button access composer state?\nfunction ForwardMessageDialog() {\n  return (\n    <Dialog>\n      <ForwardMessageComposer />\n      <MessagePreview /> {/* Needs composer state */}\n      <DialogActions>\n        <CancelButton />\n        <ForwardButton /> {/* Needs to call submit */}\n      </DialogActions>\n    </Dialog>\n  )\n}\n```\n\n**Incorrect: useEffect to sync state up**\n\n```tsx\nfunction ForwardMessageDialog() {\n  const [input, setInput] = useState('')\n  return (\n    <Dialog>\n      <ForwardMessageComposer onInputChange={setInput} />\n      <MessagePreview input={input} />\n    </Dialog>\n  )\n}\n\nfunction ForwardMessageComposer({ onInputChange }) {\n  const [state, setState] = useState(initialState)\n  useEffect(() => {\n    onInputChange(state.input) // Sync on every change 😬\n  }, [state.input])\n}\n```\n\n**Incorrect: reading state from ref on submit**\n\n```tsx\nfunction ForwardMessageDialog() {\n  const stateRef = useRef(null)\n  return (\n    <Dialog>\n      <ForwardMessageComposer stateRef={stateRef} />\n      <ForwardButton onPress={() => submit(stateRef.current)} />\n    </Dialog>\n  )\n}\n```\n\n**Correct: state lifted to provider**\n\n```tsx\nfunction ForwardMessageProvider({ children }: { children: React.ReactNode }) {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n  const inputRef = useRef(null)\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update: setState, submit: forwardMessage }}\n      meta={{ inputRef }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\nfunction ForwardMessageDialog() {\n  return (\n    <ForwardMessageProvider>\n      <Dialog>\n        <ForwardMessageComposer />\n        <MessagePreview /> {/* Custom components can access state and actions */}\n        <DialogActions>\n          <CancelButton />\n          <ForwardButton /> {/* Custom components can access state and actions */}\n        </DialogActions>\n      </Dialog>\n    </ForwardMessageProvider>\n  )\n}\n\nfunction ForwardButton() {\n  const { actions } = use(Composer.Context)\n  return <Button onPress={actions.submit}>Forward</Button>\n}\n```\n\nThe ForwardButton lives outside the Composer.Frame but still has access to the\n\nsubmit action because it's within the provider. Even though it's a one-off\n\ncomponent, it can still access the composer's state and actions from outside the\n\nUI itself.\n\n**Key insight:** Components that need shared state don't have to be visually\n\nnested inside each other—they just need to be within the same provider.\n\n---\n\n## 3. Implementation Patterns\n\n**Impact: MEDIUM**\n\nSpecific techniques for implementing compound components and\ncontext providers.\n\n### 3.1 Create Explicit Component Variants\n\n**Impact: MEDIUM (self-documenting code, no hidden conditionals)**\n\nInstead of one component with many boolean props, create explicit variant\n\ncomponents. Each variant composes the pieces it needs. The code documents\n\nitself.\n\n**Incorrect: one component, many modes**\n\n```tsx\n// What does this component actually render?\n<Composer\n  isThread\n  isEditing={false}\n  channelId='abc'\n  showAttachments\n  showFormatting={false}\n/>\n```\n\n**Correct: explicit variants**\n\n```tsx\n// Immediately clear what this renders\n<ThreadComposer channelId=\"abc\" />\n\n// Or\n<EditMessageComposer messageId=\"xyz\" />\n\n// Or\n<ForwardMessageComposer messageId=\"123\" />\n```\n\nEach implementation is unique, explicit and self-contained. Yet they can each\n\nuse shared parts.\n\n**Implementation:**\n\n```tsx\nfunction ThreadComposer({ channelId }: { channelId: string }) {\n  return (\n    <ThreadProvider channelId={channelId}>\n      <Composer.Frame>\n        <Composer.Input />\n        <AlsoSendToChannelField channelId={channelId} />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.Submit />\n        </Composer.Footer>\n      </Composer.Frame>\n    </ThreadProvider>\n  )\n}\n\nfunction EditMessageComposer({ messageId }: { messageId: string }) {\n  return (\n    <EditMessageProvider messageId={messageId}>\n      <Composer.Frame>\n        <Composer.Input />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.CancelEdit />\n          <Composer.SaveEdit />\n        </Composer.Footer>\n      </Composer.Frame>\n    </EditMessageProvider>\n  )\n}\n\nfunction ForwardMessageComposer({ messageId }: { messageId: string }) {\n  return (\n    <ForwardMessageProvider messageId={messageId}>\n      <Composer.Frame>\n        <Composer.Input placeholder=\"Add a message, if you'd like.\" />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.Mentions />\n        </Composer.Footer>\n      </Composer.Frame>\n    </ForwardMessageProvider>\n  )\n}\n```\n\nEach variant is explicit about:\n\n- What provider/state it uses\n\n- What UI elements it includes\n\n- What actions are available\n\nNo boolean prop combinations to reason about. No impossible states.\n\n### 3.2 Prefer Composing Children Over Render Props\n\n**Impact: MEDIUM (cleaner composition, better readability)**\n\nUse `children` for composition instead of `renderX` props. Children are more\n\nreadable, compose naturally, and don't require understanding callback\n\nsignatures.\n\n**Incorrect: render props**\n\n```tsx\nfunction Composer({\n  renderHeader,\n  renderFooter,\n  renderActions,\n}: {\n  renderHeader?: () => React.ReactNode\n  renderFooter?: () => React.ReactNode\n  renderActions?: () => React.ReactNode\n}) {\n  return (\n    <form>\n      {renderHeader?.()}\n      <Input />\n      {renderFooter ? renderFooter() : <DefaultFooter />}\n      {renderActions?.()}\n    </form>\n  )\n}\n\n// Usage is awkward and inflexible\nreturn (\n  <Composer\n    renderHeader={() => <CustomHeader />}\n    renderFooter={() => (\n      <>\n        <Formatting />\n        <Emojis />\n      </>\n    )}\n    renderActions={() => <SubmitButton />}\n  />\n)\n```\n\n**Correct: compound components with children**\n\n```tsx\nfunction ComposerFrame({ children }: { children: React.ReactNode }) {\n  return <form>{children}</form>\n}\n\nfunction ComposerFooter({ children }: { children: React.ReactNode }) {\n  return <footer className='flex'>{children}</footer>\n}\n\n// Usage is flexible\nreturn (\n  <Composer.Frame>\n    <CustomHeader />\n    <Composer.Input />\n    <Composer.Footer>\n      <Composer.Formatting />\n      <Composer.Emojis />\n      <SubmitButton />\n    </Composer.Footer>\n  </Composer.Frame>\n)\n```\n\n**When render props are appropriate:**\n\n```tsx\n// Render props work well when you need to pass data back\n<List\n  data={items}\n  renderItem={({ item, index }) => <Item item={item} index={index} />}\n/>\n```\n\nUse render props when the parent needs to provide data or state to the child.\n\nUse children when composing static structure.\n\n---\n\n## 4. React 19 APIs\n\n**Impact: MEDIUM**\n\nReact 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.\n\n### 4.1 React 19 API Changes\n\n**Impact: MEDIUM (cleaner component definitions and context usage)**\n\n> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.\n\nIn React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.\n\n**Incorrect: forwardRef in React 19**\n\n```tsx\nconst ComposerInput = forwardRef<TextInput, Props>((props, ref) => {\n  return <TextInput ref={ref} {...props} />\n})\n```\n\n**Correct: ref as a regular prop**\n\n```tsx\nfunction ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {\n  return <TextInput ref={ref} {...props} />\n}\n```\n\n**Incorrect: useContext in React 19**\n\n```tsx\nconst value = useContext(MyContext)\n```\n\n**Correct: use instead of useContext**\n\n```tsx\nconst value = use(MyContext)\n```\n\n`use()` can also be called conditionally, unlike `useContext()`.\n\n---\n\n## References\n\n1. [https://react.dev](https://react.dev)\n2. [https://react.dev/learn/passing-data-deeply-with-context](https://react.dev/learn/passing-data-deeply-with-context)\n3. [https://react.dev/reference/react/use](https://react.dev/reference/react/use)\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/README.md",
    "content": "# React Composition Patterns\n\nA structured repository for React composition patterns that scale. These\npatterns help avoid boolean prop proliferation by using compound components,\nlifting state, and composing internals.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `metadata.json` - Document metadata (version, organization, abstract)\n- **`AGENTS.md`** - Compiled output (generated)\n\n## Rules\n\n### Component Architecture (CRITICAL)\n\n- `architecture-avoid-boolean-props.md` - Don't add boolean props to customize\n  behavior\n- `architecture-compound-components.md` - Structure as compound components with\n  shared context\n\n### State Management (HIGH)\n\n- `state-lift-state.md` - Lift state into provider components\n- `state-context-interface.md` - Define clear context interfaces\n  (state/actions/meta)\n- `state-decouple-implementation.md` - Decouple state management from UI\n\n### Implementation Patterns (MEDIUM)\n\n- `patterns-children-over-render-props.md` - Prefer children over renderX props\n- `patterns-explicit-variants.md` - Create explicit component variants\n\n## Core Principles\n\n1. **Composition over configuration** — Instead of adding props, let consumers\n   compose\n2. **Lift your state** — State in providers, not trapped in components\n3. **Compose your internals** — Subcomponents access context, not props\n4. **Explicit variants** — Create ThreadComposer, EditComposer, not Composer\n   with isThread\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `architecture-` for Component Architecture\n   - `state-` for State Management\n   - `patterns-` for Implementation Patterns\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n\n## Impact Levels\n\n- `CRITICAL` - Foundational patterns, prevents unmaintainable code\n- `HIGH` - Significant maintainability improvements\n- `MEDIUM` - Good practices for cleaner code\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/SKILL.md",
    "content": "---\nname: vercel-composition-patterns\ndescription:\n  React composition patterns that scale. Use when refactoring components with\n  boolean prop proliferation, building flexible component libraries, or\n  designing reusable APIs. Triggers on tasks involving compound components,\n  render props, context providers, or component architecture. Includes React 19\n  API changes.\nlicense: MIT\nmetadata:\n  author: vercel\n  version: '1.0.0'\n---\n\n# React Composition Patterns\n\nComposition patterns for building flexible, maintainable React components. Avoid\nboolean prop proliferation by using compound components, lifting state, and\ncomposing internals. These patterns make codebases easier for both humans and AI\nagents to work with as they scale.\n\n## When to Apply\n\nReference these guidelines when:\n\n- Refactoring components with many boolean props\n- Building reusable component libraries\n- Designing flexible component APIs\n- Reviewing component architecture\n- Working with compound components or context providers\n\n## Rule Categories by Priority\n\n| Priority | Category                | Impact | Prefix          |\n| -------- | ----------------------- | ------ | --------------- |\n| 1        | Component Architecture  | HIGH   | `architecture-` |\n| 2        | State Management        | MEDIUM | `state-`        |\n| 3        | Implementation Patterns | MEDIUM | `patterns-`     |\n| 4        | React 19 APIs           | MEDIUM | `react19-`      |\n\n## Quick Reference\n\n### 1. Component Architecture (HIGH)\n\n- `architecture-avoid-boolean-props` - Don't add boolean props to customize\n  behavior; use composition\n- `architecture-compound-components` - Structure complex components with shared\n  context\n\n### 2. State Management (MEDIUM)\n\n- `state-decouple-implementation` - Provider is the only place that knows how\n  state is managed\n- `state-context-interface` - Define generic interface with state, actions, meta\n  for dependency injection\n- `state-lift-state` - Move state into provider components for sibling access\n\n### 3. Implementation Patterns (MEDIUM)\n\n- `patterns-explicit-variants` - Create explicit variant components instead of\n  boolean modes\n- `patterns-children-over-render-props` - Use children for composition instead\n  of renderX props\n\n### 4. React 19 APIs (MEDIUM)\n\n> **⚠️ React 19+ only.** Skip this section if using React 18 or earlier.\n\n- `react19-no-forwardref` - Don't use `forwardRef`; use `use()` instead of `useContext()`\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/architecture-avoid-boolean-props.md\nrules/state-context-interface.md\n```\n\nEach rule file contains:\n\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md",
    "content": "---\ntitle: Avoid Boolean Prop Proliferation\nimpact: CRITICAL\nimpactDescription: prevents unmaintainable component variants\ntags: composition, props, architecture\n---\n\n## Avoid Boolean Prop Proliferation\n\nDon't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize\ncomponent behavior. Each boolean doubles possible states and creates\nunmaintainable conditional logic. Use composition instead.\n\n**Incorrect (boolean props create exponential complexity):**\n\n```tsx\nfunction Composer({\n  onSubmit,\n  isThread,\n  channelId,\n  isDMThread,\n  dmId,\n  isEditing,\n  isForwarding,\n}: Props) {\n  return (\n    <form>\n      <Header />\n      <Input />\n      {isDMThread ? (\n        <AlsoSendToDMField id={dmId} />\n      ) : isThread ? (\n        <AlsoSendToChannelField id={channelId} />\n      ) : null}\n      {isEditing ? (\n        <EditActions />\n      ) : isForwarding ? (\n        <ForwardActions />\n      ) : (\n        <DefaultActions />\n      )}\n      <Footer onSubmit={onSubmit} />\n    </form>\n  )\n}\n```\n\n**Correct (composition eliminates conditionals):**\n\n```tsx\n// Channel composer\nfunction ChannelComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Attachments />\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Thread composer - adds \"also send to channel\" field\nfunction ThreadComposer({ channelId }: { channelId: string }) {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <AlsoSendToChannelField id={channelId} />\n      <Composer.Footer>\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Edit composer - different footer actions\nfunction EditComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Formatting />\n        <Composer.Emojis />\n        <Composer.CancelEdit />\n        <Composer.SaveEdit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n```\n\nEach variant is explicit about what it renders. We can share internals without\nsharing a single monolithic parent.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md",
    "content": "---\ntitle: Use Compound Components\nimpact: HIGH\nimpactDescription: enables flexible composition without prop drilling\ntags: composition, compound-components, architecture\n---\n\n## Use Compound Components\n\nStructure complex components as compound components with a shared context. Each\nsubcomponent accesses shared state via context, not props. Consumers compose the\npieces they need.\n\n**Incorrect (monolithic component with render props):**\n\n```tsx\nfunction Composer({\n  renderHeader,\n  renderFooter,\n  renderActions,\n  showAttachments,\n  showFormatting,\n  showEmojis,\n}: Props) {\n  return (\n    <form>\n      {renderHeader?.()}\n      <Input />\n      {showAttachments && <Attachments />}\n      {renderFooter ? (\n        renderFooter()\n      ) : (\n        <Footer>\n          {showFormatting && <Formatting />}\n          {showEmojis && <Emojis />}\n          {renderActions?.()}\n        </Footer>\n      )}\n    </form>\n  )\n}\n```\n\n**Correct (compound components with shared context):**\n\n```tsx\nconst ComposerContext = createContext<ComposerContextValue | null>(null)\n\nfunction ComposerProvider({ children, state, actions, meta }: ProviderProps) {\n  return (\n    <ComposerContext value={{ state, actions, meta }}>\n      {children}\n    </ComposerContext>\n  )\n}\n\nfunction ComposerFrame({ children }: { children: React.ReactNode }) {\n  return <form>{children}</form>\n}\n\nfunction ComposerInput() {\n  const {\n    state,\n    actions: { update },\n    meta: { inputRef },\n  } = use(ComposerContext)\n  return (\n    <TextInput\n      ref={inputRef}\n      value={state.input}\n      onChangeText={(text) => update((s) => ({ ...s, input: text }))}\n    />\n  )\n}\n\nfunction ComposerSubmit() {\n  const {\n    actions: { submit },\n  } = use(ComposerContext)\n  return <Button onPress={submit}>Send</Button>\n}\n\n// Export as compound component\nconst Composer = {\n  Provider: ComposerProvider,\n  Frame: ComposerFrame,\n  Input: ComposerInput,\n  Submit: ComposerSubmit,\n  Header: ComposerHeader,\n  Footer: ComposerFooter,\n  Attachments: ComposerAttachments,\n  Formatting: ComposerFormatting,\n  Emojis: ComposerEmojis,\n}\n```\n\n**Usage:**\n\n```tsx\n<Composer.Provider state={state} actions={actions} meta={meta}>\n  <Composer.Frame>\n    <Composer.Header />\n    <Composer.Input />\n    <Composer.Footer>\n      <Composer.Formatting />\n      <Composer.Submit />\n    </Composer.Footer>\n  </Composer.Frame>\n</Composer.Provider>\n```\n\nConsumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md",
    "content": "---\ntitle: Prefer Composing Children Over Render Props\nimpact: MEDIUM\nimpactDescription: cleaner composition, better readability\ntags: composition, children, render-props\n---\n\n## Prefer Children Over Render Props\n\nUse `children` for composition instead of `renderX` props. Children are more\nreadable, compose naturally, and don't require understanding callback\nsignatures.\n\n**Incorrect (render props):**\n\n```tsx\nfunction Composer({\n  renderHeader,\n  renderFooter,\n  renderActions,\n}: {\n  renderHeader?: () => React.ReactNode\n  renderFooter?: () => React.ReactNode\n  renderActions?: () => React.ReactNode\n}) {\n  return (\n    <form>\n      {renderHeader?.()}\n      <Input />\n      {renderFooter ? renderFooter() : <DefaultFooter />}\n      {renderActions?.()}\n    </form>\n  )\n}\n\n// Usage is awkward and inflexible\nreturn (\n  <Composer\n    renderHeader={() => <CustomHeader />}\n    renderFooter={() => (\n      <>\n        <Formatting />\n        <Emojis />\n      </>\n    )}\n    renderActions={() => <SubmitButton />}\n  />\n)\n```\n\n**Correct (compound components with children):**\n\n```tsx\nfunction ComposerFrame({ children }: { children: React.ReactNode }) {\n  return <form>{children}</form>\n}\n\nfunction ComposerFooter({ children }: { children: React.ReactNode }) {\n  return <footer className='flex'>{children}</footer>\n}\n\n// Usage is flexible\nreturn (\n  <Composer.Frame>\n    <CustomHeader />\n    <Composer.Input />\n    <Composer.Footer>\n      <Composer.Formatting />\n      <Composer.Emojis />\n      <SubmitButton />\n    </Composer.Footer>\n  </Composer.Frame>\n)\n```\n\n**When render props are appropriate:**\n\n```tsx\n// Render props work well when you need to pass data back\n<List\n  data={items}\n  renderItem={({ item, index }) => <Item item={item} index={index} />}\n/>\n```\n\nUse render props when the parent needs to provide data or state to the child.\nUse children when composing static structure.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md",
    "content": "---\ntitle: Create Explicit Component Variants\nimpact: MEDIUM\nimpactDescription: self-documenting code, no hidden conditionals\ntags: composition, variants, architecture\n---\n\n## Create Explicit Component Variants\n\nInstead of one component with many boolean props, create explicit variant\ncomponents. Each variant composes the pieces it needs. The code documents\nitself.\n\n**Incorrect (one component, many modes):**\n\n```tsx\n// What does this component actually render?\n<Composer\n  isThread\n  isEditing={false}\n  channelId='abc'\n  showAttachments\n  showFormatting={false}\n/>\n```\n\n**Correct (explicit variants):**\n\n```tsx\n// Immediately clear what this renders\n<ThreadComposer channelId=\"abc\" />\n\n// Or\n<EditMessageComposer messageId=\"xyz\" />\n\n// Or\n<ForwardMessageComposer messageId=\"123\" />\n```\n\nEach implementation is unique, explicit and self-contained. Yet they can each\nuse shared parts.\n\n**Implementation:**\n\n```tsx\nfunction ThreadComposer({ channelId }: { channelId: string }) {\n  return (\n    <ThreadProvider channelId={channelId}>\n      <Composer.Frame>\n        <Composer.Input />\n        <AlsoSendToChannelField channelId={channelId} />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.Submit />\n        </Composer.Footer>\n      </Composer.Frame>\n    </ThreadProvider>\n  )\n}\n\nfunction EditMessageComposer({ messageId }: { messageId: string }) {\n  return (\n    <EditMessageProvider messageId={messageId}>\n      <Composer.Frame>\n        <Composer.Input />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.CancelEdit />\n          <Composer.SaveEdit />\n        </Composer.Footer>\n      </Composer.Frame>\n    </EditMessageProvider>\n  )\n}\n\nfunction ForwardMessageComposer({ messageId }: { messageId: string }) {\n  return (\n    <ForwardMessageProvider messageId={messageId}>\n      <Composer.Frame>\n        <Composer.Input placeholder=\"Add a message, if you'd like.\" />\n        <Composer.Footer>\n          <Composer.Formatting />\n          <Composer.Emojis />\n          <Composer.Mentions />\n        </Composer.Footer>\n      </Composer.Frame>\n    </ForwardMessageProvider>\n  )\n}\n```\n\nEach variant is explicit about:\n\n- What provider/state it uses\n- What UI elements it includes\n- What actions are available\n\nNo boolean prop combinations to reason about. No impossible states.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md",
    "content": "---\ntitle: React 19 API Changes\nimpact: MEDIUM\nimpactDescription: cleaner component definitions and context usage\ntags: react19, refs, context, hooks\n---\n\n## React 19 API Changes\n\n> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.\n\nIn React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.\n\n**Incorrect (forwardRef in React 19):**\n\n```tsx\nconst ComposerInput = forwardRef<TextInput, Props>((props, ref) => {\n  return <TextInput ref={ref} {...props} />\n})\n```\n\n**Correct (ref as a regular prop):**\n\n```tsx\nfunction ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {\n  return <TextInput ref={ref} {...props} />\n}\n```\n\n**Incorrect (useContext in React 19):**\n\n```tsx\nconst value = useContext(MyContext)\n```\n\n**Correct (use instead of useContext):**\n\n```tsx\nconst value = use(MyContext)\n```\n\n`use()` can also be called conditionally, unlike `useContext()`.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-context-interface.md",
    "content": "---\ntitle: Define Generic Context Interfaces for Dependency Injection\nimpact: HIGH\nimpactDescription: enables dependency-injectable state across use-cases\ntags: composition, context, state, typescript, dependency-injection\n---\n\n## Define Generic Context Interfaces for Dependency Injection\n\nDefine a **generic interface** for your component context with three parts:\n`state`, `actions`, and `meta`. This interface is a contract that any provider\ncan implement—enabling the same UI components to work with completely different\nstate implementations.\n\n**Core principle:** Lift state, compose internals, make state\ndependency-injectable.\n\n**Incorrect (UI coupled to specific state implementation):**\n\n```tsx\nfunction ComposerInput() {\n  // Tightly coupled to a specific hook\n  const { input, setInput } = useChannelComposerState()\n  return <TextInput value={input} onChangeText={setInput} />\n}\n```\n\n**Correct (generic interface enables dependency injection):**\n\n```tsx\n// Define a GENERIC interface that any provider can implement\ninterface ComposerState {\n  input: string\n  attachments: Attachment[]\n  isSubmitting: boolean\n}\n\ninterface ComposerActions {\n  update: (updater: (state: ComposerState) => ComposerState) => void\n  submit: () => void\n}\n\ninterface ComposerMeta {\n  inputRef: React.RefObject<TextInput>\n}\n\ninterface ComposerContextValue {\n  state: ComposerState\n  actions: ComposerActions\n  meta: ComposerMeta\n}\n\nconst ComposerContext = createContext<ComposerContextValue | null>(null)\n```\n\n**UI components consume the interface, not the implementation:**\n\n```tsx\nfunction ComposerInput() {\n  const {\n    state,\n    actions: { update },\n    meta,\n  } = use(ComposerContext)\n\n  // This component works with ANY provider that implements the interface\n  return (\n    <TextInput\n      ref={meta.inputRef}\n      value={state.input}\n      onChangeText={(text) => update((s) => ({ ...s, input: text }))}\n    />\n  )\n}\n```\n\n**Different providers implement the same interface:**\n\n```tsx\n// Provider A: Local state for ephemeral forms\nfunction ForwardMessageProvider({ children }: { children: React.ReactNode }) {\n  const [state, setState] = useState(initialState)\n  const inputRef = useRef(null)\n  const submit = useForwardMessage()\n\n  return (\n    <ComposerContext\n      value={{\n        state,\n        actions: { update: setState, submit },\n        meta: { inputRef },\n      }}\n    >\n      {children}\n    </ComposerContext>\n  )\n}\n\n// Provider B: Global synced state for channels\nfunction ChannelProvider({ channelId, children }: Props) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n  const inputRef = useRef(null)\n\n  return (\n    <ComposerContext\n      value={{\n        state,\n        actions: { update, submit },\n        meta: { inputRef },\n      }}\n    >\n      {children}\n    </ComposerContext>\n  )\n}\n```\n\n**The same composed UI works with both:**\n\n```tsx\n// Works with ForwardMessageProvider (local state)\n<ForwardMessageProvider>\n  <Composer.Frame>\n    <Composer.Input />\n    <Composer.Submit />\n  </Composer.Frame>\n</ForwardMessageProvider>\n\n// Works with ChannelProvider (global synced state)\n<ChannelProvider channelId=\"abc\">\n  <Composer.Frame>\n    <Composer.Input />\n    <Composer.Submit />\n  </Composer.Frame>\n</ChannelProvider>\n```\n\n**Custom UI outside the component can access state and actions:**\n\nThe provider boundary is what matters—not the visual nesting. Components that\nneed shared state don't have to be inside the `Composer.Frame`. They just need\nto be within the provider.\n\n```tsx\nfunction ForwardMessageDialog() {\n  return (\n    <ForwardMessageProvider>\n      <Dialog>\n        {/* The composer UI */}\n        <Composer.Frame>\n          <Composer.Input placeholder=\"Add a message, if you'd like.\" />\n          <Composer.Footer>\n            <Composer.Formatting />\n            <Composer.Emojis />\n          </Composer.Footer>\n        </Composer.Frame>\n\n        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}\n        <MessagePreview />\n\n        {/* Actions at the bottom of the dialog */}\n        <DialogActions>\n          <CancelButton />\n          <ForwardButton />\n        </DialogActions>\n      </Dialog>\n    </ForwardMessageProvider>\n  )\n}\n\n// This button lives OUTSIDE Composer.Frame but can still submit based on its context!\nfunction ForwardButton() {\n  const {\n    actions: { submit },\n  } = use(ComposerContext)\n  return <Button onPress={submit}>Forward</Button>\n}\n\n// This preview lives OUTSIDE Composer.Frame but can read composer's state!\nfunction MessagePreview() {\n  const { state } = use(ComposerContext)\n  return <Preview message={state.input} attachments={state.attachments} />\n}\n```\n\nThe `ForwardButton` and `MessagePreview` are not visually inside the composer\nbox, but they can still access its state and actions. This is the power of\nlifting state into providers.\n\nThe UI is reusable bits you compose together. The state is dependency-injected\nby the provider. Swap the provider, keep the UI.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md",
    "content": "---\ntitle: Decouple State Management from UI\nimpact: MEDIUM\nimpactDescription: enables swapping state implementations without changing UI\ntags: composition, state, architecture\n---\n\n## Decouple State Management from UI\n\nThe provider component should be the only place that knows how state is managed.\nUI components consume the context interface—they don't know if state comes from\nuseState, Zustand, or a server sync.\n\n**Incorrect (UI coupled to state implementation):**\n\n```tsx\nfunction ChannelComposer({ channelId }: { channelId: string }) {\n  // UI component knows about global state implementation\n  const state = useGlobalChannelState(channelId)\n  const { submit, updateInput } = useChannelSync(channelId)\n\n  return (\n    <Composer.Frame>\n      <Composer.Input\n        value={state.input}\n        onChange={(text) => sync.updateInput(text)}\n      />\n      <Composer.Submit onPress={() => sync.submit()} />\n    </Composer.Frame>\n  )\n}\n```\n\n**Correct (state management isolated in provider):**\n\n```tsx\n// Provider handles all state management details\nfunction ChannelProvider({\n  channelId,\n  children,\n}: {\n  channelId: string\n  children: React.ReactNode\n}) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n  const inputRef = useRef(null)\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update, submit }}\n      meta={{ inputRef }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\n// UI component only knows about the context interface\nfunction ChannelComposer() {\n  return (\n    <Composer.Frame>\n      <Composer.Header />\n      <Composer.Input />\n      <Composer.Footer>\n        <Composer.Submit />\n      </Composer.Footer>\n    </Composer.Frame>\n  )\n}\n\n// Usage\nfunction Channel({ channelId }: { channelId: string }) {\n  return (\n    <ChannelProvider channelId={channelId}>\n      <ChannelComposer />\n    </ChannelProvider>\n  )\n}\n```\n\n**Different providers, same UI:**\n\n```tsx\n// Local state for ephemeral forms\nfunction ForwardMessageProvider({ children }) {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update: setState, submit: forwardMessage }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\n// Global synced state for channels\nfunction ChannelProvider({ channelId, children }) {\n  const { state, update, submit } = useGlobalChannel(channelId)\n\n  return (\n    <Composer.Provider state={state} actions={{ update, submit }}>\n      {children}\n    </Composer.Provider>\n  )\n}\n```\n\nThe same `Composer.Input` component works with both providers because it only\ndepends on the context interface, not the implementation.\n"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-lift-state.md",
    "content": "---\ntitle: Lift State into Provider Components\nimpact: HIGH\nimpactDescription: enables state sharing outside component boundaries\ntags: composition, state, context, providers\n---\n\n## Lift State into Provider Components\n\nMove state management into dedicated provider components. This allows sibling\ncomponents outside the main UI to access and modify state without prop drilling\nor awkward refs.\n\n**Incorrect (state trapped inside component):**\n\n```tsx\nfunction ForwardMessageComposer() {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n\n  return (\n    <Composer.Frame>\n      <Composer.Input />\n      <Composer.Footer />\n    </Composer.Frame>\n  )\n}\n\n// Problem: How does this button access composer state?\nfunction ForwardMessageDialog() {\n  return (\n    <Dialog>\n      <ForwardMessageComposer />\n      <MessagePreview /> {/* Needs composer state */}\n      <DialogActions>\n        <CancelButton />\n        <ForwardButton /> {/* Needs to call submit */}\n      </DialogActions>\n    </Dialog>\n  )\n}\n```\n\n**Incorrect (useEffect to sync state up):**\n\n```tsx\nfunction ForwardMessageDialog() {\n  const [input, setInput] = useState('')\n  return (\n    <Dialog>\n      <ForwardMessageComposer onInputChange={setInput} />\n      <MessagePreview input={input} />\n    </Dialog>\n  )\n}\n\nfunction ForwardMessageComposer({ onInputChange }) {\n  const [state, setState] = useState(initialState)\n  useEffect(() => {\n    onInputChange(state.input) // Sync on every change 😬\n  }, [state.input])\n}\n```\n\n**Incorrect (reading state from ref on submit):**\n\n```tsx\nfunction ForwardMessageDialog() {\n  const stateRef = useRef(null)\n  return (\n    <Dialog>\n      <ForwardMessageComposer stateRef={stateRef} />\n      <ForwardButton onPress={() => submit(stateRef.current)} />\n    </Dialog>\n  )\n}\n```\n\n**Correct (state lifted to provider):**\n\n```tsx\nfunction ForwardMessageProvider({ children }: { children: React.ReactNode }) {\n  const [state, setState] = useState(initialState)\n  const forwardMessage = useForwardMessage()\n  const inputRef = useRef(null)\n\n  return (\n    <Composer.Provider\n      state={state}\n      actions={{ update: setState, submit: forwardMessage }}\n      meta={{ inputRef }}\n    >\n      {children}\n    </Composer.Provider>\n  )\n}\n\nfunction ForwardMessageDialog() {\n  return (\n    <ForwardMessageProvider>\n      <Dialog>\n        <ForwardMessageComposer />\n        <MessagePreview /> {/* Custom components can access state and actions */}\n        <DialogActions>\n          <CancelButton />\n          <ForwardButton /> {/* Custom components can access state and actions */}\n        </DialogActions>\n      </Dialog>\n    </ForwardMessageProvider>\n  )\n}\n\nfunction ForwardButton() {\n  const { actions } = use(Composer.Context)\n  return <Button onPress={actions.submit}>Forward</Button>\n}\n```\n\nThe ForwardButton lives outside the Composer.Frame but still has access to the\nsubmit action because it's within the provider. Even though it's a one-off\ncomponent, it can still access the composer's state and actions from outside the\nUI itself.\n\n**Key insight:** Components that need shared state don't have to be visually\nnested inside each other—they just need to be within the same provider.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/AGENTS.md",
    "content": "# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly for agents and LLMs to follow when maintaining,  \n> generating, or refactoring React and Next.js codebases. Humans  \n> may also find it useful, but guidance here is optimized for automation  \n> and consistency by AI-assisted workflows.\n\n---\n\n## Abstract\n\nComprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.\n\n---\n\n## Table of Contents\n\n1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**\n   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)\n   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)\n   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)\n   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)\n   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)\n2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**\n   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)\n   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)\n   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)\n   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)\n   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)\n3. [Server-Side Performance](#3-server-side-performance) — **HIGH**\n   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)\n   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)\n   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)\n   - 3.4 [Minimize Serialization at RSC Boundaries](#34-minimize-serialization-at-rsc-boundaries)\n   - 3.5 [Parallel Data Fetching with Component Composition](#35-parallel-data-fetching-with-component-composition)\n   - 3.6 [Per-Request Deduplication with React.cache()](#36-per-request-deduplication-with-reactcache)\n   - 3.7 [Use after() for Non-Blocking Operations](#37-use-after-for-non-blocking-operations)\n4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**\n   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)\n   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)\n   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)\n   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)\n5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**\n   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)\n   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)\n   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)\n   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)\n   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)\n   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)\n   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)\n   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)\n   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)\n   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)\n   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)\n   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)\n6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**\n   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)\n   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)\n   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)\n   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)\n   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)\n   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)\n   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)\n   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)\n   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)\n7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**\n   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)\n   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)\n   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)\n   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)\n   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)\n   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)\n   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)\n   - 7.8 [Early Return from Functions](#78-early-return-from-functions)\n   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)\n   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)\n   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)\n   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)\n8. [Advanced Patterns](#8-advanced-patterns) — **LOW**\n   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)\n   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)\n   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)\n\n---\n\n## 1. Eliminating Waterfalls\n\n**Impact: CRITICAL**\n\nWaterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.\n\n### 1.1 Defer Await Until Needed\n\n**Impact: HIGH (avoids blocking unused code paths)**\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect: blocks both branches**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct: only blocks when needed**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example: early return optimization**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n\n### 1.2 Dependency-Based Parallelization\n\n**Impact: CRITICAL (2-10× improvement)**\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect: profile waits for config unnecessarily**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct: config and profile run in parallel**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n\n### 1.3 Prevent Waterfall Chains in API Routes\n\n**Impact: CRITICAL (2-10× improvement)**\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect: config waits for auth, data waits for both**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct: auth and config start immediately**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n\n### 1.4 Promise.all() for Independent Operations\n\n**Impact: CRITICAL (2-10× improvement)**\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect: sequential execution, 3 round trips**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct: parallel execution, 1 round trip**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n\n### 1.5 Strategic Suspense Boundaries\n\n**Impact: HIGH (faster initial paint)**\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect: wrapper blocked by data fetching**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct: wrapper shows immediately, data streams in**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative: share promise across components**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n\n- SEO-critical content above the fold\n\n- Small, fast queries where suspense overhead isn't worth it\n\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n\n---\n\n## 2. Bundle Size Optimization\n\n**Impact: CRITICAL**\n\nReducing initial bundle size improves Time to Interactive and Largest Contentful Paint.\n\n### 2.1 Avoid Barrel File Imports\n\n**Impact: CRITICAL (200-800ms import cost, slow builds)**\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect: imports entire library**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct: imports only what you need**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative: Next.js 13.5+**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n\n### 2.2 Conditional Module Loading\n\n**Impact: HIGH (loads large data only when needed)**\n\nLoad large data or modules only when a feature is activated.\n\n**Example: lazy-load animation frames**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n\n### 2.3 Defer Non-Critical Third-Party Libraries\n\n**Impact: MEDIUM (loads after hydration)**\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect: blocks initial bundle**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct: loads after hydration**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n### 2.4 Dynamic Imports for Heavy Components\n\n**Impact: CRITICAL (directly affects TTI and LCP)**\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect: Monaco bundles with main chunk ~300KB**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct: Monaco loads on demand**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n### 2.5 Preload Based on User Intent\n\n**Impact: MEDIUM (reduces perceived latency)**\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example: preload on hover/focus**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example: preload when feature flag is enabled**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n\n---\n\n## 3. Server-Side Performance\n\n**Impact: HIGH**\n\nOptimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.\n\n### 3.1 Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect: no authentication check**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct: authentication inside the action**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n\n### 3.2 Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect: duplicates array**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct: sends 3 strings**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n**Operations breaking deduplication: create new references**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n\n### 3.3 Cross-Request LRU Caching\n\n**Impact: HIGH (caches across requests)**\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n\n### 3.4 Minimize Serialization at RSC Boundaries\n\n**Impact: HIGH (reduces data transfer size)**\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect: serializes all 50 fields**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct: serializes only 1 field**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n\n### 3.5 Parallel Data Fetching with Component Composition\n\n**Impact: CRITICAL (eliminates server-side waterfalls)**\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect: Sidebar waits for Page's fetch to complete**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct: both fetch simultaneously**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n\n### 3.6 Per-Request Deduplication with React.cache()\n\n**Impact: MEDIUM (deduplicates within request)**\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect: always cache miss**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct: cache hit**\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\nIf you must pass objects, pass the same reference:\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n\n- Heavy computations\n\n- Authentication checks\n\n- File system operations\n\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)\n\n### 3.7 Use after() for Non-Blocking Operations\n\n**Impact: MEDIUM (faster response times)**\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect: blocks response**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct: non-blocking**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n\n- Audit logging\n\n- Sending notifications\n\n- Cache invalidation\n\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n\n---\n\n## 4. Client-Side Data Fetching\n\n**Impact: MEDIUM-HIGH**\n\nAutomatic deduplication and efficient data fetching patterns reduce redundant network requests.\n\n### 4.1 Deduplicate Global Event Listeners\n\n**Impact: LOW (single listener for N components)**\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect: N instances = N listeners**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct: N instances = 1 listener**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n\n### 4.2 Use Passive Event Listeners for Scrolling Performance\n\n**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n\n### 4.3 Use SWR for Automatic Deduplication\n\n**Impact: MEDIUM-HIGH (automatic deduplication)**\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect: no deduplication, each instance fetches**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct: multiple instances share one request**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n\n### 4.4 Version and Minimize localStorage Data\n\n**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n\n---\n\n## 5. Re-render Optimization\n\n**Impact: MEDIUM**\n\nReducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.\n\n### 5.1 Calculate Derived State During Rendering\n\n**Impact: MEDIUM (avoids redundant renders and state drift)**\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect: redundant state and effect**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct: derive during render**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)\n\n### 5.2 Defer State Reads to Usage Point\n\n**Impact: MEDIUM (avoids unnecessary subscriptions)**\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect: subscribes to all searchParams changes**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct: reads on demand, no subscription**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n### 5.3 Do not wrap a simple expression with a primitive result type in useMemo\n\n**Impact: LOW-MEDIUM (wasted computation on every render)**\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\n\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\n**Impact: MEDIUM (restores memoization by using a constant for default value)**\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect: `onClick` has different values on every rerender**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct: stable default value**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n### 5.5 Extract to Memoized Components\n\n**Impact: MEDIUM (enables early returns)**\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect: computes avatar even when loading**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct: skips computation when loading**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n\n### 5.6 Narrow Effect Dependencies\n\n**Impact: LOW (minimizes effect re-runs)**\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect: re-runs on any user field change**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct: re-runs only when id changes**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n\n### 5.7 Put Interaction Logic in Event Handlers\n\n**Impact: MEDIUM (avoids effect re-runs and duplicate side effects)**\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect: event modeled as state + effect**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct: do it in the handler**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n\n### 5.8 Subscribe to Derived State\n\n**Impact: MEDIUM (reduces re-render frequency)**\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect: re-renders on every pixel change**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct: re-renders only when boolean changes**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n### 5.9 Use Functional setState Updates\n\n**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)**\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect: requires state as dependency**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct: stable callbacks, no stale closures**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n\n2. **No stale closures** - Always operates on the latest state value\n\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n\n- Inside useCallback/useMemo when state is needed\n\n- Event handlers that reference state\n\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n\n- Setting state from props/arguments only: `setName(newName)`\n\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n\n### 5.10 Use Lazy State Initialization\n\n**Impact: MEDIUM (wasted computation on every render)**\n\nPass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.\n\n**Incorrect: runs on every render**\n\n```tsx\nfunction FilteredList({ items }: { items: Item[] }) {\n  // buildSearchIndex() runs on EVERY render, even after initialization\n  const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))\n  const [query, setQuery] = useState('')\n  \n  // When query changes, buildSearchIndex runs again unnecessarily\n  return <SearchResults index={searchIndex} query={query} />\n}\n\nfunction UserProfile() {\n  // JSON.parse runs on every render\n  const [settings, setSettings] = useState(\n    JSON.parse(localStorage.getItem('settings') || '{}')\n  )\n  \n  return <SettingsForm settings={settings} onChange={setSettings} />\n}\n```\n\n**Correct: runs only once**\n\n```tsx\nfunction FilteredList({ items }: { items: Item[] }) {\n  // buildSearchIndex() runs ONLY on initial render\n  const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))\n  const [query, setQuery] = useState('')\n  \n  return <SearchResults index={searchIndex} query={query} />\n}\n\nfunction UserProfile() {\n  // JSON.parse runs only on initial render\n  const [settings, setSettings] = useState(() => {\n    const stored = localStorage.getItem('settings')\n    return stored ? JSON.parse(stored) : {}\n  })\n  \n  return <SettingsForm settings={settings} onChange={setSettings} />\n}\n```\n\nUse lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.\n\nFor simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.\n\n### 5.11 Use Transitions for Non-Urgent Updates\n\n**Impact: MEDIUM (maintains UI responsiveness)**\n\nMark frequent, non-urgent state updates as transitions to maintain UI responsiveness.\n\n**Incorrect: blocks UI on every scroll**\n\n```tsx\nfunction ScrollTracker() {\n  const [scrollY, setScrollY] = useState(0)\n  useEffect(() => {\n    const handler = () => setScrollY(window.scrollY)\n    window.addEventListener('scroll', handler, { passive: true })\n    return () => window.removeEventListener('scroll', handler)\n  }, [])\n}\n```\n\n**Correct: non-blocking updates**\n\n```tsx\nimport { startTransition } from 'react'\n\nfunction ScrollTracker() {\n  const [scrollY, setScrollY] = useState(0)\n  useEffect(() => {\n    const handler = () => {\n      startTransition(() => setScrollY(window.scrollY))\n    }\n    window.addEventListener('scroll', handler, { passive: true })\n    return () => window.removeEventListener('scroll', handler)\n  }, [])\n}\n```\n\n### 5.12 Use useRef for Transient Values\n\n**Impact: MEDIUM (avoids unnecessary re-renders on frequent updates)**\n\nWhen a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`. Keep component state for UI; use refs for temporary DOM-adjacent values. Updating a ref does not trigger a re-render.\n\n**Incorrect: renders every update**\n\n```tsx\nfunction Tracker() {\n  const [lastX, setLastX] = useState(0)\n\n  useEffect(() => {\n    const onMove = (e: MouseEvent) => setLastX(e.clientX)\n    window.addEventListener('mousemove', onMove)\n    return () => window.removeEventListener('mousemove', onMove)\n  }, [])\n\n  return (\n    <div\n      style={{\n        position: 'fixed',\n        top: 0,\n        left: lastX,\n        width: 8,\n        height: 8,\n        background: 'black',\n      }}\n    />\n  )\n}\n```\n\n**Correct: no re-render for tracking**\n\n```tsx\nfunction Tracker() {\n  const lastXRef = useRef(0)\n  const dotRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    const onMove = (e: MouseEvent) => {\n      lastXRef.current = e.clientX\n      const node = dotRef.current\n      if (node) {\n        node.style.transform = `translateX(${e.clientX}px)`\n      }\n    }\n    window.addEventListener('mousemove', onMove)\n    return () => window.removeEventListener('mousemove', onMove)\n  }, [])\n\n  return (\n    <div\n      ref={dotRef}\n      style={{\n        position: 'fixed',\n        top: 0,\n        left: 0,\n        width: 8,\n        height: 8,\n        background: 'black',\n        transform: 'translateX(0px)',\n      }}\n    />\n  )\n}\n```\n\n---\n\n## 6. Rendering Performance\n\n**Impact: MEDIUM**\n\nOptimizing the rendering process reduces the work the browser needs to do.\n\n### 6.1 Animate SVG Wrapper Instead of SVG Element\n\n**Impact: LOW (enables hardware acceleration)**\n\nMany browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.\n\n**Incorrect: animating SVG directly - no hardware acceleration**\n\n```tsx\nfunction LoadingSpinner() {\n  return (\n    <svg \n      className=\"animate-spin\"\n      width=\"24\" \n      height=\"24\" \n      viewBox=\"0 0 24 24\"\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" />\n    </svg>\n  )\n}\n```\n\n**Correct: animating wrapper div - hardware accelerated**\n\n```tsx\nfunction LoadingSpinner() {\n  return (\n    <div className=\"animate-spin\">\n      <svg \n        width=\"24\" \n        height=\"24\" \n        viewBox=\"0 0 24 24\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" />\n      </svg>\n    </div>\n  )\n}\n```\n\nThis applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.\n\n### 6.2 CSS content-visibility for Long Lists\n\n**Impact: HIGH (faster initial render)**\n\nApply `content-visibility: auto` to defer off-screen rendering.\n\n**CSS:**\n\n```css\n.message-item {\n  content-visibility: auto;\n  contain-intrinsic-size: 0 80px;\n}\n```\n\n**Example:**\n\n```tsx\nfunction MessageList({ messages }: { messages: Message[] }) {\n  return (\n    <div className=\"overflow-y-auto h-screen\">\n      {messages.map(msg => (\n        <div key={msg.id} className=\"message-item\">\n          <Avatar user={msg.author} />\n          <div>{msg.content}</div>\n        </div>\n      ))}\n    </div>\n  )\n}\n```\n\nFor 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).\n\n### 6.3 Hoist Static JSX Elements\n\n**Impact: LOW (avoids re-creation)**\n\nExtract static JSX outside components to avoid re-creation.\n\n**Incorrect: recreates element every render**\n\n```tsx\nfunction LoadingSkeleton() {\n  return <div className=\"animate-pulse h-20 bg-gray-200\" />\n}\n\nfunction Container() {\n  return (\n    <div>\n      {loading && <LoadingSkeleton />}\n    </div>\n  )\n}\n```\n\n**Correct: reuses same element**\n\n```tsx\nconst loadingSkeleton = (\n  <div className=\"animate-pulse h-20 bg-gray-200\" />\n)\n\nfunction Container() {\n  return (\n    <div>\n      {loading && loadingSkeleton}\n    </div>\n  )\n}\n```\n\nThis is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.\n\n### 6.4 Optimize SVG Precision\n\n**Impact: LOW (reduces file size)**\n\nReduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.\n\n**Incorrect: excessive precision**\n\n```svg\n<path d=\"M 10.293847 20.847362 L 30.938472 40.192837\" />\n```\n\n**Correct: 1 decimal place**\n\n```svg\n<path d=\"M 10.3 20.8 L 30.9 40.2\" />\n```\n\n**Automate with SVGO:**\n\n```bash\nnpx svgo --precision=1 --multipass icon.svg\n```\n\n### 6.5 Prevent Hydration Mismatch Without Flickering\n\n**Impact: MEDIUM (avoids visual flicker and hydration errors)**\n\nWhen rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.\n\n**Incorrect: breaks SSR**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  // localStorage is not available on server - throws error\n  const theme = localStorage.getItem('theme') || 'light'\n  \n  return (\n    <div className={theme}>\n      {children}\n    </div>\n  )\n}\n```\n\nServer-side rendering will fail because `localStorage` is undefined.\n\n**Incorrect: visual flickering**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  const [theme, setTheme] = useState('light')\n  \n  useEffect(() => {\n    // Runs after hydration - causes visible flash\n    const stored = localStorage.getItem('theme')\n    if (stored) {\n      setTheme(stored)\n    }\n  }, [])\n  \n  return (\n    <div className={theme}>\n      {children}\n    </div>\n  )\n}\n```\n\nComponent first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.\n\n**Correct: no flicker, no hydration mismatch**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  return (\n    <>\n      <div id=\"theme-wrapper\">\n        {children}\n      </div>\n      <script\n        dangerouslySetInnerHTML={{\n          __html: `\n            (function() {\n              try {\n                var theme = localStorage.getItem('theme') || 'light';\n                var el = document.getElementById('theme-wrapper');\n                if (el) el.className = theme;\n              } catch (e) {}\n            })();\n          `,\n        }}\n      />\n    </>\n  )\n}\n```\n\nThe inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.\n\nThis pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.\n\n### 6.6 Suppress Expected Hydration Mismatches\n\n**Impact: LOW-MEDIUM (avoids noisy hydration warnings for known differences)**\n\nIn SSR frameworks (e.g., Next.js), some values are intentionally different on server vs client (random IDs, dates, locale/timezone formatting). For these *expected* mismatches, wrap the dynamic text in an element with `suppressHydrationWarning` to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.\n\n**Incorrect: known mismatch warnings**\n\n```tsx\nfunction Timestamp() {\n  return <span>{new Date().toLocaleString()}</span>\n}\n```\n\n**Correct: suppress expected mismatch only**\n\n```tsx\nfunction Timestamp() {\n  return (\n    <span suppressHydrationWarning>\n      {new Date().toLocaleString()}\n    </span>\n  )\n}\n```\n\n### 6.7 Use Activity Component for Show/Hide\n\n**Impact: MEDIUM (preserves state/DOM)**\n\nUse React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.\n\n**Usage:**\n\n```tsx\nimport { Activity } from 'react'\n\nfunction Dropdown({ isOpen }: Props) {\n  return (\n    <Activity mode={isOpen ? 'visible' : 'hidden'}>\n      <ExpensiveMenu />\n    </Activity>\n  )\n}\n```\n\nAvoids expensive re-renders and state loss.\n\n### 6.8 Use Explicit Conditional Rendering\n\n**Impact: LOW (prevents rendering 0 or NaN)**\n\nUse explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.\n\n**Incorrect: renders \"0\" when count is 0**\n\n```tsx\nfunction Badge({ count }: { count: number }) {\n  return (\n    <div>\n      {count && <span className=\"badge\">{count}</span>}\n    </div>\n  )\n}\n\n// When count = 0, renders: <div>0</div>\n// When count = 5, renders: <div><span class=\"badge\">5</span></div>\n```\n\n**Correct: renders nothing when count is 0**\n\n```tsx\nfunction Badge({ count }: { count: number }) {\n  return (\n    <div>\n      {count > 0 ? <span className=\"badge\">{count}</span> : null}\n    </div>\n  )\n}\n\n// When count = 0, renders: <div></div>\n// When count = 5, renders: <div><span class=\"badge\">5</span></div>\n```\n\n### 6.9 Use useTransition Over Manual Loading States\n\n**Impact: LOW (reduces re-renders and improves code clarity)**\n\nUse `useTransition` instead of manual `useState` for loading states. This provides built-in `isPending` state and automatically manages transitions.\n\n**Incorrect: manual loading state**\n\n```tsx\nfunction SearchResults() {\n  const [query, setQuery] = useState('')\n  const [results, setResults] = useState([])\n  const [isLoading, setIsLoading] = useState(false)\n\n  const handleSearch = async (value: string) => {\n    setIsLoading(true)\n    setQuery(value)\n    const data = await fetchResults(value)\n    setResults(data)\n    setIsLoading(false)\n  }\n\n  return (\n    <>\n      <input onChange={(e) => handleSearch(e.target.value)} />\n      {isLoading && <Spinner />}\n      <ResultsList results={results} />\n    </>\n  )\n}\n```\n\n**Correct: useTransition with built-in pending state**\n\n```tsx\nimport { useTransition, useState } from 'react'\n\nfunction SearchResults() {\n  const [query, setQuery] = useState('')\n  const [results, setResults] = useState([])\n  const [isPending, startTransition] = useTransition()\n\n  const handleSearch = (value: string) => {\n    setQuery(value) // Update input immediately\n    \n    startTransition(async () => {\n      // Fetch and update results\n      const data = await fetchResults(value)\n      setResults(data)\n    })\n  }\n\n  return (\n    <>\n      <input onChange={(e) => handleSearch(e.target.value)} />\n      {isPending && <Spinner />}\n      <ResultsList results={results} />\n    </>\n  )\n}\n```\n\n**Benefits:**\n\n- **Automatic pending state**: No need to manually manage `setIsLoading(true/false)`\n\n- **Error resilience**: Pending state correctly resets even if the transition throws\n\n- **Better responsiveness**: Keeps the UI responsive during updates\n\n- **Interrupt handling**: New transitions automatically cancel pending ones\n\nReference: [https://react.dev/reference/react/useTransition](https://react.dev/reference/react/useTransition)\n\n---\n\n## 7. JavaScript Performance\n\n**Impact: LOW-MEDIUM**\n\nMicro-optimizations for hot paths can add up to meaningful improvements.\n\n### 7.1 Avoid Layout Thrashing\n\n**Impact: MEDIUM (prevents forced synchronous layouts and reduces performance bottlenecks)**\n\nAvoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.\n\n**This is OK: browser batches style changes**\n\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Each line invalidates style, but browser batches the recalculation\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n}\n```\n\n**Incorrect: interleaved reads and writes force reflows**\n\n```typescript\nfunction layoutThrashing(element: HTMLElement) {\n  element.style.width = '100px'\n  const width = element.offsetWidth  // Forces reflow\n  element.style.height = '200px'\n  const height = element.offsetHeight  // Forces another reflow\n}\n```\n\n**Correct: batch writes, then read once**\n\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Batch all writes together\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n  \n  // Read after all writes are done (single reflow)\n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Correct: batch reads, then writes**\n\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  element.classList.add('highlighted-box')\n  \n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Better: use CSS classes**\n\n**React example:**\n\n```tsx\n// Incorrect: interleaving style changes with layout queries\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  const ref = useRef<HTMLDivElement>(null)\n  \n  useEffect(() => {\n    if (ref.current && isHighlighted) {\n      ref.current.style.width = '100px'\n      const width = ref.current.offsetWidth // Forces layout\n      ref.current.style.height = '200px'\n    }\n  }, [isHighlighted])\n  \n  return <div ref={ref}>Content</div>\n}\n\n// Correct: toggle class\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  return (\n    <div className={isHighlighted ? 'highlighted-box' : ''}>\n      Content\n    </div>\n  )\n}\n```\n\nPrefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.\n\nSee [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.\n\n### 7.2 Build Index Maps for Repeated Lookups\n\n**Impact: LOW-MEDIUM (1M ops to 2K ops)**\n\nMultiple `.find()` calls by the same key should use a Map.\n\n**Incorrect (O(n) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  return orders.map(order => ({\n    ...order,\n    user: users.find(u => u.id === order.userId)\n  }))\n}\n```\n\n**Correct (O(1) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  const userById = new Map(users.map(u => [u.id, u]))\n\n  return orders.map(order => ({\n    ...order,\n    user: userById.get(order.userId)\n  }))\n}\n```\n\nBuild map once (O(n)), then all lookups are O(1).\n\nFor 1000 orders × 1000 users: 1M ops → 2K ops.\n\n### 7.3 Cache Property Access in Loops\n\n**Impact: LOW-MEDIUM (reduces lookups)**\n\nCache object property lookups in hot paths.\n\n**Incorrect: 3 lookups × N iterations**\n\n```typescript\nfor (let i = 0; i < arr.length; i++) {\n  process(obj.config.settings.value)\n}\n```\n\n**Correct: 1 lookup total**\n\n```typescript\nconst value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n  process(value)\n}\n```\n\n### 7.4 Cache Repeated Function Calls\n\n**Impact: MEDIUM (avoid redundant computation)**\n\nUse a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.\n\n**Incorrect: redundant computation**\n\n```typescript\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // slugify() called 100+ times for same project names\n        const slug = slugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Correct: cached results**\n\n```typescript\n// Module-level cache\nconst slugifyCache = new Map<string, string>()\n\nfunction cachedSlugify(text: string): string {\n  if (slugifyCache.has(text)) {\n    return slugifyCache.get(text)!\n  }\n  const result = slugify(text)\n  slugifyCache.set(text, result)\n  return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // Computed only once per unique project name\n        const slug = cachedSlugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Simpler pattern for single-value functions:**\n\n```typescript\nlet isLoggedInCache: boolean | null = null\n\nfunction isLoggedIn(): boolean {\n  if (isLoggedInCache !== null) {\n    return isLoggedInCache\n  }\n  \n  isLoggedInCache = document.cookie.includes('auth=')\n  return isLoggedInCache\n}\n\n// Clear cache when auth changes\nfunction onAuthChange() {\n  isLoggedInCache = null\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\nReference: [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n\n### 7.5 Cache Storage API Calls\n\n**Impact: LOW-MEDIUM (reduces expensive I/O)**\n\n`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.\n\n**Incorrect: reads storage on every call**\n\n```typescript\nfunction getTheme() {\n  return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads\n```\n\n**Correct: Map cache**\n\n```typescript\nconst storageCache = new Map<string, string | null>()\n\nfunction getLocalStorage(key: string) {\n  if (!storageCache.has(key)) {\n    storageCache.set(key, localStorage.getItem(key))\n  }\n  return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n  localStorage.setItem(key, value)\n  storageCache.set(key, value)  // keep cache in sync\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\n**Cookie caching:**\n\n```typescript\nlet cookieCache: Record<string, string> | null = null\n\nfunction getCookie(name: string) {\n  if (!cookieCache) {\n    cookieCache = Object.fromEntries(\n      document.cookie.split('; ').map(c => c.split('='))\n    )\n  }\n  return cookieCache[name]\n}\n```\n\n**Important: invalidate on external changes**\n\n```typescript\nwindow.addEventListener('storage', (e) => {\n  if (e.key) storageCache.delete(e.key)\n})\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'visible') {\n    storageCache.clear()\n  }\n})\n```\n\nIf storage can change externally (another tab, server-set cookies), invalidate cache:\n\n### 7.6 Combine Multiple Array Iterations\n\n**Impact: LOW-MEDIUM (reduces iterations)**\n\nMultiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.\n\n**Incorrect: 3 iterations**\n\n```typescript\nconst admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)\n```\n\n**Correct: 1 iteration**\n\n```typescript\nconst admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n  if (user.isAdmin) admins.push(user)\n  if (user.isTester) testers.push(user)\n  if (!user.isActive) inactive.push(user)\n}\n```\n\n### 7.7 Early Length Check for Array Comparisons\n\n**Impact: MEDIUM-HIGH (avoids expensive operations when lengths differ)**\n\nWhen comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.\n\nIn real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).\n\n**Incorrect: always runs expensive comparison**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Always sorts and joins, even when lengths differ\n  return current.sort().join() !== original.sort().join()\n}\n```\n\nTwo O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.\n\n**Correct (O(1) length check first):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Early return if lengths differ\n  if (current.length !== original.length) {\n    return true\n  }\n  // Only sort when lengths match\n  const currentSorted = current.toSorted()\n  const originalSorted = original.toSorted()\n  for (let i = 0; i < currentSorted.length; i++) {\n    if (currentSorted[i] !== originalSorted[i]) {\n      return true\n    }\n  }\n  return false\n}\n```\n\nThis new approach is more efficient because:\n\n- It avoids the overhead of sorting and joining the arrays when lengths differ\n\n- It avoids consuming memory for the joined strings (especially important for large arrays)\n\n- It avoids mutating the original arrays\n\n- It returns early when a difference is found\n\n### 7.8 Early Return from Functions\n\n**Impact: LOW-MEDIUM (avoids unnecessary computation)**\n\nReturn early when result is determined to skip unnecessary processing.\n\n**Incorrect: processes all items even after finding answer**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  let hasError = false\n  let errorMessage = ''\n  \n  for (const user of users) {\n    if (!user.email) {\n      hasError = true\n      errorMessage = 'Email required'\n    }\n    if (!user.name) {\n      hasError = true\n      errorMessage = 'Name required'\n    }\n    // Continues checking all users even after error found\n  }\n  \n  return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}\n```\n\n**Correct: returns immediately on first error**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  for (const user of users) {\n    if (!user.email) {\n      return { valid: false, error: 'Email required' }\n    }\n    if (!user.name) {\n      return { valid: false, error: 'Name required' }\n    }\n  }\n\n  return { valid: true }\n}\n```\n\n### 7.9 Hoist RegExp Creation\n\n**Impact: LOW-MEDIUM (avoids recreation)**\n\nDon't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.\n\n**Incorrect: new RegExp every render**\n\n```tsx\nfunction Highlighter({ text, query }: Props) {\n  const regex = new RegExp(`(${query})`, 'gi')\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Correct: memoize or hoist**\n\n```tsx\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n  const regex = useMemo(\n    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n    [query]\n  )\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Warning: global regex has mutable state**\n\n```typescript\nconst regex = /foo/g\nregex.test('foo')  // true, lastIndex = 3\nregex.test('foo')  // false, lastIndex = 0\n```\n\nGlobal regex (`/g`) has mutable `lastIndex` state:\n\n### 7.10 Use Loop for Min/Max Instead of Sort\n\n**Impact: LOW (O(n) instead of O(n log n))**\n\nFinding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.\n\n**Incorrect (O(n log n) - sort to find latest):**\n\n```typescript\ninterface Project {\n  id: string\n  name: string\n  updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n  return sorted[0]\n}\n```\n\nSorts the entire array just to find the maximum value.\n\n**Incorrect (O(n log n) - sort for oldest and newest):**\n\n```typescript\nfunction getOldestAndNewest(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}\n```\n\nStill sorts unnecessarily when only min/max are needed.\n\n**Correct (O(n) - single loop):**\n\n```typescript\nfunction getLatestProject(projects: Project[]) {\n  if (projects.length === 0) return null\n  \n  let latest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt > latest.updatedAt) {\n      latest = projects[i]\n    }\n  }\n  \n  return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n  if (projects.length === 0) return { oldest: null, newest: null }\n  \n  let oldest = projects[0]\n  let newest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n  }\n  \n  return { oldest, newest }\n}\n```\n\nSingle pass through the array, no copying, no sorting.\n\n**Alternative: Math.min/Math.max for small arrays**\n\n```typescript\nconst numbers = [5, 2, 8, 1, 9]\nconst min = Math.min(...numbers)\nconst max = Math.max(...numbers)\n```\n\nThis works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.\n\n### 7.11 Use Set/Map for O(1) Lookups\n\n**Impact: LOW-MEDIUM (O(n) to O(1))**\n\nConvert arrays to Set/Map for repeated membership checks.\n\n**Incorrect (O(n) per check):**\n\n```typescript\nconst allowedIds = ['a', 'b', 'c', ...]\nitems.filter(item => allowedIds.includes(item.id))\n```\n\n**Correct (O(1) per check):**\n\n```typescript\nconst allowedIds = new Set(['a', 'b', 'c', ...])\nitems.filter(item => allowedIds.has(item.id))\n```\n\n### 7.12 Use toSorted() Instead of sort() for Immutability\n\n**Impact: MEDIUM-HIGH (prevents mutation bugs in React state)**\n\n`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.\n\n**Incorrect: mutates original array**\n\n```typescript\nfunction UserList({ users }: { users: User[] }) {\n  // Mutates the users prop array!\n  const sorted = useMemo(\n    () => users.sort((a, b) => a.name.localeCompare(b.name)),\n    [users]\n  )\n  return <div>{sorted.map(renderUser)}</div>\n}\n```\n\n**Correct: creates new array**\n\n```typescript\nfunction UserList({ users }: { users: User[] }) {\n  // Creates new sorted array, original unchanged\n  const sorted = useMemo(\n    () => users.toSorted((a, b) => a.name.localeCompare(b.name)),\n    [users]\n  )\n  return <div>{sorted.map(renderUser)}</div>\n}\n```\n\n**Why this matters in React:**\n\n1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only\n\n2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior\n\n**Browser support: fallback for older browsers**\n\n```typescript\n// Fallback for older browsers\nconst sorted = [...items].sort((a, b) => a.value - b.value)\n```\n\n`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:\n\n**Other immutable array methods:**\n\n- `.toSorted()` - immutable sort\n\n- `.toReversed()` - immutable reverse\n\n- `.toSpliced()` - immutable splice\n\n- `.with()` - immutable element replacement\n\n---\n\n## 8. Advanced Patterns\n\n**Impact: LOW**\n\nAdvanced patterns for specific cases that require careful implementation.\n\n### 8.1 Initialize App Once, Not Per Mount\n\n**Impact: LOW-MEDIUM (avoids duplicate init in development)**\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect: runs twice in dev, re-runs on remount**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct: once per app load**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n\n### 8.2 Store Event Handlers in Refs\n\n**Impact: LOW (stable subscriptions)**\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect: re-subscribes on every render**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct: stable subscription**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n\n### 8.3 useEffectEvent for Stable Callback Refs\n\n**Impact: LOW (prevents effect re-runs)**\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect: effect re-runs on every callback change**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct: using React's useEffectEvent**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n\n---\n\n## References\n\n1. [https://react.dev](https://react.dev)\n2. [https://nextjs.org](https://nextjs.org)\n3. [https://swr.vercel.app](https://swr.vercel.app)\n4. [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n5. [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n6. [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n7. [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/README.md",
    "content": "# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.\n\n## Structure\n\n- `rules/` - Individual rule files (one per rule)\n  - `_sections.md` - Section metadata (titles, impacts, descriptions)\n  - `_template.md` - Template for creating new rules\n  - `area-description.md` - Individual rule files\n- `src/` - Build scripts and utilities\n- `metadata.json` - Document metadata (version, organization, abstract)\n- __`AGENTS.md`__ - Compiled output (generated)\n- __`test-cases.json`__ - Test cases for LLM evaluation (generated)\n\n## Getting Started\n\n1. Install dependencies:\n   ```bash\n   pnpm install\n   ```\n\n2. Build AGENTS.md from rules:\n   ```bash\n   pnpm build\n   ```\n\n3. Validate rule files:\n   ```bash\n   pnpm validate\n   ```\n\n4. Extract test cases:\n   ```bash\n   pnpm extract-tests\n   ```\n\n## Creating a New Rule\n\n1. Copy `rules/_template.md` to `rules/area-description.md`\n2. Choose the appropriate area prefix:\n   - `async-` for Eliminating Waterfalls (Section 1)\n   - `bundle-` for Bundle Size Optimization (Section 2)\n   - `server-` for Server-Side Performance (Section 3)\n   - `client-` for Client-Side Data Fetching (Section 4)\n   - `rerender-` for Re-render Optimization (Section 5)\n   - `rendering-` for Rendering Performance (Section 6)\n   - `js-` for JavaScript Performance (Section 7)\n   - `advanced-` for Advanced Patterns (Section 8)\n3. Fill in the frontmatter and content\n4. Ensure you have clear examples with explanations\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n\n## Rule File Structure\n\nEach rule file should follow this structure:\n\n```markdown\n---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description\ntags: tag1, tag2, tag3\n---\n\n## Rule Title Here\n\nBrief explanation of the rule and why it matters.\n\n**Incorrect (description of what's wrong):**\n\n```typescript\n// Bad code example\n```\n\n**Correct (description of what's right):**\n\n```typescript\n// Good code example\n```\n\nOptional explanatory text after examples.\n\nReference: [Link](https://example.com)\n\n## File Naming Convention\n\n- Files starting with `_` are special (excluded from build)\n- Rule files: `area-description.md` (e.g., `async-parallel.md`)\n- Section is automatically inferred from filename prefix\n- Rules are sorted alphabetically by title within each section\n- IDs (e.g., 1.1, 1.2) are auto-generated during build\n\n## Impact Levels\n\n- `CRITICAL` - Highest priority, major performance gains\n- `HIGH` - Significant performance improvements\n- `MEDIUM-HIGH` - Moderate-high gains\n- `MEDIUM` - Moderate performance improvements\n- `LOW-MEDIUM` - Low-medium gains\n- `LOW` - Incremental improvements\n\n## Scripts\n\n- `pnpm build` - Compile rules into AGENTS.md\n- `pnpm validate` - Validate all rule files\n- `pnpm extract-tests` - Extract test cases for LLM evaluation\n- `pnpm dev` - Build and validate\n\n## Contributing\n\nWhen adding or modifying rules:\n\n1. Use the correct filename prefix for your section\n2. Follow the `_template.md` structure\n3. Include clear bad/good examples with explanations\n4. Add appropriate tags\n5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json\n6. Rules are automatically sorted by title - no need to manage numbers!\n\n## Acknowledgments\n\nOriginally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/SKILL.md",
    "content": "---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.\nlicense: MIT\nmetadata:\n  author: vercel\n  version: \"1.0.0\"\n---\n\n# Vercel React Best Practices\n\nComprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 57 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.\n\n## When to Apply\n\nReference these guidelines when:\n- Writing new React components or Next.js pages\n- Implementing data fetching (client or server-side)\n- Reviewing code for performance issues\n- Refactoring existing React/Next.js code\n- Optimizing bundle size or load times\n\n## Rule Categories by Priority\n\n| Priority | Category | Impact | Prefix |\n|----------|----------|--------|--------|\n| 1 | Eliminating Waterfalls | CRITICAL | `async-` |\n| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |\n| 3 | Server-Side Performance | HIGH | `server-` |\n| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |\n| 5 | Re-render Optimization | MEDIUM | `rerender-` |\n| 6 | Rendering Performance | MEDIUM | `rendering-` |\n| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |\n| 8 | Advanced Patterns | LOW | `advanced-` |\n\n## Quick Reference\n\n### 1. Eliminating Waterfalls (CRITICAL)\n\n- `async-defer-await` - Move await into branches where actually used\n- `async-parallel` - Use Promise.all() for independent operations\n- `async-dependencies` - Use better-all for partial dependencies\n- `async-api-routes` - Start promises early, await late in API routes\n- `async-suspense-boundaries` - Use Suspense to stream content\n\n### 2. Bundle Size Optimization (CRITICAL)\n\n- `bundle-barrel-imports` - Import directly, avoid barrel files\n- `bundle-dynamic-imports` - Use next/dynamic for heavy components\n- `bundle-defer-third-party` - Load analytics/logging after hydration\n- `bundle-conditional` - Load modules only when feature is activated\n- `bundle-preload` - Preload on hover/focus for perceived speed\n\n### 3. Server-Side Performance (HIGH)\n\n- `server-auth-actions` - Authenticate server actions like API routes\n- `server-cache-react` - Use React.cache() for per-request deduplication\n- `server-cache-lru` - Use LRU cache for cross-request caching\n- `server-dedup-props` - Avoid duplicate serialization in RSC props\n- `server-serialization` - Minimize data passed to client components\n- `server-parallel-fetching` - Restructure components to parallelize fetches\n- `server-after-nonblocking` - Use after() for non-blocking operations\n\n### 4. Client-Side Data Fetching (MEDIUM-HIGH)\n\n- `client-swr-dedup` - Use SWR for automatic request deduplication\n- `client-event-listeners` - Deduplicate global event listeners\n- `client-passive-event-listeners` - Use passive listeners for scroll\n- `client-localstorage-schema` - Version and minimize localStorage data\n\n### 5. Re-render Optimization (MEDIUM)\n\n- `rerender-defer-reads` - Don't subscribe to state only used in callbacks\n- `rerender-memo` - Extract expensive work into memoized components\n- `rerender-memo-with-default-value` - Hoist default non-primitive props\n- `rerender-dependencies` - Use primitive dependencies in effects\n- `rerender-derived-state` - Subscribe to derived booleans, not raw values\n- `rerender-derived-state-no-effect` - Derive state during render, not effects\n- `rerender-functional-setstate` - Use functional setState for stable callbacks\n- `rerender-lazy-state-init` - Pass function to useState for expensive values\n- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives\n- `rerender-move-effect-to-event` - Put interaction logic in event handlers\n- `rerender-transitions` - Use startTransition for non-urgent updates\n- `rerender-use-ref-transient-values` - Use refs for transient frequent values\n\n### 6. Rendering Performance (MEDIUM)\n\n- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element\n- `rendering-content-visibility` - Use content-visibility for long lists\n- `rendering-hoist-jsx` - Extract static JSX outside components\n- `rendering-svg-precision` - Reduce SVG coordinate precision\n- `rendering-hydration-no-flicker` - Use inline script for client-only data\n- `rendering-hydration-suppress-warning` - Suppress expected mismatches\n- `rendering-activity` - Use Activity component for show/hide\n- `rendering-conditional-render` - Use ternary, not && for conditionals\n- `rendering-usetransition-loading` - Prefer useTransition for loading state\n\n### 7. JavaScript Performance (LOW-MEDIUM)\n\n- `js-batch-dom-css` - Group CSS changes via classes or cssText\n- `js-index-maps` - Build Map for repeated lookups\n- `js-cache-property-access` - Cache object properties in loops\n- `js-cache-function-results` - Cache function results in module-level Map\n- `js-cache-storage` - Cache localStorage/sessionStorage reads\n- `js-combine-iterations` - Combine multiple filter/map into one loop\n- `js-length-check-first` - Check array length before expensive comparison\n- `js-early-exit` - Return early from functions\n- `js-hoist-regexp` - Hoist RegExp creation outside loops\n- `js-min-max-loop` - Use loop for min/max instead of sort\n- `js-set-map-lookups` - Use Set/Map for O(1) lookups\n- `js-tosorted-immutable` - Use toSorted() for immutability\n\n### 8. Advanced Patterns (LOW)\n\n- `advanced-event-handler-refs` - Store event handlers in refs\n- `advanced-init-once` - Initialize app once per app load\n- `advanced-use-latest` - useLatest for stable callback refs\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/async-parallel.md\nrules/bundle-barrel-imports.md\n```\n\nEach rule file contains:\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md",
    "content": "---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs, event-handlers, optimization\n---\n\n## Store Event Handlers in Refs\n\nStore callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.\n\n**Incorrect (re-subscribes on every render):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  useEffect(() => {\n    window.addEventListener(event, handler)\n    return () => window.removeEventListener(event, handler)\n  }, [event, handler])\n}\n```\n\n**Correct (stable subscription):**\n\n```tsx\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const handlerRef = useRef(handler)\n  useEffect(() => {\n    handlerRef.current = handler\n  }, [handler])\n\n  useEffect(() => {\n    const listener = (e) => handlerRef.current(e)\n    window.addEventListener(event, listener)\n    return () => window.removeEventListener(event, listener)\n  }, [event])\n}\n```\n\n**Alternative: use `useEffectEvent` if you're on latest React:**\n\n```tsx\nimport { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n  const onEvent = useEffectEvent(handler)\n\n  useEffect(() => {\n    window.addEventListener(event, onEvent)\n    return () => window.removeEventListener(event, onEvent)\n  }, [event])\n}\n```\n\n`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md",
    "content": "---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development\ntags: initialization, useEffect, app-startup, side-effects\n---\n\n## Initialize App Once, Not Per Mount\n\nDo not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.\n\n**Incorrect (runs twice in dev, re-runs on remount):**\n\n```tsx\nfunction Comp() {\n  useEffect(() => {\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\n**Correct (once per app load):**\n\n```tsx\nlet didInit = false\n\nfunction Comp() {\n  useEffect(() => {\n    if (didInit) return\n    didInit = true\n    loadFromStorage()\n    checkAuthToken()\n  }, [])\n\n  // ...\n}\n```\n\nReference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md",
    "content": "---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hooks, useEffectEvent, refs, optimization\n---\n\n## useEffectEvent for Stable Callback Refs\n\nAccess latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.\n\n**Incorrect (effect re-runs on every callback change):**\n\n```tsx\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearch(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query, onSearch])\n}\n```\n\n**Correct (using React's useEffectEvent):**\n\n```tsx\nimport { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n  const [query, setQuery] = useState('')\n  const onSearchEvent = useEffectEvent(onSearch)\n\n  useEffect(() => {\n    const timeout = setTimeout(() => onSearchEvent(query), 300)\n    return () => clearTimeout(timeout)\n  }, [query])\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-api-routes.md",
    "content": "---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes, server-actions, waterfalls, parallelization\n---\n\n## Prevent Waterfall Chains in API Routes\n\nIn API routes and Server Actions, start independent operations immediately, even if you don't await them yet.\n\n**Incorrect (config waits for auth, data waits for both):**\n\n```typescript\nexport async function GET(request: Request) {\n  const session = await auth()\n  const config = await fetchConfig()\n  const data = await fetchData(session.user.id)\n  return Response.json({ data, config })\n}\n```\n\n**Correct (auth and config start immediately):**\n\n```typescript\nexport async function GET(request: Request) {\n  const sessionPromise = auth()\n  const configPromise = fetchConfig()\n  const session = await sessionPromise\n  const [config, data] = await Promise.all([\n    configPromise,\n    fetchData(session.user.id)\n  ])\n  return Response.json({ data, config })\n}\n```\n\nFor operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-defer-await.md",
    "content": "---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await, conditional, optimization\n---\n\n## Defer Await Until Needed\n\nMove `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.\n\n**Incorrect (blocks both branches):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  const userData = await fetchUserData(userId)\n  \n  if (skipProcessing) {\n    // Returns immediately but still waited for userData\n    return { skipped: true }\n  }\n  \n  // Only this branch uses userData\n  return processUserData(userData)\n}\n```\n\n**Correct (only blocks when needed):**\n\n```typescript\nasync function handleRequest(userId: string, skipProcessing: boolean) {\n  if (skipProcessing) {\n    // Returns immediately without waiting\n    return { skipped: true }\n  }\n  \n  // Fetch only when needed\n  const userData = await fetchUserData(userId)\n  return processUserData(userData)\n}\n```\n\n**Another example (early return optimization):**\n\n```typescript\n// Incorrect: always fetches permissions\nasync function updateResource(resourceId: string, userId: string) {\n  const permissions = await fetchPermissions(userId)\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n\n// Correct: fetches only when needed\nasync function updateResource(resourceId: string, userId: string) {\n  const resource = await getResource(resourceId)\n  \n  if (!resource) {\n    return { error: 'Not found' }\n  }\n  \n  const permissions = await fetchPermissions(userId)\n  \n  if (!permissions.canEdit) {\n    return { error: 'Forbidden' }\n  }\n  \n  return await updateResourceData(resource, permissions)\n}\n```\n\nThis optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-dependencies.md",
    "content": "---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, dependencies, better-all\n---\n\n## Dependency-Based Parallelization\n\nFor operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.\n\n**Incorrect (profile waits for config unnecessarily):**\n\n```typescript\nconst [user, config] = await Promise.all([\n  fetchUser(),\n  fetchConfig()\n])\nconst profile = await fetchProfile(user.id)\n```\n\n**Correct (config and profile run in parallel):**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n  async user() { return fetchUser() },\n  async config() { return fetchConfig() },\n  async profile() {\n    return fetchProfile((await this.$.user).id)\n  }\n})\n```\n\n**Alternative without extra dependencies:**\n\nWe can also create all the promises first, and do `Promise.all()` at the end.\n\n```typescript\nconst userPromise = fetchUser()\nconst profilePromise = userPromise.then(user => fetchProfile(user.id))\n\nconst [user, config, profile] = await Promise.all([\n  userPromise,\n  fetchConfig(),\n  profilePromise\n])\n```\n\nReference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-parallel.md",
    "content": "---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, parallelization, promises, waterfalls\n---\n\n## Promise.all() for Independent Operations\n\nWhen async operations have no interdependencies, execute them concurrently using `Promise.all()`.\n\n**Incorrect (sequential execution, 3 round trips):**\n\n```typescript\nconst user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()\n```\n\n**Correct (parallel execution, 1 round trip):**\n\n```typescript\nconst [user, posts, comments] = await Promise.all([\n  fetchUser(),\n  fetchPosts(),\n  fetchComments()\n])\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md",
    "content": "---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, streaming, layout-shift\n---\n\n## Strategic Suspense Boundaries\n\nInstead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.\n\n**Incorrect (wrapper blocked by data fetching):**\n\n```tsx\nasync function Page() {\n  const data = await fetchData() // Blocks entire page\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <DataDisplay data={data} />\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n```\n\nThe entire layout waits for data even though only the middle section needs it.\n\n**Correct (wrapper shows immediately, data streams in):**\n\n```tsx\nfunction Page() {\n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <div>\n        <Suspense fallback={<Skeleton />}>\n          <DataDisplay />\n        </Suspense>\n      </div>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nasync function DataDisplay() {\n  const data = await fetchData() // Only blocks this component\n  return <div>{data.content}</div>\n}\n```\n\nSidebar, Header, and Footer render immediately. Only DataDisplay waits for data.\n\n**Alternative (share promise across components):**\n\n```tsx\nfunction Page() {\n  // Start fetch immediately, but don't await\n  const dataPromise = fetchData()\n  \n  return (\n    <div>\n      <div>Sidebar</div>\n      <div>Header</div>\n      <Suspense fallback={<Skeleton />}>\n        <DataDisplay dataPromise={dataPromise} />\n        <DataSummary dataPromise={dataPromise} />\n      </Suspense>\n      <div>Footer</div>\n    </div>\n  )\n}\n\nfunction DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Unwraps the promise\n  return <div>{data.content}</div>\n}\n\nfunction DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {\n  const data = use(dataPromise) // Reuses the same promise\n  return <div>{data.summary}</div>\n}\n```\n\nBoth components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.\n\n**When NOT to use this pattern:**\n\n- Critical data needed for layout decisions (affects positioning)\n- SEO-critical content above the fold\n- Small, fast queries where suspense overhead isn't worth it\n- When you want to avoid layout shift (loading → content jump)\n\n**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md",
    "content": "---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle, imports, tree-shaking, barrel-files, performance\n---\n\n## Avoid Barrel File Imports\n\nImport directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).\n\nPopular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.\n\n**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.\n\n**Incorrect (imports entire library):**\n\n```tsx\nimport { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev\n```\n\n**Correct (imports only what you need):**\n\n```tsx\nimport Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use\n```\n\n**Alternative (Next.js 13.5+):**\n\n```js\n// next.config.js - use optimizePackageImports\nmodule.exports = {\n  experimental: {\n    optimizePackageImports: ['lucide-react', '@mui/material']\n  }\n}\n\n// Then you can keep the ergonomic barrel imports:\nimport { Check, X, Menu } from 'lucide-react'\n// Automatically transformed to direct imports at build time\n```\n\nDirect imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.\n\nLibraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.\n\nReference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md",
    "content": "---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, conditional-loading, lazy-loading\n---\n\n## Conditional Module Loading\n\nLoad large data or modules only when a feature is activated.\n\n**Example (lazy-load animation frames):**\n\n```tsx\nfunction AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {\n  const [frames, setFrames] = useState<Frame[] | null>(null)\n\n  useEffect(() => {\n    if (enabled && !frames && typeof window !== 'undefined') {\n      import('./animation-frames.js')\n        .then(mod => setFrames(mod.frames))\n        .catch(() => setEnabled(false))\n    }\n  }, [enabled, frames, setEnabled])\n\n  if (!frames) return <Skeleton />\n  return <Canvas frames={frames} />\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md",
    "content": "---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle, third-party, analytics, defer\n---\n\n## Defer Non-Critical Third-Party Libraries\n\nAnalytics, logging, and error tracking don't block user interaction. Load them after hydration.\n\n**Incorrect (blocks initial bundle):**\n\n```tsx\nimport { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n\n**Correct (loads after hydration):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n  () => import('@vercel/analytics/react').then(m => m.Analytics),\n  { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n  return (\n    <html>\n      <body>\n        {children}\n        <Analytics />\n      </body>\n    </html>\n  )\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md",
    "content": "---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: bundle, dynamic-import, code-splitting, next-dynamic\n---\n\n## Dynamic Imports for Heavy Components\n\nUse `next/dynamic` to lazy-load large components not needed on initial render.\n\n**Incorrect (Monaco bundles with main chunk ~300KB):**\n\n```tsx\nimport { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n\n**Correct (Monaco loads on demand):**\n\n```tsx\nimport dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n  () => import('./monaco-editor').then(m => m.MonacoEditor),\n  { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n  return <MonacoEditor value={code} />\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-preload.md",
    "content": "---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preload, user-intent, hover\n---\n\n## Preload Based on User Intent\n\nPreload heavy bundles before they're needed to reduce perceived latency.\n\n**Example (preload on hover/focus):**\n\n```tsx\nfunction EditorButton({ onClick }: { onClick: () => void }) {\n  const preload = () => {\n    if (typeof window !== 'undefined') {\n      void import('./monaco-editor')\n    }\n  }\n\n  return (\n    <button\n      onMouseEnter={preload}\n      onFocus={preload}\n      onClick={onClick}\n    >\n      Open Editor\n    </button>\n  )\n}\n```\n\n**Example (preload when feature flag is enabled):**\n\n```tsx\nfunction FlagsProvider({ children, flags }: Props) {\n  useEffect(() => {\n    if (flags.editorEnabled && typeof window !== 'undefined') {\n      void import('./monaco-editor').then(mod => mod.init())\n    }\n  }, [flags.editorEnabled])\n\n  return <FlagsContext.Provider value={flags}>\n    {children}\n  </FlagsContext.Provider>\n}\n```\n\nThe `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md",
    "content": "---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: client, swr, event-listeners, subscription\n---\n\n## Deduplicate Global Event Listeners\n\nUse `useSWRSubscription()` to share global event listeners across component instances.\n\n**Incorrect (N instances = N listeners):**\n\n```tsx\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  useEffect(() => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && e.key === key) {\n        callback()\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  }, [key, callback])\n}\n```\n\nWhen using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.\n\n**Correct (N instances = 1 listener):**\n\n```tsx\nimport useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map<string, Set<() => void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n  // Register this callback in the Map\n  useEffect(() => {\n    if (!keyCallbacks.has(key)) {\n      keyCallbacks.set(key, new Set())\n    }\n    keyCallbacks.get(key)!.add(callback)\n\n    return () => {\n      const set = keyCallbacks.get(key)\n      if (set) {\n        set.delete(callback)\n        if (set.size === 0) {\n          keyCallbacks.delete(key)\n        }\n      }\n    }\n  }, [key, callback])\n\n  useSWRSubscription('global-keydown', () => {\n    const handler = (e: KeyboardEvent) => {\n      if (e.metaKey && keyCallbacks.has(e.key)) {\n        keyCallbacks.get(e.key)!.forEach(cb => cb())\n      }\n    }\n    window.addEventListener('keydown', handler)\n    return () => window.removeEventListener('keydown', handler)\n  })\n}\n\nfunction Profile() {\n  // Multiple shortcuts will share the same listener\n  useKeyboardShortcut('p', () => { /* ... */ }) \n  useKeyboardShortcut('k', () => { /* ... */ })\n  // ...\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md",
    "content": "---\ntitle: Version and Minimize localStorage Data\nimpact: MEDIUM\nimpactDescription: prevents schema conflicts, reduces storage size\ntags: client, localStorage, storage, versioning, data-minimization\n---\n\n## Version and Minimize localStorage Data\n\nAdd version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.\n\n**Incorrect:**\n\n```typescript\n// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')\n```\n\n**Correct:**\n\n```typescript\nconst VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n  try {\n    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n  } catch {\n    // Throws in incognito/private browsing, quota exceeded, or disabled\n  }\n}\n\nfunction loadConfig() {\n  try {\n    const data = localStorage.getItem(`userConfig:${VERSION}`)\n    return data ? JSON.parse(data) : null\n  } catch {\n    return null\n  }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n  try {\n    const v1 = localStorage.getItem('userConfig:v1')\n    if (v1) {\n      const old = JSON.parse(v1)\n      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n      localStorage.removeItem('userConfig:v1')\n    }\n  } catch {}\n}\n```\n\n**Store minimal fields from server responses:**\n\n```typescript\n// User object has 20+ fields, only store what UI needs\nfunction cachePrefs(user: FullUser) {\n  try {\n    localStorage.setItem('prefs:v1', JSON.stringify({\n      theme: user.preferences.theme,\n      notifications: user.preferences.notifications\n    }))\n  } catch {}\n}\n```\n\n**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.\n\n**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md",
    "content": "---\ntitle: Use Passive Event Listeners for Scrolling Performance\nimpact: MEDIUM\nimpactDescription: eliminates scroll delay caused by event listeners\ntags: client, event-listeners, scrolling, performance, touch, wheel\n---\n\n## Use Passive Event Listeners for Scrolling Performance\n\nAdd `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.\n\n**Incorrect:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch)\n  document.addEventListener('wheel', handleWheel)\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Correct:**\n\n```typescript\nuseEffect(() => {\n  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n  \n  document.addEventListener('touchstart', handleTouch, { passive: true })\n  document.addEventListener('wheel', handleWheel, { passive: true })\n  \n  return () => {\n    document.removeEventListener('touchstart', handleTouch)\n    document.removeEventListener('wheel', handleWheel)\n  }\n}, [])\n```\n\n**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.\n\n**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md",
    "content": "---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: client, swr, deduplication, data-fetching\n---\n\n## Use SWR for Automatic Deduplication\n\nSWR enables request deduplication, caching, and revalidation across component instances.\n\n**Incorrect (no deduplication, each instance fetches):**\n\n```tsx\nfunction UserList() {\n  const [users, setUsers] = useState([])\n  useEffect(() => {\n    fetch('/api/users')\n      .then(r => r.json())\n      .then(setUsers)\n  }, [])\n}\n```\n\n**Correct (multiple instances share one request):**\n\n```tsx\nimport useSWR from 'swr'\n\nfunction UserList() {\n  const { data: users } = useSWR('/api/users', fetcher)\n}\n```\n\n**For immutable data:**\n\n```tsx\nimport { useImmutableSWR } from '@/lib/swr'\n\nfunction StaticContent() {\n  const { data } = useImmutableSWR('/api/config', fetcher)\n}\n```\n\n**For mutations:**\n\n```tsx\nimport { useSWRMutation } from 'swr/mutation'\n\nfunction UpdateButton() {\n  const { trigger } = useSWRMutation('/api/user', updateUser)\n  return <button onClick={() => trigger()}>Update</button>\n}\n```\n\nReference: [https://swr.vercel.app](https://swr.vercel.app)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md",
    "content": "---\ntitle: Avoid Layout Thrashing\nimpact: MEDIUM\nimpactDescription: prevents forced synchronous layouts and reduces performance bottlenecks\ntags: javascript, dom, css, performance, reflow, layout-thrashing\n---\n\n## Avoid Layout Thrashing\n\nAvoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.\n\n**This is OK (browser batches style changes):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Each line invalidates style, but browser batches the recalculation\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n}\n```\n\n**Incorrect (interleaved reads and writes force reflows):**\n```typescript\nfunction layoutThrashing(element: HTMLElement) {\n  element.style.width = '100px'\n  const width = element.offsetWidth  // Forces reflow\n  element.style.height = '200px'\n  const height = element.offsetHeight  // Forces another reflow\n}\n```\n\n**Correct (batch writes, then read once):**\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  // Batch all writes together\n  element.style.width = '100px'\n  element.style.height = '200px'\n  element.style.backgroundColor = 'blue'\n  element.style.border = '1px solid black'\n  \n  // Read after all writes are done (single reflow)\n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**Correct (batch reads, then writes):**\n```typescript\nfunction avoidThrashing(element: HTMLElement) {\n  // Read phase - all layout queries first\n  const rect1 = element.getBoundingClientRect()\n  const offsetWidth = element.offsetWidth\n  const offsetHeight = element.offsetHeight\n  \n  // Write phase - all style changes after\n  element.style.width = '100px'\n  element.style.height = '200px'\n}\n```\n\n**Better: use CSS classes**\n```css\n.highlighted-box {\n  width: 100px;\n  height: 200px;\n  background-color: blue;\n  border: 1px solid black;\n}\n```\n```typescript\nfunction updateElementStyles(element: HTMLElement) {\n  element.classList.add('highlighted-box')\n  \n  const { width, height } = element.getBoundingClientRect()\n}\n```\n\n**React example:**\n```tsx\n// Incorrect: interleaving style changes with layout queries\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  const ref = useRef<HTMLDivElement>(null)\n  \n  useEffect(() => {\n    if (ref.current && isHighlighted) {\n      ref.current.style.width = '100px'\n      const width = ref.current.offsetWidth // Forces layout\n      ref.current.style.height = '200px'\n    }\n  }, [isHighlighted])\n  \n  return <div ref={ref}>Content</div>\n}\n\n// Correct: toggle class\nfunction Box({ isHighlighted }: { isHighlighted: boolean }) {\n  return (\n    <div className={isHighlighted ? 'highlighted-box' : ''}>\n      Content\n    </div>\n  )\n}\n```\n\nPrefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.\n\nSee [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md",
    "content": "---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript, cache, memoization, performance\n---\n\n## Cache Repeated Function Calls\n\nUse a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.\n\n**Incorrect (redundant computation):**\n\n```typescript\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // slugify() called 100+ times for same project names\n        const slug = slugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Correct (cached results):**\n\n```typescript\n// Module-level cache\nconst slugifyCache = new Map<string, string>()\n\nfunction cachedSlugify(text: string): string {\n  if (slugifyCache.has(text)) {\n    return slugifyCache.get(text)!\n  }\n  const result = slugify(text)\n  slugifyCache.set(text, result)\n  return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n  return (\n    <div>\n      {projects.map(project => {\n        // Computed only once per unique project name\n        const slug = cachedSlugify(project.name)\n        \n        return <ProjectCard key={project.id} slug={slug} />\n      })}\n    </div>\n  )\n}\n```\n\n**Simpler pattern for single-value functions:**\n\n```typescript\nlet isLoggedInCache: boolean | null = null\n\nfunction isLoggedIn(): boolean {\n  if (isLoggedInCache !== null) {\n    return isLoggedInCache\n  }\n  \n  isLoggedInCache = document.cookie.includes('auth=')\n  return isLoggedInCache\n}\n\n// Clear cache when auth changes\nfunction onAuthChange() {\n  isLoggedInCache = null\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\nReference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md",
    "content": "---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops, optimization, caching\n---\n\n## Cache Property Access in Loops\n\nCache object property lookups in hot paths.\n\n**Incorrect (3 lookups × N iterations):**\n\n```typescript\nfor (let i = 0; i < arr.length; i++) {\n  process(obj.config.settings.value)\n}\n```\n\n**Correct (1 lookup total):**\n\n```typescript\nconst value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n  process(value)\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md",
    "content": "---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localStorage, storage, caching, performance\n---\n\n## Cache Storage API Calls\n\n`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.\n\n**Incorrect (reads storage on every call):**\n\n```typescript\nfunction getTheme() {\n  return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads\n```\n\n**Correct (Map cache):**\n\n```typescript\nconst storageCache = new Map<string, string | null>()\n\nfunction getLocalStorage(key: string) {\n  if (!storageCache.has(key)) {\n    storageCache.set(key, localStorage.getItem(key))\n  }\n  return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n  localStorage.setItem(key, value)\n  storageCache.set(key, value)  // keep cache in sync\n}\n```\n\nUse a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.\n\n**Cookie caching:**\n\n```typescript\nlet cookieCache: Record<string, string> | null = null\n\nfunction getCookie(name: string) {\n  if (!cookieCache) {\n    cookieCache = Object.fromEntries(\n      document.cookie.split('; ').map(c => c.split('='))\n    )\n  }\n  return cookieCache[name]\n}\n```\n\n**Important (invalidate on external changes):**\n\nIf storage can change externally (another tab, server-set cookies), invalidate cache:\n\n```typescript\nwindow.addEventListener('storage', (e) => {\n  if (e.key) storageCache.delete(e.key)\n})\n\ndocument.addEventListener('visibilitychange', () => {\n  if (document.visibilityState === 'visible') {\n    storageCache.clear()\n  }\n})\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md",
    "content": "---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, arrays, loops, performance\n---\n\n## Combine Multiple Array Iterations\n\nMultiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.\n\n**Incorrect (3 iterations):**\n\n```typescript\nconst admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)\n```\n\n**Correct (1 iteration):**\n\n```typescript\nconst admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n  if (user.isAdmin) admins.push(user)\n  if (user.isTester) testers.push(user)\n  if (!user.isActive) inactive.push(user)\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-early-exit.md",
    "content": "---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javascript, functions, optimization, early-return\n---\n\n## Early Return from Functions\n\nReturn early when result is determined to skip unnecessary processing.\n\n**Incorrect (processes all items even after finding answer):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  let hasError = false\n  let errorMessage = ''\n  \n  for (const user of users) {\n    if (!user.email) {\n      hasError = true\n      errorMessage = 'Email required'\n    }\n    if (!user.name) {\n      hasError = true\n      errorMessage = 'Name required'\n    }\n    // Continues checking all users even after error found\n  }\n  \n  return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}\n```\n\n**Correct (returns immediately on first error):**\n\n```typescript\nfunction validateUsers(users: User[]) {\n  for (const user of users) {\n    if (!user.email) {\n      return { valid: false, error: 'Email required' }\n    }\n    if (!user.name) {\n      return { valid: false, error: 'Name required' }\n    }\n  }\n\n  return { valid: true }\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md",
    "content": "---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optimization, memoization\n---\n\n## Hoist RegExp Creation\n\nDon't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.\n\n**Incorrect (new RegExp every render):**\n\n```tsx\nfunction Highlighter({ text, query }: Props) {\n  const regex = new RegExp(`(${query})`, 'gi')\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Correct (memoize or hoist):**\n\n```tsx\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n  const regex = useMemo(\n    () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n    [query]\n  )\n  const parts = text.split(regex)\n  return <>{parts.map((part, i) => ...)}</>\n}\n```\n\n**Warning (global regex has mutable state):**\n\nGlobal regex (`/g`) has mutable `lastIndex` state:\n\n```typescript\nconst regex = /foo/g\nregex.test('foo')  // true, lastIndex = 3\nregex.test('foo')  // false, lastIndex = 0\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-index-maps.md",
    "content": "---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript, map, indexing, optimization, performance\n---\n\n## Build Index Maps for Repeated Lookups\n\nMultiple `.find()` calls by the same key should use a Map.\n\n**Incorrect (O(n) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  return orders.map(order => ({\n    ...order,\n    user: users.find(u => u.id === order.userId)\n  }))\n}\n```\n\n**Correct (O(1) per lookup):**\n\n```typescript\nfunction processOrders(orders: Order[], users: User[]) {\n  const userById = new Map(users.map(u => [u.id, u]))\n\n  return orders.map(order => ({\n    ...order,\n    user: userById.get(order.userId)\n  }))\n}\n```\n\nBuild map once (O(n)), then all lookups are O(1).\nFor 1000 orders × 1000 users: 1M ops → 2K ops.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md",
    "content": "---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations when lengths differ\ntags: javascript, arrays, performance, optimization, comparison\n---\n\n## Early Length Check for Array Comparisons\n\nWhen comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.\n\nIn real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).\n\n**Incorrect (always runs expensive comparison):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Always sorts and joins, even when lengths differ\n  return current.sort().join() !== original.sort().join()\n}\n```\n\nTwo O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.\n\n**Correct (O(1) length check first):**\n\n```typescript\nfunction hasChanges(current: string[], original: string[]) {\n  // Early return if lengths differ\n  if (current.length !== original.length) {\n    return true\n  }\n  // Only sort when lengths match\n  const currentSorted = current.toSorted()\n  const originalSorted = original.toSorted()\n  for (let i = 0; i < currentSorted.length; i++) {\n    if (currentSorted[i] !== originalSorted[i]) {\n      return true\n    }\n  }\n  return false\n}\n```\n\nThis new approach is more efficient because:\n- It avoids the overhead of sorting and joining the arrays when lengths differ\n- It avoids consuming memory for the joined strings (especially important for large arrays)\n- It avoids mutating the original arrays\n- It returns early when a difference is found\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md",
    "content": "---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascript, arrays, performance, sorting, algorithms\n---\n\n## Use Loop for Min/Max Instead of Sort\n\nFinding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.\n\n**Incorrect (O(n log n) - sort to find latest):**\n\n```typescript\ninterface Project {\n  id: string\n  name: string\n  updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n  return sorted[0]\n}\n```\n\nSorts the entire array just to find the maximum value.\n\n**Incorrect (O(n log n) - sort for oldest and newest):**\n\n```typescript\nfunction getOldestAndNewest(projects: Project[]) {\n  const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n  return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}\n```\n\nStill sorts unnecessarily when only min/max are needed.\n\n**Correct (O(n) - single loop):**\n\n```typescript\nfunction getLatestProject(projects: Project[]) {\n  if (projects.length === 0) return null\n  \n  let latest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt > latest.updatedAt) {\n      latest = projects[i]\n    }\n  }\n  \n  return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n  if (projects.length === 0) return { oldest: null, newest: null }\n  \n  let oldest = projects[0]\n  let newest = projects[0]\n  \n  for (let i = 1; i < projects.length; i++) {\n    if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n    if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n  }\n  \n  return { oldest, newest }\n}\n```\n\nSingle pass through the array, no copying, no sorting.\n\n**Alternative (Math.min/Math.max for small arrays):**\n\n```typescript\nconst numbers = [5, 2, 8, 1, 9]\nconst min = Math.min(...numbers)\nconst max = Math.max(...numbers)\n```\n\nThis works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md",
    "content": "---\ntitle: Use Set/Map for O(1) Lookups\nimpact: LOW-MEDIUM\nimpactDescription: O(n) to O(1)\ntags: javascript, set, map, data-structures, performance\n---\n\n## Use Set/Map for O(1) Lookups\n\nConvert arrays to Set/Map for repeated membership checks.\n\n**Incorrect (O(n) per check):**\n\n```typescript\nconst allowedIds = ['a', 'b', 'c', ...]\nitems.filter(item => allowedIds.includes(item.id))\n```\n\n**Correct (O(1) per check):**\n\n```typescript\nconst allowedIds = new Set(['a', 'b', 'c', ...])\nitems.filter(item => allowedIds.has(item.id))\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md",
    "content": "---\ntitle: Use toSorted() Instead of sort() for Immutability\nimpact: MEDIUM-HIGH\nimpactDescription: prevents mutation bugs in React state\ntags: javascript, arrays, immutability, react, state, mutation\n---\n\n## Use toSorted() Instead of sort() for Immutability\n\n`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.\n\n**Incorrect (mutates original array):**\n\n```typescript\nfunction UserList({ users }: { users: User[] }) {\n  // Mutates the users prop array!\n  const sorted = useMemo(\n    () => users.sort((a, b) => a.name.localeCompare(b.name)),\n    [users]\n  )\n  return <div>{sorted.map(renderUser)}</div>\n}\n```\n\n**Correct (creates new array):**\n\n```typescript\nfunction UserList({ users }: { users: User[] }) {\n  // Creates new sorted array, original unchanged\n  const sorted = useMemo(\n    () => users.toSorted((a, b) => a.name.localeCompare(b.name)),\n    [users]\n  )\n  return <div>{sorted.map(renderUser)}</div>\n}\n```\n\n**Why this matters in React:**\n\n1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only\n2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior\n\n**Browser support (fallback for older browsers):**\n\n`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:\n\n```typescript\n// Fallback for older browsers\nconst sorted = [...items].sort((a, b) => a.value - b.value)\n```\n\n**Other immutable array methods:**\n\n- `.toSorted()` - immutable sort\n- `.toReversed()` - immutable reverse\n- `.toSpliced()` - immutable splice\n- `.with()` - immutable element replacement\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-activity.md",
    "content": "---\ntitle: Use Activity Component for Show/Hide\nimpact: MEDIUM\nimpactDescription: preserves state/DOM\ntags: rendering, activity, visibility, state-preservation\n---\n\n## Use Activity Component for Show/Hide\n\nUse React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.\n\n**Usage:**\n\n```tsx\nimport { Activity } from 'react'\n\nfunction Dropdown({ isOpen }: Props) {\n  return (\n    <Activity mode={isOpen ? 'visible' : 'hidden'}>\n      <ExpensiveMenu />\n    </Activity>\n  )\n}\n```\n\nAvoids expensive re-renders and state loss.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md",
    "content": "---\ntitle: Animate SVG Wrapper Instead of SVG Element\nimpact: LOW\nimpactDescription: enables hardware acceleration\ntags: rendering, svg, css, animation, performance\n---\n\n## Animate SVG Wrapper Instead of SVG Element\n\nMany browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.\n\n**Incorrect (animating SVG directly - no hardware acceleration):**\n\n```tsx\nfunction LoadingSpinner() {\n  return (\n    <svg \n      className=\"animate-spin\"\n      width=\"24\" \n      height=\"24\" \n      viewBox=\"0 0 24 24\"\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" />\n    </svg>\n  )\n}\n```\n\n**Correct (animating wrapper div - hardware accelerated):**\n\n```tsx\nfunction LoadingSpinner() {\n  return (\n    <div className=\"animate-spin\">\n      <svg \n        width=\"24\" \n        height=\"24\" \n        viewBox=\"0 0 24 24\"\n      >\n        <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" />\n      </svg>\n    </div>\n  )\n}\n```\n\nThis applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md",
    "content": "---\ntitle: Use Explicit Conditional Rendering\nimpact: LOW\nimpactDescription: prevents rendering 0 or NaN\ntags: rendering, conditional, jsx, falsy-values\n---\n\n## Use Explicit Conditional Rendering\n\nUse explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.\n\n**Incorrect (renders \"0\" when count is 0):**\n\n```tsx\nfunction Badge({ count }: { count: number }) {\n  return (\n    <div>\n      {count && <span className=\"badge\">{count}</span>}\n    </div>\n  )\n}\n\n// When count = 0, renders: <div>0</div>\n// When count = 5, renders: <div><span class=\"badge\">5</span></div>\n```\n\n**Correct (renders nothing when count is 0):**\n\n```tsx\nfunction Badge({ count }: { count: number }) {\n  return (\n    <div>\n      {count > 0 ? <span className=\"badge\">{count}</span> : null}\n    </div>\n  )\n}\n\n// When count = 0, renders: <div></div>\n// When count = 5, renders: <div><span class=\"badge\">5</span></div>\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md",
    "content": "---\ntitle: CSS content-visibility for Long Lists\nimpact: HIGH\nimpactDescription: faster initial render\ntags: rendering, css, content-visibility, long-lists\n---\n\n## CSS content-visibility for Long Lists\n\nApply `content-visibility: auto` to defer off-screen rendering.\n\n**CSS:**\n\n```css\n.message-item {\n  content-visibility: auto;\n  contain-intrinsic-size: 0 80px;\n}\n```\n\n**Example:**\n\n```tsx\nfunction MessageList({ messages }: { messages: Message[] }) {\n  return (\n    <div className=\"overflow-y-auto h-screen\">\n      {messages.map(msg => (\n        <div key={msg.id} className=\"message-item\">\n          <Avatar user={msg.author} />\n          <div>{msg.content}</div>\n        </div>\n      ))}\n    </div>\n  )\n}\n```\n\nFor 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md",
    "content": "---\ntitle: Hoist Static JSX Elements\nimpact: LOW\nimpactDescription: avoids re-creation\ntags: rendering, jsx, static, optimization\n---\n\n## Hoist Static JSX Elements\n\nExtract static JSX outside components to avoid re-creation.\n\n**Incorrect (recreates element every render):**\n\n```tsx\nfunction LoadingSkeleton() {\n  return <div className=\"animate-pulse h-20 bg-gray-200\" />\n}\n\nfunction Container() {\n  return (\n    <div>\n      {loading && <LoadingSkeleton />}\n    </div>\n  )\n}\n```\n\n**Correct (reuses same element):**\n\n```tsx\nconst loadingSkeleton = (\n  <div className=\"animate-pulse h-20 bg-gray-200\" />\n)\n\nfunction Container() {\n  return (\n    <div>\n      {loading && loadingSkeleton}\n    </div>\n  )\n}\n```\n\nThis is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md",
    "content": "---\ntitle: Prevent Hydration Mismatch Without Flickering\nimpact: MEDIUM\nimpactDescription: avoids visual flicker and hydration errors\ntags: rendering, ssr, hydration, localStorage, flicker\n---\n\n## Prevent Hydration Mismatch Without Flickering\n\nWhen rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.\n\n**Incorrect (breaks SSR):**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  // localStorage is not available on server - throws error\n  const theme = localStorage.getItem('theme') || 'light'\n  \n  return (\n    <div className={theme}>\n      {children}\n    </div>\n  )\n}\n```\n\nServer-side rendering will fail because `localStorage` is undefined.\n\n**Incorrect (visual flickering):**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  const [theme, setTheme] = useState('light')\n  \n  useEffect(() => {\n    // Runs after hydration - causes visible flash\n    const stored = localStorage.getItem('theme')\n    if (stored) {\n      setTheme(stored)\n    }\n  }, [])\n  \n  return (\n    <div className={theme}>\n      {children}\n    </div>\n  )\n}\n```\n\nComponent first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.\n\n**Correct (no flicker, no hydration mismatch):**\n\n```tsx\nfunction ThemeWrapper({ children }: { children: ReactNode }) {\n  return (\n    <>\n      <div id=\"theme-wrapper\">\n        {children}\n      </div>\n      <script\n        dangerouslySetInnerHTML={{\n          __html: `\n            (function() {\n              try {\n                var theme = localStorage.getItem('theme') || 'light';\n                var el = document.getElementById('theme-wrapper');\n                if (el) el.className = theme;\n              } catch (e) {}\n            })();\n          `,\n        }}\n      />\n    </>\n  )\n}\n```\n\nThe inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.\n\nThis pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md",
    "content": "---\ntitle: Suppress Expected Hydration Mismatches\nimpact: LOW-MEDIUM\nimpactDescription: avoids noisy hydration warnings for known differences\ntags: rendering, hydration, ssr, nextjs\n---\n\n## Suppress Expected Hydration Mismatches\n\nIn SSR frameworks (e.g., Next.js), some values are intentionally different on server vs client (random IDs, dates, locale/timezone formatting). For these *expected* mismatches, wrap the dynamic text in an element with `suppressHydrationWarning` to prevent noisy warnings. Do not use this to hide real bugs. Don’t overuse it.\n\n**Incorrect (known mismatch warnings):**\n\n```tsx\nfunction Timestamp() {\n  return <span>{new Date().toLocaleString()}</span>\n}\n```\n\n**Correct (suppress expected mismatch only):**\n\n```tsx\nfunction Timestamp() {\n  return (\n    <span suppressHydrationWarning>\n      {new Date().toLocaleString()}\n    </span>\n  )\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md",
    "content": "---\ntitle: Optimize SVG Precision\nimpact: LOW\nimpactDescription: reduces file size\ntags: rendering, svg, optimization, svgo\n---\n\n## Optimize SVG Precision\n\nReduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.\n\n**Incorrect (excessive precision):**\n\n```svg\n<path d=\"M 10.293847 20.847362 L 30.938472 40.192837\" />\n```\n\n**Correct (1 decimal place):**\n\n```svg\n<path d=\"M 10.3 20.8 L 30.9 40.2\" />\n```\n\n**Automate with SVGO:**\n\n```bash\nnpx svgo --precision=1 --multipass icon.svg\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md",
    "content": "---\ntitle: Use useTransition Over Manual Loading States\nimpact: LOW\nimpactDescription: reduces re-renders and improves code clarity\ntags: rendering, transitions, useTransition, loading, state\n---\n\n## Use useTransition Over Manual Loading States\n\nUse `useTransition` instead of manual `useState` for loading states. This provides built-in `isPending` state and automatically manages transitions.\n\n**Incorrect (manual loading state):**\n\n```tsx\nfunction SearchResults() {\n  const [query, setQuery] = useState('')\n  const [results, setResults] = useState([])\n  const [isLoading, setIsLoading] = useState(false)\n\n  const handleSearch = async (value: string) => {\n    setIsLoading(true)\n    setQuery(value)\n    const data = await fetchResults(value)\n    setResults(data)\n    setIsLoading(false)\n  }\n\n  return (\n    <>\n      <input onChange={(e) => handleSearch(e.target.value)} />\n      {isLoading && <Spinner />}\n      <ResultsList results={results} />\n    </>\n  )\n}\n```\n\n**Correct (useTransition with built-in pending state):**\n\n```tsx\nimport { useTransition, useState } from 'react'\n\nfunction SearchResults() {\n  const [query, setQuery] = useState('')\n  const [results, setResults] = useState([])\n  const [isPending, startTransition] = useTransition()\n\n  const handleSearch = (value: string) => {\n    setQuery(value) // Update input immediately\n    \n    startTransition(async () => {\n      // Fetch and update results\n      const data = await fetchResults(value)\n      setResults(data)\n    })\n  }\n\n  return (\n    <>\n      <input onChange={(e) => handleSearch(e.target.value)} />\n      {isPending && <Spinner />}\n      <ResultsList results={results} />\n    </>\n  )\n}\n```\n\n**Benefits:**\n\n- **Automatic pending state**: No need to manually manage `setIsLoading(true/false)`\n- **Error resilience**: Pending state correctly resets even if the transition throws\n- **Better responsiveness**: Keeps the UI responsive during updates\n- **Interrupt handling**: New transitions automatically cancel pending ones\n\nReference: [useTransition](https://react.dev/reference/react/useTransition)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md",
    "content": "---\ntitle: Defer State Reads to Usage Point\nimpact: MEDIUM\nimpactDescription: avoids unnecessary subscriptions\ntags: rerender, searchParams, localStorage, optimization\n---\n\n## Defer State Reads to Usage Point\n\nDon't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.\n\n**Incorrect (subscribes to all searchParams changes):**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const searchParams = useSearchParams()\n\n  const handleShare = () => {\n    const ref = searchParams.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n\n**Correct (reads on demand, no subscription):**\n\n```tsx\nfunction ShareButton({ chatId }: { chatId: string }) {\n  const handleShare = () => {\n    const params = new URLSearchParams(window.location.search)\n    const ref = params.get('ref')\n    shareChat(chatId, { ref })\n  }\n\n  return <button onClick={handleShare}>Share</button>\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md",
    "content": "---\ntitle: Narrow Effect Dependencies\nimpact: LOW\nimpactDescription: minimizes effect re-runs\ntags: rerender, useEffect, dependencies, optimization\n---\n\n## Narrow Effect Dependencies\n\nSpecify primitive dependencies instead of objects to minimize effect re-runs.\n\n**Incorrect (re-runs on any user field change):**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user])\n```\n\n**Correct (re-runs only when id changes):**\n\n```tsx\nuseEffect(() => {\n  console.log(user.id)\n}, [user.id])\n```\n\n**For derived state, compute outside effect:**\n\n```tsx\n// Incorrect: runs on width=767, 766, 765...\nuseEffect(() => {\n  if (width < 768) {\n    enableMobileMode()\n  }\n}, [width])\n\n// Correct: runs only on boolean transition\nconst isMobile = width < 768\nuseEffect(() => {\n  if (isMobile) {\n    enableMobileMode()\n  }\n}, [isMobile])\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md",
    "content": "---\ntitle: Calculate Derived State During Rendering\nimpact: MEDIUM\nimpactDescription: avoids redundant renders and state drift\ntags: rerender, derived-state, useEffect, state\n---\n\n## Calculate Derived State During Rendering\n\nIf a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.\n\n**Incorrect (redundant state and effect):**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const [fullName, setFullName] = useState('')\n\n  useEffect(() => {\n    setFullName(firstName + ' ' + lastName)\n  }, [firstName, lastName])\n\n  return <p>{fullName}</p>\n}\n```\n\n**Correct (derive during render):**\n\n```tsx\nfunction Form() {\n  const [firstName, setFirstName] = useState('First')\n  const [lastName, setLastName] = useState('Last')\n  const fullName = firstName + ' ' + lastName\n\n  return <p>{fullName}</p>\n}\n```\n\nReferences: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md",
    "content": "---\ntitle: Subscribe to Derived State\nimpact: MEDIUM\nimpactDescription: reduces re-render frequency\ntags: rerender, derived-state, media-query, optimization\n---\n\n## Subscribe to Derived State\n\nSubscribe to derived boolean state instead of continuous values to reduce re-render frequency.\n\n**Incorrect (re-renders on every pixel change):**\n\n```tsx\nfunction Sidebar() {\n  const width = useWindowWidth()  // updates continuously\n  const isMobile = width < 768\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n\n**Correct (re-renders only when boolean changes):**\n\n```tsx\nfunction Sidebar() {\n  const isMobile = useMediaQuery('(max-width: 767px)')\n  return <nav className={isMobile ? 'mobile' : 'desktop'} />\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md",
    "content": "---\ntitle: Use Functional setState Updates\nimpact: MEDIUM\nimpactDescription: prevents stale closures and unnecessary callback recreations\ntags: react, hooks, useState, useCallback, callbacks, closures\n---\n\n## Use Functional setState Updates\n\nWhen updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.\n\n**Incorrect (requires state as dependency):**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Callback must depend on items, recreated on every items change\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems([...items, ...newItems])\n  }, [items])  // ❌ items dependency causes recreations\n  \n  // Risk of stale closure if dependency is forgotten\n  const removeItem = useCallback((id: string) => {\n    setItems(items.filter(item => item.id !== id))\n  }, [])  // ❌ Missing items dependency - will use stale items!\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\nThe first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.\n\n**Correct (stable callbacks, no stale closures):**\n\n```tsx\nfunction TodoList() {\n  const [items, setItems] = useState(initialItems)\n  \n  // Stable callback, never recreated\n  const addItems = useCallback((newItems: Item[]) => {\n    setItems(curr => [...curr, ...newItems])\n  }, [])  // ✅ No dependencies needed\n  \n  // Always uses latest state, no stale closure risk\n  const removeItem = useCallback((id: string) => {\n    setItems(curr => curr.filter(item => item.id !== id))\n  }, [])  // ✅ Safe and stable\n  \n  return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />\n}\n```\n\n**Benefits:**\n\n1. **Stable callback references** - Callbacks don't need to be recreated when state changes\n2. **No stale closures** - Always operates on the latest state value\n3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks\n4. **Prevents bugs** - Eliminates the most common source of React closure bugs\n\n**When to use functional updates:**\n\n- Any setState that depends on the current state value\n- Inside useCallback/useMemo when state is needed\n- Event handlers that reference state\n- Async operations that update state\n\n**When direct updates are fine:**\n\n- Setting state to a static value: `setCount(0)`\n- Setting state from props/arguments only: `setName(newName)`\n- State doesn't depend on previous value\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md",
    "content": "---\ntitle: Use Lazy State Initialization\nimpact: MEDIUM\nimpactDescription: wasted computation on every render\ntags: react, hooks, useState, performance, initialization\n---\n\n## Use Lazy State Initialization\n\nPass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.\n\n**Incorrect (runs on every render):**\n\n```tsx\nfunction FilteredList({ items }: { items: Item[] }) {\n  // buildSearchIndex() runs on EVERY render, even after initialization\n  const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))\n  const [query, setQuery] = useState('')\n  \n  // When query changes, buildSearchIndex runs again unnecessarily\n  return <SearchResults index={searchIndex} query={query} />\n}\n\nfunction UserProfile() {\n  // JSON.parse runs on every render\n  const [settings, setSettings] = useState(\n    JSON.parse(localStorage.getItem('settings') || '{}')\n  )\n  \n  return <SettingsForm settings={settings} onChange={setSettings} />\n}\n```\n\n**Correct (runs only once):**\n\n```tsx\nfunction FilteredList({ items }: { items: Item[] }) {\n  // buildSearchIndex() runs ONLY on initial render\n  const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))\n  const [query, setQuery] = useState('')\n  \n  return <SearchResults index={searchIndex} query={query} />\n}\n\nfunction UserProfile() {\n  // JSON.parse runs only on initial render\n  const [settings, setSettings] = useState(() => {\n    const stored = localStorage.getItem('settings')\n    return stored ? JSON.parse(stored) : {}\n  })\n  \n  return <SettingsForm settings={settings} onChange={setSettings} />\n}\n```\n\nUse lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.\n\nFor simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md",
    "content": "---\n\ntitle: Extract Default Non-primitive Parameter Value from Memoized Component to Constant\nimpact: MEDIUM\nimpactDescription: restores memoization by using a constant for default value\ntags: rerender, memo, optimization\n\n---\n\n## Extract Default Non-primitive Parameter Value from Memoized Component to Constant\n\nWhen memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.\n\nTo address this issue, extract the default value into a constant.\n\n**Incorrect (`onClick` has different values on every rerender):**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n\n**Correct (stable default value):**\n\n```tsx\nconst NOOP = () => {};\n\nconst UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {\n  // ...\n})\n\n// Used without optional onClick\n<UserAvatar />\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-memo.md",
    "content": "---\ntitle: Extract to Memoized Components\nimpact: MEDIUM\nimpactDescription: enables early returns\ntags: rerender, memo, useMemo, optimization\n---\n\n## Extract to Memoized Components\n\nExtract expensive work into memoized components to enable early returns before computation.\n\n**Incorrect (computes avatar even when loading):**\n\n```tsx\nfunction Profile({ user, loading }: Props) {\n  const avatar = useMemo(() => {\n    const id = computeAvatarId(user)\n    return <Avatar id={id} />\n  }, [user])\n\n  if (loading) return <Skeleton />\n  return <div>{avatar}</div>\n}\n```\n\n**Correct (skips computation when loading):**\n\n```tsx\nconst UserAvatar = memo(function UserAvatar({ user }: { user: User }) {\n  const id = useMemo(() => computeAvatarId(user), [user])\n  return <Avatar id={id} />\n})\n\nfunction Profile({ user, loading }: Props) {\n  if (loading) return <Skeleton />\n  return (\n    <div>\n      <UserAvatar user={user} />\n    </div>\n  )\n}\n```\n\n**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md",
    "content": "---\ntitle: Put Interaction Logic in Event Handlers\nimpact: MEDIUM\nimpactDescription: avoids effect re-runs and duplicate side effects\ntags: rerender, useEffect, events, side-effects, dependencies\n---\n\n## Put Interaction Logic in Event Handlers\n\nIf a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action.\n\n**Incorrect (event modeled as state + effect):**\n\n```tsx\nfunction Form() {\n  const [submitted, setSubmitted] = useState(false)\n  const theme = useContext(ThemeContext)\n\n  useEffect(() => {\n    if (submitted) {\n      post('/api/register')\n      showToast('Registered', theme)\n    }\n  }, [submitted, theme])\n\n  return <button onClick={() => setSubmitted(true)}>Submit</button>\n}\n```\n\n**Correct (do it in the handler):**\n\n```tsx\nfunction Form() {\n  const theme = useContext(ThemeContext)\n\n  function handleSubmit() {\n    post('/api/register')\n    showToast('Registered', theme)\n  }\n\n  return <button onClick={handleSubmit}>Submit</button>\n}\n```\n\nReference: [Should this code move to an event handler?](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md",
    "content": "---\ntitle: Do not wrap a simple expression with a primitive result type in useMemo\nimpact: LOW-MEDIUM\nimpactDescription: wasted computation on every render\ntags: rerender, useMemo, optimization\n---\n\n## Do not wrap a simple expression with a primitive result type in useMemo\n\nWhen an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.\nCalling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.\n\n**Incorrect:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = useMemo(() => {\n    return user.isLoading || notifications.isLoading\n  }, [user.isLoading, notifications.isLoading])\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n\n**Correct:**\n\n```tsx\nfunction Header({ user, notifications }: Props) {\n  const isLoading = user.isLoading || notifications.isLoading\n\n  if (isLoading) return <Skeleton />\n  // return some markup\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md",
    "content": "---\ntitle: Use Transitions for Non-Urgent Updates\nimpact: MEDIUM\nimpactDescription: maintains UI responsiveness\ntags: rerender, transitions, startTransition, performance\n---\n\n## Use Transitions for Non-Urgent Updates\n\nMark frequent, non-urgent state updates as transitions to maintain UI responsiveness.\n\n**Incorrect (blocks UI on every scroll):**\n\n```tsx\nfunction ScrollTracker() {\n  const [scrollY, setScrollY] = useState(0)\n  useEffect(() => {\n    const handler = () => setScrollY(window.scrollY)\n    window.addEventListener('scroll', handler, { passive: true })\n    return () => window.removeEventListener('scroll', handler)\n  }, [])\n}\n```\n\n**Correct (non-blocking updates):**\n\n```tsx\nimport { startTransition } from 'react'\n\nfunction ScrollTracker() {\n  const [scrollY, setScrollY] = useState(0)\n  useEffect(() => {\n    const handler = () => {\n      startTransition(() => setScrollY(window.scrollY))\n    }\n    window.addEventListener('scroll', handler, { passive: true })\n    return () => window.removeEventListener('scroll', handler)\n  }, [])\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md",
    "content": "---\ntitle: Use useRef for Transient Values\nimpact: MEDIUM\nimpactDescription: avoids unnecessary re-renders on frequent updates\ntags: rerender, useref, state, performance\n---\n\n## Use useRef for Transient Values\n\nWhen a value changes frequently and you don't want a re-render on every update (e.g., mouse trackers, intervals, transient flags), store it in `useRef` instead of `useState`. Keep component state for UI; use refs for temporary DOM-adjacent values. Updating a ref does not trigger a re-render.\n\n**Incorrect (renders every update):**\n\n```tsx\nfunction Tracker() {\n  const [lastX, setLastX] = useState(0)\n\n  useEffect(() => {\n    const onMove = (e: MouseEvent) => setLastX(e.clientX)\n    window.addEventListener('mousemove', onMove)\n    return () => window.removeEventListener('mousemove', onMove)\n  }, [])\n\n  return (\n    <div\n      style={{\n        position: 'fixed',\n        top: 0,\n        left: lastX,\n        width: 8,\n        height: 8,\n        background: 'black',\n      }}\n    />\n  )\n}\n```\n\n**Correct (no re-render for tracking):**\n\n```tsx\nfunction Tracker() {\n  const lastXRef = useRef(0)\n  const dotRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    const onMove = (e: MouseEvent) => {\n      lastXRef.current = e.clientX\n      const node = dotRef.current\n      if (node) {\n        node.style.transform = `translateX(${e.clientX}px)`\n      }\n    }\n    window.addEventListener('mousemove', onMove)\n    return () => window.removeEventListener('mousemove', onMove)\n  }, [])\n\n  return (\n    <div\n      ref={dotRef}\n      style={{\n        position: 'fixed',\n        top: 0,\n        left: 0,\n        width: 8,\n        height: 8,\n        background: 'black',\n        transform: 'translateX(0px)',\n      }}\n    />\n  )\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md",
    "content": "---\ntitle: Use after() for Non-Blocking Operations\nimpact: MEDIUM\nimpactDescription: faster response times\ntags: server, async, logging, analytics, side-effects\n---\n\n## Use after() for Non-Blocking Operations\n\nUse Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.\n\n**Incorrect (blocks response):**\n\n```tsx\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Logging blocks the response\n  const userAgent = request.headers.get('user-agent') || 'unknown'\n  await logUserAction({ userAgent })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\n**Correct (non-blocking):**\n\n```tsx\nimport { after } from 'next/server'\nimport { headers, cookies } from 'next/headers'\nimport { logUserAction } from '@/app/utils'\n\nexport async function POST(request: Request) {\n  // Perform mutation\n  await updateDatabase(request)\n  \n  // Log after response is sent\n  after(async () => {\n    const userAgent = (await headers()).get('user-agent') || 'unknown'\n    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'\n    \n    logUserAction({ sessionCookie, userAgent })\n  })\n  \n  return new Response(JSON.stringify({ status: 'success' }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  })\n}\n```\n\nThe response is sent immediately while logging happens in the background.\n\n**Common use cases:**\n\n- Analytics tracking\n- Audit logging\n- Sending notifications\n- Cache invalidation\n- Cleanup tasks\n\n**Important notes:**\n\n- `after()` runs even if the response fails or redirects\n- Works in Server Actions, Route Handlers, and Server Components\n\nReference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md",
    "content": "---\ntitle: Authenticate Server Actions Like API Routes\nimpact: CRITICAL\nimpactDescription: prevents unauthorized access to server mutations\ntags: server, server-actions, authentication, security, authorization\n---\n\n## Authenticate Server Actions Like API Routes\n\n**Impact: CRITICAL (prevents unauthorized access to server mutations)**\n\nServer Actions (functions with `\"use server\"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.\n\nNext.js documentation explicitly states: \"Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.\"\n\n**Incorrect (no authentication check):**\n\n```typescript\n'use server'\n\nexport async function deleteUser(userId: string) {\n  // Anyone can call this! No auth check\n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**Correct (authentication inside the action):**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { unauthorized } from '@/lib/errors'\n\nexport async function deleteUser(userId: string) {\n  // Always check auth inside the action\n  const session = await verifySession()\n  \n  if (!session) {\n    throw unauthorized('Must be logged in')\n  }\n  \n  // Check authorization too\n  if (session.user.role !== 'admin' && session.user.id !== userId) {\n    throw unauthorized('Cannot delete other users')\n  }\n  \n  await db.user.delete({ where: { id: userId } })\n  return { success: true }\n}\n```\n\n**With input validation:**\n\n```typescript\n'use server'\n\nimport { verifySession } from '@/lib/auth'\nimport { z } from 'zod'\n\nconst updateProfileSchema = z.object({\n  userId: z.string().uuid(),\n  name: z.string().min(1).max(100),\n  email: z.string().email()\n})\n\nexport async function updateProfile(data: unknown) {\n  // Validate input first\n  const validated = updateProfileSchema.parse(data)\n  \n  // Then authenticate\n  const session = await verifySession()\n  if (!session) {\n    throw new Error('Unauthorized')\n  }\n  \n  // Then authorize\n  if (session.user.id !== validated.userId) {\n    throw new Error('Can only update own profile')\n  }\n  \n  // Finally perform the mutation\n  await db.user.update({\n    where: { id: validated.userId },\n    data: {\n      name: validated.name,\n      email: validated.email\n    }\n  })\n  \n  return { success: true }\n}\n```\n\nReference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md",
    "content": "---\ntitle: Cross-Request LRU Caching\nimpact: HIGH\nimpactDescription: caches across requests\ntags: server, cache, lru, cross-request\n---\n\n## Cross-Request LRU Caching\n\n`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.\n\n**Implementation:**\n\n```typescript\nimport { LRUCache } from 'lru-cache'\n\nconst cache = new LRUCache<string, any>({\n  max: 1000,\n  ttl: 5 * 60 * 1000  // 5 minutes\n})\n\nexport async function getUser(id: string) {\n  const cached = cache.get(id)\n  if (cached) return cached\n\n  const user = await db.user.findUnique({ where: { id } })\n  cache.set(id, user)\n  return user\n}\n\n// Request 1: DB query, result cached\n// Request 2: cache hit, no DB query\n```\n\nUse when sequential user actions hit multiple endpoints needing the same data within seconds.\n\n**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.\n\n**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.\n\nReference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-cache-react.md",
    "content": "---\ntitle: Per-Request Deduplication with React.cache()\nimpact: MEDIUM\nimpactDescription: deduplicates within request\ntags: server, cache, react-cache, deduplication\n---\n\n## Per-Request Deduplication with React.cache()\n\nUse `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.\n\n**Usage:**\n\n```typescript\nimport { cache } from 'react'\n\nexport const getCurrentUser = cache(async () => {\n  const session = await auth()\n  if (!session?.user?.id) return null\n  return await db.user.findUnique({\n    where: { id: session.user.id }\n  })\n})\n```\n\nWithin a single request, multiple calls to `getCurrentUser()` execute the query only once.\n\n**Avoid inline objects as arguments:**\n\n`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.\n\n**Incorrect (always cache miss):**\n\n```typescript\nconst getUser = cache(async (params: { uid: number }) => {\n  return await db.user.findUnique({ where: { id: params.uid } })\n})\n\n// Each call creates new object, never hits cache\ngetUser({ uid: 1 })\ngetUser({ uid: 1 })  // Cache miss, runs query again\n```\n\n**Correct (cache hit):**\n\n```typescript\nconst getUser = cache(async (uid: number) => {\n  return await db.user.findUnique({ where: { id: uid } })\n})\n\n// Primitive args use value equality\ngetUser(1)\ngetUser(1)  // Cache hit, returns cached result\n```\n\nIf you must pass objects, pass the same reference:\n\n```typescript\nconst params = { uid: 1 }\ngetUser(params)  // Query runs\ngetUser(params)  // Cache hit (same reference)\n```\n\n**Next.js-Specific Note:**\n\nIn Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:\n\n- Database queries (Prisma, Drizzle, etc.)\n- Heavy computations\n- Authentication checks\n- File system operations\n- Any non-fetch async work\n\nUse `React.cache()` to deduplicate these operations across your component tree.\n\nReference: [React.cache documentation](https://react.dev/reference/react/cache)\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md",
    "content": "---\ntitle: Avoid Duplicate Serialization in RSC Props\nimpact: LOW\nimpactDescription: reduces network payload by avoiding duplicate serialization\ntags: server, rsc, serialization, props, client-components\n---\n\n## Avoid Duplicate Serialization in RSC Props\n\n**Impact: LOW (reduces network payload by avoiding duplicate serialization)**\n\nRSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.\n\n**Incorrect (duplicates array):**\n\n```tsx\n// RSC: sends 6 strings (2 arrays × 3 items)\n<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />\n```\n\n**Correct (sends 3 strings):**\n\n```tsx\n// RSC: send once\n<ClientList usernames={usernames} />\n\n// Client: transform there\n'use client'\nconst sorted = useMemo(() => [...usernames].sort(), [usernames])\n```\n\n**Nested deduplication behavior:**\n\nDeduplication works recursively. Impact varies by data type:\n\n- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated\n- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference\n\n```tsx\n// string[] - duplicates everything\nusernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings\n\n// object[] - duplicates array structure only\nusers={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)\n```\n\n**Operations breaking deduplication (create new references):**\n\n- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`\n- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`\n\n**More examples:**\n\n```tsx\n// ❌ Bad\n<C users={users} active={users.filter(u => u.active)} />\n<C product={product} productName={product.name} />\n\n// ✅ Good\n<C users={users} />\n<C product={product} />\n// Do filtering/destructuring in client\n```\n\n**Exception:** Pass derived data when transformation is expensive or client doesn't need original.\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md",
    "content": "---\ntitle: Parallel Data Fetching with Component Composition\nimpact: CRITICAL\nimpactDescription: eliminates server-side waterfalls\ntags: server, rsc, parallel-fetching, composition\n---\n\n## Parallel Data Fetching with Component Composition\n\nReact Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.\n\n**Incorrect (Sidebar waits for Page's fetch to complete):**\n\n```tsx\nexport default async function Page() {\n  const header = await fetchHeader()\n  return (\n    <div>\n      <div>{header}</div>\n      <Sidebar />\n    </div>\n  )\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n```\n\n**Correct (both fetch simultaneously):**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <Header />\n      <Sidebar />\n    </div>\n  )\n}\n```\n\n**Alternative with children prop:**\n\n```tsx\nasync function Header() {\n  const data = await fetchHeader()\n  return <div>{data}</div>\n}\n\nasync function Sidebar() {\n  const items = await fetchSidebarItems()\n  return <nav>{items.map(renderItem)}</nav>\n}\n\nfunction Layout({ children }: { children: ReactNode }) {\n  return (\n    <div>\n      <Header />\n      {children}\n    </div>\n  )\n}\n\nexport default function Page() {\n  return (\n    <Layout>\n      <Sidebar />\n    </Layout>\n  )\n}\n```\n"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-serialization.md",
    "content": "---\ntitle: Minimize Serialization at RSC Boundaries\nimpact: HIGH\nimpactDescription: reduces data transfer size\ntags: server, rsc, serialization, props\n---\n\n## Minimize Serialization at RSC Boundaries\n\nThe React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.\n\n**Incorrect (serializes all 50 fields):**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()  // 50 fields\n  return <Profile user={user} />\n}\n\n'use client'\nfunction Profile({ user }: { user: User }) {\n  return <div>{user.name}</div>  // uses 1 field\n}\n```\n\n**Correct (serializes only 1 field):**\n\n```tsx\nasync function Page() {\n  const user = await fetchUser()\n  return <Profile name={user.name} />\n}\n\n'use client'\nfunction Profile({ name }: { name: string }) {\n  return <div>{name}</div>\n}\n```\n"
  },
  {
    "path": ".agents/skills/web-design-guidelines/SKILL.md",
    "content": "---\nname: web-design-guidelines\ndescription: Review UI code for Web Interface Guidelines compliance. Use when asked to \"review my UI\", \"check accessibility\", \"audit design\", \"review UX\", or \"check my site against best practices\".\nmetadata:\n  author: vercel\n  version: \"1.0.0\"\n  argument-hint: <file-or-pattern>\n---\n\n# Web Interface Guidelines\n\nReview files for compliance with Web Interface Guidelines.\n\n## How It Works\n\n1. Fetch the latest guidelines from the source URL below\n2. Read the specified files (or prompt user for files/pattern)\n3. Check against all rules in the fetched guidelines\n4. Output findings in the terse `file:line` format\n\n## Guidelines Source\n\nFetch fresh guidelines before each review:\n\n```\nhttps://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md\n```\n\nUse WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.\n\n## Usage\n\nWhen a user provides a file or pattern argument:\n1. Fetch guidelines from the source URL above\n2. Read the specified files\n3. Apply all rules from the fetched guidelines\n4. Output findings using the format specified in the guidelines\n\nIf no files specified, ask the user which files to review.\n"
  },
  {
    "path": ".browserslistrc",
    "content": "last 2 versions\nie >= 9\n"
  },
  {
    "path": ".claude/docs/architecture.md",
    "content": "# Architecture\n\n## Project Structure\n\n```\npackages/\n└── dinero.js/            # Single consolidated package\n    └── src/\n        ├── api/          # All API functions (add, subtract, allocate, etc.)\n        ├── bigint/       # BigInt entry point (dinero.js/bigint)\n        ├── calculator/   # Number and BigInt calculator implementations\n        ├── core/         # Types, helpers, utilities\n        ├── currencies/   # ISO 4217 currency exports (dinero.js/currencies)\n        └── dinero/       # Dinero factory function\n\ndocs/                     # VitePress documentation site\nexamples/                 # Sample projects (cart-react, cart-vue, etc.)\ntest/                     # Shared test utilities (imported as 'test-utils')\n```\n\n## Core Concepts\n\n1. **Dinero Object**: Immutable representation of money with `amount` (in minor units), `currency`, and `scale`\n2. **Calculator Pattern**: Pluggable arithmetic backends (number by default, bigint for precision)\n3. **Pure Functions**: All operations are side-effect free and return new Dinero objects\n\n## Build System\n\n- **tsdown**: Single tool for bundling (powered by Rolldown) and type generation\n- **Turborepo**: Build orchestration across workspaces\n- **Globals**: `__DEV__` and `__TEST__` flags replaced at build time for tree-shaking\n\nPackage outputs:\n- `dist/esm/` - ES modules with TypeScript declarations (main entry)\n- `dist/umd/` - UMD bundles (for script tags)\n\n## Testing\n\n- Test files: `packages/dinero.js/src/**/__tests__/*.test.ts`\n- Shared utilities in `/test/utils/` (import as `test-utils`)\n"
  },
  {
    "path": ".claude/docs/git-workflow.md",
    "content": "# Git Workflow\n\n## Branches\n\n- Always branch from `main` (unless doing a stacked PR, in which case branch from the target branch)\n- Always create a dedicated branch for changes\n\n## Pull Requests\n\n- Always reference the issue to close (e.g., `Fixes #123`) in the PR body so the issue closes on merge\n- Always use semantic PR titles following Conventional Commits: `type(scope): subject`\n"
  },
  {
    "path": ".claude/docs/linear.md",
    "content": "# Linear Integration\n\nThis project uses Linear for project management (project: **Dinero.js v2.0.0 Stable Release**).\n\nUse the `/commit` skill to create commits with automatic Linear issue linking. The skill will:\n1. Analyze staged changes\n2. Search for related Linear issues\n3. Help you link commits to issues with `Fixes SAR-XXX` or `Part of SAR-XXX`\n"
  },
  {
    "path": ".claude/skills/commit/SKILL.md",
    "content": "---\nname: commit\ndescription: Create a git commit with optional automatic Linear issue linking. Use when the user asks to commit.\n---\n\n## Workflow\n\n### Step 1: Analyze Changes\n\nRun these commands to understand what's being committed:\n\n```bash\ngit status\ngit diff --staged\ngit diff\n```\n\nIf there are no staged changes, ask the user what to stage or suggest relevant files based on unstaged changes.\n\n### Step 2: Find Related Linear Issues\n\nUse the Linear MCP tools to search for related issues:\n\n1. **List open issues** in the Dinero.js project:\n   ```\n   mcp__linear-server__list_issues with:\n   - project: \"Dinero.js v2.0.0 Stable Release\"\n   - state: \"backlog\" or \"in_progress\" or \"todo\"\n   ```\n\n2. **Search by keywords** from the changes (function names, file paths, feature areas)\n\n3. Present matching issues to the user in a table:\n   | ID | Title | Status |\n   |----|-------|--------|\n   | SAR-110 | Restructure packages... | Backlog |\n\n### Step 3: User Selection\n\nAsk the user using AskUserQuestion:\n- Which issue(s) does this commit relate to?\n- Does this commit **complete** the issue (Fixes) or is it **partial progress** (Part of)?\n\nOptions:\n- Select from found issues\n- Enter a different issue ID\n- No Linear issue (skip linking)\n\n### Step 4: Create Commit\n\nGenerate a commit message following Conventional Commits format:\n\n```\ntype(scope): subject\n\nDescription of changes.\n\n[Fixes|Part of] SAR-XXX\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\n```\n\n**Types:** feat, fix, docs, refactor, test, chore, style, perf\n**Scopes:** core, currencies, calculator-number, calculator-bigint, dinero.js, examples, docs, build, ci\n\n### Step 5: Execute\n\n1. Stage files if needed (prefer specific files over `git add -A`)\n2. Create the commit using a HEREDOC for proper formatting\n3. Run `git status` to confirm success\n4. If the commit completes an issue, offer to update it in Linear using `mcp__linear-server__update_issue`\n\n## Examples\n\n### Completing an issue\n```bash\ngit commit -m \"$(cat <<'EOF'\nfeat(core): add subpath exports for currencies\n\nImplemented exports field in package.json with proper TypeScript support\nfor all subpath entries.\n\nFixes SAR-111\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\"\n```\n\n### Partial progress\n```bash\ngit commit -m \"$(cat <<'EOF'\nrefactor(core): extract currency types to separate module\n\nFirst step in restructuring packages for consolidated dinero.js package.\n\nPart of SAR-110\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\"\n```\n\n### No Linear issue\n```bash\ngit commit -m \"$(cat <<'EOF'\nchore: update .gitignore\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\"\n```\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 2\nend_of_line = lf\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n\n*.png binary\n*.jpg binary\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "@sarahdayan\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG.yml",
    "content": "name: Bug Report\ndescription: File a bug report\ntitle: '[Bug]: '\nlabels: [bug]\nassignees:\n  - sarahdayan\nbody:\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the bug you encountered.\n      options:\n        - label: I have searched the existing issues\n          required: true\n  - type: textarea\n    attributes:\n      label: Current behavior\n      description: A concise description of what you're experiencing.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Expected behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Steps to reproduce\n      description: Steps to reproduce the behavior.\n      placeholder: |\n        1. In this environment...\n        2. Run '...'\n        3. See error...\n        4. Or: https://codesandbox.io/s/xyz\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Version\n      description: What version of Dinero.js are you running?\n      placeholder: e.g., 2.0.0\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Environment\n      description: In what environment are you experiencing the problem?\n      placeholder: e.g., Firefox 89.0.2, Node.js 14.17.3\n    validations:\n      required: true\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/dinerojs/dinero.js/blob/main/CODE_OF_CONDUCT.md)\n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "titleOnly: true\nallowMergeCommits: false\nallowRevertCommits: false\ntypes:\n  - feat\n  - fix\n  - docs\n  - style\n  - refactor\n  - perf\n  - test\n  - build\n  - ci\n  - chore\n  - revert\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    branches: [main]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Run linter\n        run: npm run lint\n\n  types:\n    name: Type Check (Node ${{ matrix.node-version }})\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [20, 22, 24]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Run type check\n        run: npm run test:types\n\n  test:\n    name: Test (Node ${{ matrix.node-version }})\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [20, 22, 24]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Run unit tests\n        run: npm test\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Cache Turborepo\n        uses: actions/cache@v5\n        with:\n          path: node_modules/.cache/turbo\n          key: ${{ runner.os }}-turbo-${{ github.sha }}\n          restore-keys: |\n            ${{ runner.os }}-turbo-\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Build packages\n        run: npm run build\n\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: build-artifacts\n          path: |\n            packages/*/dist\n            packages/*/lib\n          retention-days: 1\n\n  size:\n    name: Bundle Size\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: build-artifacts\n          path: packages\n\n      - name: Check bundle size\n        run: npm run test:size\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    branches: [main]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Cache Turborepo\n        uses: actions/cache@v5\n        with:\n          path: node_modules/.cache/turbo\n          key: ${{ runner.os }}-turbo-${{ github.sha }}\n          restore-keys: |\n            ${{ runner.os }}-turbo-\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Build packages\n        run: npm run build\n\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: release-build-artifacts\n          path: |\n            packages/*/dist\n            packages/*/lib\n          retention-days: 1\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: release-build-artifacts\n          path: packages\n\n      - name: Run unit tests\n        run: npm test\n\n  types:\n    name: Type Check\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: release-build-artifacts\n          path: packages\n\n      - name: Run type check\n        run: npm run test:types\n\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: release-build-artifacts\n          path: packages\n\n      - name: Run linter\n        run: npm run lint\n\n  size:\n    name: Bundle Size\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: release-build-artifacts\n          path: packages\n\n      - name: Check bundle size\n        run: npm run test:size\n\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    needs: [build, test, types, lint, size]\n    permissions:\n      id-token: write\n      contents: write\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          registry-url: 'https://registry.npmjs.org'\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: release-build-artifacts\n          path: packages\n\n      - name: Configure git\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n\n      - name: Release if needed\n        run: npx shipjs trigger\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ncoverage\ndist\ntemp\n*.tsbuildinfo\n.DS_Store\n.env\n.turbo\n*/.vitepress/cache\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx lint-staged\n"
  },
  {
    "path": ".npmrc",
    "content": "legacy-peer-deps=true\n"
  },
  {
    "path": ".nvmrc",
    "content": "24\n"
  },
  {
    "path": ".oxlintrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n  \"plugins\": [\"import\", \"promise\", \"vitest\", \"typescript\"],\n  \"categories\": {\n    \"correctness\": \"error\",\n    \"suspicious\": \"warn\",\n    \"perf\": \"warn\"\n  },\n  \"rules\": {\n    \"import/no-named-as-default\": \"off\",\n    \"no-console\": \"off\"\n  },\n  \"settings\": {\n    \"vitest\": {\n      \"typecheck\": false\n    }\n  },\n  \"env\": {\n    \"builtin\": true\n  },\n  \"globals\": {\n    \"describe\": \"readonly\",\n    \"it\": \"readonly\",\n    \"expect\": \"readonly\",\n    \"vi\": \"readonly\",\n    \"beforeEach\": \"readonly\",\n    \"afterEach\": \"readonly\",\n    \"beforeAll\": \"readonly\",\n    \"afterAll\": \"readonly\",\n    \"__DEV__\": \"readonly\",\n    \"__TEST__\": \"readonly\"\n  },\n  \"ignorePatterns\": [\n    \"dist\",\n    \"lib\",\n    \"temp\",\n    \"node_modules\",\n    \"docs\",\n    \"examples\",\n    \"coverage\",\n    \"*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": ".prettierignore",
    "content": "coverage\nnode_modules\ndist\nbuild\n**/*.md\n**/*.mdx\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"proseWrap\": \"never\",\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": ".size-limit.json",
    "content": "[\n  {\n    \"path\": \"packages/dinero.js/dist/umd/index.production.js\",\n    \"limit\": \"4.5 KB\"\n  },\n  {\n    \"path\": \"packages/dinero.js/dist/umd/bigint/index.production.js\",\n    \"limit\": \"4.5 KB\"\n  }\n]\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Test Spec File\",\n      \"protocol\": \"inspector\",\n      \"program\": \"${workspaceRoot}/node_modules/jest/bin/jest.js\",\n      \"args\": [\n        \"--collectCoverage=false\",\n        \"--colors\",\n        \"--config\",\n        \"${workspaceRoot}/jest.config.ts\",\n        \"--runInBand\",\n        \"--runTestsByPath\",\n        \"${relativeFile}\",\n        \"--testPathPattern=${fileDirname}\",\n        \"--testTimeout=10000000\"\n      ],\n      \"outputCapture\": \"std\",\n      \"internalConsoleOptions\": \"openOnSessionStart\",\n      \"skipFiles\": [\n        \"${workspaceRoot}/node_modules/**/*\",\n        \"<node_internals>/**/*\"\n      ],\n      \"windows\": {\n        \"skipFiles\": [\"C:\\\\**\\\\node_modules\\\\**\\\\*\", \"<node_internals>/**/*\"]\n      },\n      \"disableOptimisticBPs\": true\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"path-intellisense.mappings\": {\n    \"@dinero.js\": \"${workspaceRoot}/packages\"\n  }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [2.0.2](https://github.com/dinerojs/dinero.js/compare/v2.0.1...v2.0.2) (2026-03-13)\n\n\n### Bug Fixes\n\n* **dinero.js:** export DineroComparisonOperator as value, not type-only ([124eb05](https://github.com/dinerojs/dinero.js/commit/124eb05f78f0964efd7bbc529880105ada3799f1)), closes [#847](https://github.com/dinerojs/dinero.js/issues/847)\n\n\n\n## [2.0.1](https://github.com/dinerojs/dinero.js/compare/v2.0.0...v2.0.1) (2026-03-07)\n\n\n### Bug Fixes\n\n* **core:** add \"type\": \"module\" to package.json ([#845](https://github.com/dinerojs/dinero.js/issues/845)) ([c8098ad](https://github.com/dinerojs/dinero.js/commit/c8098adb2e92ca6969e39576be6fc186eb157200)), closes [#843](https://github.com/dinerojs/dinero.js/issues/843)\n\n\n\n# [2.0.0](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.17...v2.0.0) (2026-03-02)\n\nDinero.js v2 is a complete rewrite of the library, designed from the ground up for modern JavaScript and TypeScript. It replaces the object-oriented, chainable API with a functional architecture of pure, standalone functions that are fully tree-shakeable.\n\nFor a step-by-step migration guide, see the [upgrade guide](https://dinerojs.com/getting-started/upgrade-guide).\n\n## Breaking Changes\n\n### Functional API\n\nAll chainable methods are now standalone functions. This enables bundlers to tree-shake unused operations.\n\n```diff\n- import Dinero from 'dinero.js';\n- const price = Dinero({ amount: 500, currency: 'USD' });\n- price.add(Dinero({ amount: 100, currency: 'USD' }));\n+ import { dinero, add } from 'dinero.js';\n+ import { USD } from 'dinero.js/currencies';\n+ const price = dinero({ amount: 500, currency: USD });\n+ add(price, dinero({ amount: 100, currency: USD }));\n```\n\n### Structured currencies\n\nCurrencies are now objects with `code`, `base`, and `exponent` properties instead of ISO 4217 strings. All 166 ISO 4217 currencies ship with the library.\n\n```diff\n- Dinero({ amount: 500, currency: 'USD' })\n+ import { USD } from 'dinero.js/currencies';\n+ dinero({ amount: 500, currency: USD })\n```\n\n### Scale replaces precision\n\nThe `precision` parameter is renamed to `scale`. Scale tracks the decimal point position independently of the currency exponent, allowing Dinero to preserve full precision through multi-step calculations.\n\n```diff\n- Dinero({ amount: 500, currency: 'USD', precision: 3 })\n+ dinero({ amount: 500, currency: USD, scale: 3 })\n```\n\n### Scaled amounts replace floats\n\nOperations like `multiply`, `allocate`, and `convert` no longer accept floating-point numbers. All fractional values must be passed as scaled amounts to prevent IEEE 754 rounding errors.\n\n```diff\n- price.multiply(0.055)\n+ multiply(price, { amount: 55, scale: 3 })\n```\n\n### Removed APIs\n\n- **`divide`** — use `allocate` for lossless distribution\n- **`percentage`** — use `allocate` or `multiply`\n- **`toFormat`**, **`toUnit`**, **`toRoundedUnit`** — use `toDecimal` or `toUnits` with a transformer\n- **`getAmount`**, **`getCurrency`**, **`getPrecision`** — use `toSnapshot`\n- **`getLocale`**, **`setLocale`** — locale support removed; use `toDecimal` with `Intl.NumberFormat`\n- **Global defaults** (`Dinero.defaultCurrency`, `Dinero.globalLocale`, etc.) — no global state\n\n### Type renames\n\nAll public types use a `Dinero` prefix to avoid naming conflicts:\n\n- `Calculator` → `DineroCalculator`\n- `Currency` → `DineroCurrency`\n- `Snapshot` → `DineroSnapshot`\n- `Options` → `DineroOptions`\n\n### Platform requirements\n\n- **Node.js 20+** required (v1 had no formal engine requirement)\n- **Internet Explorer** is no longer supported\n\n## New Features\n\n### BigInt support\n\nThe `dinero.js/bigint` entry point provides a `dinero` function backed by native `bigint` arithmetic, enabling representation of amounts beyond `Number.MAX_SAFE_INTEGER` — essential for cryptocurrencies, high-precision finance, and currencies with many decimal places.\n\n```js\nimport { dinero, add } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst d = dinero({ amount: 999999999999999999n, currency: USD });\n```\n\n### Pluggable calculator\n\nThe `createDinero` factory accepts any object implementing `DineroCalculator<T>`, allowing third-party arbitrary-precision libraries (big.js, JSBI, etc.) as the numeric backend.\n\n### Non-decimal currencies\n\nThe `currency.base` field supports arrays for currencies with multiple subdivisions (e.g., pre-decimal British pounds: `[20, 12]` for 20 shillings/pound and 12 pence/shilling). The `toUnits` function decomposes amounts across all subdivisions.\n\n### Compile-time currency safety\n\nCurrency objects are typed with literal codes (`DineroCurrency<number, 'USD'>`), enabling TypeScript to catch currency mismatches at compile time — for example, preventing addition of USD and EUR values.\n\n### Automatic scale tracking\n\nScale propagates automatically during calculations. The `trimScale` function reduces it back to the smallest safe representation when needed, eliminating manual precision management.\n\n### New functions\n\n- **`compare`** — three-way comparison returning `-1 | 0 | 1`\n- **`toDecimal`** — string decimal representation with optional transformer\n- **`toUnits`** — array of amounts per currency subdivision\n- **`trimScale`** — reduce scale to smallest safe representation\n- **`normalizeScale`** — align multiple Dinero objects to a common scale\n- **`transformScale`** — convert to a specific scale\n\n### Rounding modes\n\nEight rounding functions for precise control: `up`, `down`, `halfUp`, `halfDown`, `halfEven`, `halfOdd`, `halfTowardsZero`, `halfAwayFromZero`.\n\n### Full tree-shaking\n\nThe package is marked `sideEffects: false`. Only the functions you import are included in your bundle.\n\n## Bug Fixes\n\n* **allocate:** distribute remainder to largest ratio first ([#776](https://github.com/dinerojs/dinero.js/issues/776))\n* **allocate:** prevent infinite loop with large amounts ([#771](https://github.com/dinerojs/dinero.js/issues/771))\n* **convert:** throw when converting between currencies with different bases ([#477](https://github.com/dinerojs/dinero.js/issues/477))\n* **isPositive:** return `false` for zero values ([#728](https://github.com/dinerojs/dinero.js/issues/728))\n* **toDecimal:** handle negative units correctly ([#690](https://github.com/dinerojs/dinero.js/issues/690))\n* **toDecimal:** preserve negative sign for leading zeros ([#692](https://github.com/dinerojs/dinero.js/issues/692))\n* **toDecimal:** do not append decimal string when scale is zero ([#751](https://github.com/dinerojs/dinero.js/issues/751))\n* **rounding:** fix `up`, `down`, `halfUp` handling of numbers close to 0 ([#710](https://github.com/dinerojs/dinero.js/issues/710), [#713](https://github.com/dinerojs/dinero.js/issues/713))\n* **currencies:** use proper base and exponents for MRU and MGA\n* **currencies:** update to ISO 4217 amendments 169-179\n\n## Package Changes\n\nThe library is distributed as a single `dinero.js` package with subpath exports:\n\n| Import path | Description |\n|---|---|\n| `dinero.js` | Core API (number amounts) |\n| `dinero.js/currencies` | ISO 4217 currency objects |\n| `dinero.js/bigint` | Core API (bigint amounts) |\n| `dinero.js/bigint/currencies` | ISO 4217 currency objects for bigint |\n\nESM and UMD bundles are available. TypeScript declarations are included.\n\n## Infrastructure\n\n- **Build system:** [tsdown](https://tsdown.dev/) (powered by Rolldown) for bundling and type generation\n- **Linting:** [Oxlint](https://oxc.rs/) (Rust-based)\n- **Testing:** [Vitest](https://vitest.dev/) with native TypeScript support\n- **Documentation:** new [VitePress](https://vitepress.dev/) site at [dinerojs.com](https://dinerojs.com), with Algolia DocSearch and AskAI\n- **Node.js:** 20+ required\n\n## Documentation\n\nThe documentation has been completely rewritten and is available at [dinerojs.com](https://dinerojs.com). It includes:\n\n- Core concepts guide (amount, currency, scale, mutations, comparisons, formatting)\n- Practical guides (serialization, database storage, payment services, cryptocurrencies, and more)\n- Full API reference with examples\n- Interactive examples: shopping cart (React + Vue), invoice builder, expense splitter, portfolio tracker, and pricing page\n\n\n\n# [2.0.0-alpha.17](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.16...v2.0.0-alpha.17) (2026-02-28)\n\n\n### Bug Fixes\n\n* **api:** throw when converting between currencies with different bases ([#832](https://github.com/dinerojs/dinero.js/issues/832)) ([47d6145](https://github.com/dinerojs/dinero.js/commit/47d6145ddcdb07fcc1dcea9c163cde94e070bf24)), closes [#477](https://github.com/dinerojs/dinero.js/issues/477)\n* **docs:** add .html extension to documentation links in README ([2536a1f](https://github.com/dinerojs/dinero.js/commit/2536a1fb8cfbb5ff9694886168ebe74b57281505))\n* **docs:** add Vercel redirects for old URLs ([4002439](https://github.com/dinerojs/dinero.js/commit/40024399ad16e7a84991f55a06023eafddbc1ca8))\n* **docs:** remove unnecessary npm install from Vercel build command ([72f0a91](https://github.com/dinerojs/dinero.js/commit/72f0a91249bc1912e9098872c3b7fb195fb73b52))\n\n\n### Features\n\n* add compile-time currency safety with `TCurrency` type parameter ([#833](https://github.com/dinerojs/dinero.js/issues/833)) ([ab5d4a3](https://github.com/dinerojs/dinero.js/commit/ab5d4a3245fc496b6d29e0fdfe2e628d8be1e3c7))\n* **docs:** add DocSearch AskAI integration ([a343fb2](https://github.com/dinerojs/dinero.js/commit/a343fb2687a4f500fd889b1a785b628d71b4f291))\n\n\n\n# [2.0.0-alpha.16](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.15...v2.0.0-alpha.16) (2026-02-03)\n\n## ⚠️ Breaking Changes\n\n### Package Consolidation (RFC #722)\n\n**All `@dinero.js/*` packages have been removed.** The library is now distributed as a single `dinero.js` package with subpath exports.\n\n#### Before (alpha.15 and earlier):\n```js\nimport { dinero, add } from '@dinero.js/core';\nimport { USD } from '@dinero.js/currencies';\nimport { calculator } from '@dinero.js/calculator-bigint';\n```\n\n#### After (alpha.16+):\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\nimport { dinero as dineroBigint } from 'dinero.js/bigint';\n```\n\n#### Migration steps:\n1. Uninstall all `@dinero.js/*` packages\n2. Install `dinero.js`\n3. Update imports as shown above\n\n### Type Renaming\n\nAll public types now use a `Dinero` prefix to avoid naming conflicts:\n- `Calculator` → `DineroCalculator`\n- `Currency` → `DineroCurrency`\n- `Snapshot` → `DineroSnapshot`\n- `Options` → `DineroOptions`\n- etc.\n\n---\n\n### Features\n\n* **dinero.js:** consolidate packages with subpath exports ([16b0ad8](https://github.com/dinerojs/dinero.js/commit/16b0ad80315902e0893db8c3306bfcdd91835651))\n* **dinero.js:** add granular UMD bundles per RFC #722 ([aea9dc2](https://github.com/dinerojs/dinero.js/commit/aea9dc2b086f5d68b55d591d464c04d52a972fd3))\n* **bigint:** include bigint currencies in UMD bundle ([1054497](https://github.com/dinerojs/dinero.js/commit/10544971fd4eb1adb4b6e902d212e8a72021ba99))\n* **currencies:** add bigint currency definitions ([0e4aef8](https://github.com/dinerojs/dinero.js/commit/0e4aef8524ac3ec0d294a0b90fbbef329801cbe1)), closes [#582](https://github.com/dinerojs/dinero.js/issues/582)\n* **currencies:** add SLE, VED, XAD, XCG, ZWG (amendments 172-179)\n* **currencies:** remove HRK, SLL, CUC, ANG, ZWL (amendments 172-179)\n\n### Bug Fixes\n\n* **currencies:** correct UYW to amendment 169 and fix generation ([0ea9d7e](https://github.com/dinerojs/dinero.js/commit/0ea9d7ef1e2096c763f72348af3bfe6ec0ba25d9))\n* **docs:** correct OG image path and hero animation target ([f9a8585](https://github.com/dinerojs/dinero.js/commit/f9a8585691b230ad3f0572ce64068319370cf15b))\n\n\n\n# [2.0.0-alpha.15](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.14...v2.0.0-alpha.15) (2026-01-31)\n\n\n### Bug Fixes\n\n* **allocate:** distribute remainder to largest ratio first ([6049038](https://github.com/dinerojs/dinero.js/commit/604903871ff14a67861534906ec5a28d6f10ff24)), closes [#776](https://github.com/dinerojs/dinero.js/issues/776)\n* **allocate:** prevent infinite loop with large amounts ([d787b98](https://github.com/dinerojs/dinero.js/commit/d787b9883f08f3e1ecf286979959b1628b0da2c0)), closes [#771](https://github.com/dinerojs/dinero.js/issues/771)\n* **release:** rename ship.config.js to .cjs for ESM compatibility ([5f5ed24](https://github.com/dinerojs/dinero.js/commit/5f5ed249cb274331f81ad6b2112cf42f835c150d))\n* **release:** upgrade Ship.js to 0.27.0 for Node 22 compatibility ([0121dc5](https://github.com/dinerojs/dinero.js/commit/0121dc5f588a011938b6d504877b922744852495))\n* **release:** use npm ci for install command in Ship.js ([d098801](https://github.com/dinerojs/dinero.js/commit/d0988012b8e0027d406dc58cb0143d3aab136c1b))\n* **toDecimal:** do not append decimal string when scale is zero. ([#759](https://github.com/dinerojs/dinero.js/issues/759)) ([80a1dd7](https://github.com/dinerojs/dinero.js/commit/80a1dd7c8c93ca48e6a93998da18df5ad533c38c)), closes [#751](https://github.com/dinerojs/dinero.js/issues/751)\n\n\n\n# [2.0.0-alpha.14](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.13...v2.0.0-alpha.14) (2023-02-27)\n\n\n### Bug Fixes\n\n* **isPositive:** return `false` with zero ([#728](https://github.com/dinerojs/dinero.js/issues/728)) ([140fe68](https://github.com/dinerojs/dinero.js/commit/140fe68ba516ce47a12a01a06a6425c7df5bd455))\n* **up, down, halfUp:** fix handling of numbers close to 0 and rounding of integer results ([#711](https://github.com/dinerojs/dinero.js/issues/711)) ([6b30aa0](https://github.com/dinerojs/dinero.js/commit/6b30aa09aa5d887cd00170d2659d0bf044081d93)), closes [#710](https://github.com/dinerojs/dinero.js/issues/710) [#713](https://github.com/dinerojs/dinero.js/issues/713)\n\n\n\n# [2.0.0-alpha.13](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.12...v2.0.0-alpha.13) (2022-12-19)\n\n\n### Bug Fixes\n\n* **toDecimal:** preserve negative sign for leading zeros ([#693](https://github.com/dinerojs/dinero.js/issues/693)) ([e6f290d](https://github.com/dinerojs/dinero.js/commit/e6f290dfd754826f20eeafff6c3a505ee19bf05f)), closes [#692](https://github.com/dinerojs/dinero.js/issues/692)\n\n\n\n# [2.0.0-alpha.12](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.11...v2.0.0-alpha.12) (2022-12-09)\n\n\n### Bug Fixes\n\n* **toDecimal:** teach `toDecimal` how to handle negative units ([#690](https://github.com/dinerojs/dinero.js/issues/690)) ([81c5566](https://github.com/dinerojs/dinero.js/commit/81c5566f1219707b2bfb3416e94d832170dd2cf0))\n\n\n\n# [2.0.0-alpha.11](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.10...v2.0.0-alpha.11) (2022-12-04)\n\n\n### Features\n\n* provide better support for non-decimal currencies ([#309](https://github.com/dinerojs/dinero.js/issues/309)) ([e7e9a19](https://github.com/dinerojs/dinero.js/commit/e7e9a19e6eb8e4ff8903867a60c1457a8d241d0c)), closes [#294](https://github.com/dinerojs/dinero.js/issues/294)\n\n\n### BREAKING CHANGES\n\n* ** the `toUnit` and the `toFormat` functions were\nremoved.\n\n\n\n# [2.0.0-alpha.10](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.9...v2.0.0-alpha.10) (2022-11-19)\n\n### Bug Fixes\n\n* **dinero.js:** don't accept extra fields inside the currency options on create ([#673](https://github.com/dinerojs/dinero.js/issues/673)) ([9e72f9e](https://github.com/dinerojs/dinero.js/commit/9e72f9efdc75349d9fd01e4efe57f38b4b59102c))\n\n# [2.0.0-alpha.9](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.8...v2.0.0-alpha.9) (2022-11-04)\n\n### Bug Fixes\n\n* **currencies:** add missing ANG export ([#447](https://github.com/dinerojs/dinero.js/issues/447)) ([8a0f67b](https://github.com/dinerojs/dinero.js/commit/8a0f67bda699ca8082d7a68def21a9d11fa5f1a8))\n* **trimScale:** check for zero value in countTrailingZeros ([#448](https://github.com/dinerojs/dinero.js/issues/448)) ([6eefe8b](https://github.com/dinerojs/dinero.js/commit/6eefe8b17c2a3497f836301e6001b05901ac9dec))\n\n### Reverts\n\n* revert snapshot process ([78db2dd](https://github.com/dinerojs/dinero.js/commit/78db2ddf2914a81d1e2c10ea0d1c72d3bdeee3b1))\n\n# [2.0.0-alpha.8](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.7...v2.0.0-alpha.8) (2021-08-08)\n\n### Bug Fixes\n\n* **core:** use equal function to compare currency base and exponent ([ac4724f](https://github.com/dinerojs/dinero.js/commit/ac4724f12d6625e4838dd49a517d0cd214f57f6e))\n* **calculator-bigint:** avoid transpiling exponentiation operator ([53bf974](https://github.com/dinerojs/dinero.js/commit/53bf974de377455c2e1156c1c9a321276dfb11a3))\n\n### Features\n\n* expose calculator types ([b17bfc1](https://github.com/dinerojs/dinero.js/commit/b17bfc111c2462c9226b1a7fa7d6786b055a54ca))\n\n# [2.0.0-alpha.7](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) (2021-07-26)\n\n### Bug Fixes\n\n* **dinero.js:** properly re-export types and rounding functions ([d40a7e2](https://github.com/dinerojs/dinero.js/commit/d40a7e29aff102c4e16b8416a2600cc9e0d6add6))\n\n### Features\n\n* **dinero.js:** re-export createDinero ([d8d149f](https://github.com/dinerojs/dinero.js/commit/d8d149f77e8efce20a60a22aba1df6b21f0f4f25))\n\n# [2.0.0-alpha.6](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.5...v2.0.0-alpha.6) (2021-07-21)\n\n### Bug Fixes\n\n* **currencies:** use proper base and exponents for MRU and MGA ([ac2bb8d](https://github.com/dinerojs/dinero.js/commit/ac2bb8da8f53e8f461423745c2aaf4c5730e0421))\n\n# [2.0.0-alpha.5](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2021-07-19)\n\n### Features\n\n* introduce compare function ([#266](https://github.com/dinerojs/dinero.js/issues/266)) ([53f84c2](https://github.com/dinerojs/dinero.js/commit/53f84c28c78ba8bf04249615267f01f60603c674))\n\n# [2.0.0-alpha.4](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2021-07-16)\n\n# [2.0.0-alpha.3](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2021-07-13)\n\n# [2.0.0-alpha.2](https://github.com/dinerojs/dinero.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2) (2021-07-12)\n\n# 2.0.0-alpha.1 (2021-07-09)\n\nThis alpha release brings the new major version of Dinero.js. To learn more, head over to the [documentation](https://dinerojs.com).\n\n---\n\nSee [previous releases](https://github.com/dinerojs/dinero.js/releases?after=v2.0.0-alpha.1).\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nDinero.js: a JavaScript/TypeScript money library. npm workspaces + Turborepo monorepo.\n\n## Commands\n\n```bash\nnpm run test:types         # Type-check with TypeScript (noEmit)\nnpm run test:size          # Check bundle sizes against limits\n```\n\n## Key Conventions\n\n- All functions are pure and immutable, never mutate Dinero objects\n- `__DEV__` and `__TEST__` globals are replaced at build time for tree-shaking\n\n## Path Aliases\n\n- `dinero.js` → `packages/dinero.js/src/`\n- `dinero.js/currencies` → `packages/dinero.js/src/currencies/`\n- `dinero.js/bigint` → `packages/dinero.js/src/bigint/`\n- `test-utils` → `test/utils/`\n\n## References\n\n- [Project structure & architecture](.claude/docs/architecture.md)\n- [Git workflow & PRs](.claude/docs/git-workflow.md)\n- [Linear integration](.claude/docs/linear.md)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at yo@dinerojs.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<p align=\"center\">\n  <a href=\"https://dinerojs.com\">\n    <img alt=\"Dinero.js\" src=\".github/banner.png\">\n  </a>\n</p>\n\n# Contributing to Dinero.js\n\nYou want to contribute to Dinero.js, and that's awesome 🎉👍 Thanks for that!\n\nBefore you dive in head first, there are a couple of guidelines to follow. Please make sure you read and understand them before you contribute. **Note that this isn't set in stone**. Polite debate and suggestions are welcome, as long as it's compliant with the [code of conduct](CODE_OF_CONDUCT.md), and it's done with the best interest of the library and the end-users in mind.\n\n## ❓ Should I contribute?\n\nPushing your first contribution can be intimidating. A great way to start is by [fixing issues](https://github.com/dinerojs/dinero.js/issues). Find an open and confirmed issue, and open a pull request that fixes it.\n\n✅ Please do:\n\n- Fix [bugs](https://github.com/dinerojs/dinero.js/issues).\n- Improve performance.\n- Refactor with better design patterns.\n- Improve the build process (speed, error handling, deprecations, etc.)\n- Improve the docs (typos, lack of clarity, etc.)\n\n🚫 Please don't:\n\n- Go against the library's philosophy (immutability, modularity, etc.)\n- Make changes based on personal preferences rather than problem-solving.\n- Develop features that aren't in the scope of the library (if not sure, ask before you code).\n- Introduce breaking changes.\n\n## 💻 Install\n\nThe project relies on [Node.js](https://nodejs.org/). It's also recommended to use [Node Version Manager](https://github.com/nvm-sh/nvm) to ensure you're using the right Node.js version.\n\nTo get started, clone the project and install the dependencies from your terminal:\n\n```sh\ngit clone https://github.com/dinerojs/dinero.js.git\ncd dinero.js\nnvm use\nnpm install\n```\n\n## 🖥️ Project organization\n\nThe project source lives in the `packages/dinero.js/` directory.\n\n```txt\npackages/\n└── dinero.js/\n    └── src/\n        ├── api/          # All API functions\n        ├── bigint/       # BigInt entry point\n        ├── calculator/   # Calculator implementations\n        ├── core/         # Types and utilities\n        ├── currencies/   # ISO 4217 currencies\n        └── dinero/       # Dinero factory\n```\n\n## 📖 Conventions\n\nThe project observes a few rules and conventions when it comes to code. Most of them are automated, but make sure you understand them before submitting changes.\n\n### Commit messages\n\nThe project follows the [conventional commits](https://www.conventionalcommits.org/) approach to standardize commit messages, generate the changelog and resolve the next version. It means that all commit messages should be formatted using the following scheme:\n\n```txt\ntype(optional scope): subject\n```\n\nAvailable types are:\n\n- `build`: changes affecting the build system or external dependencies\n- `ci`: changes to CI configuration files and scripts\n- `docs`: documentation changes\n- `feat`: a new feature\n- `fix`: a bug fix\n- `perf`: changes improving performance\n- `refactor`: changes that neither fixes a bug nor adds a feature\n- `test`: adding or fixing tests\n- `style`: a linting commit\n- `revert`: a revert of a previous commit\n\n### Pull requests\n\nPull requests must target `main`. As with commit messages, pull request titles must follow the [conventional commits](https://www.conventionalcommits.org/) convention.\n\n### Branch organization\n\nThe project uses two sorts of long-lived branches:\n\n- `main` as the default branch, where the latest version of the project lives\n- `vx` (where `x` is a number, for example, `v1`) to version previous major versions\n\nPull requests should target the `main` branch unless fixing critical issues in previous major versions.\n\n### Tests\n\nThe project uses [Vitest](https://vitest.dev/) for testing. **Every public API should be tested**. You should run tests before you commit, or at least before you open a pull request. Pull requests need to pass all checks to be reviewed, so doing it beforehand will save you time.\n\n```sh\nnpm test\n```\n\n### Linting\n\nThe project uses [Prettier](https://prettier.io/) for code formatting and [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) for linting. Both run automatically when you commit, so you can go ahead and format as you like while writing code; it will be overridden anyway.\n\nIf Oxlint finds issues, you can fix them manually and try committing again.\n\n## 📚 Documentation\n\nThe documentation lives in the `docs/` directory and is built with [VitePress](https://vitepress.dev/). You can run it locally:\n\n```sh\nnpm run docs:dev\n```\n\nAll documentation files are Markdown (`.md`) files organized by section.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018-present Sarah Dayan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://dinerojs.com\">\n    <img alt=\"Dinero.js\" src=\"https://raw.githubusercontent.com/dinerojs/dinero.js/main/.github/banner.png\">\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/dinero.js\"><img alt=\"npm version\" src=\"https://img.shields.io/npm/v/dinero.js\" /></a>\n  <a href=\"https://www.npmjs.com/package/dinero.js\"><img alt=\"npm monthly downloads\" src=\"https://img.shields.io/npm/dm/dinero.js\" /></a>\n  <a href=\"https://github.com/dinerojs/dinero.js/network/dependents\"><img alt=\"Used by\" src=\"https://img.shields.io/endpoint?url=https://api.usedby.dev/npm/dinero.js/shield.json\" /></a>\n  <a href=\"https://github.com/dinerojs/dinero.js/stargazers\"><img alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/dinerojs/dinero.js\" /></a><br />\n  <a href=\"https://www.typescriptlang.org/\"><img alt=\"TypeScript\" src=\"https://img.shields.io/badge/TypeScript-Ready-blue\" /></a>\n  <a href=\"https://github.com/dinerojs/dinero.js/actions/workflows/ci.yml\"><img alt=\"GitHub Actions\" src=\"https://img.shields.io/github/actions/workflow/status/dinerojs/dinero.js/ci.yml?branch=main\"></a>\n  <a href=\"https://github.com/dinerojs/dinero.js/blob/master/LICENSE\"><img alt=\"NPM\" src=\"https://img.shields.io/npm/l/dinero.js\"></a>\n</p>\n\n<p align=\"center\">\n  Dinero.js lets you create, calculate, and format money safely in JavaScript and TypeScript.<br>\n  <a href=\"https://dinerojs.com\"><strong>dinerojs.com</strong></a>\n</p>\n\n---\n\nMoney is complex, and the primitives of the language aren't enough to properly represent it. Dinero.js is a JavaScript library that lets you express monetary values, but also perform mutations, conversions, comparisons, formatting, and overall make money manipulation easier and safer in your application.\n\n## ✨ Features\n\n- **Immutable & pure:** every operation returns a new object, no side effects\n- **Type-safe:** first-class TypeScript support with full type inference\n- **Tree-shakeable:** import only what you use, keep bundles small\n- **Pluggable precision:** use `number` by default or `bigint` for large amounts\n- **Non-decimal currencies:** support for any base, including multi-subdivision currencies\n- **Framework-agnostic:** works everywhere JavaScript runs\n\n## 📦 Install\n\n```sh\nnpm install dinero.js\n\n# or\n\nyarn add dinero.js\n```\n\n## ⚡️ Quick start\n\n`Dinero` objects are minimal. Every function in `dinero.js` is side-effect free, allowing you only to bundle exactly what you use.\n\n```js\nimport { dinero, add, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nconst total = add(d1, d2);\n\ntoDecimal(total); // \"13.00\"\n```\n\nCheck out the [quick start guide](https://dinerojs.com/getting-started/quick-start) on the documentation.\n\n## 📚 Documentation\n\nThe documentation lets you learn about all aspects of the library.\n\n- [**Getting started**](https://dinerojs.com/getting-started/quick-start) to get up and running quickly with Dinero.js\n- [**Core concepts**](https://dinerojs.com/core-concepts/amount) to learn about the underlying principles behind the library\n- [**Guides**](https://dinerojs.com/guides/precision-and-large-numbers) to dig deeper into specific use cases.\n- [**API**](https://dinerojs.com/api/mutations/add) for a comprehensive list of available functions and their parameters.\n- [**FAQ**](https://dinerojs.com/faq/why-no-currency-formatting) for common answers to questions you might have\n\nVisit the [full documentation](https://dinerojs.com) to know more.\n\n## 🤖 Agent Skills\n\nIf you use AI coding agents (Claude Code, Cursor, GitHub Copilot, etc.), install the [Dinero.js skills](https://github.com/dinerojs/skills) to teach your agent best practices, common pitfalls, and correct usage patterns.\n\n```sh\nnpx skills add dinerojs/skills\n```\n\n## 🏢 Used by\n\nDinero.js is used by [WooCommerce](https://github.com/woocommerce/woocommerce), [Highlight](https://github.com/highlight/highlight), [Cypress](https://github.com/cypress-io/cypress-realworld-app), [Vercel](https://github.com/vercel/next-app-router-playground), [AWS Labs](https://github.com/awslabs/aws-lambda-web-adapter), [Module Federation](https://github.com/module-federation/core), and [many more](https://github.com/dinerojs/dinero.js/network/dependents).\n\n[![Used by](https://api.usedby.dev/npm/dinero.js?max=50&sort=stars)](https://github.com/dinerojs/dinero.js/network/dependents)\n\n## 👥 Contributors\n\n[![Dinero.js contributors](https://contrib.rocks/image?repo=dinerojs/dinero.js)](https://github.com/dinerojs/dinero.js/graphs/contributors)\n\n**From v1:** <a href=\"https://sarahdayan.dev\"><img src=\"https://avatars1.githubusercontent.com/u/5370675?v=4\" alt=\"Sarah Dayan\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://github.com/yacinehmito\"><img src=\"https://avatars1.githubusercontent.com/u/6893840?v=4\" alt=\"Yacine Hmito\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://github.com/scotttrinh\"><img src=\"https://avatars1.githubusercontent.com/u/1682194?v=4\" alt=\"Scott Trinh\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://rolandasb.com\"><img src=\"https://avatars0.githubusercontent.com/u/1409998?v=4\" alt=\"Rolandas Barysas\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://www.luizpb.com/en/\"><img src=\"https://avatars1.githubusercontent.com/u/1798830?v=4\" alt=\"Luiz Bills\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://kunst.com.br\"><img src=\"https://avatars2.githubusercontent.com/u/8649362?v=4\" alt=\"Leonardo Dino\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://www.kizu.ru/\"><img src=\"https://avatars3.githubusercontent.com/u/177485?v=4\" alt=\"Roman Komarov\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"http://jotaoncode.com/\"><img src=\"https://avatars3.githubusercontent.com/u/4575026?v=4\" alt=\"Juan Garcia\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://github.com/frobinsonj\"><img src=\"https://avatars3.githubusercontent.com/u/16726902?v=4\" alt=\"Freddy Robinson\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://twitter.com/andybrk\"><img src=\"https://avatars0.githubusercontent.com/u/273857?v=4\" alt=\"Andy Burke\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://github.com/andrewiggins\"><img src=\"https://avatars3.githubusercontent.com/u/459878?v=4\" alt=\"Andre Wiggins\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://desandro.com\"><img src=\"https://avatars0.githubusercontent.com/u/85566?v=4\" alt=\"David DeSandro\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"http://maxk.se\"><img src=\"https://avatars1.githubusercontent.com/u/19932622?v=4\" alt=\"Max Körlinge\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://github.com/dotpack\"><img src=\"https://avatars2.githubusercontent.com/u/1175814?v=4\" alt=\"Ilia Ermolin\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://coina.ge\"><img src=\"https://avatars1.githubusercontent.com/u/1531750?v=4\" alt=\"Kevin Brown\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://seankwalker.com\"><img src=\"https://avatars.githubusercontent.com/u/20524136?v=4\" alt=\"Sean Walker\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"http://jnguyen.me/\"><img src=\"https://avatars0.githubusercontent.com/u/1127677?v=4\" alt=\"John Nguyen\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a> <a href=\"https://journal.artfuldev.com\"><img src=\"https://avatars.githubusercontent.com/u/3091087?v=4\" alt=\"Sudarsan Balaji\" width=\"30\" height=\"30\" style=\"border-radius:50%\" /></a>\n\n## 📜 License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "content": "import { defineConfig } from 'vitepress';\nimport { version } from '../package.json';\n\nexport default defineConfig({\n  title: 'Dinero.js',\n  description:\n    'Create, calculate, and format money in JavaScript and TypeScript.',\n  lang: 'en',\n  cleanUrls: true,\n  sitemap: {\n    hostname: 'https://dinerojs.com/',\n  },\n  head: [\n    ['link', { rel: 'icon', href: '/favicon.ico' }],\n    [\n      'link',\n      {\n        rel: 'preconnect',\n        href: 'https://UV1B3CJN3X-dsn.algolia.net',\n        crossorigin: '',\n      },\n    ],\n    // Open Graph\n    ['meta', { property: 'og:type', content: 'website' }],\n    ['meta', { property: 'og:title', content: 'Dinero.js' }],\n    [\n      'meta',\n      {\n        property: 'og:description',\n        content:\n          'Create, calculate, and format money in JavaScript and TypeScript.',\n      },\n    ],\n    [\n      'meta',\n      {\n        property: 'og:image',\n        content: 'https://dinerojs.com/open-graph.jpg',\n      },\n    ],\n    ['meta', { property: 'og:url', content: 'https://dinerojs.com' }],\n    // Twitter\n    ['meta', { name: 'twitter:card', content: 'summary_large_image' }],\n    ['meta', { name: 'twitter:title', content: 'Dinero.js' }],\n    [\n      'meta',\n      {\n        name: 'twitter:description',\n        content:\n          'Create, calculate, and format money in JavaScript and TypeScript.',\n      },\n    ],\n    [\n      'meta',\n      {\n        name: 'twitter:image',\n        content: 'https://dinerojs.com/open-graph.jpg',\n      },\n    ],\n    [\n      'script',\n      {\n        src: 'https://cdn.usefathom.com/script.js',\n        'data-site': 'PSUFDDGC',\n        defer: '',\n      },\n    ],\n    [\n      'script',\n      {\n        src: 'https://cdn.jsdelivr.net/npm/dinero.js@latest/dist/umd/index.production.min.js',\n        defer: '',\n      },\n    ],\n    [\n      'script',\n      {},\n      `window.addEventListener('load', function() {\n  console.log('%cDinero.js is available globally!\\\\n\\\\n%cTry it out:\\\\n\\\\nconst { dinero, toDecimal, USD } = window.dinerojs;\\\\nconst price = dinero({ amount: 1999, currency: USD });\\\\ntoDecimal(price); // \"19.99\"', 'font-weight: bold; font-size: 14px;', '');\n});`,\n    ],\n  ],\n  themeConfig: {\n    logo: '/logo.svg',\n    nav: [\n      { text: 'Docs', link: '/getting-started/quick-start' },\n      { text: 'API', link: '/api/mutations/add' },\n      {\n        text: version,\n        items: [\n          {\n            text: 'Changelog',\n            link: 'https://github.com/dinerojs/dinero.js/releases',\n          },\n          {\n            text: 'v1 docs',\n            link: 'https://v1.dinerojs.com/',\n          },\n        ],\n      },\n    ],\n    sidebar: {\n      '/': [\n        {\n          text: 'Getting Started',\n          items: [\n            { text: 'Quick start', link: '/getting-started/quick-start' },\n            {\n              text: 'Upgrade guide',\n              link: '/getting-started/upgrade-guide',\n            },\n            {\n              text: 'Optimizing for production',\n              link: '/getting-started/optimizing-for-production',\n            },\n            { text: 'Compatibility', link: '/getting-started/compatibility' },\n          ],\n        },\n        {\n          text: 'Core Concepts',\n          items: [\n            { text: 'Amount', link: '/core-concepts/amount' },\n            { text: 'Currency', link: '/core-concepts/currency' },\n            { text: 'Scale', link: '/core-concepts/scale' },\n            { text: 'Mutations', link: '/core-concepts/mutations' },\n            { text: 'Comparisons', link: '/core-concepts/comparisons' },\n            { text: 'Formatting', link: '/core-concepts/formatting' },\n          ],\n        },\n        {\n          text: 'Guides',\n          items: [\n            {\n              text: 'Precision and large numbers',\n              link: '/guides/precision-and-large-numbers',\n            },\n            {\n              text: 'Currency type safety',\n              link: '/guides/currency-type-safety',\n            },\n            {\n              text: 'Serialization',\n              link: '/guides/transporting-and-restoring',\n            },\n            {\n              text: 'Database storage',\n              link: '/guides/storing-in-a-database',\n            },\n            {\n              text: 'Multilingual support',\n              link: '/guides/formatting-in-a-multilingual-site',\n            },\n            {\n              text: 'Payment services',\n              link: '/guides/integrating-with-payment-services',\n            },\n            {\n              text: 'Non-decimal currencies',\n              link: '/guides/formatting-non-decimal-currencies',\n            },\n            {\n              text: 'Creating from floats',\n              link: '/guides/creating-from-floats',\n            },\n            {\n              text: 'Calculating percentages',\n              link: '/guides/calculating-percentages',\n            },\n            {\n              text: 'Cryptocurrency support',\n              link: '/guides/cryptocurrencies',\n            },\n          ],\n        },\n        {\n          text: 'API Reference',\n          collapsed: false,\n          items: [\n            { text: 'dinero', link: '/api/dinero' },\n            { text: 'Currencies', link: '/api/currencies' },\n            {\n              text: 'Mutations',\n              collapsed: true,\n              items: [\n                { text: 'add', link: '/api/mutations/add' },\n                { text: 'subtract', link: '/api/mutations/subtract' },\n                { text: 'multiply', link: '/api/mutations/multiply' },\n                { text: 'allocate', link: '/api/mutations/allocate' },\n              ],\n            },\n            {\n              text: 'Conversions',\n              collapsed: true,\n              items: [\n                { text: 'convert', link: '/api/conversions/convert' },\n                {\n                  text: 'normalizeScale',\n                  link: '/api/conversions/normalize-scale',\n                },\n                {\n                  text: 'transformScale',\n                  link: '/api/conversions/transform-scale',\n                },\n                { text: 'trimScale', link: '/api/conversions/trim-scale' },\n              ],\n            },\n            {\n              text: 'Comparisons',\n              collapsed: true,\n              items: [\n                { text: 'equal', link: '/api/comparisons/equal' },\n                { text: 'compare', link: '/api/comparisons/compare' },\n                { text: 'greaterThan', link: '/api/comparisons/greater-than' },\n                {\n                  text: 'greaterThanOrEqual',\n                  link: '/api/comparisons/greater-than-or-equal',\n                },\n                { text: 'lessThan', link: '/api/comparisons/less-than' },\n                {\n                  text: 'lessThanOrEqual',\n                  link: '/api/comparisons/less-than-or-equal',\n                },\n                { text: 'minimum', link: '/api/comparisons/minimum' },\n                { text: 'maximum', link: '/api/comparisons/maximum' },\n                { text: 'isZero', link: '/api/comparisons/is-zero' },\n                { text: 'isPositive', link: '/api/comparisons/is-positive' },\n                { text: 'isNegative', link: '/api/comparisons/is-negative' },\n                {\n                  text: 'haveSameAmount',\n                  link: '/api/comparisons/have-same-amount',\n                },\n                {\n                  text: 'haveSameCurrency',\n                  link: '/api/comparisons/have-same-currency',\n                },\n                { text: 'hasSubUnits', link: '/api/comparisons/has-sub-units' },\n              ],\n            },\n            {\n              text: 'Formatting',\n              collapsed: true,\n              items: [\n                { text: 'toSnapshot', link: '/api/formatting/to-snapshot' },\n                { text: 'toUnits', link: '/api/formatting/to-units' },\n                { text: 'toDecimal', link: '/api/formatting/to-decimal' },\n              ],\n            },\n            {\n              text: 'Rounding',\n              collapsed: true,\n              items: [\n                { text: 'down', link: '/api/rounding/down' },\n                { text: 'halfUp', link: '/api/rounding/half-up' },\n                { text: 'halfDown', link: '/api/rounding/half-down' },\n                { text: 'halfEven', link: '/api/rounding/half-even' },\n                { text: 'halfOdd', link: '/api/rounding/half-odd' },\n                {\n                  text: 'halfAwayFromZero',\n                  link: '/api/rounding/half-away-from-zero',\n                },\n                {\n                  text: 'halfTowardsZero',\n                  link: '/api/rounding/half-towards-zero',\n                },\n                { text: 'up', link: '/api/rounding/up' },\n              ],\n            },\n          ],\n        },\n        {\n          text: 'FAQ',\n          items: [\n            {\n              text: \"Why can't I use currencies with bigint?\",\n              link: '/faq/why-cant-i-use-currencies-with-bigint',\n            },\n            {\n              text: 'Why no currency symbols?',\n              link: '/faq/why-no-currency-formatting',\n            },\n            {\n              text: 'Can I multiply by a decimal?',\n              link: '/faq/can-i-multiply-by-a-decimal',\n            },\n            {\n              text: 'Why functions instead of methods?',\n              link: '/faq/why-functions-instead-of-methods',\n            },\n            {\n              text: 'How to look up a currency by code',\n              link: '/faq/how-to-look-up-a-currency-by-code',\n            },\n          ],\n        },\n        {\n          text: 'Resources',\n          items: [\n            { text: 'About', link: '/about' },\n            { text: 'Demos', link: '/demos' },\n            { text: 'Agent Skills', link: '/agent-skills' },\n          ],\n        },\n      ],\n    },\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/dinerojs/dinero.js' },\n    ],\n    editLink: {\n      pattern: 'https://github.com/dinerojs/dinero.js/edit/main/docs/:path',\n      text: 'Edit this page on GitHub',\n    },\n    search: {\n      provider: 'algolia',\n      options: {\n        appId: 'UV1B3CJN3X',\n        apiKey: 'fd50d842eb6683b120b6920b223d41f7',\n        indexName: 'dinerojs',\n        searchParameters: {\n          facetFilters: ['tags:v2'],\n        },\n        askAi: {\n          assistantId: 'iYbEdJJBSUWU',\n          searchParameters: {\n            facetFilters: ['tags:v2'],\n          },\n          sidePanel: {\n            panel: {\n              variant: 'floating',\n              side: 'right',\n              width: '360px',\n              expandedWidth: '580px',\n            },\n          },\n        },\n      },\n    },\n    footer: {\n      message: 'Released under the MIT License.',\n      copyright: 'Copyright © 2018-present Sarah Dayan',\n    },\n  },\n});\n"
  },
  {
    "path": "docs/.vitepress/theme/HomeFeatures.vue",
    "content": "<script setup lang=\"ts\">\nconst features = [\n  {\n    title: 'Immutable & safe',\n    details:\n      'All operations return new Dinero objects. Your original values are never mutated.',\n  },\n  {\n    title: 'Precise calculations',\n    details:\n      'Handles money as integers in minor units to avoid floating-point precision issues.',\n  },\n  {\n    title: 'Multi-currency',\n    details:\n      'Built-in ISO 4217 currencies with compile-time safety to catch mismatches.',\n  },\n  {\n    title: 'Tree-shakeable',\n    details:\n      'Import only what you need. Functional API designed for optimal bundle size.',\n  },\n];\n</script>\n\n<template>\n  <section class=\"home-features\">\n    <div class=\"grid\">\n      <div v-for=\"feature in features\" :key=\"feature.title\" class=\"card\">\n        <h3 class=\"title\">{{ feature.title }}</h3>\n        <p class=\"details\">{{ feature.details }}</p>\n      </div>\n    </div>\n  </section>\n</template>\n\n<style scoped>\n.home-features {\n  max-width: 1152px;\n  margin: 0 auto;\n  padding: 0 24px 80px;\n}\n\n@media (min-width: 960px) {\n  .home-features {\n    padding: 0 32px 96px;\n  }\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 1px;\n  border-top: 1px solid var(--vp-c-border);\n}\n\n@media (min-width: 640px) {\n  .grid {\n    grid-template-columns: 1fr 1fr;\n  }\n}\n\n@media (min-width: 960px) {\n  .grid {\n    grid-template-columns: repeat(4, 1fr);\n  }\n}\n\n.card {\n  padding: 28px 24px;\n}\n\n.title {\n  font-size: 0.8125rem;\n  font-weight: 700;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  color: var(--vp-c-text-1);\n  margin-bottom: 8px;\n}\n\n.details {\n  font-size: 0.9375rem;\n  line-height: 1.55;\n  color: var(--vp-c-text-2);\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/HomeHero.vue",
    "content": "<script setup lang=\"ts\">\nimport { withBase } from 'vitepress';\n</script>\n\n<template>\n  <section class=\"home-hero\">\n    <div class=\"container\">\n      <div class=\"content\">\n        <p class=\"name\">Dinero.js</p>\n        <h1 class=\"heading\">Create, calculate, and format money safely</h1>\n        <p class=\"tagline\">\n          An immutable library for expressing monetary values in JavaScript and\n          TypeScript.\n        </p>\n        <p class=\"tagline\">Designed for humans, ready for agents.</p>\n        <div class=\"actions\">\n          <a\n            class=\"action brand\"\n            :href=\"withBase('/getting-started/quick-start')\"\n            >Get Started</a\n          >\n          <a\n            class=\"action ghost\"\n            href=\"https://github.com/dinerojs/dinero.js\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            >View on GitHub</a\n          >\n        </div>\n      </div>\n      <div class=\"code-area\">\n        <div class=\"code-block\">\n          <p class=\"code-label\">Install</p>\n          <pre><code><span class=\"fn\">npm</span> install dinero.js</code></pre>\n        </div>\n        <div class=\"code-block\">\n          <p class=\"code-label\">Usage</p>\n          <pre><code><span class=\"kw\">import</span> { dinero, add, toDecimal } <span class=\"kw\">from</span> <span class=\"str\">'dinero.js'</span>;\n<span class=\"kw\">import</span> { USD } <span class=\"kw\">from</span> <span class=\"str\">'dinero.js/currencies'</span>;\n\n<span class=\"kw\">const</span> price = <span class=\"fn\">dinero</span>({ amount: <span class=\"num\">1999</span>, currency: USD });\n<span class=\"kw\">const</span> tax = <span class=\"fn\">dinero</span>({ amount: <span class=\"num\">160</span>, currency: USD });\n\n<span class=\"kw\">const</span> total = <span class=\"fn\">add</span>(price, tax);\n\n<span class=\"fn\">toDecimal</span>(total); <span class=\"cmt\">// \"21.59\"</span></code></pre>\n        </div>\n      </div>\n    </div>\n  </section>\n</template>\n\n<style scoped>\n.home-hero {\n  max-width: 1152px;\n  margin: 0 auto;\n  padding: 80px 24px 56px;\n}\n\n.container {\n  display: flex;\n  flex-direction: column;\n  gap: 48px;\n}\n\n@media (min-width: 960px) {\n  .home-hero {\n    padding: 62px 32px 72px;\n  }\n\n  .container {\n    flex-direction: row;\n    align-items: center;\n    gap: 72px;\n  }\n\n  .content {\n    flex: 1;\n    min-width: 0;\n  }\n\n  .code-area {\n    flex: 1;\n    min-width: 0;\n  }\n}\n\n.name {\n  font-size: 1rem;\n  font-weight: 700;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  color: var(--vp-c-brand-1);\n  margin-bottom: 16px;\n}\n\n.heading {\n  font-size: 2rem;\n  font-weight: 700;\n  letter-spacing: -0.03em;\n  line-height: 1.15;\n  color: var(--vp-c-text-1);\n}\n\n@media (min-width: 640px) {\n  .heading {\n    font-size: 2.5rem;\n  }\n}\n\n@media (min-width: 960px) {\n  .heading {\n    font-size: 3rem;\n  }\n}\n\n.tagline {\n  font-size: 1.375rem;\n  line-height: 1.5;\n  color: var(--vp-c-text-2);\n  margin-top: 16px;\n  max-width: 480px;\n}\n\n.actions {\n  display: flex;\n  gap: 12px;\n  margin-top: 32px;\n}\n\n.action {\n  display: inline-flex;\n  align-items: center;\n  padding: 12px 24px;\n  font-size: 1rem;\n  font-weight: 600;\n  letter-spacing: -0.01em;\n  border-radius: 6px;\n  text-decoration: none;\n  transition:\n    opacity 160ms ease,\n    border-color 160ms ease;\n}\n\n.action.brand {\n  background-color: var(--vp-c-brand-1);\n  color: #fff;\n  border: 1px solid var(--vp-c-brand-1);\n}\n\n.action.brand:hover {\n  opacity: 0.85;\n}\n\n.action.ghost {\n  background-color: transparent;\n  color: var(--vp-c-text-2);\n  border: 1px solid var(--vp-c-border);\n}\n\n.action.ghost:hover {\n  border-color: var(--vp-c-text-3);\n  color: var(--vp-c-text-1);\n}\n\n.code-area {\n  display: flex;\n  flex-direction: column;\n  gap: 12px;\n}\n\n.code-block {\n  border-radius: 8px;\n  border: 1px solid var(--vp-c-border);\n  background: var(--vp-code-block-bg);\n  overflow-x: auto;\n}\n\n.code-label {\n  font-family: var(--vp-font-family-mono);\n  font-size: 0.75rem;\n  font-weight: 500;\n  color: var(--vp-c-text-3);\n  padding: 12px 16px 0;\n  margin: 0;\n}\n\n.code-block pre {\n  margin: 0;\n  padding: 12px 16px 14px;\n}\n\n.code-block code {\n  font-family: var(--vp-font-family-mono);\n  font-size: 0.8125rem;\n  line-height: 1.7;\n  color: var(--hero-code-text);\n}\n\n.code-block .kw {\n  color: var(--hero-code-kw);\n}\n\n.code-block .str {\n  color: var(--hero-code-str);\n}\n\n.code-block .num {\n  color: var(--hero-code-num);\n}\n\n.code-block .fn {\n  color: var(--hero-code-fn);\n}\n\n.code-block .cmt {\n  color: var(--hero-code-cmt);\n  font-style: italic;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/NotFound.vue",
    "content": "<script setup>\nimport { useRouter } from 'vitepress';\n\nconst router = useRouter();\n\nfunction goHome() {\n  router.go('/');\n}\n</script>\n\n<template>\n  <div class=\"not-found\">\n    <img class=\"mascot\" src=\"/logo.svg\" alt=\"Dinero.js\" />\n    <div class=\"code\">404</div>\n    <h1 class=\"title\">Page not found</h1>\n    <p class=\"message\">This page doesn't exist or may have moved.</p>\n    <button class=\"action\" @click=\"goHome\">Take me home</button>\n  </div>\n</template>\n\n<style scoped>\n.not-found {\n  padding: 64px 24px 96px;\n  text-align: center;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n}\n\n.mascot {\n  width: 180px;\n  height: auto;\n  margin-bottom: 32px;\n  opacity: 0.4;\n  filter: grayscale(50%);\n}\n\n.code {\n  font-size: 4rem;\n  font-weight: 700;\n  color: var(--vp-c-text-3);\n  line-height: 1;\n  margin-bottom: 16px;\n}\n\n.title {\n  font-size: 1.5rem;\n  font-weight: 600;\n  color: var(--vp-c-text-1);\n  margin-bottom: 8px;\n}\n\n.message {\n  color: var(--vp-c-text-2);\n  margin-bottom: 32px;\n}\n\n.action {\n  display: inline-block;\n  padding: 12px 24px;\n  background-color: var(--vp-c-brand-1);\n  color: white;\n  font-weight: 500;\n  border-radius: 6px;\n  border: none;\n  cursor: pointer;\n  font-size: 1rem;\n  transition: opacity 160ms ease;\n}\n\n.action:hover {\n  opacity: 0.85;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "import { h } from 'vue';\nimport type { Theme } from 'vitepress';\nimport DefaultTheme from 'vitepress/theme';\nimport NotFound from './NotFound.vue';\nimport HomeHero from './HomeHero.vue';\nimport HomeFeatures from './HomeFeatures.vue';\nimport './style.css';\n\nexport default {\n  extends: DefaultTheme,\n  Layout: () => {\n    return h(DefaultTheme.Layout, null, {\n      'not-found': () => h(NotFound),\n      'home-hero-before': () => h(HomeHero),\n      'home-features-before': () => h(HomeFeatures),\n    });\n  },\n} satisfies Theme;\n"
  },
  {
    "path": "docs/.vitepress/theme/style.css",
    "content": "/**\n * Dinero.js docs theme\n * Near-black dark mode, restrained color, subtle interactions.\n */\n\n/* Custom fonts */\n@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');\n\n/* ===== Global ===== */\n\n* {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n:root {\n  /* Typography */\n  --vp-font-family-base:\n    'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;\n  --vp-font-family-mono: 'JetBrains Mono', ui-monospace, monospace;\n\n  /* Brand colors */\n  --vp-c-brand-1: #1144ee;\n  --vp-c-brand-2: #3366ff;\n  --vp-c-brand-3: #5588ff;\n  --vp-c-brand-soft: rgba(17, 68, 238, 0.08);\n\n  /* Override tip color with brand */\n  --vp-c-tip-1: var(--vp-c-brand-1);\n  --vp-c-tip-2: var(--vp-c-brand-2);\n  --vp-c-tip-3: var(--vp-c-brand-3);\n  --vp-c-tip-soft: var(--vp-c-brand-soft);\n\n  /* Backgrounds */\n  --vp-c-bg: #ffffff;\n  --vp-c-bg-soft: #f8f8fa;\n  --vp-c-bg-alt: #f3f4f6;\n\n  /* Text */\n  --vp-c-text-1: #1a1a1e;\n  --vp-c-text-2: #6b6b74;\n  --vp-c-text-3: #9c9ca5;\n\n  /* Borders */\n  --vp-c-divider: rgba(0, 0, 0, 0.06);\n  --vp-c-border: rgba(0, 0, 0, 0.08);\n\n  /* Code blocks */\n  --vp-code-block-bg: #f8f8fa;\n  --vp-code-tab-divider: var(--vp-c-divider);\n  --vp-code-copy-code-border-color: var(--vp-c-border);\n  --vp-code-copy-code-bg: var(--vp-c-bg-soft);\n  --vp-code-copy-code-hover-border-color: var(--vp-c-brand-1);\n  --vp-code-copy-code-hover-bg: var(--vp-c-brand-soft);\n  --vp-code-copy-code-active-text: var(--vp-c-brand-1);\n\n  /* Hero code — github-light token colors */\n  --hero-code-text: #24292e;\n  --hero-code-kw: #d73a49;\n  --hero-code-str: #032f62;\n  --hero-code-num: #005cc5;\n  --hero-code-fn: #6f42c1;\n  --hero-code-cmt: #6a737d;\n\n  /* Warning (amber, not pink) */\n  --vp-c-warning-1: #b45309;\n  --vp-c-warning-2: #d97706;\n  --vp-c-warning-3: #f59e0b;\n  --vp-c-warning-soft: rgba(245, 158, 11, 0.04);\n}\n\n.dark {\n  --vp-c-brand-1: #4466ff;\n  --vp-c-brand-2: #3355ee;\n  --vp-c-brand-3: #2244dd;\n  --vp-c-brand-soft: rgba(68, 102, 255, 0.1);\n\n  /* Backgrounds */\n  --vp-c-bg: #0c0d0f;\n  --vp-c-bg-soft: #131416;\n  --vp-c-bg-alt: #08090a;\n\n  /* Text */\n  --vp-c-text-1: #ededef;\n  --vp-c-text-2: #a1a1a9;\n  --vp-c-text-3: #6e6e76;\n\n  /* Borders */\n  --vp-c-divider: rgba(255, 255, 255, 0.04);\n  --vp-c-border: rgba(255, 255, 255, 0.08);\n\n  /* Code blocks */\n  --vp-code-block-bg: #08090a;\n\n  /* Hero code — github-dark token colors */\n  --hero-code-text: #e1e4e8;\n  --hero-code-kw: #f97583;\n  --hero-code-str: #9ecbff;\n  --hero-code-num: #79b8ff;\n  --hero-code-fn: #b392f0;\n  --hero-code-cmt: #6a737d;\n\n  /* Warning */\n  --vp-c-warning-1: #f59e0b;\n  --vp-c-warning-2: #d97706;\n  --vp-c-warning-3: #b45309;\n  --vp-c-warning-soft: rgba(245, 158, 11, 0.04);\n}\n\n/* ===== Navigation (frosted glass) ===== */\n\n/*\n * Apply the frosted‐glass effect to VPNavBar, NOT VPNav.\n * backdrop-filter on VPNav creates a stacking context that traps the\n * position:fixed VPNavScreen (mobile menu) and makes it invisible.\n */\n.VPNavBar {\n  background: rgba(255, 255, 255, 0.8) !important;\n  backdrop-filter: blur(12px);\n  -webkit-backdrop-filter: blur(12px);\n}\n\n.dark .VPNavBar {\n  background: rgba(12, 13, 15, 0.8) !important;\n}\n\n.VPNavBarTitle .title {\n  font-weight: 700;\n  letter-spacing: -0.01em;\n  border-bottom: none !important;\n}\n\n.VPNavBarMenuLink:hover,\n.VPNavBarMenuLink.active {\n  color: var(--vp-c-text-1);\n  font-weight: 600;\n}\n\n/* Sidebar */\n.VPSidebar {\n  font-size: 0.875rem;\n}\n\n.VPSidebar .VPSidebarItem.is-active > .item > .link > .text {\n  color: var(--vp-c-brand-1);\n  font-weight: 600;\n}\n\n/* ===== Buttons ===== */\n\n.VPButton.brand {\n  border-color: var(--vp-c-brand-1);\n  background-color: var(--vp-c-brand-1);\n  font-weight: 500;\n  letter-spacing: -0.01em;\n  border-radius: 6px;\n  transition: opacity 160ms ease;\n}\n\n.VPButton.brand:hover {\n  border-color: var(--vp-c-brand-1);\n  background-color: var(--vp-c-brand-1);\n  opacity: 0.85;\n}\n\n.VPButton.alt {\n  font-weight: 500;\n  border-radius: 6px;\n  background-color: transparent;\n  border-color: var(--vp-c-border);\n  transition: border-color 160ms ease;\n}\n\n.VPButton.alt:hover {\n  border-color: var(--vp-c-text-3);\n}\n\n/* ===== Content ===== */\n\n.vp-doc h1 {\n  font-weight: 700;\n  letter-spacing: -0.02em;\n}\n\n.vp-doc h2 {\n  font-weight: 600;\n  letter-spacing: -0.01em;\n  border-top: 1px solid var(--vp-c-divider);\n  padding-top: 32px;\n  margin-top: 48px;\n}\n\n.vp-doc h2 .header-anchor {\n  top: 32px;\n}\n\n.vp-doc table {\n  display: table;\n  width: 100%;\n}\n\n.vp-doc a {\n  font-weight: 500;\n  text-decoration: underline;\n  text-decoration-color: var(--vp-c-divider);\n  text-underline-offset: 3px;\n  transition:\n    text-decoration-color 160ms ease,\n    color 160ms ease;\n}\n\n.vp-doc a:hover {\n  color: var(--vp-c-brand-1);\n  text-decoration-color: var(--vp-c-brand-1);\n}\n\n/* Inline code */\n.vp-doc :not(pre) > code {\n  background-color: var(--vp-c-bg-soft);\n  color: var(--vp-c-text-1);\n  border: 1px solid var(--vp-c-border);\n}\n\n/* Code blocks */\n.vp-doc div[class*='language-'] {\n  border-radius: 8px;\n  border: 1px solid var(--vp-c-border);\n}\n\n/* ===== Custom containers ===== */\n\n.vp-doc .custom-block.info {\n  border-color: var(--vp-c-brand-1);\n  background-color: var(--vp-c-brand-soft);\n}\n\n.vp-doc .custom-block.info code {\n  background-color: rgba(17, 68, 238, 0.06);\n}\n\n.dark .vp-doc .custom-block.info code {\n  background-color: rgba(107, 138, 255, 0.06);\n}\n\n.vp-doc .custom-block.tip {\n  border-color: var(--vp-c-brand-1);\n  background-color: var(--vp-c-brand-soft);\n}\n\n.vp-doc .custom-block.warning {\n  border-color: var(--vp-c-warning-2);\n  background-color: var(--vp-c-warning-soft);\n}\n\n/* ===== Footer ===== */\n\n.VPFooter {\n  border-top: 1px solid var(--vp-c-divider);\n  color: var(--vp-c-text-3);\n  font-size: 0.875rem;\n}\n\n/* ===== Misc ===== */\n\n/* Scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: var(--vp-c-border);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: var(--vp-c-text-3);\n}\n\n/* Selection */\n::selection {\n  background-color: var(--vp-c-brand-soft);\n  color: inherit;\n}\n"
  },
  {
    "path": "docs/about.md",
    "content": "---\ntitle: About\ndescription: Dinero.js is an open-source library by Sarah Dayan and contributors.\n---\n\n# About\n\nDinero.js is an open-source library by [Sarah Dayan](https://sarahdayan.dev/) and [contributors](https://github.com/dinerojs/dinero.js/graphs/contributors).\n\nIt's licensed under [MIT](https://opensource.org/licenses/MIT).\n\n## Acknowledgements\n\nDinero.js is based on [Martin Fowler's money pattern](https://martinfowler.com/eaaCatalog/money.html).\n\nThe documentation and some of its patterns are inspired from [Algolia](https://www.algolia.com/doc/), [Tailwind CSS](https://tailwindcss.com/docs) and [date-fns](https://date-fns.org/).\n\nThe logo was designed by [David DeSandro](https://github.com/desandro).\n"
  },
  {
    "path": "docs/agent-skills.md",
    "content": "---\ntitle: Agent Skills\ndescription: Teach your AI coding agent Dinero.js best practices with installable skills.\n---\n\n# Agent Skills\n\nIf you use AI coding agents like [Claude Code](https://claude.ai/code), [Cursor](https://cursor.sh/), [GitHub Copilot](https://github.com/features/copilot), or any of the [40+ supported agents](https://skills.sh), you can install the Dinero.js skills to teach your agent best practices, common pitfalls, and correct usage patterns.\n\n## Installation\n\nInstall all skills:\n\n```bash\nnpx skills add dinerojs/skills\n```\n\nInstall a specific skill:\n\n```bash\nnpx skills add dinerojs/skills --skill dinero-best-practices\n```\n\n## Available skills\n\n### `dinero-best-practices`\n\nCore rules for working with Dinero.js: representing amounts in minor units, converting from floats, immutability, using `allocate` instead of manual division, scaled amounts for decimal multipliers, and choosing between `number` and `bigint`.\n\n```bash\nnpx skills add dinerojs/skills --skill dinero-best-practices\n```\n\n### `dinero-formatting`\n\nFormatting money for display: using `toDecimal` for output, composing with `Intl.NumberFormat` for currency symbols and locale-aware formatting, serializing with `toSnapshot`, and formatting non-decimal currencies with `toUnits`.\n\n```bash\nnpx skills add dinerojs/skills --skill dinero-formatting\n```\n\n### `dinero-currency-patterns`\n\nCurrency handling patterns: defining type-safe custom currencies, compile-time currency mismatch detection, validating currency codes from external sources, converting with scaled exchange rates, database storage schemas, and payment service integration (Stripe, PayPal, Square).\n\n```bash\nnpx skills add dinerojs/skills --skill dinero-currency-patterns\n```\n\n## What are agent skills?\n\n[Agent skills](https://skills.sh) are reusable instruction sets that extend the capabilities of AI coding agents. They provide specialized guidelines and code patterns that agents follow when working with specific libraries or frameworks.\n\nSkills are agent-agnostic and work across 40+ platforms. When installed, they help your agent write correct Dinero.js code from the start, avoiding common mistakes like passing floats, mixing `number` and `bigint` currency imports, or using `toSnapshot` for display.\n"
  },
  {
    "path": "docs/api/comparisons/compare.md",
    "content": "---\ntitle: compare\ndescription: Compare the value of a Dinero object relative to another.\nreturns: number\n---\n\n# compare\n\nCompare the value of a Dinero object relative to another. This is useful for sorting Dinero objects.\n\nPossible return values are:\n- `-1` if the first Dinero object is less than the other\n- `1` if the first Dinero object is greater than the other\n- `0` if both objects are equal\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two objects\n\n```js\nimport { dinero, compare } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 800, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\ncompare(d1, d2); // 1\n```\n\n### Compare two objects after normalization\n\n```js\nimport { dinero, compare } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\nconst d2 = dinero({ amount: 800, currency: USD });\n\ncompare(d1, d2); // -1\n\nconst d3 = dinero({ amount: 5000, currency: USD, scale: 3 });\nconst d4 = dinero({ amount: 500, currency: USD });\n\ncompare(d3, d4); // 0\n```\n\n### Sort arrays of objects\n\nOne of the main use cases of the `compare` function is to sort Dinero objects. For example, you can use it with [`Array.prototype.sort`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort).\n\n```js\nimport { dinero, compare } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 900, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\nconst d3 = dinero({ amount: 800, currency: USD });\n\nconst lowToHigh = [d1, d2, d3].sort(compare);\nconst highToLow = [d1, d2, d3].sort((a, b) => compare(b, a));\n```\n"
  },
  {
    "path": "docs/api/comparisons/equal.md",
    "content": "---\ntitle: equal\ndescription: Check whether the value of a Dinero object is equal to another.\nreturns: boolean\n---\n\n# equal\n\nCheck whether the value of a Dinero object is equal to another.\n\nThis function does same-value equality, determining whether two Dinero objects are functionally equivalent. It also normalizes objects to the same scale (the highest) before comparing them.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two identical objects\n\n```js\nimport { dinero, equal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\nequal(d1, d2); // true\n```\n\n### Compare two objects with different amounts\n\n```js\nimport { dinero, equal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nequal(d1, d2); // false\n```\n\n### Compare two identical objects after normalization\n\n```js\nimport { dinero, equal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\nequal(d1, d2); // true\n```\n\n### Compare two objects with different currencies\n\n```js\nimport { dinero, equal } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 500, currency: EUR });\n\nequal(d1, d2); // false\n```\n"
  },
  {
    "path": "docs/api/comparisons/greater-than-or-equal.md",
    "content": "---\ntitle: greaterThanOrEqual\ndescription: Check whether the value of a Dinero object is greater than or equal another.\nreturns: boolean\n---\n\n# greaterThanOrEqual\n\nCheck whether the value of a Dinero object is greater than or equal another.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two objects\n\n```js\nimport { dinero, greaterThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\ngreaterThanOrEqual(d1, d2); // false\n```\n\n### Compare two identical objects\n\n```js\nimport { dinero, greaterThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\ngreaterThanOrEqual(d1, d2); // true\n```\n\n### Compare two objects after normalization\n\n```js\nimport { dinero, greaterThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 800, currency: USD });\nconst d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\ngreaterThanOrEqual(d1, d2); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/greater-than.md",
    "content": "---\ntitle: greaterThan\ndescription: Check whether the value of a Dinero object is greater than another.\nreturns: boolean\n---\n\n# greaterThan\n\nCheck whether the value of a Dinero object is greater than another.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two objects\n\n```js\nimport { dinero, greaterThan } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\ngreaterThan(d1, d2); // false\n```\n\n### Compare two objects after normalization\n\n```js\nimport { dinero, greaterThan } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 800, currency: USD });\nconst d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\ngreaterThan(d1, d2); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/has-sub-units.md",
    "content": "---\ntitle: hasSubUnits\ndescription: Check whether a Dinero object has minor currency units.\nreturns: boolean\n---\n\n# hasSubUnits\n\nCheck whether a Dinero object has minor currency units.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount>` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Check an object with sub-units\n\n```js\nimport { dinero, hasSubUnits } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1150, currency: USD });\n\nhasSubUnits(d); // true\n```\n\n### Check an object without sub-units\n\n```js\nimport { dinero, hasSubUnits } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1100, currency: USD });\n\nhasSubUnits(d); // false\n```\n\n### Check an object with sub-units based on the scale\n\n```js\nimport { dinero, hasSubUnits } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1100, currency: USD, scale: 3 });\n\nhasSubUnits(d); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/have-same-amount.md",
    "content": "---\ntitle: haveSameAmount\ndescription: Check whether a set of Dinero objects have the same amount.\nreturns: boolean\n---\n\n# haveSameAmount\n\nCheck whether a set of Dinero objects have the same amount.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObjects` | `Dinero<TAmount, TCurrency>[]` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Compare two objects with the same amount\n\n```js\nimport { dinero, haveSameAmount } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: USD });\n\nhaveSameAmount([d1, d2]); // true\n```\n\n### Compare two objects with different amount\n\n```js\nimport { dinero, haveSameAmount } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD });\nconst d2 = dinero({ amount: 2000, currency: USD });\n\nhaveSameAmount([d1, d2]); // false\n```\n\n### Compare two objects with the same amount once normalized\n\n```js\nimport { dinero, haveSameAmount } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD });\nconst d2 = dinero({ amount: 10000, currency: USD, scale: 3 });\n\nhaveSameAmount([d1, d2]); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/have-same-currency.md",
    "content": "---\ntitle: haveSameCurrency\ndescription: Check whether a set of Dinero objects have the same currency.\nreturns: boolean\n---\n\n# haveSameCurrency\n\nCheck whether a set of Dinero objects have the same currency.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObjects` | `Dinero<TAmount>[]` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Compare two objects with the same currency\n\n```js\nimport { dinero, haveSameCurrency } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 2000, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: USD });\n\nhaveSameCurrency([d1, d2]); // true\n```\n\n### Compare two objects with different currencies\n\n```js\nimport { dinero, haveSameAmount } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: EUR });\n\nhaveSameAmount([d1, d2]); // false\n```\n"
  },
  {
    "path": "docs/api/comparisons/is-negative.md",
    "content": "---\ntitle: isNegative\ndescription: Check whether a Dinero object is negative.\nreturns: boolean\n---\n\n# isNegative\n\nCheck whether a Dinero object is negative.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount>` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Check a positive object\n\n```js\nimport { dinero, isNegative } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 100, currency: USD });\n\nisNegative(d); // false\n```\n\n### Check a negative object\n\n```js\nimport { dinero, isNegative } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: -100, currency: USD });\n\nisNegative(d); // true\n```\n\n### Check a zero object\n\n```js\nimport { dinero, isNegative } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 0, currency: USD });\n\nisNegative(d); // false\n```\n"
  },
  {
    "path": "docs/api/comparisons/is-positive.md",
    "content": "---\ntitle: isPositive\ndescription: Check whether a Dinero object is positive.\nreturns: boolean\n---\n\n# isPositive\n\nCheck whether a Dinero object is positive.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount>` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Check a positive object\n\n```js\nimport { dinero, isPositive } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 100, currency: USD });\n\nisPositive(d); // true\n```\n\n### Check a negative object\n\n```js\nimport { dinero, isPositive } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: -100, currency: USD });\n\nisPositive(d); // false\n```\n\n### Check a zero object\n\n```js\nimport { dinero, isPositive } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 0, currency: USD });\n\nisPositive(d); // false\n```\n"
  },
  {
    "path": "docs/api/comparisons/is-zero.md",
    "content": "---\ntitle: isZero\ndescription: Check whether the value of a Dinero object is zero.\nreturns: boolean\n---\n\n# isZero\n\nCheck whether the value of a Dinero object is zero.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount>` | The Dinero object to check. | Yes |\n\n## Code examples\n\n### Check a zero object\n\n```js\nimport { dinero, isZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 0, currency: USD });\n\nisZero(d); // true\n```\n\n### Check a non-zero object\n\n```js\nimport { dinero, isZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 100, currency: USD });\n\nisZero(d); // false\n```\n"
  },
  {
    "path": "docs/api/comparisons/less-than-or-equal.md",
    "content": "---\ntitle: lessThanOrEqual\ndescription: Check whether the value of a Dinero object is lesser than or equal to another.\nreturns: boolean\n---\n\n# lessThanOrEqual\n\nCheck whether the value of a Dinero object is lesser than or equal to another.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two objects\n\n```js\nimport { dinero, lessThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 800, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\nlessThanOrEqual(d1, d2); // false\n```\n\n### Compare two identical objects\n\n```js\nimport { dinero, lessThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\nlessThanOrEqual(d1, d2); // true\n```\n\n### Compare two objects after normalization\n\n```js\nimport { dinero, lessThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nlessThanOrEqual(d1, d2); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/less-than.md",
    "content": "---\ntitle: lessThan\ndescription: Check whether the value of a Dinero object is lesser than another.\nreturns: boolean\n---\n\n# lessThan\n\nCheck whether the value of a Dinero object is lesser than another.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The first Dinero object to compare. | Yes |\n| `comparator` | `Dinero<TAmount, TCurrency>` | The second Dinero object to compare. | Yes |\n\n## Code examples\n\n### Compare two objects\n\n```js\nimport { dinero, lessThan } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 800, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\n\nlessThan(d1, d2); // false\n```\n\n### Compare two objects after normalization\n\n```js\nimport { dinero, lessThan } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nlessThan(d1, d2); // true\n```\n"
  },
  {
    "path": "docs/api/comparisons/maximum.md",
    "content": "---\ntitle: maximum\ndescription: Get the greatest of the passed Dinero objects.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# maximum\n\nGet the greatest of the passed Dinero objects.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObjects` | `Dinero<TAmount, TCurrency>[]` | The Dinero objects to maximum. | Yes |\n\n## Code examples\n\n### Get the greatest object from a set\n\n```js\nimport { dinero, maximum } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 150, currency: USD });\nconst d2 = dinero({ amount: 50, currency: USD });\n\nmaximum([d1, d2]); // a Dinero object with amount 150\n```\n\n### Get the greatest object from a set after normalization\n\n```js\nimport { dinero, maximum } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\nmaximum([d1, d2]); // a Dinero object with amount 5000 and scale 3\n```\n"
  },
  {
    "path": "docs/api/comparisons/minimum.md",
    "content": "---\ntitle: minimum\ndescription: Get the lowest of the passed Dinero objects.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# minimum\n\nGet the lowest of the passed Dinero objects.\n\n**You can only compare objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before comparing them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObjects` | `Dinero<TAmount, TCurrency>[]` | The Dinero objects to minimum. | Yes |\n\n## Code examples\n\n### Get the lowest object from a set\n\n```js\nimport { dinero, minimum } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 150, currency: USD });\nconst d2 = dinero({ amount: 50, currency: USD });\n\nminimum([d1, d2]); // a Dinero object with amount 50\n```\n\n### Get the lowest object from a set after normalization\n\n```js\nimport { dinero, minimum } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\nminimum([d1, d2]); // a Dinero object with amount 1000 and scale 3\n```\n"
  },
  {
    "path": "docs/api/conversions/convert.md",
    "content": "---\ntitle: convert\ndescription: Create a Dinero object converter.\nreturns: Dinero<TAmount, TNewCurrency>\n---\n\n# convert\n\nConvert a Dinero object from a currency to another.\n\nIf you need to use fractional rates, you shouldn't use floats, but scaled amounts instead. For example, instead of passing `0.89`, you should pass `{ amount: 89, scale: 2 }`. When using scaled amounts, the function converts the returned object to the safest scale.\n\nIn TypeScript, the returned Dinero object carries the type of the new currency. See [Currency type safety](/guides/currency-type-safety).\n\n::: warning\nBoth currencies must share the same base. Converting between currencies with different bases (e.g., USD base 10 and MGA base 5) will throw.\n:::\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to convert. | Yes |\n| `newCurrency` | `DineroCurrency<TAmount, TNewCurrency>` | The currency to convert into. | Yes |\n| `rates` | `DineroRates<TAmount>` | The rates to convert with. | Yes |\n\n## Code examples\n\n### Convert to another currency\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst rates = { EUR: { amount: 89, scale: 2 } };\nconst d = dinero({ amount: 500, currency: USD });\n\nconvert(d, EUR, rates); // a Dinero object with amount 44500 and scale 4\n```\n\n### Convert to a currency with a different scale\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, IQD } from 'dinero.js/currencies';\n\nconst rates = { IQD: 1199 };\nconst d = dinero({ amount: 500, currency: USD });\n\nconvert(d, IQD, rates); // a Dinero object with amount 5995000 and scale 3\n```\n\n### Build a reusable converter\n\nIf you're converting many objects, you might want to reuse the same rates without having to pass them every time. To do so, you can wrap `convert` in a converter function that accepts a Dinero object and a new currency, and returns it formatted using a predefined converter.\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst rates = { EUR: { amount: 89, scale: 2 } };\n\nfunction converter(dineroObject, newCurrency) {\n  return convert(dineroObject, newCurrency, rates);\n}\n\nconst d = dinero({ amount: 500, currency: USD });\n\nconverter(d, EUR); // a Dinero object with amount 44500 and scale 4\n```\n\nYou can even build your own reusable higher-order function to build converters.\n\n```js\n// ...\n\nfunction createConverter(rates) {\n  return function converter(dineroObject, newCurrency) {\n    return convert(dineroObject, newCurrency, rates);\n  };\n}\n```\n"
  },
  {
    "path": "docs/api/conversions/normalize-scale.md",
    "content": "---\ntitle: normalizeScale\ndescription: Normalize a set of Dinero objects to the highest scale of the set.\nreturns: Dinero<TAmount, TCurrency>[]\n---\n\n# normalizeScale\n\nNormalize a set of Dinero objects to the highest scale of the set.\n\nNormalizing to a higher scale means that the internal `amount` value increases by orders of magnitude. If you're using the default Dinero.js implementation (with the `number` calculator), be careful not to exceed the minimum and maximum safe integers.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObjects` | `Dinero<TAmount, TCurrency>[]` | The Dinero objects to normalize. | Yes |\n\n## Code examples\n\n### Normalize objects to the same scale\n\n```js\nimport { dinero, normalizeScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 100, currency: USD, scale: 2 });\nconst d2 = dinero({ amount: 2000, currency: USD, scale: 3 });\n\nconst [one, two] = normalizeScale([d1, d2]);\n\none; // a Dinero object with amount 1000 and scale 3\ntwo; // a Dinero object with amount 2000 and scale 3\n```\n"
  },
  {
    "path": "docs/api/conversions/transform-scale.md",
    "content": "---\ntitle: transformScale\ndescription: Transform a Dinero object to a new scale.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# transformScale\n\nTransform a Dinero object to a new scale.\n\nWhen transforming to a higher scale, the internal `amount` value increases by orders of magnitude. If you're using the default Dinero.js implementation (with the `number` calculator), be careful not to exceed the minimum and maximum safe integers.\n\nWhen transforming to a smaller scale, the `amount` loses precision. By default, the function rounds down the amount. You can specify how to round by [passing a custom divide function](#pass-a-custom-divide-function).\n\nFor convenience, Dinero.js provides the following divide functions: `up`, `down`, `halfUp`, `halfDown`, `halfOdd`, `halfEven` ([bankers rounding](https://wiki.c2.com/?BankersRounding)), `halfTowardsZero`, and `halfAwayFromZero`.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to transform. | Yes |\n| `newScale` | `TAmount` | The new scale. | Yes |\n| `divide` | `DivideOperation` | A custom divide function. | No |\n\n## Code examples\n\n### Transform an object to a new scale\n\n```js\nimport { dinero, transformScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500, currency: USD, scale: 2 });\n\ntransformScale(d, 4); // a Dinero object with amount 50000 and scale 4\n```\n\n### Pass a custom divide function\n\n```js\nimport { dinero, transformScale, up } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\ntransformScale(d, 2, up); // a Dinero object with amount 1046 and scale 2\n```\n"
  },
  {
    "path": "docs/api/conversions/trim-scale.md",
    "content": "---\ntitle: trimScale\ndescription: Trim a Dinero object's scale as much as possible, down to the currency exponent.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# trimScale\n\nTrim a Dinero object's scale as much as possible, down to the currency exponent.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to trim. | Yes |\n\n## Code examples\n\n### Trim an object down to its currency exponent's scale\n\n```js\nimport { dinero, trimScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500000, currency: USD, scale: 5 });\n\ntrimScale(d); // a Dinero object with amount 500 and scale 2\n```\n\n### Trim an object down to the safest possible scale\n\n```js\nimport { dinero, trimScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 99950, currency: USD, scale: 4 });\n\ntrimScale(d); // a Dinero object with amount 9995 and scale 3\n```\n"
  },
  {
    "path": "docs/api/currencies.md",
    "content": "---\ntitle: Currencies\ndescription: ISO 4217 currency objects available in dinero.js/currencies.\n---\n\n# Currencies\n\nDinero.js ships with all [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currencies out of the box.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD, EUR, JPY } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1000, currency: USD });\n```\n\nIf you're using the [bigint variant](/guides/precision-and-large-numbers#using-dinero-with-bigint), import from `dinero.js/bigint/currencies` instead.\n\n::: info\nNeed a currency that isn't listed here? You can [create custom currency objects](/core-concepts/currency#creating-custom-currencies). For cryptocurrencies, see the [cryptocurrency guide](/guides/cryptocurrencies).\n:::\n\n::: warning\nCurrency data tracks the ISO 4217 standard and **may change between Dinero.js versions.** If you need stability, pin your package version or define your own currency objects.\n:::\n\n## Properties\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `code` | `string` | The ISO 4217 currency code. |\n| `base` | `number` | The number base (radix). Most currencies use `10`; non-decimal currencies like `MGA` and `MRU` use `5`. |\n| `exponent` | `number` | The number of decimal places. Determines how the [amount](/core-concepts/amount) maps to major and minor units. |\n\n## Available currencies\n\n| Code | Currency | Base | Exponent |\n|------|----------|------|----------|\n| `AED` | United Arab Emirates dirham | 10 | 2 |\n| `AFN` | Afghan afghani | 10 | 2 |\n| `ALL` | Albanian lek | 10 | 2 |\n| `AMD` | Armenian dram | 10 | 2 |\n| `AOA` | Angolan kwanza | 10 | 2 |\n| `ARS` | Argentine peso | 10 | 2 |\n| `AUD` | Australian dollar | 10 | 2 |\n| `AWG` | Aruban florin | 10 | 2 |\n| `AZN` | Azerbaijani manat | 10 | 2 |\n| `BAM` | Bosnia and Herzegovina convertible mark | 10 | 2 |\n| `BBD` | Barbados dollar | 10 | 2 |\n| `BDT` | Bangladeshi taka | 10 | 2 |\n| `BGN` | Bulgarian lev | 10 | 2 |\n| `BHD` | Bahraini dinar | 10 | 3 |\n| `BIF` | Burundian franc | 10 | 0 |\n| `BMD` | Bermudian dollar | 10 | 2 |\n| `BND` | Brunei dollar | 10 | 2 |\n| `BOB` | Bolivian boliviano | 10 | 2 |\n| `BOV` | Bolivian Mvdol | 10 | 2 |\n| `BRL` | Brazilian real | 10 | 2 |\n| `BSD` | Bahamian dollar | 10 | 2 |\n| `BTN` | Bhutanese ngultrum | 10 | 2 |\n| `BWP` | Botswana pula | 10 | 2 |\n| `BYN` | Belarusian ruble | 10 | 2 |\n| `BZD` | Belize dollar | 10 | 2 |\n| `CAD` | Canadian dollar | 10 | 2 |\n| `CDF` | Congolese franc | 10 | 2 |\n| `CHE` | WIR Euro | 10 | 2 |\n| `CHF` | Swiss franc | 10 | 2 |\n| `CHW` | WIR Franc | 10 | 2 |\n| `CLF` | Unidad de Fomento | 10 | 4 |\n| `CLP` | Chilean peso | 10 | 0 |\n| `CNY` | Renminbi (Chinese) yuan | 10 | 2 |\n| `COP` | Colombian peso | 10 | 2 |\n| `COU` | Unidad de Valor Real | 10 | 2 |\n| `CRC` | Costa Rican colón | 10 | 2 |\n| `CUP` | Cuban peso | 10 | 2 |\n| `CVE` | Cape Verdean escudo | 10 | 2 |\n| `CZK` | Czech koruna | 10 | 2 |\n| `DJF` | Djiboutian franc | 10 | 0 |\n| `DKK` | Danish krone | 10 | 2 |\n| `DOP` | Dominican peso | 10 | 2 |\n| `DZD` | Algerian dinar | 10 | 2 |\n| `EGP` | Egyptian pound | 10 | 2 |\n| `ERN` | Eritrean nakfa | 10 | 2 |\n| `ETB` | Ethiopian birr | 10 | 2 |\n| `EUR` | Euro | 10 | 2 |\n| `FJD` | Fiji dollar | 10 | 2 |\n| `FKP` | Falkland Islands pound | 10 | 2 |\n| `GBP` | Pound sterling | 10 | 2 |\n| `GEL` | Georgian lari | 10 | 2 |\n| `GHS` | Ghanaian cedi | 10 | 2 |\n| `GIP` | Gibraltar pound | 10 | 2 |\n| `GMD` | Gambian dalasi | 10 | 2 |\n| `GNF` | Guinean franc | 10 | 0 |\n| `GTQ` | Guatemalan quetzal | 10 | 2 |\n| `GYD` | Guyanese dollar | 10 | 2 |\n| `HKD` | Hong Kong dollar | 10 | 2 |\n| `HNL` | Honduran lempira | 10 | 2 |\n| `HTG` | Haitian gourde | 10 | 2 |\n| `HUF` | Hungarian forint | 10 | 2 |\n| `IDR` | Indonesian rupiah | 10 | 2 |\n| `ILS` | Israeli new shekel | 10 | 2 |\n| `INR` | Indian rupee | 10 | 2 |\n| `IQD` | Iraqi dinar | 10 | 3 |\n| `IRR` | Iranian rial | 10 | 2 |\n| `ISK` | Icelandic króna | 10 | 0 |\n| `JMD` | Jamaican dollar | 10 | 2 |\n| `JOD` | Jordanian dinar | 10 | 3 |\n| `JPY` | Japanese yen | 10 | 0 |\n| `KES` | Kenyan shilling | 10 | 2 |\n| `KGS` | Kyrgyzstani som | 10 | 2 |\n| `KHR` | Cambodian riel | 10 | 2 |\n| `KMF` | Comoro franc | 10 | 0 |\n| `KPW` | North Korean won | 10 | 2 |\n| `KRW` | South Korean won | 10 | 0 |\n| `KWD` | Kuwaiti dinar | 10 | 3 |\n| `KYD` | Cayman Islands dollar | 10 | 2 |\n| `KZT` | Kazakhstani tenge | 10 | 2 |\n| `LAK` | Lao kip | 10 | 2 |\n| `LBP` | Lebanese pound | 10 | 2 |\n| `LKR` | Sri Lankan rupee | 10 | 2 |\n| `LRD` | Liberian dollar | 10 | 2 |\n| `LSL` | Lesotho loti | 10 | 2 |\n| `LYD` | Libyan dinar | 10 | 3 |\n| `MAD` | Moroccan dirham | 10 | 2 |\n| `MDL` | Moldovan leu | 10 | 2 |\n| `MGA` | Malagasy ariary | 5 | 1 |\n| `MKD` | Macedonian denar | 10 | 2 |\n| `MMK` | Myanmar kyat | 10 | 2 |\n| `MNT` | Mongolian tögrög | 10 | 2 |\n| `MOP` | Macanese pataca | 10 | 2 |\n| `MRU` | Mauritanian ouguiya | 5 | 1 |\n| `MUR` | Mauritian rupee | 10 | 2 |\n| `MVR` | Maldivian rufiyaa | 10 | 2 |\n| `MWK` | Malawian kwacha | 10 | 2 |\n| `MXN` | Mexican peso | 10 | 2 |\n| `MXV` | Mexican Unidad de Inversion | 10 | 2 |\n| `MYR` | Malaysian ringgit | 10 | 2 |\n| `MZN` | Mozambican metical | 10 | 2 |\n| `NAD` | Namibian dollar | 10 | 2 |\n| `NGN` | Nigerian naira | 10 | 2 |\n| `NIO` | Nicaraguan córdoba | 10 | 2 |\n| `NOK` | Norwegian krone | 10 | 2 |\n| `NPR` | Nepalese rupee | 10 | 2 |\n| `NZD` | New Zealand dollar | 10 | 2 |\n| `OMR` | Omani rial | 10 | 3 |\n| `PAB` | Panamanian balboa | 10 | 2 |\n| `PEN` | Peruvian sol | 10 | 2 |\n| `PGK` | Papua New Guinean kina | 10 | 2 |\n| `PHP` | Philippine peso | 10 | 2 |\n| `PKR` | Pakistani rupee | 10 | 2 |\n| `PLN` | Polish złoty | 10 | 2 |\n| `PYG` | Paraguayan guaraní | 10 | 0 |\n| `QAR` | Qatari riyal | 10 | 2 |\n| `RON` | Romanian leu | 10 | 2 |\n| `RSD` | Serbian dinar | 10 | 2 |\n| `RUB` | Russian ruble | 10 | 2 |\n| `RWF` | Rwandan franc | 10 | 0 |\n| `SAR` | Saudi riyal | 10 | 2 |\n| `SBD` | Solomon Islands dollar | 10 | 2 |\n| `SCR` | Seychelles rupee | 10 | 2 |\n| `SDG` | Sudanese pound | 10 | 2 |\n| `SEK` | Swedish krona | 10 | 2 |\n| `SGD` | Singapore dollar | 10 | 2 |\n| `SHP` | Saint Helena pound | 10 | 2 |\n| `SLE` | Sierra Leonean leone | 10 | 2 |\n| `SOS` | Somali shilling | 10 | 2 |\n| `SRD` | Surinamese dollar | 10 | 2 |\n| `SSP` | South Sudanese pound | 10 | 2 |\n| `STN` | São Tomé and Príncipe dobra | 10 | 2 |\n| `SVC` | Salvadoran colón | 10 | 2 |\n| `SYP` | Syrian pound | 10 | 2 |\n| `SZL` | Swazi lilangeni | 10 | 2 |\n| `THB` | Thai baht | 10 | 2 |\n| `TJS` | Tajikistani somoni | 10 | 2 |\n| `TMT` | Turkmenistan manat | 10 | 2 |\n| `TND` | Tunisian dinar | 10 | 3 |\n| `TOP` | Tongan paʻanga | 10 | 2 |\n| `TRY` | Turkish lira | 10 | 2 |\n| `TTD` | Trinidad and Tobago dollar | 10 | 2 |\n| `TWD` | New Taiwan dollar | 10 | 2 |\n| `TZS` | Tanzanian shilling | 10 | 2 |\n| `UAH` | Ukrainian hryvnia | 10 | 2 |\n| `UGX` | Ugandan shilling | 10 | 0 |\n| `USD` | United States dollar | 10 | 2 |\n| `USN` | United States dollar (next day) | 10 | 2 |\n| `UYI` | Uruguay Peso en Unidades Indexadas | 10 | 0 |\n| `UYU` | Uruguayan peso | 10 | 2 |\n| `UYW` | Unidad previsional | 10 | 4 |\n| `UZS` | Uzbekistani soʻm | 10 | 2 |\n| `VED` | Venezuelan digital bolívar | 10 | 2 |\n| `VES` | Venezuelan bolívar | 10 | 2 |\n| `VND` | Vietnamese đồng | 10 | 0 |\n| `VUV` | Vanuatu vatu | 10 | 0 |\n| `WST` | Samoan tālā | 10 | 2 |\n| `XAD` | Arab Accounting Dinar | 10 | 2 |\n| `XAF` | Central African CFA franc | 10 | 0 |\n| `XCD` | East Caribbean dollar | 10 | 2 |\n| `XCG` | Caribbean guilder | 10 | 2 |\n| `XOF` | West African CFA franc | 10 | 0 |\n| `XPF` | CFP franc | 10 | 0 |\n| `YER` | Yemeni rial | 10 | 2 |\n| `ZAR` | South African rand | 10 | 2 |\n| `ZMW` | Zambian kwacha | 10 | 2 |\n| `ZWG` | Zimbabwe Gold | 10 | 2 |\n"
  },
  {
    "path": "docs/api/dinero.md",
    "content": "---\ntitle: dinero\ndescription: Create a Dinero object.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# dinero\n\nCreate a Dinero object that represents a monetary value.\n\nYou specify the amount in [minor currency units](/core-concepts/amount) (e.g., cents for the US dollar) and pass a [currency](/core-concepts/currency). The [scale](/core-concepts/scale) defaults to the currency's exponent but can be set manually for additional precision.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `amount` | `TAmount` | The amount in minor currency units. Must be an integer. | Yes |\n| `currency` | `DineroCurrency<TAmount>` | The currency object. | Yes |\n| `scale` | `TAmount` | The number of decimal places to represent. Defaults to the currency exponent. | No |\n\n## Code examples\n\n### Create a Dinero object\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// This represents $5.00\nconst d = dinero({ amount: 500, currency: USD });\n```\n\n### Create with a custom scale\n\nWhen you need more precision than the currency exponent provides, you can specify a custom scale.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// This represents $0.035 (e.g., the price of a single screw)\nconst d = dinero({ amount: 35, currency: USD, scale: 3 });\n```\n\n### Create with bigint\n\nIf you need to work with large amounts that exceed the safe range for `number`, use the bigint variant.\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst d = dinero({ amount: 5000n, currency: USD });\n```\n\n### Create with a non-decimal currency\n\nYou can use custom currency objects for non-decimal currencies.\n\n```js\nimport { dinero } from 'dinero.js';\n\nconst GBP = {\n  code: 'GBP',\n  base: [20, 12],\n  exponent: 1,\n};\n\n// This represents 50 pre-decimal Great Britain pounds\nconst d = dinero({ amount: 12000, currency: GBP });\n```\n"
  },
  {
    "path": "docs/api/formatting/to-decimal.md",
    "content": "---\ntitle: toDecimal\ndescription: Get the amount of a Dinero object in decimal format.\nreturns: TOutput = string\n---\n\n# toDecimal\n\nGet the amount of a Dinero object in a stringified decimal representation.\n\nThe number of decimal places depends on the [`scale`](/core-concepts/scale) of your object—or, when unspecified, the [`exponent`](/core-concepts/currency#currency-exponent) of its currency.\n\n::: info\nYou can only use this function with Dinero objects that are single-based and use a decimal currency.\n:::\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to format. | Yes |\n| `transformer` | `DineroTransformer<TAmount, TOutput, TCurrency>` | An optional transformer function. | No |\n\n## Code examples\n\n### Format an object in decimal format\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1050, currency: USD });\nconst d2 = dinero({ amount: 10545, currency: USD, scale: 3 });\n\ntoDecimal(d1); // \"10.50\"\ntoDecimal(d2); // \"10.545\"\n```\n\n### Use a custom transformer\n\nIf you need to further transform the value before returning it, you can pass a custom function.\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1050, currency: USD });\n\ntoDecimal(d, ({ value, currency }) => `${currency.code} ${value}`); // \"USD 10.50\"\n```\n"
  },
  {
    "path": "docs/api/formatting/to-snapshot.md",
    "content": "---\ntitle: toSnapshot\ndescription: Get a snapshot of a Dinero object.\nreturns: DineroSnapshot<TAmount, TCurrency>\n---\n\n# toSnapshot\n\nGet a snapshot of a Dinero object.\n\nSnapshots are plain JavaScript objects, suited for [transport and storage](/guides/transporting-and-restoring). They're also useful when you need to retrieve raw data from a Dinero object.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to snapshot. | Yes |\n\n## Code examples\n\n### Get a snapshot of an object\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500, currency: USD });\n\ntoSnapshot(d);\n\n// {\n//   amount: 500,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 2,\n// }\n```\n"
  },
  {
    "path": "docs/api/formatting/to-units.md",
    "content": "---\ntitle: toUnits\ndescription: Get the amount of a Dinero object in units.\nreturns: TOutput = TAmount[]\n---\n\n# toUnits\n\nGet the amount of a Dinero object in units.\n\nThis function returns the total amount divided into each unit and sub-unit, as an array. For example, an object representing $10.45 expressed as `1045` (with currency `USD` and no custom `scale`) would return `[10, 45]` for 10 dollars and 45 cents.\n\nWhen specifying multiple bases, the function returns as many units as necessary.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to format. | Yes |\n| `transformer` | `DineroTransformer<TAmount, TOutput, TCurrency>` | An optional transformer function. | No |\n\n## Code examples\n\n### Format an object in units\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1050, currency: USD });\nconst d2 = dinero({ amount: 10545, currency: USD, scale: 3 });\n\ntoUnits(d1); // [10, 50]\ntoUnits(d2); // [10, 545]\n```\n\n### Format a non-decimal object\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\n\nconst GRD = { code: 'GRD', base: 6, exponent: 1 };\nconst d = dinero({ amount: 9, currency: GRD });\n\ntoUnits(d); // [1, 3]\n```\n\n### Format an object with multiple subdivisions\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\n\nconst GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\nconst d = dinero({ amount: 267, currency: GBP });\n\ntoUnits(d); // [1, 2, 3]\n```\n\n### Use a custom transformer\n\nIf you need to further transform the value before returning it, you can pass a custom function.\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\n\nconst GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\nconst d = dinero({ amount: 267, currency: GBP });\n\nconst labels = ['pounds', 'shillings', 'pence'];\n\ntoUnits(d, ({ value }) =>\n  value\n    .filter((amount) => amount > 0)\n    .map((amount, index) => `${amount} ${labels[index]}`)\n    .join(', ')\n);\n```\n"
  },
  {
    "path": "docs/api/mutations/add.md",
    "content": "---\ntitle: add\ndescription: Adding up two Dinero objects.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# add\n\nAdd up two Dinero objects.\n\n**You can only add objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before adding them up.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `augend` | `Dinero<TAmount, TCurrency>` | The Dinero object to add to. | Yes |\n| `addend` | `Dinero<TAmount, TCurrency>` | The Dinero object to add. | Yes |\n\n## Code examples\n\n### Add objects\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 100, currency: USD });\n\nadd(d1, d2); // a Dinero object with amount 600\n```\n\n### Add objects with a different scale\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 400, currency: USD });\nconst d2 = dinero({ amount: 104545, currency: USD, scale: 4 });\n\nadd(d1, d2); // a Dinero object with amount 144545 and scale 4\n```\n\n### Add more than two objects\n\nTo retrieve the sum of multiple objects, you can call the `add` function multiple times.\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 300, currency: USD });\nconst d2 = dinero({ amount: 200, currency: USD });\nconst d3 = dinero({ amount: 100, currency: USD });\n\nconst addMany = (addends) => addends.reduce(add);\n\naddMany([d1, d2, d3]); // a Dinero object with amount 600\n```\n"
  },
  {
    "path": "docs/api/mutations/allocate.md",
    "content": "---\ntitle: allocate\ndescription: Distribute the amount of a Dinero object across a list of ratios.\nreturns: Dinero<TAmount, TCurrency>[]\n---\n\n# allocate\n\nDistribute the amount of a Dinero object across a list of ratios.\n\nMonetary values have indivisible units, meaning you can't always exactly split them. With `allocate`, you can split a monetary amount then distribute the remainder as evenly as possible.\n\nYou can use percentage or ratio style for `ratios`: `[25, 75]` and `[1, 3]` do the same thing. You can also pass zero ratios (such as `[0, 50, 50]`). If there's a remainder to distribute, zero ratios are skipped and return a Dinero object with amount zero.\n\nIf you need to use fractional ratios, you shouldn't use floats, but scaled amounts instead. For example, instead of passing `[50.5, 49.5]`, you should pass `[{ amount: 505, scale: 1 }, { amount: 495, scale: 1 }]`. When using scaled amounts, the function converts the returned objects to the safest scale.\n\n**All ratios must be positive, and you can't only pass zero ratios.**\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `dineroObject` | `Dinero<TAmount, TCurrency>` | The Dinero object to allocate from. | Yes |\n| `ratios` | `Array<DineroScaledAmount<TAmount> \\| TAmount>` | The ratios to allocate the amount to. | Yes |\n\n## Code examples\n\n### Allocate to percentages\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500, currency: USD });\n\nconst [d1, d2] = allocate(d, [50, 50]);\n\nd1; // a Dinero object with amount 250\nd2; // a Dinero object with amount 250\n```\n\n### Allocate to ratios\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 100, currency: USD });\n\nconst [d1, d2] = allocate(d, [1, 3]);\n\nd1; // a Dinero object with amount 25\nd2; // a Dinero object with amount 75\n```\n\n### Distribute as fairly as possible\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1003, currency: USD });\n\nconst [d1, d2] = allocate(d, [50, 50]);\n\nd1; // a Dinero object with amount 502\nd2; // a Dinero object with amount 501\n```\n\n### Ignore zero ratios\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1003, currency: USD });\n\nconst [d1, d2, d3] = allocate(d, [0, 50, 50]);\n\nd1; // a Dinero object with amount 0\nd2; // a Dinero object with amount 502\nd3; // a Dinero object with amount 501\n```\n\n### Use scaled ratios and convert to the safest scale\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst ratios = [\n  { amount: 505, scale: 1 },\n  { amount: 495, scale: 1 },\n]; // translates to ratios 50.5 and 49.5\nconst d = dinero({ amount: 100, currency: USD });\n\nconst [d1, d2] = allocate(d, ratios);\n\nd1; // a Dinero object with amount 505 and scale 3\nd2; // a Dinero object with amount 495 and scale 3\n```\n"
  },
  {
    "path": "docs/api/mutations/multiply.md",
    "content": "---\ntitle: multiply\ndescription: Multiply a Dinero object.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# multiply\n\nMultiply a Dinero object.\n\nIf you need to multiply by a fractional multiplier, you shouldn't use floats, but scaled amounts instead. For example, instead of passing `2.1`, you should pass `{ amount: 21, scale: 1 }`. When using scaled amounts, the function converts the returned objects to the safest scale.\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `multiplicand` | `Dinero<TAmount, TCurrency>` | The Dinero object to multiply. | Yes |\n| `multiplier` | `DineroScaledAmount<TAmount> \\| TAmount` | The number to multiply with. | Yes |\n\n## Code examples\n\n### Multiply by an integer\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 400, currency: USD });\n\nmultiply(d, 4); // a Dinero object with amount 1600\n```\n\n### Multiply by a scaled multiplier\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 401, currency: USD });\n\nmultiply(d, { amount: 2001, scale: 3 }); // a Dinero object with amount 802401 and scale 5\n```\n"
  },
  {
    "path": "docs/api/mutations/subtract.md",
    "content": "---\ntitle: subtract\ndescription: Subtracting two Dinero objects.\nreturns: Dinero<TAmount, TCurrency>\n---\n\n# subtract\n\nSubtract two Dinero objects.\n\n**You can only subtract objects that share the same currency.** The function also normalizes objects to the same scale (the highest) before subtracting them.\n\nIn TypeScript, this is enforced at compile time when using [typed currencies](/guides/currency-type-safety).\n\n## Parameters\n\n| Name | Type | Description | Required |\n|------|------|-------------|----------|\n| `minuend` | `Dinero<TAmount, TCurrency>` | The Dinero object to subtract from. | Yes |\n| `subtrahend` | `Dinero<TAmount, TCurrency>` | The Dinero object to subtract. | Yes |\n\n## Code examples\n\n### Subtract objects\n\n```js\nimport { dinero, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 100, currency: USD });\n\nsubtract(d1, d2); // a Dinero object with amount 400\n```\n\n### Subtract objects with a different scale\n\n```js\nimport { dinero, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\nsubtract(d1, d2); // a Dinero object with amount 4000 and scale 3\n```\n\n### Subtract more than two objects\n\n```js\nimport { dinero, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 400, currency: USD });\nconst d2 = dinero({ amount: 200, currency: USD });\nconst d3 = dinero({ amount: 100, currency: USD });\n\nconst subtractMany = (subtrahends) => subtrahends.reduce(subtract);\n\nsubtractMany([d1, d2, d3]); // a Dinero object with amount 100\n```\n"
  },
  {
    "path": "docs/api/rounding/down.md",
    "content": "---\ntitle: down\ndescription: Divide and round towards negative infinity.\n---\n\n# down\n\nDivide and round towards negative infinity.\n\nThis rounding mode always rounds down, regardless of the fractional part. For positive numbers, it truncates the decimal (e.g., 1.1 becomes 1, 1.9 becomes 1). For negative numbers, it rounds away from zero (e.g., -1.1 becomes -2).\n\nThis is the default rounding mode used by [`transformScale`](/api/conversions/transform-scale).\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, down } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 401, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, down); // a Dinero object with amount 8421 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, down } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\ntransformScale(d, 2, down); // a Dinero object with amount 1045 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-away-from-zero.md",
    "content": "---\ntitle: halfAwayFromZero\ndescription: Divide and round half away from zero.\n---\n\n# halfAwayFromZero\n\nDivide and round towards the nearest neighbor, rounding away from zero when exactly halfway.\n\nThis rounding mode rounds to the nearest integer. When the value is exactly halfway between two integers, it rounds away from zero. Positive halfway values round up (e.g., 1.5 becomes 2), and negative halfway values round down (e.g., -1.5 becomes -2).\n\nThis is sometimes referred to as \"commercial rounding\" or \"arithmetic rounding.\"\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfAwayFromZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfAwayFromZero); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfAwayFromZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1055, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfAwayFromZero); // a Dinero object with amount 106 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-down.md",
    "content": "---\ntitle: halfDown\ndescription: Divide and round half towards negative infinity.\n---\n\n# halfDown\n\nDivide and round towards the nearest neighbor, rounding down when exactly halfway.\n\nThis rounding mode rounds to the nearest integer. When the value is exactly halfway between two integers (e.g., 1.5), it rounds down (towards negative infinity). For non-halfway values, it behaves the same as [`halfUp`](/api/rounding/half-up).\n\nFor example, 1.5 rounds to 1, 2.5 rounds to 2, and -1.5 rounds to -2.\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfDown } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfDown); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfDown } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1055, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfDown); // a Dinero object with amount 105 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-even.md",
    "content": "---\ntitle: halfEven\ndescription: Divide and round half to the nearest even integer.\n---\n\n# halfEven\n\nDivide and round towards the nearest neighbor, rounding to the nearest even integer when exactly halfway.\n\nThis rounding mode is also known as [bankers rounding](https://wiki.c2.com/?BankersRounding). It rounds to the nearest integer, and when the value is exactly halfway between two integers, it picks the even one. This reduces cumulative rounding bias in financial calculations.\n\nFor example, 1.5 rounds to 2, 2.5 rounds to 2, 3.5 rounds to 4, and -2.5 rounds to -2.\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfEven } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfEven); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfEven } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1050, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfEven); // a Dinero object with amount 104 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-odd.md",
    "content": "---\ntitle: halfOdd\ndescription: Divide and round half to the nearest odd integer.\n---\n\n# halfOdd\n\nDivide and round towards the nearest neighbor, rounding to the nearest odd integer when exactly halfway.\n\nThis rounding mode rounds to the nearest integer. When the value is exactly halfway between two integers, it picks the odd one. For non-halfway values, it behaves the same as [`halfUp`](/api/rounding/half-up).\n\nFor example, 1.5 rounds to 1, 2.5 rounds to 3, 3.5 rounds to 3, and -2.5 rounds to -3.\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfOdd } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfOdd); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfOdd } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1050, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfOdd); // a Dinero object with amount 105 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-towards-zero.md",
    "content": "---\ntitle: halfTowardsZero\ndescription: Divide and round half towards zero.\n---\n\n# halfTowardsZero\n\nDivide and round towards the nearest neighbor, rounding towards zero when exactly halfway.\n\nThis rounding mode rounds to the nearest integer. When the value is exactly halfway between two integers, it rounds towards zero. Positive halfway values round down (e.g., 1.5 becomes 1), and negative halfway values round up (e.g., -1.5 becomes -1).\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfTowardsZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfTowardsZero); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfTowardsZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1055, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfTowardsZero); // a Dinero object with amount 105 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/half-up.md",
    "content": "---\ntitle: halfUp\ndescription: Divide and round half towards positive infinity.\n---\n\n# halfUp\n\nDivide and round towards the nearest neighbor, rounding up when exactly halfway.\n\nThis rounding mode rounds to the nearest integer. When the value is exactly halfway between two integers (e.g., 1.5), it rounds up (towards positive infinity). This is the most commonly taught rounding method.\n\nFor example, 1.5 rounds to 2, 2.5 rounds to 3, and -1.5 rounds to -1.\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, halfUp } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 305, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, halfUp); // a Dinero object with amount 6405 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, halfUp } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1055, currency: USD, scale: 3 });\n\ntransformScale(d, 2, halfUp); // a Dinero object with amount 106 and scale 2\n```\n"
  },
  {
    "path": "docs/api/rounding/up.md",
    "content": "---\ntitle: up\ndescription: Divide and round towards positive infinity.\n---\n\n# up\n\nDivide and round towards positive infinity.\n\nThis rounding mode always rounds up, regardless of the fractional part. For positive numbers, any fractional value causes the result to increase (e.g., 1.1 becomes 2, 1.9 becomes 2). For negative numbers, it rounds towards zero (e.g., -1.9 becomes -1).\n\n## Usage\n\nPass this function as the last argument to [`multiply`](/api/mutations/multiply), [`allocate`](/api/mutations/allocate), or [`transformScale`](/api/conversions/transform-scale) to control how remainders are handled.\n\n## Code examples\n\n### Use with multiply\n\n```js\nimport { dinero, multiply, up } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 401, currency: USD });\n\nmultiply(d, { amount: 21, scale: 1 }, up); // a Dinero object with amount 8422 and scale 3\n```\n\n### Use with transformScale\n\n```js\nimport { dinero, transformScale, up } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\ntransformScale(d, 2, up); // a Dinero object with amount 1046 and scale 2\n```\n"
  },
  {
    "path": "docs/build-examples.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nDOCS_DIST=\"$(cd \"$(dirname \"$0\")/.vitepress/dist\" && pwd)\"\nEXAMPLES_DIR=\"$(cd \"$(dirname \"$0\")/../examples\" && pwd)\"\n\nfor example in cart-react cart-vue pricing-react expense-splitter invoice-builder portfolio-tracker; do\n  echo \"Building $example...\"\n  cd \"$EXAMPLES_DIR/$example\"\n  npm install\n  npm run build\n  mkdir -p \"$DOCS_DIST/examples/$example\"\n  cp -r dist/* \"$DOCS_DIST/examples/$example\"\ndone\n"
  },
  {
    "path": "docs/core-concepts/amount.md",
    "content": "---\ntitle: Amount\ndescription: Passing an amount to a new Dinero object.\n---\n\n# Amount\n\nThe amount is one of the three pieces of domain data necessary to create a Dinero object. It's expressed in the smallest subdivision of the currency, as an integer.\n\nFor example, 50 US dollars equals 5,000 cents.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 5000, currency: USD });\n```\n\nYou should always pass integers. The library throws whenever you try to pass a float or any non-integer value.\n\nDinero.js comes with a [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) implementation, but the library is generic. This means you can use it with any data type you want: [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), third-parties like [big.js](https://github.com/MikeMcl/big.js), etc. To do so, check the advanced guide on [precision and large numbers](/guides/precision-and-large-numbers).\n\n## No minor units\n\nWhen using a currency with no minor units, you should express the amount in major units.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { JPY } from 'dinero.js/currencies';\n\n// This represents 5,000 Japanese yens\nconst d = dinero({ amount: 5000, currency: JPY });\n```\n\nWhen working with currencies with no minor units, you need to set the [currency exponent](/core-concepts/currency#currency-exponent) to `0`.\n\n## Non-decimal currencies\n\nWhen using a non-decimal currency, you should express the amount in the smallest subdivision. If the currency has multiple subdivisions (such as the pre-decimal British pound sterling), you can specify them with an array.\n\n```js\nimport { dinero } from 'dinero.js';\n\n// Ancient Greek drachma\nconst GRD = {\n  code: 'GRD',\n  base: 6,\n  exponent: 1,\n};\n\n// This represents 1 ancient Greek drachma\n// or 6 obols\nconst d1 = dinero({ amount: 6, currency: GRD });\n\n// Pre-decimal Great Britain pound sterling\n// 20 shillings in a pound\n// 12 pence in a shilling\nconst GBP = {\n  code: 'GBP',\n  base: [20, 12],\n  exponent: 1,\n};\n\n// This represents 50 pre-decimal Great Britain pounds\n// or 1,000 shillings, or 12,000 pence\nconst d2 = dinero({ amount: 12000, currency: GBP });\n```\n\nWhen working with non-decimal currencies, you need to set the [currency exponent](/core-concepts/currency#currency-exponent) to `1`.\n\n**See also:** [Formatting non-decimal currencies](/guides/formatting-non-decimal-currencies)\n"
  },
  {
    "path": "docs/core-concepts/comparisons.md",
    "content": "---\ntitle: Comparisons\ndescription: Comparing Dinero objects for amount, currency, equality, sign, and more.\n---\n\n# Comparisons\n\nWithin the control flow of your application, you'll inevitably need to write conditional expressions to make decisions. The Dinero.js API provides functions to compare objects.\n\n```js\nimport { dinero, lessThan } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nlessThan(d1, d2);\n```\n\n## Comparing Dinero objects\n\nFor example, if you're building a shopping cart checkout page, you'll probably need to see if an amount is greater or lesser than another.\n\n```js\nimport { dinero, greaterThanOrEqual } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst total = dinero({ amount: 25000, currency: USD });\nconst freeShippingThreshold = dinero({ amount: 10000, currency: USD });\n\nconst hasFreeShipping = greaterThanOrEqual(total, freeShippingThreshold);\n```\n\nYou can also use comparison functions to control your user interface logic.\n\n```js\nimport React, { useState } from 'react';\nimport { dinero, isZero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nimport { format } from './utils';\n\nfunction Cart() {\n  const [products] = useState([\n    {\n      name: 'Apple AirPods Pro',\n      price: dinero({ amount: 17495, currency: USD }),\n    },\n    {\n      name: 'Apple Stickers',\n      price: dinero({ amount: 0, currency: USD }),\n    },\n  ]);\n\n  return (\n    <table>\n      <thead>\n        <tr>\n          <th>Name</th>\n          <th>Price</th>\n        </tr>\n      </thead>\n      <tbody>\n        {products.map(({ name, price }) => (\n          <tr key={name}>\n            <td>{name}</td>\n            <td>{isZero(price) ? 'Complimentary' : format(price)}</td>\n          </tr>\n        ))}\n      </tbody>\n    </table>\n  );\n}\n```\n"
  },
  {
    "path": "docs/core-concepts/currency.md",
    "content": "---\ntitle: Currency\ndescription: Passing a currency to a new Dinero object.\n---\n\n# Currency\n\nThe currency is one of the three pieces of domain data necessary to create a Dinero object.\n\nA Dinero currency is composed of:\n\n- A unique **code**.\n- A **base**, or radix.\n- An **exponent**.\n\n## Currency code\n\nThe currency code is a **unique identifier for the currency.** By convention, they're usually a three-letter or number. For example, in the case of national [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currencies, the first two letters of the code are the two letters of the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code, and the third is usually the initial of the currency itself.\n\n```js\n// United States dollar\nconst USD = {\n  code: 'USD',\n  // ...\n};\n```\n\nWhen they don't refer to a specific country (e.g., euro) or aren't a traditional currency (e.g., cryptocurrencies), the code can vary into a less standard but more mnemonic scheme. Ultimately, **the code is your choice and needs to make sense in your application.** The only requirement is for it to be unique.\n\n## Currency base\n\nThe currency base (or radix) is the **number of unique digits used to represent a currency's minor unit.** Most currencies in circulation are decimal, meaning their base is 10.\n\n```js\nconst USD = {\n  code: 'USD',\n  base: 10,\n  // ...\n};\n```\n\nThere are still non-decimal currencies in circulation, such as the [Mauritanian ouguiya](https://en.wikipedia.org/wiki/Mauritanian_ouguiya) and the [Malagasy ariary](https://en.wikipedia.org/wiki/Malagasy_ariary).\n\n```js\n// Mauritanian ouguiya\nconst MRU = {\n  code: 'MRU',\n  base: 5,\n  // ...\n};\n```\n\nSome currencies have multiple subdivisions. For example, before [decimalization](https://en.wikipedia.org/wiki/Decimalisation), the British pound sterling was divided into 20 shillings, and each shilling into 12 pence. You also have examples in fiction, like Harry Potter, where one Galleon is divided into 17 Sickles, and each Sickle into 29 Knuts.\n\nTo represent these currencies, you can specify each subdivision with an array.\n\n```js\n// Pre-decimal Great Britain pound sterling\nconst GBP = {\n  code: 'GBP',\n  base: [20, 12],\n  exponent: 1,\n};\n\n// Great Britain wizarding currency (Harry Potter universe)\nconst GBW = {\n  code: 'GBW',\n  base: [17, 29],\n  exponent: 1,\n};\n```\n\n::: info\nWhen working with non-decimal currencies, you should set the exponent to `1`.\n:::\n\n## Currency exponent\n\nThe currency exponent expresses the **decimal relationship between the currency and its minor unit.** For example, there are 100 cents in a US dollar, being 10 to the power of 2, so the exponent for the US dollar is 2.\n\n```js\nconst USD = {\n  code: 'USD',\n  base: 10,\n  exponent: 2,\n};\n```\n\nAn easier way to think about it is as the number of digits after the decimal separator.\n\nWhen a currency doesn't have minor currency units (e.g., the Japanese yen), the exponent should be 0. In this case, you can express the [amount](/core-concepts/amount) in major currency units.\n\n```js\n// Japanese yen\nconst JPY = {\n  code: 'JPY',\n  base: 10,\n  exponent: 0,\n};\n```\n\nWhen you pass a [scale](/core-concepts/scale) to a Dinero object, it overrides the exponent. This has an impact on how you should specify the [amount](/core-concepts/amount).\n\n## Using built-in currencies\n\nDinero.js provides ISO 4217 currency objects out of the box via the `dinero.js/currencies` subpath export.\n\nOnce you've [installed Dinero.js](/getting-started/quick-start#install-the-library), you can import currencies:\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD });\nconst d2 = dinero({ amount: 1000, currency: EUR });\n```\n\nIf you're using the [bigint variant](/guides/precision-and-large-numbers#using-dinero-with-bigint), import currencies from `dinero.js/bigint/currencies` instead:\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { USD, EUR } from 'dinero.js/bigint/currencies';\n\nconst d1 = dinero({ amount: 1000n, currency: USD });\nconst d2 = dinero({ amount: 1000n, currency: EUR });\n```\n\nDinero.js tracks the [ISO 4217 standard](https://www.six-group.com/en/products-services/financial-information/data-standards.html) and updates currencies when amendments are published. This means currencies can be added, removed, or have their properties changed (e.g., exponent, code) when you upgrade Dinero.js.\n\n::: warning\n**Currency data may change between Dinero.js versions.** If you need stability, pin your Dinero.js version in your package manager. You can also [define your own currency objects](#creating-custom-currencies) for full control. If you look up currencies by code at runtime, always validate that the code exists. See [How to look up a currency by code](/faq/how-to-look-up-a-currency-by-code).\n:::\n\n## Creating custom currencies\n\nYou can build your own currency object if it isn't available in `dinero.js/currencies`.\n\n```js\nconst FRF = {\n  code: 'FRF',\n  base: 10,\n  exponent: 2,\n};\n```\n\nIf you're a TypeScript user, you can implement the `DineroCurrency` type. It takes a generic parameter `TAmount` which represents the type you're using for numeric values (`number` by default).\n\n```ts\nimport type { DineroCurrency } from 'dinero.js';\n\nconst FRF: DineroCurrency<number> = {\n  code: 'FRF',\n  base: 10,\n  exponent: 2,\n};\n```\n\nTo opt into [compile-time currency safety](/guides/currency-type-safety), use `as const satisfies` to preserve the literal type of the currency code:\n\n```ts\nimport type { DineroCurrency } from 'dinero.js';\n\nconst FRF = {\n  code: 'FRF',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'FRF'>;\n```\n\nThis lets TypeScript catch currency mismatches (e.g., adding USD and EUR) at compile time. The built-in ISO 4217 currencies are already typed this way.\n\n**See also:** [Currency type safety](/guides/currency-type-safety), [Precision and large numbers](/guides/precision-and-large-numbers)\n"
  },
  {
    "path": "docs/core-concepts/formatting.md",
    "content": "---\ntitle: Formatting\ndescription: Formatting Dinero objects into rounded numbers or string representation.\n---\n\n# Formatting\n\nWhen working with money on the front end, there comes a time when you need to display amounts on the user interface. **The Dinero.js API provides functions to format Dinero objects.**\n\n```js\nimport { dinero, toUnits, down } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1055, currency: USD });\n\ntoUnits(d); // [10, 55]\n```\n\n## Displaying an object\n\nThe [`toDecimal`](/api/formatting/to-decimal) function exposes a pre-formatted amount in decimal format and the object's `currency`. It lets you display objects the way you want using a transformer function.\n\n```js\nimport { dinero, toDecimal, toUnits } from 'dinero.js';\nimport { USD, MGA } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD });\nconst d2 = dinero({ amount: 13, currency: MGA });\n\ntoDecimal(d1, ({ value, currency }) => `${currency.code} ${value}`); // \"USD 50.00\"\ntoUnits(d1, ({ value }) => `${value[0]} dollars, ${value[1]} cents`); // \"50 dollars, 0 cents\"\n\ntoUnits(d2, ({ value }) => `${value[0]} ariary, ${value[1]} iraimbilanja`); // \"2 ariary, 3 iraimbilanja\"\n```\n\nDinero.js uses the object's scale to determine how many decimal places to represent. You can adjust it in the `transformer`.\n\n```js\nimport { dinero, toDecimal, up } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst transformer = ({ value, currency }) => {\n  return `${currency.code} ${Number(value).toFixed(1)}`;\n};\n\nconst d = dinero({ amount: 4545, currency: USD });\n\ntoDecimal(d, transformer); // \"USD 45.5\"\n```\n\nIf you're formatting many objects, you might want to reuse the same transformer without having to pass it every time. To do so, you can write your own higher-order function to build formatters.\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// This function lets you pass a transformer and rounding options.\n// It returns a function that takes a Dinero object and applies\n// the closured transformer.\nfunction createFormatter(transformer) {\n  return function formatter(dineroObject) {\n    return toDecimal(dineroObject, transformer);\n  };\n}\n\n// This function is reusable to format any Dinero object\n// with the same transformer.\nconst format = createFormatter(\n  ({ value, currency }) => `${currency.code} ${value}`\n);\n\nconst d = dinero({ amount: 5000, currency: USD });\n\nformat(d); // \"USD 50.00\"\n```\n\n**See also:**\n- [To decimal](/api/formatting/to-decimal)\n- [To units](/api/formatting/to-units)\n- [Formatting in a multilingual site](/guides/formatting-in-a-multilingual-site)\n- [Formatting non-decimal currencies](/guides/formatting-non-decimal-currencies)\n\n## Retrieving raw data\n\nOne of the most convenient formatting functions in Dinero.js is [`toSnapshot`](/api/formatting/to-snapshot). Its primary usage isn't the front end but to take snapshots of Dinero objects to inspect them.\n\nWhenever you need to access a Dinero object's raw data, [`toSnapshot`](/api/formatting/to-snapshot) is the go-to function.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 5000, currency: USD });\n\ntoSnapshot(d);\n\n// {\n//   amount: 5000,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 2,\n// }\n```\n\nAnother useful usage of [`toSnapshot`](/api/formatting/to-snapshot) is transport and storage. To do so, check the advanced guide on [transporting and restoring](/guides/transporting-and-restoring).\n"
  },
  {
    "path": "docs/core-concepts/mutations.md",
    "content": "---\ntitle: Mutations\ndescription: Mutating Dinero objects through mathematical operations.\n---\n\n# Mutations\n\nAt the core of manipulating money are mutations. The Dinero.js API provides functions to manipulate objects. Most of them are arithmetic-based: adding, multiplying, etc.\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nadd(d1, d2);\n```\n\n## Calculating new amounts\n\nAny application that handles money needs to manipulate them. A classic example is a checkout page where you need to calculate the total, add shipping, subtract discounts, etc.\n\n```js\nimport { dinero, add, allocate, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst products = [\n  {\n    name: 'Apple iPhone 12',\n    price: dinero({ amount: 89900, currency: USD }),\n  },\n  {\n    name: 'Apple AirPods Pro',\n    price: dinero({ amount: 17495, currency: USD }),\n  },\n];\n\nconst subtotal = products.reduce(\n  (acc, { price }) => add(acc, price),\n  dinero({ amount: 0, currency: USD })\n);\n\nconst [discount] = allocate(subtotal, [20, 80]);\nconst discounted = subtract(subtotal, discount);\n\nconst shipping = dinero({ amount: 1000, currency: USD });\n\nconst total = add(discounted, shipping);\n```\n\n## Dinero objects are immutable\n\nEven though such functions can be categorized as \"mutations\", **Dinero objects are immutable.** When you're using a mutation function, the existing objects remain intact.\n\n```js\nimport { dinero, add, toSnapshot } from 'dinero.js';\n\n// ...\n\ntoSnapshot(add(d1, d2));\n\n// {\n//   amount: 1300,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 2,\n// }\n\ntoSnapshot(d1);\n\n// {\n//   amount: 500,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 2,\n// }\n\ntoSnapshot(d2);\n\n// {\n//   amount: 800,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 2,\n// }\n```\n"
  },
  {
    "path": "docs/core-concepts/scale.md",
    "content": "---\ntitle: Scale\ndescription: Passing a scale to a new Dinero object.\n---\n\n# Scale\n\nThe scale is one of the three pieces of domain data necessary to create a Dinero object. It's conceptually close to the [currency exponent](/core-concepts/currency#currency-exponent) but serves the purpose of expressing precision as accurately as possible.\n\nMost of the time, you don't need to specify the scale. It defaults to the currency exponent.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 5000, currency: USD });\n\nconst { exponent } = USD; // `exponent` is 2\nconst { scale } = toSnapshot(d); // `scale` is 2 (picked up `USD.exponent`)\n```\n\nThis looks redundant. Why do we need a scale when we have the currency exponent?\n\nWhile you may think of money as its value in major or minor currency units—value that one can actually *pay*—it often needs a more precise representation. A good example is when you factor in tax rates, which are often fractional values. For example, let's say you have an item that costs EUR 19.95 with a VAT rate of 5.5%: you end up with a final price of EUR 21.04725. This gets rounded when it's time to pay, but **it's crucial to preserve the precision until the end of calculations**, especially if you're performing many of them.\n\nThe scale is essential to **accurately represent monetary values without losing precision.** It automatically adapts as needed to ensure you always retain accurate amounts.\n\nHere's what the 5.5% VAT rate calculation looks like with Dinero.js:\n\n```js\nimport { dinero, add, multiply, toSnapshot } from 'dinero.js';\nimport { EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1995, currency: EUR });\nconst tax = multiply(price, { amount: 55, scale: 3 });\nconst total = add(price, tax);\n\ntoSnapshot(total);\n\n// {\n//   amount: 2104725,\n//   currency: {\n//     code: 'EUR',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 5,\n// }\n```\n\nThe final Dinero object `total` has transparently adjusted to a `scale` of 5 to satisfy the need for extra precision. The amount of 2104725 can be interpreted as 21.04725 based on that scale instead of 21047.25 based on the currency exponent.\n\nNote the usage of a scale when specifying the number by which to multiply. We want to calculate 5.5% of the price, or multiply it by 0.055 (5.5 / 100). We don't want to use floats, so instead, we're passing 55 with a scale of 3 (55 / 10^3 = 0.055).\n\n## When to specify a scale manually\n\nMost of the time, you don't need to specify the scale. You can let Dinero.js pick it up from the currency exponent. However, **there are times when you need to specify more precise amounts.**\n\nFor example, imagine you're in the hardware business. You're likely not selling screws one by one but by kits instead. Let's say you sell kits of 250 for $8.75; you still might need to represent the price of a single screw for admin purposes. In this case, a screw costs $0.035, which can't be accurately represented with two digits, so you can manually pass a larger `scale`.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 35, currency: USD, scale: 3 });\n\ntoSnapshot(price);\n\n// {\n//   amount: 35,\n//   currency: {\n//     code: 'USD',\n//     base: 10,\n//     exponent: 2,\n//   },\n//   scale: 3,\n// }\n```\n\n::: tip\nWhen [storing Dinero objects in a database](/guides/storing-in-a-database), you typically only need to store the currency exponent. If you're working with custom scales, make sure to store the scale as well so you can accurately restore the object later.\n:::\n\n## Calculate objects of different scales\n\nWhen calculating Dinero objects, you don't have to care about their scale. Dinero.js automatically converts objects to the safest scale so you don't lose precision.\n\n```js\nimport { dinero, add, subtract, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 400, currency: USD });\nconst d2 = dinero({ amount: 104545, currency: USD, scale: 4 });\n\nadd(d1, d2); // a Dinero object with amount 144545 and scale 4\n\nsubtract(d2, d1); // a Dinero object with amount 64545 and scale 4\n```\n\nYou might also need to use fractional values. For example, you may need to calculate 19.6% of a total cart, or convert a Dinero object to another currency with a 0.82 conversion rate.\n\nIn such cases, you shouldn't use floats, but scaled multipliers. For example, instead of 0.82, you should pass 82 and a scale of 2.\n\n```js\nimport { dinero, multiply, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 400, currency: USD });\n\nmultiply(d, { amount: 82,  scale: 2 }); // a Dinero object with amount 32800 and scale 4\n\nconst [d1, d2] = allocate(d1, [505, 495], { scale: 1 }); // translates to ratios 50.5 and 49.5\n\nd1; // a Dinero object with amount 2020 and scale 3\nd2; // a Dinero object with amount 1980 and scale 3\n```\n\n## Trimming scale\n\nWhen calculating Dinero objects of different scales, Dinero.js goes for the safest one to avoid losing precision. This means you can end up with a higher scale than necessary.\n\nIn the previous example, both output objects have a trailing zero, meaning they could be adjusted to one order of magnitude down with a smaller scale. While using high scales isn't a problem per se, it does take more space, and can end up reaching the [minimum or maximum safe IEEE 754 integer](https://en.wikipedia.org/wiki/IEEE_754).\n\nIn such cases, you can trim down Dinero objects to drop useless precision.\n\n```js\nimport { dinero, add, trimScale } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 100, currency: USD });\nconst d2 = dinero({ amount: 2000000, currency: USD, scale: 6 });\n\nconst d3 = add(d1, d2); // a Dinero object with amount 3000000 and scale 6\n\ntrimScale(d3); // a Dinero object with amount 300 and scale 2\n```\n\n::: info\nThe [`trimScale`](/api/conversions/trim-scale) function trims Dinero objects down to the smallest, safest possible scale, down to the currency exponent at most.\n:::\n"
  },
  {
    "path": "docs/demos.md",
    "content": "---\ntitle: Demos\ndescription: Check out Dinero.js in action in working demos.\n---\n\n<style>\n.demo-entry {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 24px;\n  align-items: start;\n  margin-top: 16px;\n}\n\n.demo-entry .demo-text p {\n  margin: 0 0 12px;\n}\n\n.demo-entry .demo-image a {\n  display: block;\n  border-radius: 8px;\n  overflow: hidden;\n  border: 1px solid var(--vp-c-border);\n  transition: border-color 160ms ease;\n}\n\n.demo-entry .demo-image a:hover {\n  border-color: var(--vp-c-brand-1);\n}\n\n.demo-entry .demo-image img {\n  display: block;\n  width: 100%;\n  margin: 0;\n}\n\n@media (max-width: 640px) {\n  .demo-entry {\n    grid-template-columns: 1fr;\n  }\n}\n</style>\n\n# Demos\n\nIf you learn better by seeing code in action, or want to start from an existing project, check out the Dinero.js demos.\n\n## Shopping cart with React\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>A <a href=\"https://reactjs.org/\">React</a>-powered shopping cart that calculates line totals with <code>multiply</code>, aggregates a subtotal with <code>add</code>, extracts VAT with <code>allocate</code>, and lets you switch between USD and EUR with <code>convert</code>.</p>\n    <p><a href=\"https://dinerojs.com/examples/cart-react/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/cart-react/\">\n      <img src=\"/images/examples/cart-react.png\" alt=\"Shopping cart with React\">\n    </a>\n  </div>\n</div>\n\n## Shopping cart with Vue.js\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>The same shopping cart, built with <a href=\"https://vuejs.org/\">Vue.js</a>. Uses computed properties to recalculate line totals, tax, and currency conversions reactively as you update quantities or switch currencies.</p>\n    <p><a href=\"https://dinerojs.com/examples/cart-vue/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/cart-vue/\">\n      <img src=\"/images/examples/cart-vue.png\" alt=\"Shopping cart with Vue.js\">\n    </a>\n  </div>\n</div>\n\n## Pricing page with React\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>A <a href=\"https://reactjs.org/\">React</a>-powered SaaS pricing page with per-seat pricing. Uses <code>multiply</code> for seat-based totals, <code>allocate</code> to calculate annual discounts, and <code>hasSubUnits</code> to hide unnecessary decimals.</p>\n    <p><a href=\"https://dinerojs.com/examples/pricing-react/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/pricing-react/\">\n      <img src=\"/images/examples/pricing-react.png\" alt=\"Pricing page with React\">\n    </a>\n  </div>\n</div>\n\n## Expense splitter\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>An expense splitter. Uses <code>allocate</code> for fair bill splitting, <code>add</code> and <code>subtract</code> for balance tracking, and <code>toDecimal</code> for locale-aware formatting. Supports equal and percentage-based splits with optimal debt settlement.</p>\n    <p><a href=\"https://dinerojs.com/examples/expense-splitter/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/expense-splitter/\">\n      <img src=\"/images/examples/expense-splitter.png\" alt=\"Expense splitter\">\n    </a>\n  </div>\n</div>\n\n## Invoice builder\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>An invoice builder with live preview. Uses <code>multiply</code> for line totals, <code>add</code> for subtotals, <code>allocate</code> for discounts and tax, and <code>toDecimal</code> for locale-aware currency formatting across multiple currencies.</p>\n    <p><a href=\"https://dinerojs.com/examples/invoice-builder/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/invoice-builder/\">\n      <img src=\"/images/examples/invoice-builder.png\" alt=\"Invoice builder\">\n    </a>\n  </div>\n</div>\n\n## Portfolio tracker\n\n<div class=\"demo-entry\">\n  <div class=\"demo-text\">\n    <p>A multi-currency portfolio tracker. Uses <code>multiply</code> with scaled amounts for holding values, <code>convert</code> for cross-currency aggregation, <code>add</code> for portfolio totals, and <code>toDecimal</code> for locale-aware formatting across multiple currencies.</p>\n    <p><a href=\"https://dinerojs.com/examples/portfolio-tracker/\">View demo</a></p>\n  </div>\n  <div class=\"demo-image\">\n    <a href=\"https://dinerojs.com/examples/portfolio-tracker/\">\n      <img src=\"/images/examples/portfolio-tracker.png\" alt=\"Portfolio tracker\">\n    </a>\n  </div>\n</div>\n"
  },
  {
    "path": "docs/faq/can-i-multiply-by-a-decimal.md",
    "content": "---\ntitle: Can I multiply by a decimal?\ndescription: How to multiply Dinero objects by decimal values like 0.5 using scaled amounts.\n---\n\n# Can I multiply by a decimal?\n\nYou can pass a decimal like `0.5` to [`multiply`](/api/mutations/multiply), but **it will throw an error if the result isn't an integer.**\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 1000, currency: USD }); // $10.00\nmultiply(d1, 0.5); // Works (1000 * 0.5 = 500)\n\nconst d2 = dinero({ amount: 1001, currency: USD }); // $10.01\nmultiply(d2, 0.5); // Throws (1001 * 0.5 = 500.5)\n```\n\nDinero.js uses integer arithmetic to avoid floating-point precision issues. When you multiply by a decimal and the result isn't an integer, validation fails.\n\n## Using scaled amounts\n\nThe safe way to multiply by non-integers is to use a scaled amount. Instead of `0.5`, pass `{ amount: 5, scale: 1 }` (5 at scale 1 = 5/10 = 0.5):\n\n```js\nimport { dinero, multiply, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1001, currency: USD }); // $10.01\n\nconst result = multiply(d, { amount: 5, scale: 1 }); // $5.005\n\ntoSnapshot(result); // { amount: 5005, currency: USD, scale: 3 }\n```\n\n**The result's scale increases to preserve precision.** This keeps all values as integers throughout the calculation.\n\n::: tip\nFor percentages, the pattern is the same. To calculate 15% of an amount:\n\n```js\nmultiply(d, { amount: 15, scale: 2 }); // 15 at scale 2 = 0.15\n```\n:::\n\nSee the [Calculating percentages](/guides/calculating-percentages) guide for more examples.\n"
  },
  {
    "path": "docs/faq/how-to-look-up-a-currency-by-code.md",
    "content": "---\ntitle: How to look up a currency by code\ndescription: How to retrieve a currency object from a string code like \"USD\".\n---\n\n# How to look up a currency by code\n\nIf you receive currency codes as strings (e.g., from an API or database), you can look up the corresponding currency object using a namespace import.\n\n```ts\nimport * as currencies from 'dinero.js/currencies';\n\nfunction getCurrency(code: string) {\n  if (!(code in currencies)) {\n    throw new Error(`Unknown currency code: ${code}`);\n  }\n\n  return currencies[code as keyof typeof currencies];\n}\n```\n\nSince Dinero.js tracks the latest ISO 4217 standard, currency codes can be added or removed between versions. Always validate codes at runtime, especially if they come from stored data. See [Using built-in currencies](/core-concepts/currency#using-built-in-currencies) for more details.\n\n```ts\nimport { dinero } from 'dinero.js';\n\nconst code = 'USD';\nconst currency = getCurrency(code);\nconst price = dinero({ amount: 5000, currency });\n```\n\nThis also works with bigint currencies:\n\n```ts\nimport * as currencies from 'dinero.js/bigint/currencies';\n\nfunction getCurrency(code: string) {\n  if (!(code in currencies)) {\n    throw new Error(`Unknown currency code: ${code}`);\n  }\n\n  return currencies[code as keyof typeof currencies];\n}\n```\n"
  },
  {
    "path": "docs/faq/why-cant-i-use-currencies-with-bigint.md",
    "content": "---\ntitle: Why can't I use currencies with bigint?\ndescription: Why currencies from dinero.js/currencies don't work with bigint and how to use dinero.js/bigint/currencies instead.\n---\n\n# Why can't I use currencies with bigint?\n\nCurrencies from `dinero.js/currencies` have `number` values for `base` and `exponent`:\n\n```js\n// From dinero.js/currencies\nconst USD = { code: 'USD', base: 10, exponent: 2 };\n```\n\nWhen using `dinero.js/bigint`, **all arithmetic operations use bigint math.** JavaScript doesn't allow mixing `number` and `bigint` in operations—it throws a `TypeError`:\n\n```js\n10n + 2  // TypeError: can't convert BigInt to number\n```\n\nThis is a JavaScript language constraint. To use the bigint variant, import currencies from `dinero.js/bigint/currencies` instead:\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst d = dinero({ amount: 500n, currency: USD });\n```\n\nThese currencies have bigint values for `base` and `exponent`:\n\n```js\n// From dinero.js/bigint/currencies\nconst USD = { code: 'USD', base: 10n, exponent: 2n };\n```\n\nSee the [Precision and large numbers](/guides/precision-and-large-numbers) guide for more on when to use bigint.\n"
  },
  {
    "path": "docs/faq/why-functions-instead-of-methods.md",
    "content": "---\ntitle: Why functions instead of methods?\ndescription: Why Dinero.js uses standalone functions instead of chainable methods.\n---\n\n# Why functions instead of methods?\n\nDinero.js uses standalone functions:\n\n```js\nadd(d1, d2);\n```\n\nInstead of chainable methods:\n\n```js\nd1.add(d2);\n```\n\n**The primary reason is modularity.** With standalone functions, bundlers can eliminate unused code. With methods, every Dinero object would carry every operation on its prototype, including the ones you never use.\n\n## Composition\n\nStandalone functions compose well with functional utilities:\n\n```js\nimport { pipe } from 'ramda';\n\npipe(\n  (d) => multiply(d, 2),\n  (d) => add(d, fee),\n  (d) => toDecimal(d),\n)(price);\n```\n\n## Custom chaining\n\nNesting can get verbose and hard to understand when inlining:\n\n```js\n// Multiplies, then adds, but looks like the opposite\nadd(multiply(d1, 2), d2);\n```\n\nIf you prefer chaining, you can create your own wrapper:\n\n```js\nfunction chain(d) {\n  return {\n    multiply: (n) => chain(multiply(d, n)),\n    add: (other) => chain(add(d, other)),\n  };\n}\n\nchain(d1).multiply(2).add(d2);\n```\n\nFor most use cases, the functional style works well and keeps your bundle small.\n"
  },
  {
    "path": "docs/faq/why-no-currency-formatting.md",
    "content": "---\ntitle: Why doesn't Dinero format with currency symbols?\ndescription: Why Dinero.js doesn't format amounts with currency symbols and how to do it yourself.\n---\n\n# Why doesn't Dinero format with currency symbols?\n\nThe [`toDecimal`](/api/formatting/to-decimal) function returns a plain decimal string like `\"10.50\"`, not `\"$10.50\"` or `\"10,50 €\"`. **Dinero.js delegates locale-aware formatting to you** because there's no universal default that works for everyone.\n\nCurrency formatting varies significantly across locales:\n\n- `en-US`: $10.50\n- `fr-FR`: 10,50 $US\n- `fr-CA`: 10,50 $ US\n- `de-DE`: 10,50 $\n\nEven within the same locale, preferences vary: some users prefer `USD 10.50` over `$10.50`, some applications need `10.50 USD` for data exports. The library can't make these decisions for you.\n\n## Formatting with Intl.NumberFormat\n\nUse [`toDecimal`](/api/formatting/to-decimal) to get the numeric value, then format with [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat):\n\n```ts\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1050, currency: USD });\n\ntoDecimal(d, ({ value, currency }) => {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: currency.code,\n  }).format(value);\n}); // \"$10.50\"\n```\n\nThis lets you choose the locale and currency display style. For more control, you can build your own formatting logic on top of [`toDecimal`](/api/formatting/to-decimal).\n\nSee the [Multilingual support](/guides/formatting-in-a-multilingual-site) guide for reusable formatting patterns.\n"
  },
  {
    "path": "docs/getting-started/compatibility.md",
    "content": "---\ntitle: Compatibility\ndescription: Understand which browsers and Node.js versions Dinero.js supports and how to manage polyfills.\n---\n\n# Compatibility\n\n## Browser support\n\nDinero.js supports all modern browsers (Chrome, Edge, Firefox, and Safari).\n\n::: info\nInternet Explorer is no longer supported. Microsoft ended IE11 support in June 2022.\n:::\n\n## Node.js\n\nDinero.js runs on any [active or maintenance LTS version](https://nodejs.org/about/releases/) of Node.js.\n"
  },
  {
    "path": "docs/getting-started/optimizing-for-production.md",
    "content": "---\ntitle: Optimizing for production\ndescription: Tips for reducing bundle size and improving performance.\n---\n\n# Optimizing for production\n\n## Tree-shake your code\n\n**Tree-shaking lets you bundle only the code you're using and eliminate the rest.** For example, if you're only using Dinero.js to add and subtract monetary values, only `dinero`, [`add`](/api/mutations/add), [`subtract`](/api/mutations/subtract), and their dependencies should be in your final bundle.\n\nDinero.js is a pure library, meaning it doesn't produce side-effects. If you're using a modern build system, you can tree-shake it. To do so, make sure to import only the functions you need, and enable tree-shaking in your bundler configuration if necessary.\n\n```js\nimport { dinero, add, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// Only these functions end up in your bundle\n```\n\n**Resources:**\n- [Tree Shaking](https://webpack.js.org/guides/tree-shaking/)\n- [Scope Hoisting](https://v2.parceljs.org/features/scope-hoisting/)\n- [Building for Production](https://vitejs.dev/guide/build.html)\n\n## Compress your assets\n\nIf you're using the UMD build without a bundler, **make sure to compress it before you serve it in production.**\n\nIf you're importing Dinero.js via a CDN such as [jsDelivr](https://www.jsdelivr.com/) or [cdnjs](https://cdnjs.com/), you should get Gzip or Brotli compression out of the box. If you're hosting your own, make sure to use the production build and to compress it either manually or using an edge server like [Cloudflare](https://www.cloudflare.com/cdn) or [Cloudfront](https://aws.amazon.com/cloudfront/).\n\n**Resources:**\n- [Content delivery network](https://wikipedia.org/wiki/Content_delivery_network)\n- [Gzip](https://gnu.org/software/gzip/)\n- [Brotli](https://github.com/google/brotli)\n"
  },
  {
    "path": "docs/getting-started/quick-start.md",
    "content": "---\ntitle: Quick start\ndescription: Learn how to get Dinero.js up and running in your project.\n---\n\n# Quick start\n\n## Install the library\n\nTo get started, you need to install the `dinero.js` package.\n\n```sh\nnpm install dinero.js\n\n# or\n\nyarn add dinero.js\n```\n\nThen import it in your project:\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n```\n\nIf you don't use a package manager, you can use the HTML `script` element:\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/dinero.js/dist/umd/index.production.js\"></script>\n<script>\n  const { dinero, add, USD } = window.dinerojs;\n</script>\n```\n\n## First steps\n\n**Dinero.js lets you express monetary values in JavaScript.** You can perform mutations, conversions, comparisons, format them extensively, and overall make money manipulation in your application easier and safer.\n\nTo get started, you need to create a new Dinero object. Amounts are specified in minor currency units (like \"cents\" for the dollar) and currencies in `DineroCurrency` objects.\n\nThis represents $50:\n\n```js\nconst price = dinero({ amount: 5000, currency: USD });\n```\n\nYou can add or subtract any amount you want, by passing it another Dinero object:\n\n```js\nimport { dinero, add, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 5000, currency: USD });\n\n// returns a Dinero object with amount 6000\nadd(price, dinero({ amount: 1000, currency: USD }));\n\n// returns a Dinero object with amount 4000\nsubtract(price, dinero({ amount: 1000, currency: USD }));\n```\n\nDinero objects are immutable, meaning you always get a new Dinero object when performing transformations. Your original objects remain untouched.\n\n```js\nprice; // still returns a Dinero object with amount 5000\n```\n\nYou can ask all kinds of questions to your Dinero objects.\n\n```js\nimport { dinero, equal, isZero, hasSubUnits } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 500, currency: USD });\nequal(d1, d2); // returns true\n\nconst d3 = dinero({ amount: 100, currency: USD });\nisZero(d3); // returns false\n\nconst d4 = dinero({ amount: 1150, currency: USD });\nhasSubUnits(d4); // returns true\n```\n\nDinero.js provides [formatting functions](/core-concepts/formatting) that expose a pre-formatted amount. You can use them as-is, or pass a custom transformer function to further customize the output.\n\n```js\nimport { dinero, toDecimal, toUnits } from 'dinero.js';\nimport { USD, MGA } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 5000, currency: USD });\nconst d2 = dinero({ amount: 13, currency: MGA });\n\ntoDecimal(d1); // \"50.00\"\ntoDecimal(d1, ({ value, currency }) => `${currency.code} ${value}`); // \"USD 50.00\"\ntoUnits(d2, ({ value }) => `${value[0]} ariary, ${value[1]} iraimbilanja`); // \"2 ariary, 3 iraimbilanja\"\n```\n\nDinero objects pick up their scale from their currency exponent. If you want to represent amounts differently, you can specify a scale manually.\n\nThis represents $5:\n\n```js\nconst price = dinero({ amount: 5000, currency: USD, scale: 3 });\n```\n\nThis is only a preview of what you can do. Dinero.js provides extensive documentation with examples and guides.\n\n## Available exports\n\nDinero.js provides four entry points:\n\n| Import path | Description |\n|-------------|-------------|\n| `dinero.js` | Core functions with `number` amounts (default) |\n| `dinero.js/currencies` | ISO 4217 currency definitions for `number` |\n| `dinero.js/bigint/currencies` | ISO 4217 currency definitions for `bigint` |\n| `dinero.js/bigint` | Core functions with `bigint` amounts |\n\n```js\n// Standard usage\nimport { dinero, add, subtract } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\n// For large amounts (bigint)\nimport { dinero } from 'dinero.js/bigint';\nimport { USD, EUR } from 'dinero.js/bigint/currencies';\n```\n\n::: info\nDinero.js requires Node.js 14+ with ES modules. Use `import`, not `require()`.\n:::\n\n## Agent skills\n\nIf you use an AI coding agent (Claude Code, Cursor, GitHub Copilot, etc.), you can install the [Dinero.js skills](/agent-skills) to teach it best practices and common pitfalls.\n\n```sh\nnpx skills add dinerojs/skills\n```\n"
  },
  {
    "path": "docs/getting-started/upgrade-guide.md",
    "content": "---\ntitle: Upgrade guide\ndescription: Upgrading from Dinero.js v1.x to v2.0.\n---\n\n# Upgrade guide\n\n## Migrating from v2 alpha\n\nIf you're using Dinero.js v2 alpha with the separate `@dinero.js/*` packages, you need to update your imports to use the new consolidated package structure.\n\n### Update currency imports\n\n```diff\n- import { USD, EUR } from '@dinero.js/currencies';\n+ import { USD, EUR } from 'dinero.js/currencies';\n```\n\n### Update bigint imports\n\n```diff\n- import { calculator } from '@dinero.js/calculator-bigint';\n- import { createDinero } from '@dinero.js/core';\n+ import { calculator, createDinero } from 'dinero.js/bigint';\n```\n\nOr simply use the pre-configured `dinero` function:\n\n```js\nimport { dinero } from 'dinero.js/bigint';\n\nconst d = dinero({ amount: 1000n, currency: USD });\n```\n\n### Remove deprecated packages\n\nYou can remove the separate packages from your dependencies:\n\n```diff\n  \"dependencies\": {\n-   \"@dinero.js/core\": \"...\",\n-   \"@dinero.js/currencies\": \"...\",\n-   \"@dinero.js/calculator-number\": \"...\",\n-   \"@dinero.js/calculator-bigint\": \"...\",\n    \"dinero.js\": \"...\"\n  }\n```\n\nThe `dinero.js` package now includes everything.\n\n## Upgrading from v1.x\n\n### The Dinero function is now lowercase\n\nThe Dinero function is not a constructor, so by convention, it shouldn't be capitalized. The function is now `dinero` so there's no confusion on whether you should call it with `new` or not (you shouldn't). No longer need to disable ESLint's [`new-cap` rule](https://eslint.org/docs/rules/new-cap).\n\n```diff\n- import Dinero from 'dinero.js';\n+ import { dinero } from 'dinero.js';\n```\n\n### Currency is now an object\n\nThe `currency` is now expressed as a currency object and no longer as a string. v2 provides ISO 4217 currency objects out of the box via the `dinero.js/currencies` subpath.\n\n```diff\n- Dinero({ amount: 500, currency: 'USD' });\n+ import { USD } from 'dinero.js/currencies';\n+ dinero({ amount: 500, currency: USD });\n```\n\n**See also:** [Currency](/core-concepts/currency)\n\n### Precision is now scale\n\nThe concept of `precision` from v1.x is now called `scale`. It works the same as before.\n\n```diff\n- Dinero({ amount: 5000, currency: 'USD', precision: 3 });\n+ dinero({ amount: 5000, currency: USD, scale: 3 });\n```\n\n**See also:** [Scale](/core-concepts/scale)\n\n### Replace chainable methods with standalone functions\n\nMethods are no longer chainable, allowing you to get rid of unused code with tree-shaking. **Instead of calling methods on Dinero objects, you can import individual functions and pass Dinero objects to it.**\n\nFormer methods and new functions don't all have the same signature. Refer to the correlation tables below and the API reference for each function.\n\n#### Access\n\n| Dinero v1.x         | Dinero v2                                                                                    |\n|---------------------|----------------------------------------------------------------------------------------------|\n| `d1.getAmount()`    | Dropped, [see replacement](#replace-getamount-getcurrency-and-getprecision-with-tosnapshot). |\n| `d1.getCurrency()`  | Dropped, [see replacement](#replace-getamount-getcurrency-and-getprecision-with-tosnapshot). |\n| `d1.getPrecision()` | Dropped, [see replacement](#replace-getamount-getcurrency-and-getprecision-with-tosnapshot). |\n| `d1.getLocale()`    | [Dropped](#dropped-support-for-locale).                                                      |\n\n#### Mutations\n\n| Dinero v1.x              | Dinero v2                                                                                          |\n|--------------------------|----------------------------------------------------------------------------------------------------|\n| `d1.add(d2)`             | [`add(d1, d2)`](/api/mutations/add)                                                           |\n| `d1.subtract(d2)`        | [`subtract(d1, d2)`](/api/mutations/subtract)                                                 |\n| `d1.multiply(...args)`   | [`multiply(d1, ...args)`](/api/mutations/multiply)                                            |\n| `d1.allocate(...args)`   | [`allocate(d1, ...args)`](/api/mutations/allocate)                                            |\n| `d1.divide(...args)`     | Dropped, [see replacement](#replace-divide-with-allocate).                                         |\n| `d1.percentage(...args)` | Dropped, [see replacement](#replace-percentage-with-a-custom-solution-using-allocate-or-multiply). |\n| `d1.setLocale(...args)`  | [Dropped](#dropped-support-for-locale).                                                            |\n\n#### Conversions\n\n| Dinero v1.x                           | Dinero v2                                                              |\n|---------------------------------------|------------------------------------------------------------------------|\n| `d1.convert(...args)`                 | [`convert(d1, ...args)`](/api/conversions/convert)                |\n| `Dinero.normalizePrecision([d1, d2])` | [`normalizeScale([d1, d2])`](/api/conversions/normalize-scale)    |\n| `d1.convertPrecision(...args)`        | [`transformScale(d1, ...args)`](/api/conversions/transform-scale) |\n\n#### Comparisons\n\n| Dinero v1.x                 | Dinero v2                                                                   |\n|-----------------------------|-----------------------------------------------------------------------------|\n| `d1.equalsTo(d2)`           | [`equal(d1, d2)`](/api/comparisons/equal)                              |\n| `d1.greaterThan(d2)`        | [`greaterThan(d1, d2)`](/api/comparisons/greater-than)                 |\n| `d1.greaterThanOrEqual(d2)` | [`greaterThanOrEqual(d1, d2)`](/api/comparisons/greater-than-or-equal) |\n| `d1.lessThan(d2)`           | [`lessThan(d1, d2)`](/api/comparisons/less-than)                       |\n| `d1.lessThanOrEqual(d2)`    | [`lessThanOrEqual(d1, d2)`](/api/comparisons/less-than-or-equal)       |\n| `Dinero.minimum([d1, d2])`  | [`minimum([d1, d2])`](/api/comparisons/minimum)                        |\n| `Dinero.maximum([d1, d2])`  | [`maximum([d1, d2])`](/api/comparisons/maximum)                        |\n| `d1.isZero()`               | [`isZero(d1)`](/api/comparisons/is-zero)                               |\n| `d1.isPositive()`           | [`isPositive(d1)`](/api/comparisons/is-positive)                       |\n| `d1.isNegative()`           | [`isNegative(d1)`](/api/comparisons/is-negative)                       |\n| `d1.hasSameAmount(d2)`      | [`haveSameAmount([d1, d2])`](/api/comparisons/have-same-amount)        |\n| `d1.hasSameCurrency(d2)`    | [`haveSameCurrency([d1, d2])`](/api/comparisons/have-same-currency)    |\n| `d1.hasSubUnits()`          | [`hasSubUnits(d1)`](/api/comparisons/has-sub-units)                    |\n\n#### Formatting\n\n| Dinero v1.x                 | Dinero v2                                                                              |\n|-----------------------------|----------------------------------------------------------------------------------------|\n| `d1.toFormat(format)`       | Dropped, [see replacement](#replace-tounit-and-toroundedunit-with-tounits-or-todecimal) |\n| `d1.toObject()`             | [`toSnapshot(d1)`](/api/formatting/to-snapshot)                                   |\n| `d1.toUnit(...args)`        | Dropped, [see replacement](#replace-tounit-and-toroundedunit-with-tounits-or-todecimal) |\n| `d1.toRoundedUnit(...args)` | Dropped, [see replacement](#replace-tounit-and-toroundedunit-with-tounits-or-todecimal) |\n\n### Replace floats with scaled amounts\n\nIn v1.x, methods like [`convert`](/api/conversions/convert), [`multiply`](/api/mutations/multiply), or [`allocate`](/api/mutations/allocate) used to accept floats for rates, factors or ratios. It then rounded the result before creating new objects, resulting is precision loss.\n\n**In v2, you should use scaled amounts instead.** Scaled amounts represent a numeric value using an integer, and a scale that represents the position of the decimal point. For example, instead of passing `0.89`, you would pass `89` with a `scale` of `2`.\n\n```js\nconst scaled = { amount: 89, scale: 2 };\n```\n\nTo use fractional values, **pass scaled amounts instead of integers.**\n\n#### Convert\n\n```js\nimport { dinero, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst rates = { EUR: { amount: 89, scale: 2 } }; // 0.89\nconst d = dinero({ amount: 500, currency: USD });\n\nconvert(d, EUR, { rates });\n```\n\n#### Multiply\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst multiplier = { amount: 2001, scale: 3 }; // 2.001\nconst d = dinero({ amount: 401, currency: USD });\n\nmultiply(d, multiplier);\n```\n\n#### Allocate\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst ratios = [\n  { amount: 505, scale: 1 }, // 50.5\n  { amount: 495, scale: 1 }, // 49.5\n];\nconst d = dinero({ amount: 100, currency: USD });\n\nallocate(d, ratios);\n```\n\n**See also:**\n- [Convert](/api/conversions/convert)\n- [Multiply](/api/mutations/multiply)\n- [Allocate](/api/mutations/allocate)\n\n### Replace getAmount, getCurrency and getPrecision with toSnapshot\n\nThe `getAmount`, `getCurrency`, and `getPrecision` methods have been replaced with [`toSnapshot`](/api/formatting/to-snapshot), which returns a plain object with the amount, currency and scale (formerly known as precision).\n\n```diff\n- const amount = Dinero({ amount: 500, currency: 'USD' }).getAmount();\n- const currency = Dinero({ amount: 500, currency: 'USD' }).getCurrency();\n- const scale = Dinero({ amount: 500, currency: 'USD' }).getPrecision();\n+ const { amount, scale, currency } = toSnapshot(\n+   dinero({ amount: 500, currency: USD })\n+ );\n```\n\n**See also:** [To snapshot](/api/formatting/to-snapshot)\n\n### Replace divide with allocate\n\nDinero.js v2 no longer has a built-in `divide` function. Use [`allocate`](/api/mutations/allocate) instead.\n\n**See also:** [Allocate](/api/mutations/allocate)\n\n### Replace percentage with allocate or multiply\n\nDinero.js v2 no longer has a built-in `percentage` function. You can build your own using either [`allocate`](/api/mutations/allocate) or [`multiply`](/api/mutations/multiply).\n\n**See also:** [How do I calculate a percentage?](/guides/calculating-percentages)\n\n### Replace toUnit and toRoundedUnit with toUnits or toDecimal\n\nDinero.js v2 no longer has a built-in `toUnit` and `toRoundedUnit` functions. Use [`toUnits`](/api/formatting/to-units) or [`toDecimal`](/api/formatting/to-decimal) instead.\n\n**See also:**\n- [To units](/api/formatting/to-units)\n- [To decimal](/api/formatting/to-decimal)\n\n### Dropped support for locale\n\nIn v1.x, object formatting relied upon the [Internationalization API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). You could pass a locale to each Dinero object to control how to format it. In v2, formatting is dependency-free and provides you full control. You no longer need to rely on a locale, therefore this concept is gone.\n\nTo replicate the same formatting you had in v1.x, you can create a formatter that wraps around the Internationalization API.\n\n```js\nimport { toDecimal } from 'dinero.js';\n\nfunction createIntlFormatter(locale, options = {}) {\n  function transformer({ value, currency }) {\n    return Number(value).toLocaleString(locale, {\n      ...options,\n      style: 'currency',\n      currency: currency.code,\n    });\n  }\n\n  return function formatter(dineroObject) {\n    return toDecimal(dineroObject, transformer);\n  };\n}\n\nexport const intlFormat = createIntlFormatter('en-US');\n```\n\nYou can then pass any Dinero object to the returned function.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500, currency: USD });\n\nintlFormat(d); // \"$5.00\"\n```\n\n**See also:** [To decimal](/api/formatting/to-decimal)\n\n### Dropped support for globals\n\nDinero.js v2 no longer supports global default and settings. The entire library is side-effects free, and every object needs explicit parameters.\n\nIf you need defaults to create objects faster, you can create your own higher-order functions to partially apply Dinero objects. For example, you can write a function to creates US dollar Dinero objects.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction dineroUSD(amount) {\n  return dinero({ amount, currency: USD });\n}\n```\n\nThen, you can create objects by just passing the amount.\n\n```js\nconst d = dineroUSD(500);\n```\n"
  },
  {
    "path": "docs/guides/calculating-percentages.md",
    "content": "---\ntitle: Calculating percentages\ndescription: How to create a Dinero object that represents a percentage of another.\n---\n\n# Calculating percentages\n\nThere are two ways to calculate a percentage with Dinero.js: using [`allocate`](/api/mutations/allocate) or [`multiply`](/api/mutations/multiply).\n\nFor example, if you need to calculate 15% of a Dinero object, you can split it with [`allocate`](/api/mutations/allocate).\n\n```js\nimport { dinero, allocate } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 5000, currency: USD });\n\nconst [tax] = allocate(price, [15, 85]);\n```\n\nYou can do the same with [`multiply`](/api/mutations/multiply).\n\n```js\nimport { dinero, multiply } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 5000, currency: USD });\n\nconst tax = multiply(price, { amount: 15, scale: 2 });\n```\n\nIf you need this often, you can abstract it into your own `percentage` function.\n\n```js\nfunction percentage(dineroObject, share, scale = 0) {\n  const power = scale + 1;\n  const rest = 100 ** power - share;\n  const [chunk] = allocate(dineroObject, [share, rest], { scale });\n\n  return chunk;\n}\n```\n"
  },
  {
    "path": "docs/guides/creating-from-floats.md",
    "content": "---\ntitle: Creating from floats\ndescription: How to instantiate Dinero objects with float inputs using your own factory.\n---\n\n# Creating from floats\n\nDinero objects must be instantiated with integers, in minor currency units. For example, to create an object for $19.99, you should write the following code:\n\n```js\nimport { dinero, add, subtract } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1999, currency: USD });\n```\n\nIf you have amounts as floats (in this case, `19.99`) and you want to abstract object creation, you can write your own helper.\n\n```js\nfunction dineroFromFloat({ amount: float, currency, scale }) {\n  const factor = currency.base ** (scale ?? currency.exponent);\n  const amount = Math.round(float * factor);\n\n  return dinero({ amount, currency, scale });\n}\n```\n\n::: info\nThis code isn't tested and not guaranteed to cover edge cases. Use it as a starter and make sure it works for you by testing it in your application.\n:::\n"
  },
  {
    "path": "docs/guides/cryptocurrencies.md",
    "content": "---\ntitle: Cryptocurrency support\ndescription: How to create Dinero objects for cryptocurrencies like Bitcoin, Ethereum, and others.\n---\n\n# Cryptocurrency support\n\nDinero.js works with cryptocurrencies like any other currency, as long as they implement the `DineroCurrency` type.\n\nWhen working with these, you should use `bigint` or a third-party library like [big.js](http://mikemcl.github.io/big.js/). Cryptos usually have high exponents, making them likely to exceed the range of safe JavaScript integers.\n\n**See also:** [Precision and large numbers](/guides/precision-and-large-numbers)\n\nThe `dinero.js/currencies` subpath doesn't provide ready-made implementations for cryptocurrencies due to their non-normative and unstable nature. Maintaining such a list would be too demanding, so it makes more sense to keep them in userland. If you want to write a Dinero.js-compatible cryptocurrency, you can implement the `DineroCurrency` type.\n\n::: warning\n**Be careful how you name your files.** Crypto mining scripts often use the unofficial ISO 4217 code of the currency they're mining, like `xbt.js` or `xmr.js`. Adblockers then flag these scripts by name, causing them not to load. Make sure not to use suspicious file names, especially if you don't bundle your code, or they might not load for users with adblockers.\n:::\n"
  },
  {
    "path": "docs/guides/currency-type-safety.md",
    "content": "---\ntitle: Currency type safety\ndescription: Catch currency mismatches at compile time with TypeScript.\n---\n\n# Currency type safety\n\nIf you're using TypeScript, Dinero.js can catch currency mismatches at compile time. When you use the built-in ISO 4217 currencies, operations like `add`, `subtract`, or `equal` will reject Dinero objects of different currencies before your code even runs.\n\n## How it works\n\nEvery Dinero object carries a `TCurrency` type parameter that represents its currency code as a string literal type. When you use a built-in currency like `USD`, the Dinero object is typed as `Dinero<number, 'USD'>`. Operations that require the same currency enforce this at the type level.\n\n```ts\nimport { dinero, add } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD }); // Dinero<number, 'USD'>\nconst d2 = dinero({ amount: 100, currency: USD }); // Dinero<number, 'USD'>\nconst d3 = dinero({ amount: 100, currency: EUR }); // Dinero<number, 'EUR'>\n\nadd(d1, d2); // OK\nadd(d1, d3); // Type error: 'EUR' is not assignable to 'USD'\n```\n\nThis applies to all operations that expect the same currency: `add`, `subtract`, `equal`, `compare`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `minimum`, `maximum`, `haveSameAmount`, and `normalizeScale`.\n\n## Preserved through operations\n\nThe currency type is preserved through unary operations. When you `multiply`, `allocate`, `trimScale`, or `transformScale` a Dinero object, the result keeps the same currency type.\n\n```ts\nimport { dinero, add, multiply, allocate } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 1000, currency: USD });\n\nconst doubled = multiply(price, 2); // Dinero<number, 'USD'>\nadd(doubled, price); // OK\nadd(doubled, dinero({ amount: 100, currency: EUR })); // Type error\n\nconst [half1, half2] = allocate(price, [50, 50]); // Dinero<number, 'USD'>[]\nadd(half1, price); // OK\n```\n\n## Currency conversion\n\nWhen you `convert` a Dinero object, the result takes the type of the target currency.\n\n```ts\nimport { dinero, add, convert } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 500, currency: USD }); // Dinero<number, 'USD'>\n\nconst rates = { EUR: { amount: 89, scale: 2 } };\nconst converted = convert(d, EUR, rates); // Dinero<number, 'EUR'>\n\nadd(converted, dinero({ amount: 100, currency: EUR })); // OK\nadd(converted, d); // Type error: 'USD' is not assignable to 'EUR'\n```\n\n## Typed snapshots and formatters\n\nThe currency type flows through to snapshots and formatter callbacks.\n\n```ts\nimport { dinero, toSnapshot, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d = dinero({ amount: 1050, currency: USD });\n\nconst snapshot = toSnapshot(d);\nsnapshot.currency.code; // type is 'USD', not string\n\ntoDecimal(d, ({ currency }) => {\n  currency.code; // type is 'USD', not string\n  return `${currency.code} ...`;\n});\n```\n\n## Custom currencies\n\nIf you define custom currencies, you can opt into currency type safety by using `as const satisfies`:\n\n```ts\nimport type { DineroCurrency } from 'dinero.js';\n\nconst BTC = {\n  code: 'BTC',\n  base: 10,\n  exponent: 8,\n} as const satisfies DineroCurrency<number, 'BTC'>;\n```\n\nThe `as const` gives the `code` field the literal type `'BTC'` instead of `string`, and `satisfies` validates the object conforms to the `DineroCurrency` shape.\n\nWithout `as const`, the currency code is inferred as `string`, and Dinero objects using it won't enforce currency matching. This is intentional: it keeps Dinero.js backward compatible and lets you opt into stricter checking only when you want it.\n\n## Backward compatibility\n\nThe `TCurrency` type parameter defaults to `string`. Existing code that doesn't use typed currencies continues to work without changes.\n\n```ts\n// These are both Dinero<number, string> — no type enforcement\nconst currency = { code: 'USD', base: 10, exponent: 2 };\nconst d1 = dinero({ amount: 500, currency });\nconst d2 = dinero({ amount: 100, currency });\nadd(d1, d2); // OK — both are string-typed\n```\n\nRuntime currency checks (`haveSameCurrency` assertions) remain active regardless of typing, so JavaScript users and defensive programming patterns still work.\n"
  },
  {
    "path": "docs/guides/formatting-in-a-multilingual-site.md",
    "content": "---\ntitle: Formatting in a multilingual site\ndescription: Displaying currencies in a site or application that supports several languages.\n---\n\n# Formatting in a multilingual site\n\nDifferent languages and locations can have radically different formatting styles when it comes to money. For example, ten U.S. dollars in American English should be written down \"$10.00\". However, in Canadian French, the same amount would be \"10,00 $ US\".\n\nDinero.js provides formatting functions that give you full control over how to format a Dinero object.\n\n## Building a custom Internationalization formatter\n\nECMAScript provides an [Internationalization API](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl) (`Intl`) that lets you natively format monetary values into a given language by passing a locale. You can create your own `Intl` formatter by wrapping [`toDecimal`](/api/formatting/to-decimal).\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction intlFormat(dineroObject, locale, options = {}) {\n  function transformer({ value, currency }) {\n    return Number(value).toLocaleString(locale, {\n      ...options,\n      style: 'currency',\n      currency: currency.code,\n    });\n  }\n\n  return toDecimal(dineroObject, transformer);\n};\n\nconst d = dinero({ amount: 1000, currency: USD });\n\nintlFormat(d, 'en-US'); // \"$10.00\"\nintlFormat(d, 'fr-CA'); // \"10,00 $ US\"\n```\n\n::: info\nThe Internationalization API is well-supported in modern browsers and Node.js. For full locale data in Node.js, make sure to use a build with [full ICU support](https://nodejs.org/api/intl.html#intl_options_for_building_node_js).\n:::\n\n## Using the custom formatter\n\nYou can use the formatter to display monetary values differently according to the current language of your site or app. For example, a React implementation could look like the following.\n\n```jsx\nimport React from 'react';\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nimport { intlFormat } from './intlFormat';\n\nconst languages = [\n  {\n    label: 'English (U.S.)',\n    locale: 'en-US',\n  },\n  {\n    label: 'Français (Canada)',\n    locale: 'fr-CA',\n  },\n];\n\nfunction App() {\n  const [defaultLanguage] = languages;\n  const [language, setLanguage] = React.useState(defaultLanguage);\n\n  const price = dinero({ amount: 1000, currency: USD });\n\n  return (\n    <>\n      <select\n        value={language.locale}\n        onChange={(event) =>\n          setLanguage(languages.find(({ locale }) => locale === event.target.value))\n        }\n      >\n        {languages.map(({ label, locale }) => (\n          <option key={locale} value={locale}>\n            {label}\n          </option>\n        ))}\n      </select>\n      <p>Price: {intlFormat(price, language.locale)}</p>\n    </>\n  );\n}\n```\n"
  },
  {
    "path": "docs/guides/formatting-non-decimal-currencies.md",
    "content": "---\ntitle: Formatting non-decimal currencies\ndescription: Displaying non-decimal currencies and currencies with multiple subdivisions.\n---\n\n# Formatting non-decimal currencies\n\nThe great majority of circulating currencies are decimal. If you're working with those, the Dinero.js formatting utility should cover most of your use cases.\n\nHowever, **you might also work with non-decimal currencies**. Typical use cases are ancient currencies such as the ancient Greek drachma, or fictional currencies like the wizarding currencies in the Harry Potter universe. If you're building a numismatic site or a game with its own currency, you might have advanced formatting needs.\n\n## Handling currencies with a single subdivision\n\nOut of the box, you can format any non-decimal Dinero object using [`toUnits`](/api/formatting/to-units).\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\nimport pluralize from 'pluralize';\n\nconst labels = ['drachma', 'obol'];\n\nfunction transformer({ value, currency }) {\n  return value\n    .filter((amount) => amount > 0)\n    .map((amount, index) => `${amount} ${pluralize(labels[index], amount)}`)\n    .join(', ');\n}\n\nconst d = dinero({\n  amount: 9,\n  currency: {\n    code: 'GRD',\n    base: 6,\n    exponent: 1,\n  },\n});\n\ntoUnits(d, transformer); // \"1 drachma, 3 obols\"\n```\n\n## Handling currencies with multiple subdivisions\n\nWhile most circulating currencies have a single minor currency unit, **many ancient currencies have multiple subdivisions.** That's the case for most pre-decimalization European currencies such as the livre tournois in the French Old Regime or the pound sterling in Great Britain before 1971. That's also the case of some fictional currencies.\n\nWhen working with such currencies, **you can specify each subdivision with an array.**\n\nFor example, let's say you're building a Candy Crush clone where users can buy bonuses with an in-game currency: donuts, cookies, and lollipops. In your game, a donut equals 30 cookies, and a cookie equals 16 lollipops. If a bonus costs 720 lollipops, you might want to format it as \"1 donut and 15 cookies\".\n\n```js\nimport { dinero, toUnits } from 'dinero.js';\n\nconst POP = {\n  code: 'POP',\n  base: [30, 16],\n  exponent: 1,\n};\n\nconst labels = ['donut', 'cookie', 'lollipop'];\n\nfunction transformer({ value }) {\n  return value\n    .filter((amount) => amount > 0)\n    .map((amount, index) => `${amount} ${amount > 1 ? `${labels[index]}s` : labels[index]}`)\n    .join(' and ');\n}\n\nconst d = dinero({ amount: 720, currency: POP });\n\ntoUnits(d, transformer); // \"1 donut and 15 cookies\"\n```\n"
  },
  {
    "path": "docs/guides/integrating-with-payment-services.md",
    "content": "---\ntitle: Integrating with payment services\ndescription: How to integrate Dinero.js with payment services like Stripe, Adyen, or Square.\n---\n\n# Integrating with payment services\n\nOne of the most common use cases when manipulating money is payment. Many services such as [Stripe](https://stripe.com/) helps you process orders and payments programmatically.\n\nSuch solutions integrate well with Dinero.js. If you're building an application that manipulates and charges money, you can use Dinero objects to represent prices and write small connectors for your payment service. **Most payment services represent money in minor units, making it straightforward to turn a Dinero object into a payment.**\n\n::: info\nThe following code is purely illustrative. Make sure to test it out in your application.\n:::\n\n## Integrating with Stripe\n\nThe [Stripe](https://stripe.com/) payment platform provides APIs to process payments and manage orders. Like many other platforms, it expects [money representations](https://stripe.com/docs/currencies#zero-decimal) with an amount in minor currency units, and a currency as an ISO 4217 currency code.\n\nWhen using Stripe's Node.js client, [the currency must be in lowercase](https://stripe.com/docs/api/charges/create?lang=node#create_charge-currency).\n\n```js\nimport stripe from 'stripe';\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction toStripeMoney(dineroObject) {\n  const { amount, currency } = toSnapshot(dineroObject);\n\n  return { amount, currency: currency.code.toLowerCase() };\n}\n\n// ... Stripe client setup\n\nconst price = dinero({ amount: 2000, currency: USD });\n\nconst response = await client.charges.create({\n  // ...\n  ...toStripeMoney(price),\n});\n```\n\n## Integrating with Paypal\n\nThe [Paypal](https://www.paypal.com/) payment platform provides APIs to process payments and manage orders. Unlike most platforms, it expects a string representation with an amount in major currency units. You can use [`toDecimal`](/api/formatting/to-decimal) to format the object and pass this value to Paypal.\n\n```js\nconst paypal = require('@paypal/checkout-server-sdk');\nconst { dinero, toSnapshot, toDecimal } = require('dinero.js');\nconst { USD } = require('dinero.js/currencies');\n\nfunction toPaypalMoney(dineroObject) {\n  const { currency, scale } = toSnapshot(dineroObject);\n\n  return {\n    value: toDecimal(dineroObject),\n    currency_code: currency.code,\n  };\n}\n\nconst price = dinero({ amount: 2000, currency: USD });\n\nlet request = new paypal.orders.OrdersCreateRequest();\nrequest.requestBody({\n  // ...\n  purchase_units: [\n    {\n      amount: toPaypalMoney(price),\n    },\n  ],\n});\n```\n\n## Integrating with Adyen\n\nThe [Adyen](https://www.adyen.com/) payment platform provides APIs to process payments and manage orders. Like many other platforms, it expects [money representations](https://developer.squareup.com/reference/square/objects/Money) with an amount in minor currency units and a currency as an ISO 4217 currency code.\n\n```js\nimport { Client, Config, CheckoutAPI } from '@adyen/api-library';\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction toAdyenMoney(dineroObject) {\n  const { amount, currency } = toSnapshot(dineroObject);\n\n  return { value: amount, currency: currency.code };\n};\n\n// ... Adyen client setup\n\nconst price = dinero({ amount: 2000, currency: USD });\n\nconst response = await checkout.paymentMethods({\n  // ...\n  amount: toAdyenMoney(price),\n});\n```\n\n## Integrating with Square\n\nThe [Square](https://squareup.com/) digital payment platform provides APIs to process payments and manage orders. Like many other platforms, it expects [money representations](https://docs.adyen.com/development-resources/currency-codes) with an amount in minor currency units and a currency as an ISO 4217 currency code.\n\nWhen using Square's Node.js client, [the amount must be of type `bigint`](https://github.com/square/square-nodejs-sdk/blob/master/src/models/money.ts). If you're using Dinero.js with the `number` calculator (default behavior), you can cast the amount into a `bigint` when transforming your Dinero object into a Square `Money` object. Otherwise, if you're using Dinero with the `bigint` calculator, you can pass the amount directly.\n\n```js\nimport { Client } from 'square';\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nfunction toSquareMoney(dineroObject) {\n  const { amount, currency } = toSnapshot(dineroObject);\n\n  return { amount: BigInt(amount), currency: currency.code };\n}\n\n// ... Square client setup\n\nconst price = dinero({ amount: 2000, currency: USD });\n\nconst response = await client.paymentsApi.createPayment({\n  // ...\n  amountMoney: toSquareMoney(price),\n});\n```\n"
  },
  {
    "path": "docs/guides/precision-and-large-numbers.md",
    "content": "---\ntitle: Precision and large numbers\ndescription: Using Dinero.js with bigint or third-party arbitrary-precision libraries for large amounts or high-precision currencies.\n---\n\n# Precision and large numbers\n\nDinero expects amounts as `number` by default. In most cases, this is more than enough, but there are times when you might hit the limitations of the [biggest](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) and [smallest](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER) numbers you can safely represent.\n\nA typical use case is **when you need to represent colossal amounts of money.** Take the world debt, which reached $258 trillion in 2020. In JavaScript, the biggest number you can accurately represent is 9007199254740991 (9 quadrillions and some spare change). Still, since Dinero requires you to pass amounts in minor currency units, you actually \"lose\" two orders of magnitude, and can *only* represent around $90 trillion.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\n// Don't do this!\n// 25800000000000000 is too big for accurate representation\n// in IEEE 754 numbers.\nconst price = dinero({ amount: 25800000000000000, currency: USD });\n```\n\nAnother example is when you need to represent cryptocurrencies, which typically have high exponents. In 2021, the Ether can be subdivided down to 18 fraction digits, meaning you can't even represent 1 ETH with the `number` type.\n\n```js\nimport { dinero } from 'dinero.js';\n\nconst ETH = {\n  code: 'ETH',\n  base: 10,\n  exponent: 18,\n};\n\n// Don't do this!\n// 1000000000000000000 is too big for accurate representation\n// in IEEE 754 numbers.\nconst price = dinero({ amount: 1000000000000000000, currency: ETH });\n```\n\nIn such cases, you need to rely on safer alternatives, such as the [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) primitive or third-parties like [big.js](https://github.com/MikeMcl/big.js).\n\n## Using Dinero with bigint\n\nDinero provides a ready-to-use `dinero` function for bigints via the `dinero.js/bigint` subpath. Bigint-compatible currencies are available from `dinero.js/bigint/currencies`:\n\n```js\nimport { dinero, add } from 'dinero.js/bigint';\nimport { USD } from 'dinero.js/bigint/currencies';\n\nconst d1 = dinero({ amount: 500n, currency: USD });\nconst d2 = dinero({ amount: 100n, currency: USD });\n\nadd(d1, d2); // a Dinero object with amount `600n`\n```\n\n::: warning\n**You cannot use currencies from `dinero.js/currencies` with bigint.** Those have `number` values for `base` and `exponent`. Always import from `dinero.js/bigint/currencies` when using the bigint variant.\n:::\n\n## Using Dinero with a custom amount type\n\nDinero.js delegates all calculations to a type-specific calculator object. **The calculator fully determines what amount type you can pass to Dinero objects.** Therefore, by changing the calculator with one of a different type, you can create Dinero objects of this type.\n\nYou can implement your own if you want to use a third-party library.\n\n### Implementing a custom calculator\n\nDinero.js delegates all calculations to a type-specific calculator object. You can implement a custom calculator for a given type and pass it to Dinero to use the library with amounts of this type.\n\nA calculator implements the `DineroCalculator` interface. For example, here's what it can look like with [big.js](https://github.com/MikeMcl/big.js).\n\n```ts\nimport Big from 'big.js';\nimport { DineroCalculator, DineroComparisonOperator } from 'dinero.js';\n\nconst calculator: DineroCalculator<Big> = {\n  add: (a, b) => a.plus(b),\n  compare: (a, b) => a.cmp(b) as unknown as DineroComparisonOperator,\n  decrement: (v) => v.minus(new Big(1)),\n  increment: (v) => v.plus(new Big(1)),\n  integerDivide: (a, b) => a.div(b).round(0, Big.roundDown),\n  modulo: (a, b) => a.mod(b),\n  multiply: (a, b) => a.times(b),\n  power: (a, b) => a.pow(Number(b)),\n  subtract: (a, b) => a.minus(b),\n  zero: () => new Big(0),\n};\n```\n\nOnce you have your calculator, you can build a custom `dinero` function.\n\n```js\nimport { createDinero } from 'dinero.js';\n\n// ...\n\nconst bigDinero = createDinero({ calculator });\n```\n\nYou might notice that you're passing the full calculator, meaning you're shipping calculator methods you might not use. **This is unlikely to represent a bottleneck**, especially if you're using Dinero with a third-party library like [big.js](https://github.com/MikeMcl/big.js) because you're only referencing methods that already exist on every `Big` object you create.\n\n### Providing a custom formatter\n\nWhen using a custom amount type, you also need a **custom formatter** so that functions like `toDecimal` can correctly convert your amounts to strings. The default formatter uses JavaScript's `String` constructor, which produces scientific notation for large values (e.g., `\"1e+22\"` instead of `\"10000000000000000000000\"`). This breaks formatting when working with high-precision amounts.\n\n```ts\nimport Big from 'big.js';\nimport { DineroFormatter } from 'dinero.js';\n\nconst formatter: DineroFormatter<Big> = {\n  toNumber: (value) => value.toNumber(),\n  toString: (value) => value.toFixed(),\n};\n```\n\nPass it alongside the calculator when creating your custom `dinero` function:\n\n```js\nconst bigDinero = createDinero({ calculator, formatter });\n```\n\n## Picking the right amount type\n\nDepending on what you use Dinero.js for, you might want to choose a different amount type better suited to your needs. Knowing what to pick depends on **your constraints, use case, and what compromises you can to make.**\n\nWith amount types, the main trade-off is between precision and performance. Safe arbitrary precision comes at the cost of speed, so you need to properly assess your needs before deciding.\n\n### When to use number\n\nBy default, Dinero.js uses the [`number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) type. It's ideal when you need to **express monetary values that will never exceed what the type can safely represent.**\n\nThe `number` primitive type lets you create double-precision floats (or \"doubles\") using the [IEEE 754 standard](https://wikipedia.org/wiki/IEEE_754). It works well for many use cases and provides excellent performance. However, doubles can only represent a limited range of numbers (from `-(2^53 - 1)` to `2^53 - 1`). Anything below or above gets truncated when converted to binary and stored in memory, resulting in imprecisions.\n\nUsing Dinero.js with `number`s works well when you know and control the numbers to represent. It works for many use cases including dynamic pricing pages, ecommerce sites, or money management applications, as long as you're confident you'll never exceed the type limitations.\n\n#### Benefits\n\n- Great performance, [implemented in hardware](https://wikipedia.org/wiki/Floating-point_unit)\n- Full browser and Node.js compatibility (requires [some polyfills](/getting-started/compatibility#browser-support) for some static and `Math` functions)\n\n#### Drawbacks\n\n- Limited range of numbers that can be accurately represented\n\n### When to use bigint\n\nDinero.js provides a [`bigint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) calculator, allowing you to [use the library with native `bigint`s](#using-dinero-with-bigint). It's ideal when you need to **represent monetary values with large amounts, beyond what the `number` type safely supports.**\n\nThe `bigint` primitive type lets you create arbitrarily large integers and ensures arithmetic precision. However, `bigint`s are much slower than `number`s, and not available in all environments. They're also [impossible to polyfill and hard to transpile](https://v8.dev/features/bigint#polyfilling-transpiling) without incurring significant performance costs.\n\nUsing Dinero.js with the `bigint` type is recommended when you need to use numbers that exceed the `number` limitations. It can also act as a safeguard when you don't control the monetary amounts in your app, and you have reasons to believe you might exceed the limits. This applies to use cases such as cryptocurrency or stock trading applications.\n\n#### Benefits\n\n- Arbitrary-precision integer support\n- Faster than any userland arbitrary-precision library (see [Chrome benchmark](https://v8.dev/features/bigint#use-cases))\n\n#### Drawbacks\n\n- Significantly slower than `number`s, [implemented in software](https://v8.dev/blog/bigint)\n- Impossible to polyfill directly or to efficiently transpile down to ES5\n- No native [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) and [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) support, requires a custom [replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter) and [reviver](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#using_the_reviver_parameter)\n\n### When to use libraries\n\nIf you need to support arbitrarily large integers in browsers that don't support `bigint`s, you can [write a custom calculator](#implementing-a-custom-calculator) to use Dinero.js with libraries like [big.js](https://github.com/MikeMcl/big.js) or [JSBI](https://github.com/GoogleChromeLabs/jsbi).\n\nContrary to the `bigint` type which relies on operators, libraries expose APIs to safely manipulate arbitrarily large integers. Such solutions usually rely on `string`s or arrays of `number`s, making them more widely supported across browsers. However, this has a significant impact on performance because of the extra runtime logic, algorithmic complexity, and increase in bundle size.\n\nUsing Dinero.js with arbitrary precision arithmetic libraries makes sense **when [you wish you could use `bigint`s](#when-to-use-bigint) but cannot because you need to support environments that don't implement them.**\n\n#### Benefits\n\n- Better browser and Node.js support than `bigint`s, transpilable and polyfillable\n\n#### Drawbacks\n\n- Significantly slower than `number`s and `bigint`s\n- Increase in bundle size, impacting download and parse time\n\n::: info\nDinero.js doesn't endorse any specific arbitrary precision library, or guarantees they work correctly. If you need to use a library, make sure to verify it works as expected.\n:::\n"
  },
  {
    "path": "docs/guides/storing-in-a-database.md",
    "content": "---\ntitle: Storing in a database\ndescription: How to persist Dinero objects in SQL and NoSQL databases.\n---\n\n# Storing in a database\n\nWhen building applications that handle money, you typically need to persist Dinero objects to a database. The way you store them depends on your database system and your application's requirements.\n\nThe safest and most portable approach is to **store the amount as an integer in minor units, along with the currency code and exponent.** This works with any database and gives you full control over how data is stored and retrieved.\n\n## Storing amount and currency separately\n\nThis approach stores each component of a Dinero object in its own column. It's the most flexible because it doesn't depend on any database-specific features.\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  price_amount BIGINT NOT NULL,\n  price_currency VARCHAR(3) NOT NULL,\n  price_exponent INTEGER NOT NULL DEFAULT 2\n);\n\nINSERT INTO products (name, price_amount, price_currency, price_exponent)\nVALUES ('Mass Effect: Legendary Edition', 6999, 'EUR', 2);\n```\n\nWhen restoring from the database, you can reconstruct a Dinero object by passing the stored values to the `dinero` function.\n\n```js\nimport { dinero } from 'dinero.js';\n\n// After fetching from database\nconst row = {\n  name: 'Mass Effect: Legendary Edition',\n  price_amount: 6999,\n  price_currency: 'EUR',\n  price_exponent: 2,\n};\n\nconst product = {\n  name: row.name,\n  price: dinero({\n    amount: row.price_amount,\n    currency: { code: row.price_currency, base: 10, exponent: row.price_exponent },\n  }),\n};\n```\n\n::: tip\nIf you're working with amounts that have a [custom scale](/core-concepts/scale) different from the currency's exponent, you'll need to store the scale as well and pass it when restoring.\n:::\n\n## Storing as JSON\n\nIf your database supports JSON columns (PostgreSQL with `JSONB`, MySQL 5.7+, SQLite with JSON1), you can store the entire snapshot as a single value. This simplifies your schema but ties you to databases with JSON support.\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  price JSONB NOT NULL\n);\n```\n\nYou can store a snapshot directly and restore it with minimal transformation.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 6999, currency: EUR });\n\n// Insert\nawait db.query(\n  'INSERT INTO products (name, price) VALUES ($1, $2)',\n  ['Mass Effect: Legendary Edition', JSON.stringify(toSnapshot(price))]\n);\n\n// Restore\nconst { rows } = await db.query('SELECT * FROM products WHERE id = $1', [1]);\n\nconst product = {\n  ...rows[0],\n  price: dinero(rows[0].price),\n};\n```\n\n## MongoDB with Decimal128\n\nMongoDB's BSON format includes a `Decimal128` type specifically designed for monetary data. It avoids floating-point precision issues that can occur with regular JavaScript numbers.\n\nFor typical use cases where amounts fit within JavaScript's safe integer range, you can store the snapshot directly as an embedded document.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { EUR } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 6999, currency: EUR });\n\n// Insert snapshot as-is\nawait collection.insertOne({\n  name: 'Mass Effect: Legendary Edition',\n  price: toSnapshot(price),\n});\n\n// Restore\nconst document = await collection.findOne({ name: 'Mass Effect: Legendary Edition' });\nconst product = {\n  ...document,\n  price: dinero(document.price),\n};\n```\n\nWhen working with very large amounts or using the [`bigint` calculator](/guides/precision-and-large-numbers#using-dinero-with-bigint), you should use `Decimal128` for the amount to ensure precision.\n\n```js\nimport { calculator } from 'dinero.js/bigint';\nimport { createDinero, toSnapshot } from 'dinero.js';\nimport { Decimal128 } from 'mongodb';\n\nconst dinero = createDinero({ calculator });\n\nconst ETH = {\n  code: 'ETH',\n  base: 10n,\n  exponent: 18n,\n};\n\n// 1 ETH in wei (10^18)\nconst balance = dinero({ amount: 1000000000000000000n, currency: ETH });\nconst snapshot = toSnapshot(balance);\n\n// Insert with Decimal128 for precise amount storage\nawait collection.insertOne({\n  name: 'Wallet balance',\n  balance: {\n    amount: Decimal128.fromString(String(snapshot.amount)),\n    currency: snapshot.currency,\n    scale: snapshot.scale,\n  },\n});\n\n// Restore\nconst doc = await collection.findOne({ name: 'Wallet balance' });\nconst wallet = {\n  ...doc,\n  balance: dinero({\n    amount: BigInt(doc.balance.amount.toString()),\n    currency: doc.balance.currency,\n    scale: doc.balance.scale,\n  }),\n};\n```\n\n## PostgreSQL's money type\n\nPostgreSQL has a built-in `money` type, but it comes with significant limitations that make it unsuitable for most applications:\n\n- **No currency information**: it only stores the amount, not which currency it represents.\n- **Locale-dependent**: formatting depends on the `lc_monetary` setting, which can cause issues when moving data between systems.\n- **Fixed precision**: always uses 2 decimal places, which doesn't work for currencies like JPY (0 decimals) or BHD (3 decimals).\n\nIf you still want to use it for single-currency applications where these limitations don't apply, you can convert Dinero objects to decimal strings for storage.\n\n```sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  price MONEY NOT NULL\n);\n```\n\n```js\nimport { dinero, toDecimal } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 6999, currency: USD });\n\n// Insert (convert to decimal string)\nawait db.query(\n  'INSERT INTO products (name, price) VALUES ($1, $2::money)',\n  ['Mass Effect: Legendary Edition', toDecimal(price)]\n);\n\n// Restore (PostgreSQL returns money as a string like \"$69.99\")\nconst { rows } = await db.query('SELECT * FROM products WHERE id = $1', [1]);\nconst amountString = rows[0].price.replace(/[^0-9.-]/g, ''); // Remove currency symbol\nconst product = {\n  ...rows[0],\n  price: dinero({\n    amount: Math.round(parseFloat(amountString) * 100),\n    currency: USD,\n  }),\n};\n```\n\n::: warning\nFor most applications, we recommend storing amount and currency separately rather than using the `money` type. This gives you full control over precision and currency handling.\n:::\n"
  },
  {
    "path": "docs/guides/transporting-and-restoring.md",
    "content": "---\ntitle: Serialization\ndescription: How to serialize Dinero objects for transport over the network and restore them in your application.\n---\n\n# Serialization\n\nIf you want to send a Dinero object over the network, you need to serialize it first. Conversely, when retrieving a serialized object, you need to restore it as an actual Dinero object before using it in your application and manipulating it with Dinero functions.\n\n**Dinero lets you turn objects into snapshots.** Snapshots are plain JavaScript objects, suited for transport and storage. To create a snapshot, you can use the [`toSnapshot`](/api/formatting/to-snapshot) function.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst price = dinero({ amount: 500, currency: USD });\n\nconst snapshot = toSnapshot(price);\n\n/**\n * {\n *   amount: 500,\n *   currency: {\n *     code: 'USD',\n *     base: 10,\n *     exponent: 2,\n *   },\n *   scale: 2,\n * }\n */\n```\n\nYou can use snapshots with any API that accepts serializable data types.\n\n```js\nimport { dinero, toSnapshot } from 'dinero.js';\nimport { EUR } from 'dinero.js/currencies';\nimport axios from 'axios';\n\nconst price = dinero({ amount: 6999, currency: EUR });\n\naxios.post('http://example.org/api/products', {\n  name: 'Mass Effect: Legendary Edition',\n  platform: 'Xbox One',\n  price: toSnapshot(price),\n});\n```\n\n## Serializing to JSON\n\nIf you want to serialize a Dinero object into JSON, you can directly call [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) on it, without turning them into a snapshot first.\n\n```js\nimport { dinero } from 'dinero.js';\nimport { EUR } from 'dinero.js/currencies';\n\nconst product = {\n  name: 'Mass Effect: Legendary Edition',\n  platform: 'Xbox One',\n  price: dinero({ amount: 6999, currency: EUR }),\n};\n\nfetch('http://example.org/api/products', {\n  method: 'POST',\n  body: JSON.stringify(product),\n});\n```\n\n## Restoring an object\n\nWhen retrieving a snapshot, you can restore it into an actual Dinero object for usage in your application. To do so, you can pass the snapshot to the `dinero` function.\n\n```js\nimport { dinero } from 'dinero.js';\nimport axios from 'axios';\n\naxios.get('http://example.org/api/products', {\n  params: {\n    id: '69e89575-fe87-4eb2-8b1d-b445bbe41a47',\n  },\n})\n.then(({ data }) => {\n  const product = {\n    ...data,\n    price: dinero(data.price),\n  };\n});\n```\n\n## Handling arbitrary precision amounts\n\nIf you're using Dinero.js with the [`bigint` calculator](/guides/precision-and-large-numbers#using-dinero-with-bigint) or a [custom library](/guides/precision-and-large-numbers#implementing-a-custom-calculator), you need to cast the amount to a `string` for serialization, so you can retain precision and safely restore it later.\n\nWhile many arbitrary precision libraries support this out of the box, **you can't use [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) directly with `bigint`s.**\n\nWhen serializing, make sure to pass a [custom replacer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter) to coerce every `bigint` into a `string`.\n\n```js\nimport { dinero } from 'dinero.js/bigint';\nimport { EUR } from 'dinero.js/bigint/currencies';\n\nconst product = {\n  name: 'Mass Effect: Legendary Edition',\n  platform: 'Xbox One',\n  price: dinero({ amount: 6999n, currency: EUR }),\n};\n\nfetch('http://example.org/api/products', {\n  method: 'POST',\n  body: JSON.stringify(product, (key, value) => {\n    if (typeof value === 'bigint') {\n      return String(value);\n    }\n\n    return value;\n  }),\n});\n```\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\n---\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@dinero.js/docs\",\n  \"version\": \"2.0.2\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vitepress dev\",\n    \"build\": \"vitepress build\",\n    \"preview\": \"vitepress preview\"\n  },\n  \"devDependencies\": {\n    \"vitepress\": \"^2.0.0-alpha.16\"\n  }\n}\n"
  },
  {
    "path": "docs/vercel.json",
    "content": "{\n  \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n  \"cleanUrls\": true,\n  \"buildCommand\": \"npx vitepress build && bash build-examples.sh\",\n  \"outputDirectory\": \".vitepress/dist\",\n  \"redirects\": [\n    {\n      \"source\": \"/docs/api/formatting/to-unit\",\n      \"destination\": \"/api/formatting/to-units\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/docs/api/formatting/to-format\",\n      \"destination\": \"/api/formatting/to-decimal\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/docs/advanced/:path*\",\n      \"destination\": \"/guides/:path*\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/api/formatting/to-unit\",\n      \"destination\": \"/api/formatting/to-units\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/api/formatting/to-format\",\n      \"destination\": \"/api/formatting/to-decimal\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/advanced/:path*\",\n      \"destination\": \"/guides/:path*\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/docs/:path*\",\n      \"destination\": \"/:path*\",\n      \"statusCode\": 308\n    },\n    {\n      \"source\": \"/sandboxes\",\n      \"destination\": \"/demos\",\n      \"statusCode\": 308\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/cart-react/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n*.tsbuildinfo"
  },
  {
    "path": "examples/cart-react/README.md",
    "content": "# Shopping Cart (React)\n\nA React app that manages a multi-currency shopping cart with VAT and shipping, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/cart-react/)\n\n## What you'll learn\n\n- Creating monetary values with `dinero`\n- Multiplying amounts with `multiply` for quantity pricing\n- Summing values with `add` for cart subtotals\n- Computing VAT with `allocate` for precise tax distribution\n- Converting currencies with `convert` and scaled exchange rates\n- Formatting money for display with `toDecimal`\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/cart-react\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/cart-react/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Shopping Cart — Dinero.js Example</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/cart-react/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-cart-react\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-react\": \"^0.475.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/cart-react/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/cart-react/src/App.tsx",
    "content": "import { useState } from 'react';\n\nimport { CartLine } from '@/components/cart-line';\nimport { OrderSummary } from '@/components/order-summary';\nimport { items as initialItems, shipping as shippingOptions } from '@/data';\nimport {\n  add,\n  multiply,\n  allocate,\n  fromMinorUnits,\n  convertCurrency,\n  zero,\n} from '@/lib/money';\nimport type { CurrencyCode, CartItem } from '@/types';\n\nconst CURRENCY_OPTIONS: Array<{ code: CurrencyCode; symbol: string }> = [\n  { code: 'USD', symbol: '$' },\n  { code: 'EUR', symbol: '€' },\n];\n\nconst VAT_RATE = 20;\n\nconst LOGO = (\n  <svg\n    viewBox=\"0 0 361.4 213.6\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"h-6 w-auto\"\n    aria-hidden=\"true\"\n  >\n    <path\n      fill=\"#4466ff\"\n      d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n    />\n    <path\n      fill=\"#fff\"\n      d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n    />\n    <path\n      fill=\"#f7a\"\n      d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n    />\n    <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n    <path\n      fill=\"#bcf\"\n      d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n    />\n    <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n  </svg>\n);\n\nexport default function App() {\n  const [items, setItems] = useState<CartItem[]>(initialItems);\n  const [shipping, setShipping] = useState(shippingOptions[0].label);\n  const [currencyCode, setCurrencyCode] = useState<CurrencyCode>('USD');\n\n  const hasItems = items.length > 0;\n\n  const convertedItems = items.map((item) => ({\n    ...item,\n    dineroPrice: convertCurrency(\n      fromMinorUnits(item.price, 'USD'),\n      currencyCode\n    ),\n  }));\n\n  const convertedShippingOptions = shippingOptions.map((option) => ({\n    ...option,\n    convertedPrice: convertCurrency(\n      fromMinorUnits(option.price, 'USD'),\n      currencyCode\n    ),\n  }));\n\n  const shippingOption = convertedShippingOptions.find(\n    ({ label }) => label === shipping\n  )!;\n\n  const { count, subtotal } = convertedItems.reduce(\n    (acc, item) => ({\n      count: acc.count + item.quantity,\n      subtotal: add(acc.subtotal, multiply(item.dineroPrice, item.quantity)),\n    }),\n    { count: 0, subtotal: zero(currencyCode) }\n  );\n\n  const shippingAmount = hasItems\n    ? shippingOption.convertedPrice\n    : zero(currencyCode);\n  const [vatAmount] = allocate(subtotal, [VAT_RATE, 100 - VAT_RATE]);\n  const total = [subtotal, vatAmount, shippingAmount].reduce(add);\n\n  function updateItem(\n    name: string,\n    updater: (item: CartItem) => CartItem | null\n  ) {\n    setItems((prev) => {\n      const index = prev.findIndex((item) => item.name === name);\n\n      if (index === -1) return prev;\n\n      const result = updater(prev[index]);\n\n      if (result === null) {\n        return prev.filter((_, i) => i !== index);\n      }\n\n      return prev.map((item, i) => (i === index ? result : item));\n    });\n  }\n\n  return (\n    <main className=\"flex h-screen flex-col\">\n      <header className=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\">\n        <div className=\"flex min-w-0 items-center gap-2 sm:gap-3\">\n          {LOGO}\n          <h1 className=\"truncate text-sm font-semibold text-foreground\">\n            Shopping Cart\n          </h1>\n          <span className=\"hidden whitespace-nowrap rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline\">\n            Built with Dinero.js\n          </span>\n        </div>\n        <a\n          href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/cart-react\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"shrink-0 rounded-sm text-xs text-text-muted transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/50\"\n        >\n          GitHub\n          <span className=\"hidden sm:inline\"> source</span>\n        </a>\n      </header>\n\n      <div className=\"flex min-h-0 flex-1 flex-col lg:flex-row\">\n        <div className=\"flex flex-1 flex-col border-b border-border lg:border-b-0 lg:border-r\">\n          <div className=\"flex items-center justify-between border-b border-border px-5 py-3\">\n            <span className=\"text-xs font-medium text-text-muted\">\n              {items.length} {items.length === 1 ? 'item' : 'items'}\n            </span>\n            <div className=\"flex items-center gap-2\">\n              <label htmlFor=\"currency\" className=\"text-xs text-text-muted\">\n                Currency\n              </label>\n              <select\n                id=\"currency\"\n                name=\"currency\"\n                value={currencyCode}\n                onChange={(event) =>\n                  setCurrencyCode(event.target.value as CurrencyCode)\n                }\n                className=\"rounded-lg border border-border bg-muted px-3 py-1.5 text-sm text-foreground transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring\"\n              >\n                {CURRENCY_OPTIONS.map((option) => (\n                  <option key={option.code} value={option.code}>\n                    {option.code} ({option.symbol})\n                  </option>\n                ))}\n              </select>\n            </div>\n          </div>\n          <div className=\"flex-1 overflow-y-auto p-5\">\n            {hasItems ? (\n              <div>\n                <div className=\"mb-2 hidden items-center px-4 sm:flex\">\n                  <span className=\"min-w-0 flex-1 text-xs font-medium tracking-wide text-text-muted\">\n                    Product\n                  </span>\n                  <span className=\"w-[7.5rem] text-right text-xs font-medium tracking-wide text-text-muted\">\n                    Quantity\n                  </span>\n                  <span className=\"w-24 text-right text-xs font-medium tracking-wide text-text-muted\">\n                    Price\n                  </span>\n                  <span className=\"w-24 text-right text-xs font-medium tracking-wide text-text-muted\">\n                    Total\n                  </span>\n                </div>\n                <div className=\"space-y-1\">\n                  {convertedItems.map((item) => (\n                    <CartLine\n                      key={item.name}\n                      name={item.name}\n                      brand={item.brand}\n                      image={item.image}\n                      quantity={item.quantity}\n                      price={item.dineroPrice}\n                      currencyCode={currencyCode}\n                      onIncrease={() =>\n                        updateItem(item.name, (prev) => ({\n                          ...prev,\n                          quantity: prev.quantity + 1,\n                        }))\n                      }\n                      onDecrease={() =>\n                        updateItem(item.name, (prev) =>\n                          prev.quantity > 1\n                            ? { ...prev, quantity: prev.quantity - 1 }\n                            : prev\n                        )\n                      }\n                      onRemove={() => updateItem(item.name, () => null)}\n                    />\n                  ))}\n                </div>\n              </div>\n            ) : (\n              <div className=\"flex h-full items-center justify-center\">\n                <p className=\"text-sm text-text-muted\">Your cart is empty.</p>\n              </div>\n            )}\n          </div>\n        </div>\n        <aside className=\"w-full shrink-0 bg-card p-6 lg:w-96\">\n          <OrderSummary\n            itemCount={count}\n            subtotal={subtotal}\n            vatAmount={vatAmount}\n            vatRate={VAT_RATE}\n            shippingAmount={shippingAmount}\n            total={total}\n            currencyCode={currencyCode}\n            shipping={shipping}\n            shippingOptions={convertedShippingOptions}\n            hasItems={hasItems}\n            onShippingChange={setShipping}\n          />\n        </aside>\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "examples/cart-react/src/components/cart-line.tsx",
    "content": "import type { Dinero } from 'dinero.js';\nimport { Minus, Plus, Trash2 } from 'lucide-react';\n\nimport { multiply, formatMoney } from '@/lib/money';\nimport type { CurrencyCode } from '@/types';\n\ninterface CartLineProps {\n  name: string;\n  brand: string;\n  image: string;\n  quantity: number;\n  price: Dinero<number>;\n  currencyCode: CurrencyCode;\n  onIncrease: () => void;\n  onDecrease: () => void;\n  onRemove: () => void;\n}\n\nexport function CartLine({\n  name,\n  brand,\n  image,\n  quantity,\n  price,\n  currencyCode,\n  onIncrease,\n  onDecrease,\n  onRemove,\n}: CartLineProps) {\n  const totalPrice = multiply(price, quantity);\n  const canDecrease = quantity > 1;\n\n  return (\n    <div className=\"flex flex-col gap-3 rounded-lg px-4 py-4 transition-colors duration-150 hover:bg-muted/50 sm:flex-row sm:items-center sm:gap-4\">\n      <div className=\"flex min-w-0 flex-1 items-center gap-4\">\n        <div className=\"flex h-16 w-16 shrink-0 items-center justify-center rounded-lg bg-white p-2 xl:h-20 xl:w-20\">\n          <img\n            className=\"h-12 w-auto object-contain xl:h-16\"\n            src={image}\n            alt=\"\"\n            width={64}\n            height={64}\n            loading=\"lazy\"\n          />\n        </div>\n        <div className=\"flex min-w-0 flex-col gap-1\">\n          <h3 className=\"truncate text-sm font-semibold text-foreground\">\n            {name}\n          </h3>\n          <p className=\"text-xs text-text-muted\">{brand}</p>\n          <button\n            onClick={onRemove}\n            className=\"mt-1 inline-flex w-fit items-center gap-1 rounded text-xs text-text-muted transition-colors duration-150 hover:text-destructive focus-visible:ring-2 focus-visible:ring-ring\"\n            aria-label={`Remove ${name} from cart`}\n          >\n            <Trash2 className=\"h-3 w-3\" aria-hidden=\"true\" />\n            Remove\n          </button>\n        </div>\n      </div>\n      <div className=\"flex items-center justify-between gap-4 pl-20 sm:justify-end sm:pl-0\">\n        <div className=\"flex items-center gap-1.5\">\n          <button\n            className=\"touch-manipulation rounded-md border border-border p-1 transition-colors duration-150 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-30\"\n            disabled={!canDecrease}\n            onClick={onDecrease}\n            aria-label={`Decrease quantity of ${name}`}\n          >\n            <Minus\n              className=\"h-3.5 w-3.5 text-text-secondary\"\n              aria-hidden=\"true\"\n            />\n          </button>\n          <span className=\"w-8 text-center text-sm tabular-nums text-foreground\">\n            {quantity}\n          </span>\n          <button\n            className=\"touch-manipulation rounded-md border border-border p-1 transition-colors duration-150 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring\"\n            onClick={onIncrease}\n            aria-label={`Increase quantity of ${name}`}\n          >\n            <Plus\n              className=\"h-3.5 w-3.5 text-text-secondary\"\n              aria-hidden=\"true\"\n            />\n          </button>\n        </div>\n        <span className=\"text-right text-sm tabular-nums text-text-secondary sm:w-24\">\n          {formatMoney(price, currencyCode)}\n        </span>\n        <span className=\"text-right text-sm font-medium tabular-nums text-foreground sm:w-24\">\n          {formatMoney(totalPrice, currencyCode)}\n        </span>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/cart-react/src/components/order-summary.tsx",
    "content": "import type { Dinero } from 'dinero.js';\nimport { ShoppingCart } from 'lucide-react';\n\nimport { formatMoney } from '@/lib/money';\nimport type { CurrencyCode, ShippingOption } from '@/types';\n\ninterface OrderSummaryProps {\n  itemCount: number;\n  subtotal: Dinero<number>;\n  vatAmount: Dinero<number>;\n  vatRate: number;\n  shippingAmount: Dinero<number>;\n  total: Dinero<number>;\n  currencyCode: CurrencyCode;\n  shipping: string;\n  shippingOptions: Array<ShippingOption & { convertedPrice: Dinero<number> }>;\n  hasItems: boolean;\n  onShippingChange: (value: string) => void;\n}\n\nexport function OrderSummary({\n  itemCount,\n  subtotal,\n  vatAmount,\n  vatRate,\n  shippingAmount,\n  total,\n  currencyCode,\n  shipping,\n  shippingOptions,\n  hasItems,\n  onShippingChange,\n}: OrderSummaryProps) {\n  return (\n    <div className=\"flex h-full flex-col justify-between\">\n      <div>\n        <div className=\"flex items-center justify-between border-b border-border pb-6\">\n          <h2 className=\"text-lg font-semibold text-foreground\">\n            Order Summary\n          </h2>\n          <div className=\"relative\">\n            <ShoppingCart\n              className=\"h-5 w-5 text-text-secondary\"\n              aria-hidden=\"true\"\n            />\n            {itemCount > 0 && (\n              <span className=\"absolute -right-2 -top-1.5 rounded-full bg-primary px-1.5 text-[10px] font-medium text-primary-foreground\">\n                {itemCount}\n              </span>\n            )}\n          </div>\n        </div>\n        <div className=\"mt-6 space-y-4\">\n          <div className=\"flex items-center justify-between\">\n            <span className=\"text-sm text-text-secondary\">Subtotal</span>\n            <span className=\"text-sm font-medium tabular-nums text-foreground\">\n              {formatMoney(subtotal, currencyCode)}\n            </span>\n          </div>\n          <div className=\"flex items-center justify-between\">\n            <span className=\"text-sm text-text-secondary\">\n              VAT ({vatRate}%)\n            </span>\n            <span className=\"text-sm font-medium tabular-nums text-foreground\">\n              {formatMoney(vatAmount, currencyCode)}\n            </span>\n          </div>\n          <div>\n            <label\n              htmlFor=\"shipping\"\n              className=\"mb-2 block text-sm text-text-secondary\"\n            >\n              Shipping\n            </label>\n            <div className=\"flex items-center justify-between gap-4\">\n              <select\n                id=\"shipping\"\n                name=\"shipping\"\n                value={shipping}\n                onChange={(event) => onShippingChange(event.target.value)}\n                disabled={!hasItems}\n                className=\"block w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-30\"\n              >\n                {shippingOptions.map(({ label, convertedPrice }) => (\n                  <option key={label} value={label}>\n                    {label} — {formatMoney(convertedPrice, currencyCode)}\n                  </option>\n                ))}\n              </select>\n              <span className=\"shrink-0 text-sm font-medium tabular-nums text-foreground\">\n                {formatMoney(shippingAmount, currencyCode)}\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div className=\"mt-8 border-t border-border pt-6\">\n        <div className=\"mb-5 flex items-center justify-between\">\n          <span className=\"text-sm font-semibold text-foreground\">Total</span>\n          <span className=\"text-base font-semibold tabular-nums text-foreground\">\n            {formatMoney(total, currencyCode)}\n          </span>\n        </div>\n        <button\n          type=\"button\"\n          className=\"w-full touch-manipulation rounded-lg bg-primary py-3 text-sm font-semibold text-primary-foreground transition-colors duration-150 hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card\"\n        >\n          Checkout\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/cart-react/src/data/index.ts",
    "content": "export { default as items } from './items.json';\nexport { default as shipping } from './shipping.json';\n"
  },
  {
    "path": "examples/cart-react/src/data/items.json",
    "content": "[\n  {\n    \"name\": \"Apple iPhone 12\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/71ZOtNdaZCL._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 89900,\n    \"quantity\": 1\n  },\n  {\n    \"name\": \"Apple AirPods Pro\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/71lj9Fdeq0L._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 17495,\n    \"quantity\": 1\n  },\n  {\n    \"name\": \"Apple Lightning to USB-C Cable\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/41%2B7SQNld6L._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 1700,\n    \"quantity\": 2\n  }\n]\n"
  },
  {
    "path": "examples/cart-react/src/data/shipping.json",
    "content": "[\n  {\n    \"label\": \"Standard\",\n    \"price\": 500\n  },\n  {\n    \"label\": \"Tracked\",\n    \"price\": 1000\n  },\n  {\n    \"label\": \"Expedite\",\n    \"price\": 1500\n  }\n]\n"
  },
  {
    "path": "examples/cart-react/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #ededef;\n  --card: #131416;\n  --card-foreground: #ededef;\n  --popover: #131416;\n  --popover-foreground: #ededef;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #131416;\n  --secondary-foreground: #ededef;\n  --muted: #08090a;\n  --muted-foreground: #a1a1a9;\n  --accent: #131416;\n  --accent-foreground: #ededef;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.08);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.5rem;\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.2);\n}\n\n/* Hide number input spinners */\ninput[type='number']::-webkit-outer-spin-button,\ninput[type='number']::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n\n/* Select dropdown arrow */\nselect {\n  appearance: none;\n  cursor: pointer;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236e6e76' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\");\n  background-position: right 0.75rem center;\n  background-repeat: no-repeat;\n  background-size: 1.25em 1.25em;\n  padding-right: 2.5rem;\n}\n"
  },
  {
    "path": "examples/cart-react/src/lib/money.ts",
    "content": "import { dinero, add, multiply, allocate, toDecimal, convert } from 'dinero.js';\nimport type { Dinero, DineroCurrency } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nimport type { CurrencyCode } from '@/types';\n\nconst CURRENCIES_MAP = { USD, EUR } as const;\n\nconst CURRENCY_LOCALES: Record<CurrencyCode, string> = {\n  USD: 'en-US',\n  EUR: 'fr-FR',\n};\n\nexport function currencyFor<TCode extends CurrencyCode>(\n  code: TCode\n): DineroCurrency<number, TCode> {\n  return CURRENCIES_MAP[code] as DineroCurrency<number, TCode>;\n}\n\nexport function zero(code: CurrencyCode): Dinero<number> {\n  return dinero({ amount: 0, currency: currencyFor(code) });\n}\n\nexport function fromMinorUnits(\n  amount: number,\n  code: CurrencyCode\n): Dinero<number> {\n  return dinero({ amount, currency: currencyFor(code) });\n}\n\nexport function convertCurrency(\n  amount: Dinero<number>,\n  targetCode: CurrencyCode\n): Dinero<number> {\n  if (targetCode === 'USD') {\n    return amount;\n  }\n\n  return convert(amount, currencyFor(targetCode), {\n    [currencyFor(targetCode).code]: { amount: 83, scale: 2 },\n  });\n}\n\nexport function formatMoney(\n  amount: Dinero<number>,\n  code: CurrencyCode\n): string {\n  const locale = CURRENCY_LOCALES[code];\n\n  return toDecimal(amount, ({ value, currency }) =>\n    Number(value).toLocaleString(locale, {\n      style: 'currency',\n      currency: currency.code,\n    })\n  );\n}\n\nexport { add, multiply, allocate };\n"
  },
  {
    "path": "examples/cart-react/src/main.tsx",
    "content": "import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport './index.css';\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>\n);\n"
  },
  {
    "path": "examples/cart-react/src/types/index.ts",
    "content": "export type CurrencyCode = 'USD' | 'EUR';\n\nexport interface CartItem {\n  name: string;\n  image: string;\n  brand: string;\n  price: number;\n  quantity: number;\n}\n\nexport interface ShippingOption {\n  label: string;\n  price: number;\n}\n"
  },
  {
    "path": "examples/cart-react/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/cart-react/vite.config.ts",
    "content": "import path from 'node:path';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/cart-react/',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "examples/cart-vue/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n*.tsbuildinfo"
  },
  {
    "path": "examples/cart-vue/README.md",
    "content": "# Shopping Cart (Vue)\n\nA Vue app that manages a multi-currency shopping cart with VAT and shipping, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/cart-vue/)\n\n## What you'll learn\n\n- Creating monetary values with `dinero`\n- Multiplying amounts with `multiply` for quantity pricing\n- Summing values with `add` for cart subtotals\n- Computing VAT with `allocate` for precise tax distribution\n- Converting currencies with `convert` and scaled exchange rates\n- Formatting money for display with `toDecimal`\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/cart-vue\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/cart-vue/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.json' {\n  const value: unknown;\n  export default value;\n}\n"
  },
  {
    "path": "examples/cart-vue/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Shopping Cart — Dinero.js Example</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/cart-vue/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-cart-vue\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"vue-tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-vue-next\": \"^0.475.0\",\n    \"vue\": \"^3.5.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@vitejs/plugin-vue\": \"^5.2.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\",\n    \"vue-tsc\": \"^2.2.0\"\n  }\n}\n"
  },
  {
    "path": "examples/cart-vue/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/cart-vue/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, computed } from 'vue';\n\nimport CartLine from '@/components/CartLine.vue';\nimport OrderSummary from '@/components/OrderSummary.vue';\nimport { items as initialItems, shipping as shippingOptions } from '@/data';\nimport {\n  add,\n  multiply,\n  allocate,\n  fromMinorUnits,\n  convertCurrency,\n  zero,\n} from '@/lib/money';\nimport type { CurrencyCode, CartItem } from '@/types';\n\nconst CURRENCY_OPTIONS: Array<{ code: CurrencyCode; symbol: string }> = [\n  { code: 'USD', symbol: '$' },\n  { code: 'EUR', symbol: '€' },\n];\n\nconst VAT_RATE = 20;\n\nconst items = ref<CartItem[]>(initialItems as CartItem[]);\nconst shipping = ref(shippingOptions[0].label);\nconst currencyCode = ref<CurrencyCode>('USD');\n\nconst hasItems = computed(() => items.value.length > 0);\n\nconst convertedItems = computed(() =>\n  items.value.map((item) => ({\n    ...item,\n    dineroPrice: convertCurrency(\n      fromMinorUnits(item.price, 'USD'),\n      currencyCode.value\n    ),\n  }))\n);\n\nconst convertedShippingOptions = computed(() =>\n  shippingOptions.map((option) => ({\n    ...option,\n    convertedPrice: convertCurrency(\n      fromMinorUnits(option.price, 'USD'),\n      currencyCode.value\n    ),\n  }))\n);\n\nconst shippingOption = computed(\n  () =>\n    convertedShippingOptions.value.find(\n      ({ label }) => label === shipping.value\n    )!\n);\n\nconst calculated = computed(() =>\n  convertedItems.value.reduce(\n    (acc, item) => ({\n      count: acc.count + item.quantity,\n      subtotal: add(acc.subtotal, multiply(item.dineroPrice, item.quantity)),\n    }),\n    { count: 0, subtotal: zero(currencyCode.value) }\n  )\n);\n\nconst shippingAmount = computed(() =>\n  hasItems.value\n    ? shippingOption.value.convertedPrice\n    : zero(currencyCode.value)\n);\n\nconst vatAmount = computed(\n  () => allocate(calculated.value.subtotal, [VAT_RATE, 100 - VAT_RATE])[0]\n);\n\nconst total = computed(() =>\n  [calculated.value.subtotal, vatAmount.value, shippingAmount.value].reduce(add)\n);\n\nfunction updateItem(\n  name: string,\n  updater: (item: CartItem) => CartItem | null\n) {\n  const index = items.value.findIndex((item) => item.name === name);\n\n  if (index === -1) return;\n\n  const result = updater(items.value[index]);\n\n  if (result === null) {\n    items.value = items.value.filter((_, i) => i !== index);\n  } else {\n    items.value = items.value.map((item, i) => (i === index ? result : item));\n  }\n}\n</script>\n\n<template>\n  <main class=\"flex h-screen flex-col\">\n    <header\n      class=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\"\n    >\n      <div class=\"flex min-w-0 items-center gap-2 sm:gap-3\">\n        <svg\n          viewBox=\"0 0 361.4 213.6\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          class=\"h-6 w-auto\"\n          aria-hidden=\"true\"\n        >\n          <path\n            fill=\"#4466ff\"\n            d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n          />\n          <path\n            fill=\"#fff\"\n            d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n          />\n          <path\n            fill=\"#4466ff\"\n            d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n          />\n          <path\n            fill=\"#f7a\"\n            d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n          />\n          <path\n            fill=\"#4466ff\"\n            d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n          />\n          <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n          <path\n            fill=\"#bcf\"\n            d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n          />\n          <path\n            fill=\"#4466ff\"\n            d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n          />\n          <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n        </svg>\n        <h1 class=\"truncate text-sm font-semibold text-foreground\">\n          Shopping Cart\n        </h1>\n        <span\n          class=\"hidden whitespace-nowrap rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline\"\n        >\n          Built with Dinero.js\n        </span>\n      </div>\n      <a\n        href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/cart-vue\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        class=\"shrink-0 rounded-sm text-xs text-text-muted transition-colors duration-150 hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/50\"\n      >\n        GitHub\n        <span class=\"hidden sm:inline\"> source</span>\n      </a>\n    </header>\n\n    <div class=\"flex min-h-0 flex-1 flex-col lg:flex-row\">\n      <div\n        class=\"flex flex-1 flex-col border-b border-border lg:border-b-0 lg:border-r\"\n      >\n        <div\n          class=\"flex items-center justify-between border-b border-border px-5 py-3\"\n        >\n          <span class=\"text-xs font-medium text-text-muted\">\n            {{ items.length }} {{ items.length === 1 ? 'item' : 'items' }}\n          </span>\n          <div class=\"flex items-center gap-2\">\n            <label for=\"currency\" class=\"text-xs text-text-muted\">\n              Currency\n            </label>\n            <select\n              id=\"currency\"\n              name=\"currency\"\n              :value=\"currencyCode\"\n              @change=\"\n                currencyCode = ($event.target as HTMLSelectElement)\n                  .value as CurrencyCode\n              \"\n              class=\"rounded-lg border border-border bg-muted px-3 py-1.5 text-sm text-foreground transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring\"\n            >\n              <option\n                v-for=\"option in CURRENCY_OPTIONS\"\n                :key=\"option.code\"\n                :value=\"option.code\"\n              >\n                {{ option.code }} ({{ option.symbol }})\n              </option>\n            </select>\n          </div>\n        </div>\n        <div class=\"flex-1 overflow-y-auto p-5\">\n          <div v-if=\"hasItems\">\n            <div class=\"mb-2 hidden items-center px-4 sm:flex\">\n              <span\n                class=\"min-w-0 flex-1 text-xs font-medium tracking-wide text-text-muted\"\n              >\n                Product\n              </span>\n              <span\n                class=\"w-[7.5rem] text-right text-xs font-medium tracking-wide text-text-muted\"\n              >\n                Quantity\n              </span>\n              <span\n                class=\"w-24 text-right text-xs font-medium tracking-wide text-text-muted\"\n              >\n                Price\n              </span>\n              <span\n                class=\"w-24 text-right text-xs font-medium tracking-wide text-text-muted\"\n              >\n                Total\n              </span>\n            </div>\n            <div class=\"space-y-1\">\n              <CartLine\n                v-for=\"item in convertedItems\"\n                :key=\"item.name\"\n                :name=\"item.name\"\n                :brand=\"item.brand\"\n                :image=\"item.image\"\n                :quantity=\"item.quantity\"\n                :price=\"item.dineroPrice\"\n                :currency-code=\"currencyCode\"\n                @increase=\"\n                  updateItem(item.name, (prev) => ({\n                    ...prev,\n                    quantity: prev.quantity + 1,\n                  }))\n                \"\n                @decrease=\"\n                  updateItem(item.name, (prev) =>\n                    prev.quantity > 1\n                      ? { ...prev, quantity: prev.quantity - 1 }\n                      : prev\n                  )\n                \"\n                @remove=\"updateItem(item.name, () => null)\"\n              />\n            </div>\n          </div>\n          <div v-else class=\"flex h-full items-center justify-center\">\n            <p class=\"text-sm text-text-muted\">Your cart is empty.</p>\n          </div>\n        </div>\n      </div>\n      <aside class=\"w-full shrink-0 bg-card p-6 lg:w-96\">\n        <OrderSummary\n          :item-count=\"calculated.count\"\n          :subtotal=\"calculated.subtotal\"\n          :vat-amount=\"vatAmount\"\n          :vat-rate=\"VAT_RATE\"\n          :shipping-amount=\"shippingAmount\"\n          :total=\"total\"\n          :currency-code=\"currencyCode\"\n          :shipping=\"shipping\"\n          :shipping-options=\"convertedShippingOptions\"\n          :has-items=\"hasItems\"\n          @update:shipping=\"shipping = $event\"\n        />\n      </aside>\n    </div>\n  </main>\n</template>\n"
  },
  {
    "path": "examples/cart-vue/src/components/CartLine.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Dinero } from 'dinero.js';\nimport { Minus, Plus, Trash2 } from 'lucide-vue-next';\nimport { computed } from 'vue';\n\nimport { multiply, formatMoney } from '@/lib/money';\nimport type { CurrencyCode } from '@/types';\n\nconst props = defineProps<{\n  name: string;\n  brand: string;\n  image: string;\n  quantity: number;\n  price: Dinero<number>;\n  currencyCode: CurrencyCode;\n}>();\n\nconst emit = defineEmits<{\n  increase: [];\n  decrease: [];\n  remove: [];\n}>();\n\nconst totalPrice = computed(() => multiply(props.price, props.quantity));\nconst canDecrease = computed(() => props.quantity > 1);\n</script>\n\n<template>\n  <div\n    class=\"flex flex-col gap-3 rounded-lg px-4 py-4 transition-colors duration-150 hover:bg-muted/50 sm:flex-row sm:items-center sm:gap-4\"\n  >\n    <div class=\"flex min-w-0 flex-1 items-center gap-4\">\n      <div\n        class=\"flex h-16 w-16 shrink-0 items-center justify-center rounded-lg bg-white p-2 xl:h-20 xl:w-20\"\n      >\n        <img\n          class=\"h-12 w-auto object-contain xl:h-16\"\n          :src=\"image\"\n          alt=\"\"\n          :width=\"64\"\n          :height=\"64\"\n          loading=\"lazy\"\n        />\n      </div>\n      <div class=\"flex min-w-0 flex-col gap-1\">\n        <h3 class=\"truncate text-sm font-semibold text-foreground\">\n          {{ name }}\n        </h3>\n        <p class=\"text-xs text-text-muted\">{{ brand }}</p>\n        <button\n          @click=\"emit('remove')\"\n          class=\"mt-1 inline-flex w-fit items-center gap-1 rounded text-xs text-text-muted transition-colors duration-150 hover:text-destructive focus-visible:ring-2 focus-visible:ring-ring\"\n          :aria-label=\"`Remove ${name} from cart`\"\n        >\n          <Trash2 class=\"h-3 w-3\" aria-hidden=\"true\" />\n          Remove\n        </button>\n      </div>\n    </div>\n    <div\n      class=\"flex items-center justify-between gap-4 pl-20 sm:justify-end sm:pl-0\"\n    >\n      <div class=\"flex items-center gap-1.5\">\n        <button\n          class=\"touch-manipulation rounded-md border border-border p-1 transition-colors duration-150 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-30\"\n          :disabled=\"!canDecrease\"\n          @click=\"emit('decrease')\"\n          :aria-label=\"`Decrease quantity of ${name}`\"\n        >\n          <Minus class=\"h-3.5 w-3.5 text-text-secondary\" aria-hidden=\"true\" />\n        </button>\n        <span class=\"w-8 text-center text-sm tabular-nums text-foreground\">\n          {{ quantity }}\n        </span>\n        <button\n          class=\"touch-manipulation rounded-md border border-border p-1 transition-colors duration-150 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring\"\n          @click=\"emit('increase')\"\n          :aria-label=\"`Increase quantity of ${name}`\"\n        >\n          <Plus class=\"h-3.5 w-3.5 text-text-secondary\" aria-hidden=\"true\" />\n        </button>\n      </div>\n      <span class=\"text-right text-sm tabular-nums text-text-secondary sm:w-24\">\n        {{ formatMoney(price, currencyCode) }}\n      </span>\n      <span\n        class=\"text-right text-sm font-medium tabular-nums text-foreground sm:w-24\"\n      >\n        {{ formatMoney(totalPrice, currencyCode) }}\n      </span>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/cart-vue/src/components/OrderSummary.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Dinero } from 'dinero.js';\nimport { ShoppingCart } from 'lucide-vue-next';\n\nimport { formatMoney } from '@/lib/money';\nimport type { CurrencyCode, ShippingOption } from '@/types';\n\ndefineProps<{\n  itemCount: number;\n  subtotal: Dinero<number>;\n  vatAmount: Dinero<number>;\n  vatRate: number;\n  shippingAmount: Dinero<number>;\n  total: Dinero<number>;\n  currencyCode: CurrencyCode;\n  shipping: string;\n  shippingOptions: Array<ShippingOption & { convertedPrice: Dinero<number> }>;\n  hasItems: boolean;\n}>();\n\nconst emit = defineEmits<{\n  'update:shipping': [value: string];\n}>();\n</script>\n\n<template>\n  <div class=\"flex h-full flex-col justify-between\">\n    <div>\n      <div\n        class=\"flex items-center justify-between border-b border-border pb-6\"\n      >\n        <h2 class=\"text-lg font-semibold text-foreground\">Order Summary</h2>\n        <div class=\"relative\">\n          <ShoppingCart\n            class=\"h-5 w-5 text-text-secondary\"\n            aria-hidden=\"true\"\n          />\n          <span\n            v-if=\"itemCount > 0\"\n            class=\"absolute -right-2 -top-1.5 rounded-full bg-primary px-1.5 text-[10px] font-medium text-primary-foreground\"\n          >\n            {{ itemCount }}\n          </span>\n        </div>\n      </div>\n      <div class=\"mt-6 space-y-4\">\n        <div class=\"flex items-center justify-between\">\n          <span class=\"text-sm text-text-secondary\">Subtotal</span>\n          <span class=\"text-sm font-medium tabular-nums text-foreground\">\n            {{ formatMoney(subtotal, currencyCode) }}\n          </span>\n        </div>\n        <div class=\"flex items-center justify-between\">\n          <span class=\"text-sm text-text-secondary\">\n            VAT ({{ vatRate }}%)\n          </span>\n          <span class=\"text-sm font-medium tabular-nums text-foreground\">\n            {{ formatMoney(vatAmount, currencyCode) }}\n          </span>\n        </div>\n        <div>\n          <label for=\"shipping\" class=\"mb-2 block text-sm text-text-secondary\">\n            Shipping\n          </label>\n          <div class=\"flex items-center justify-between gap-4\">\n            <select\n              id=\"shipping\"\n              name=\"shipping\"\n              :value=\"shipping\"\n              @change=\"\n                emit(\n                  'update:shipping',\n                  ($event.target as HTMLSelectElement).value\n                )\n              \"\n              :disabled=\"!hasItems\"\n              class=\"block w-full rounded-lg border border-border bg-muted px-3 py-2 text-sm text-foreground transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-30\"\n            >\n              <option\n                v-for=\"option in shippingOptions\"\n                :key=\"option.label\"\n                :value=\"option.label\"\n              >\n                {{ option.label }} —\n                {{ formatMoney(option.convertedPrice, currencyCode) }}\n              </option>\n            </select>\n            <span\n              class=\"shrink-0 text-sm font-medium tabular-nums text-foreground\"\n            >\n              {{ formatMoney(shippingAmount, currencyCode) }}\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"mt-8 border-t border-border pt-6\">\n      <div class=\"mb-5 flex items-center justify-between\">\n        <span class=\"text-sm font-semibold text-foreground\">Total</span>\n        <span class=\"text-base font-semibold tabular-nums text-foreground\">\n          {{ formatMoney(total, currencyCode) }}\n        </span>\n      </div>\n      <button\n        type=\"button\"\n        class=\"w-full touch-manipulation rounded-lg bg-primary py-3 text-sm font-semibold text-primary-foreground transition-colors duration-150 hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card\"\n      >\n        Checkout\n      </button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "examples/cart-vue/src/data/index.ts",
    "content": "export { default as items } from './items.json';\nexport { default as shipping } from './shipping.json';\n"
  },
  {
    "path": "examples/cart-vue/src/data/items.json",
    "content": "[\n  {\n    \"name\": \"Apple iPhone 12\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/71ZOtNdaZCL._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 89900,\n    \"quantity\": 1\n  },\n  {\n    \"name\": \"Apple AirPods Pro\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/71lj9Fdeq0L._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 17495,\n    \"quantity\": 1\n  },\n  {\n    \"name\": \"Apple Lightning to USB-C Cable\",\n    \"image\": \"https://images-na.ssl-images-amazon.com/images/I/41%2B7SQNld6L._AC_SX679_.jpg\",\n    \"brand\": \"Apple\",\n    \"price\": 1700,\n    \"quantity\": 2\n  }\n]\n"
  },
  {
    "path": "examples/cart-vue/src/data/shipping.json",
    "content": "[\n  {\n    \"label\": \"Standard\",\n    \"price\": 500\n  },\n  {\n    \"label\": \"Tracked\",\n    \"price\": 1000\n  },\n  {\n    \"label\": \"Expedite\",\n    \"price\": 1500\n  }\n]\n"
  },
  {
    "path": "examples/cart-vue/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #ededef;\n  --card: #131416;\n  --card-foreground: #ededef;\n  --popover: #131416;\n  --popover-foreground: #ededef;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #131416;\n  --secondary-foreground: #ededef;\n  --muted: #08090a;\n  --muted-foreground: #a1a1a9;\n  --accent: #131416;\n  --accent-foreground: #ededef;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.08);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.5rem;\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.2);\n}\n\n/* Hide number input spinners */\ninput[type='number']::-webkit-outer-spin-button,\ninput[type='number']::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n\n/* Select dropdown arrow */\nselect {\n  appearance: none;\n  cursor: pointer;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236e6e76' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\");\n  background-position: right 0.75rem center;\n  background-repeat: no-repeat;\n  background-size: 1.25em 1.25em;\n  padding-right: 2.5rem;\n}\n"
  },
  {
    "path": "examples/cart-vue/src/lib/money.ts",
    "content": "import { dinero, add, multiply, allocate, toDecimal, convert } from 'dinero.js';\nimport type { Dinero, DineroCurrency } from 'dinero.js';\nimport { USD, EUR } from 'dinero.js/currencies';\n\nimport type { CurrencyCode } from '@/types';\n\nconst CURRENCIES_MAP = { USD, EUR } as const;\n\nconst CURRENCY_LOCALES: Record<CurrencyCode, string> = {\n  USD: 'en-US',\n  EUR: 'fr-FR',\n};\n\nexport function currencyFor<TCode extends CurrencyCode>(\n  code: TCode\n): DineroCurrency<number, TCode> {\n  return CURRENCIES_MAP[code] as DineroCurrency<number, TCode>;\n}\n\nexport function zero(code: CurrencyCode): Dinero<number> {\n  return dinero({ amount: 0, currency: currencyFor(code) });\n}\n\nexport function fromMinorUnits(\n  amount: number,\n  code: CurrencyCode\n): Dinero<number> {\n  return dinero({ amount, currency: currencyFor(code) });\n}\n\nexport function convertCurrency(\n  amount: Dinero<number>,\n  targetCode: CurrencyCode\n): Dinero<number> {\n  if (targetCode === 'USD') {\n    return amount;\n  }\n\n  return convert(amount, currencyFor(targetCode), {\n    [currencyFor(targetCode).code]: { amount: 83, scale: 2 },\n  });\n}\n\nexport function formatMoney(\n  amount: Dinero<number>,\n  code: CurrencyCode\n): string {\n  const locale = CURRENCY_LOCALES[code];\n\n  return toDecimal(amount, ({ value, currency }) =>\n    Number(value).toLocaleString(locale, {\n      style: 'currency',\n      currency: currency.code,\n    })\n  );\n}\n\nexport { add, multiply, allocate };\n"
  },
  {
    "path": "examples/cart-vue/src/main.ts",
    "content": "import { createApp } from 'vue';\n\nimport App from './App.vue';\n\nimport './index.css';\n\ncreateApp(App).mount('#app');\n"
  },
  {
    "path": "examples/cart-vue/src/types/index.ts",
    "content": "export type CurrencyCode = 'USD' | 'EUR';\n\nexport interface CartItem {\n  name: string;\n  image: string;\n  brand: string;\n  price: number;\n  quantity: number;\n}\n\nexport interface ShippingOption {\n  label: string;\n  price: number;\n}\n"
  },
  {
    "path": "examples/cart-vue/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"preserve\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.vue\", \"env.d.ts\"]\n}\n"
  },
  {
    "path": "examples/cart-vue/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [{ \"path\": \"./tsconfig.app.json\" }]\n}\n"
  },
  {
    "path": "examples/cart-vue/vite.config.ts",
    "content": "import path from 'node:path';\nimport vue from '@vitejs/plugin-vue';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/cart-vue/',\n  plugins: [vue()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "examples/expense-splitter/.gitignore",
    "content": "node_modules\ndist\n"
  },
  {
    "path": "examples/expense-splitter/README.md",
    "content": "# Expense Splitter\n\nA React app that splits expenses between friends and calculates optimal settlements, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/expense-splitter/)\n\n## What you'll learn\n\nThis example demonstrates how to use Dinero.js to solve real-world money problems:\n\n- **Splitting bills fairly** with `allocate()`, handling remainders so no cent is lost\n- **Tracking balances** with `add()` and `subtract()` to know who owes whom\n- **Simplifying debts** with `compare()`, `greaterThan()`, and `isZero()` to minimize the number of payments\n- **Formatting currency** with `toDecimal()` and `Intl.NumberFormat`\n- **Persisting money objects** with `toSnapshot()` and `dinero()` for LocalStorage serialization\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/expense-splitter\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/expense-splitter/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Dinero.js &amp; React | Expense Splitter</title>\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/expense-splitter/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-expense-splitter\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-react\": \"^0.475.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/expense-splitter/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/expense-splitter/src/App.tsx",
    "content": "import { useState } from 'react';\n\nimport type { Person, Expense } from '@/types';\n\nimport {\n  AddExpense,\n  AddPerson,\n  Balances,\n  ExpenseList,\n  PersonList,\n  Settlements,\n} from '@/components';\nimport { fromAmount, snapshot } from '@/lib/money';\n\ninterface StoredExpense {\n  id: string;\n  description: string;\n  amount: number;\n  paidBy: string;\n  splitType: 'equal' | 'percentage';\n  shares: { personId: string; value: number }[];\n  createdAt: string;\n}\n\nconst LOGO = (\n  <svg\n    viewBox=\"0 0 361.4 213.6\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"h-6 w-auto\"\n    aria-hidden=\"true\"\n  >\n    <path\n      fill=\"#4466ff\"\n      d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n    />\n    <path\n      fill=\"#fff\"\n      d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n    />\n    <path\n      fill=\"#f7a\"\n      d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n    />\n    <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n    <path\n      fill=\"#bcf\"\n      d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n    />\n    <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n  </svg>\n);\n\nconst DEFAULT_PEOPLE: Person[] = [\n  { id: 'alice', name: 'Alice' },\n  { id: 'bob', name: 'Bob' },\n  { id: 'charlie', name: 'Charlie' },\n  { id: 'diana', name: 'Diana' },\n];\n\nconst DEFAULT_EXPENSES: StoredExpense[] = [\n  {\n    id: 'sample-1',\n    description: 'Dinner',\n    amount: 12000,\n    paidBy: 'alice',\n    splitType: 'equal',\n    shares: [\n      { personId: 'alice', value: 25 },\n      { personId: 'bob', value: 25 },\n      { personId: 'charlie', value: 25 },\n      { personId: 'diana', value: 25 },\n    ],\n    createdAt: '2026-02-28T19:00:00.000Z',\n  },\n  {\n    id: 'sample-2',\n    description: 'Uber ride',\n    amount: 3500,\n    paidBy: 'bob',\n    splitType: 'equal',\n    shares: [\n      { personId: 'alice', value: 34 },\n      { personId: 'bob', value: 33 },\n      { personId: 'charlie', value: 33 },\n    ],\n    createdAt: '2026-02-28T21:00:00.000Z',\n  },\n  {\n    id: 'sample-3',\n    description: 'Concert tickets',\n    amount: 20000,\n    paidBy: 'charlie',\n    splitType: 'percentage',\n    shares: [\n      { personId: 'charlie', value: 40 },\n      { personId: 'alice', value: 30 },\n      { personId: 'diana', value: 30 },\n    ],\n    createdAt: '2026-03-01T10:00:00.000Z',\n  },\n];\n\nexport default function App() {\n  const [people, setPeople] = useState<Person[]>(DEFAULT_PEOPLE);\n  const [storedExpenses, setStoredExpenses] =\n    useState<StoredExpense[]>(DEFAULT_EXPENSES);\n\n  const expenses = deserializeExpenses(storedExpenses);\n\n  function setExpenses(updater: Expense[] | ((prev: Expense[]) => Expense[])) {\n    setStoredExpenses((prev) => {\n      const prevExpenses = deserializeExpenses(prev);\n      const next =\n        typeof updater === 'function' ? updater(prevExpenses) : updater;\n\n      return serializeExpenses(next);\n    });\n  }\n\n  return (\n    <div className=\"flex min-h-screen touch-manipulation flex-col lg:h-screen lg:overflow-hidden\">\n      <header className=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\">\n        <div className=\"flex min-w-0 items-center gap-2 sm:gap-3\">\n          {LOGO}\n          <span className=\"truncate text-sm font-semibold text-foreground\">\n            Expense Splitter\n          </span>\n          <span className=\"hidden whitespace-nowrap rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline\">\n            Built with Dinero.js\n          </span>\n        </div>\n        <a\n          href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/expense-splitter\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"shrink-0 text-xs text-text-muted transition-colors hover:text-foreground\"\n        >\n          GitHub\n          <span className=\"hidden sm:inline\"> source</span>\n        </a>\n      </header>\n\n      <div className=\"flex min-h-0 flex-1 flex-col lg:flex-row\">\n        <div className=\"w-full border-b border-border lg:w-120 lg:min-w-120 lg:overflow-y-auto lg:border-b-0 lg:border-r\">\n          <div className=\"space-y-6 p-5\">\n            <section>\n              <div className=\"mb-4 flex items-center gap-3\">\n                <h2 className=\"text-sm font-semibold text-foreground\">\n                  People\n                </h2>\n              </div>\n              <div className=\"space-y-4\">\n                <AddPerson\n                  onAdd={(person) => {\n                    setPeople((prev) => [...prev, person]);\n                  }}\n                />\n                <PersonList\n                  people={people}\n                  onRemove={(id: string) => {\n                    setPeople((prev) => prev.filter((p) => p.id !== id));\n                    setExpenses((prev) =>\n                      prev\n                        .filter((expense) => expense.paidBy !== id)\n                        .map((expense) => ({\n                          ...expense,\n                          shares: expense.shares.filter(\n                            ({ personId }) => personId !== id\n                          ),\n                        }))\n                        .filter((expense) => expense.shares.length > 0)\n                    );\n                  }}\n                />\n              </div>\n            </section>\n\n            <div className=\"border-t border-border\" />\n\n            <section>\n              <div className=\"mb-4 flex items-center gap-3\">\n                <h2 className=\"text-sm font-semibold text-foreground\">\n                  Add Expense\n                </h2>\n              </div>\n              <AddExpense\n                people={people}\n                onAdd={(expense: Expense) => {\n                  setExpenses((prev) => [expense, ...prev]);\n                }}\n              />\n            </section>\n          </div>\n        </div>\n\n        <div className=\"relative flex-1 overflow-y-auto bg-muted\">\n          <div className=\"pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,_rgba(68,102,255,0.08)_0%,_transparent_50%)]\" />\n          <div\n            className=\"pointer-events-none absolute inset-0 opacity-[0.03]\"\n            style={{\n              backgroundImage:\n                'radial-gradient(circle, currentColor 1px, transparent 1px)',\n              backgroundSize: '24px 24px',\n            }}\n          />\n          <div className=\"relative space-y-5 p-5\">\n            <section className=\"rounded-xl border border-border bg-card p-5 shadow-xl shadow-black/20\">\n              <div className=\"mb-4 flex items-center justify-between\">\n                <h2 className=\"text-sm font-semibold text-foreground\">\n                  Balances\n                </h2>\n              </div>\n              <Balances expenses={expenses} people={people} />\n              {expenses.length === 0 && (\n                <p className=\"py-6 text-center text-sm text-text-muted\">\n                  Add expenses to see balances\n                </p>\n              )}\n            </section>\n\n            <section className=\"rounded-xl border border-border bg-card p-5 shadow-xl shadow-black/20\">\n              <div className=\"mb-4 flex items-center justify-between\">\n                <h2 className=\"text-sm font-semibold text-foreground\">\n                  Settle Up\n                </h2>\n              </div>\n              <Settlements expenses={expenses} people={people} />\n              {expenses.length === 0 && (\n                <p className=\"py-6 text-center text-sm text-text-muted\">\n                  Add expenses to see settlements\n                </p>\n              )}\n            </section>\n\n            <section className=\"rounded-xl border border-border bg-card p-5 shadow-xl shadow-black/20\">\n              <div className=\"mb-4 flex items-center justify-between\">\n                <div className=\"flex items-center gap-3\">\n                  <h2 className=\"text-sm font-semibold text-foreground\">\n                    Expenses\n                  </h2>\n                  {expenses.length > 0 && (\n                    <span className=\"text-xs text-text-muted\">\n                      ({expenses.length})\n                    </span>\n                  )}\n                </div>\n                {(people.length > 0 || expenses.length > 0) && (\n                  <button\n                    onClick={() => {\n                      if (\n                        window.confirm(\n                          'Are you sure you want to clear all people and expenses? This cannot be undone.'\n                        )\n                      ) {\n                        setPeople([]);\n                        setExpenses([]);\n                      }\n                    }}\n                    className=\"rounded text-xs text-text-muted transition-colors hover:text-destructive focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-card focus-visible:outline-none\"\n                    aria-label=\"Clear all people and expenses\"\n                  >\n                    Clear all\n                  </button>\n                )}\n              </div>\n              <ExpenseList\n                expenses={expenses}\n                people={people}\n                onRemove={(id: string) => {\n                  setExpenses((prev) => prev.filter((e) => e.id !== id));\n                }}\n              />\n            </section>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction serializeExpenses(expenses: Expense[]): StoredExpense[] {\n  return expenses.map((expense) => ({\n    id: expense.id,\n    description: expense.description,\n    amount: snapshot(expense.amount),\n    paidBy: expense.paidBy,\n    splitType: expense.splitType,\n    shares: expense.shares,\n    createdAt: expense.createdAt.toISOString(),\n  }));\n}\n\nfunction deserializeExpenses(stored: StoredExpense[]): Expense[] {\n  return stored.map((expense) => ({\n    id: expense.id,\n    description: expense.description,\n    amount: fromAmount(expense.amount),\n    paidBy: expense.paidBy,\n    splitType: expense.splitType,\n    shares: expense.shares,\n    createdAt: new Date(expense.createdAt),\n  }));\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/add-expense.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport { Plus, Equal, Percent } from 'lucide-react';\n\nimport { fromAmount, toMinorUnits } from '@/lib/money';\nimport type { Person, Expense, SplitType, ExpenseShare } from '@/types';\n\ninterface AddExpenseProps {\n  people: Person[];\n  onAdd(expense: Expense): void;\n}\n\nexport function AddExpense({ people, onAdd }: AddExpenseProps) {\n  const [description, setDescription] = useState('');\n  const [amount, setAmount] = useState('');\n  const [paidBy, setPaidBy] = useState(people[0]?.id || '');\n  const [splitType, setSplitType] = useState<SplitType>('equal');\n  const [selectedPeople, setSelectedPeople] = useState<Set<string>>(\n    new Set(people.map((p) => p.id))\n  );\n  const [percentages, setPercentages] = useState<Record<string, number>>({});\n\n  useEffect(() => {\n    const peopleIds = new Set(people.map((p) => p.id));\n\n    if (!peopleIds.has(paidBy)) {\n      setPaidBy(people[0]?.id || '');\n    }\n\n    setSelectedPeople((prev) => {\n      const next = new Set([...prev].filter((id) => peopleIds.has(id)));\n\n      for (const person of people) {\n        if (!prev.has(person.id) && !next.has(person.id)) {\n          next.add(person.id);\n        }\n      }\n\n      return next;\n    });\n  }, [people]);\n\n  const totalPercentage = Array.from(selectedPeople).reduce(\n    (sum, id) => sum + (percentages[id] || 0),\n    0\n  );\n\n  const inputClasses =\n    'w-full rounded-md border border-border bg-input px-3 py-2 text-sm text-foreground placeholder:text-text-muted transition-[border-color,box-shadow] duration-150 focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-primary focus-visible:outline-none';\n\n  if (people.length < 2) {\n    return (\n      <div className=\"py-8 text-center\">\n        <div className=\"mb-4 inline-flex h-16 w-16 items-center justify-center rounded-xl bg-muted\">\n          <Plus className=\"h-8 w-8 text-text-muted\" />\n        </div>\n        <p className=\"text-sm text-text-muted\">\n          Add at least 2 people to start splitting expenses\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <form\n      onSubmit={(event) => {\n        event.preventDefault();\n\n        const amountInCents = toMinorUnits(amount);\n\n        if (!description.trim() || isNaN(amountInCents) || amountInCents <= 0) {\n          return;\n        }\n\n        let shares: ExpenseShare[] = [];\n\n        if (splitType === 'equal') {\n          shares = Array.from(selectedPeople).map((personId) => ({\n            personId,\n            value: 1,\n          }));\n        } else if (splitType === 'percentage') {\n          shares = Array.from(selectedPeople).map((personId) => ({\n            personId,\n            value: percentages[personId] || 0,\n          }));\n        }\n\n        const expense: Expense = {\n          id: crypto.randomUUID(),\n          description: description.trim(),\n          amount: fromAmount(amountInCents),\n          paidBy,\n          splitType,\n          shares,\n          createdAt: new Date(),\n        };\n\n        onAdd(expense);\n        setDescription('');\n        setAmount('');\n      }}\n      className=\"space-y-4\"\n    >\n      <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n        <div>\n          <label\n            htmlFor=\"description\"\n            className=\"mb-1.5 block text-xs font-medium text-text-secondary\"\n          >\n            Description\n          </label>\n          <input\n            type=\"text\"\n            id=\"description\"\n            value={description}\n            onChange={(event) => setDescription(event.target.value)}\n            placeholder=\"e.g., Dinner, Uber…\"\n            className={inputClasses}\n          />\n        </div>\n\n        <div>\n          <label\n            htmlFor=\"amount\"\n            className=\"mb-1.5 block text-xs font-medium text-text-secondary\"\n          >\n            Amount\n          </label>\n          <div className=\"relative\">\n            <div className=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3\">\n              <span className=\"text-sm text-text-muted\">$</span>\n            </div>\n            <input\n              type=\"number\"\n              id=\"amount\"\n              value={amount}\n              onChange={(event) => setAmount(event.target.value)}\n              placeholder=\"0.00\"\n              step=\"0.01\"\n              min=\"0\"\n              className={`${inputClasses} pl-7`}\n            />\n          </div>\n        </div>\n      </div>\n\n      <div>\n        <label\n          htmlFor=\"paidBy\"\n          className=\"mb-1.5 block text-xs font-medium text-text-secondary\"\n        >\n          Paid by\n        </label>\n        <select\n          id=\"paidBy\"\n          value={paidBy}\n          onChange={(event) => setPaidBy(event.target.value)}\n          className={inputClasses}\n        >\n          {people.map((person) => (\n            <option key={person.id} value={person.id} className=\"bg-background\">\n              {person.name}\n            </option>\n          ))}\n        </select>\n      </div>\n\n      <div>\n        <label className=\"mb-2 block text-xs font-medium text-text-secondary\">\n          Split type\n        </label>\n        <div className=\"flex gap-3\">\n          <label\n            className={`flex flex-1 cursor-pointer items-center justify-center gap-2 rounded-lg border p-2.5 text-sm transition-[border-color,background-color,color] ${\n              splitType === 'equal'\n                ? 'border-primary/30 bg-primary/10 text-primary'\n                : 'border-border bg-muted text-text-secondary hover:bg-card'\n            }`}\n          >\n            <input\n              type=\"radio\"\n              value=\"equal\"\n              checked={splitType === 'equal'}\n              onChange={(event) =>\n                setSplitType(event.target.value as SplitType)\n              }\n              className=\"sr-only\"\n            />\n            <Equal className=\"h-4 w-4\" />\n            <span className=\"font-medium\">Equal</span>\n          </label>\n          <label\n            className={`flex flex-1 cursor-pointer items-center justify-center gap-2 rounded-lg border p-2.5 text-sm transition-[border-color,background-color,color] ${\n              splitType === 'percentage'\n                ? 'border-primary/30 bg-primary/10 text-primary'\n                : 'border-border bg-muted text-text-secondary hover:bg-card'\n            }`}\n          >\n            <input\n              type=\"radio\"\n              value=\"percentage\"\n              checked={splitType === 'percentage'}\n              onChange={(event) =>\n                setSplitType(event.target.value as SplitType)\n              }\n              className=\"sr-only\"\n            />\n            <Percent className=\"h-4 w-4\" />\n            <span className=\"font-medium\">Percentage</span>\n          </label>\n        </div>\n      </div>\n\n      <div>\n        <label className=\"mb-2 block text-xs font-medium text-text-secondary\">\n          Split among\n        </label>\n        <div className=\"space-y-1.5\">\n          {people.map((person) => (\n            <div\n              key={person.id}\n              className={`flex items-center gap-3 rounded-lg p-2.5 transition-[border-color,background-color,color] ${\n                selectedPeople.has(person.id)\n                  ? 'bg-muted border border-border'\n                  : 'border border-transparent'\n              }`}\n            >\n              <label className=\"inline-flex flex-1 cursor-pointer items-center\">\n                <input\n                  type=\"checkbox\"\n                  checked={selectedPeople.has(person.id)}\n                  onChange={() => {\n                    const newSelected = new Set(selectedPeople);\n\n                    if (newSelected.has(person.id)) {\n                      newSelected.delete(person.id);\n                    } else {\n                      newSelected.add(person.id);\n                    }\n\n                    setSelectedPeople(newSelected);\n                  }}\n                />\n                <span className=\"ml-3 text-sm text-foreground\">\n                  {person.name}\n                </span>\n              </label>\n              {splitType === 'percentage' && selectedPeople.has(person.id) && (\n                <div className=\"flex items-center gap-2\">\n                  <input\n                    type=\"number\"\n                    value={percentages[person.id] || ''}\n                    onChange={(event) => {\n                      setPercentages((prev) => ({\n                        ...prev,\n                        [person.id]: parseFloat(event.target.value) || 0,\n                      }));\n                    }}\n                    placeholder=\"0\"\n                    min=\"0\"\n                    max=\"100\"\n                    aria-label={`Percentage for ${person.name}`}\n                    className=\"w-20 rounded-md border border-border bg-input px-3 py-1.5 text-center text-sm text-foreground placeholder:text-text-muted transition-[border-color,box-shadow] duration-150 focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-primary focus-visible:outline-none\"\n                  />\n                  <span className=\"text-sm text-text-muted\">%</span>\n                </div>\n              )}\n            </div>\n          ))}\n        </div>\n        {splitType === 'percentage' && (\n          <p\n            className={`mt-2 text-xs font-medium ${\n              totalPercentage === 100 ? 'text-positive' : 'text-warning'\n            }`}\n          >\n            Total: {totalPercentage}%\n            {totalPercentage !== 100 && ' (should be 100%)'}\n          </p>\n        )}\n      </div>\n\n      <button\n        type=\"submit\"\n        disabled={\n          !description.trim() ||\n          !amount ||\n          parseFloat(amount) <= 0 ||\n          selectedPeople.size === 0 ||\n          (splitType === 'percentage' && totalPercentage !== 100)\n        }\n        className=\"w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n      >\n        <span className=\"flex items-center justify-center gap-2\">\n          <Plus className=\"h-4 w-4\" />\n          Add Expense\n        </span>\n      </button>\n    </form>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/add-person.tsx",
    "content": "import { useState } from 'react';\nimport { Plus } from 'lucide-react';\n\nimport type { Person } from '@/types';\n\ninterface AddPersonProps {\n  onAdd(person: Person): void;\n}\n\nexport function AddPerson({ onAdd }: AddPersonProps) {\n  const [name, setName] = useState('');\n\n  return (\n    <form\n      onSubmit={(event) => {\n        event.preventDefault();\n\n        if (!name.trim()) {\n          return;\n        }\n\n        onAdd({ id: crypto.randomUUID(), name: name.trim() });\n        setName('');\n      }}\n      className=\"flex gap-3\"\n    >\n      <input\n        type=\"text\"\n        value={name}\n        onChange={(event) => setName(event.target.value)}\n        placeholder=\"Enter name…\"\n        aria-label=\"Person name\"\n        className=\"flex-1 rounded-md border border-border bg-input px-3 py-2 text-sm text-foreground placeholder:text-text-muted transition-[border-color,box-shadow] duration-150 focus-visible:border-primary focus-visible:ring-1 focus-visible:ring-primary focus-visible:outline-none\"\n      />\n      <button\n        type=\"submit\"\n        disabled={!name.trim()}\n        className=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n      >\n        <span className=\"flex items-center gap-2\">\n          <Plus className=\"h-4 w-4\" />\n          Add\n        </span>\n      </button>\n    </form>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/balances.tsx",
    "content": "import { ArrowUp, ArrowDown, Check } from 'lucide-react';\n\nimport {\n  calculateNetBalances,\n  formatMoney,\n  isNegative,\n  isPositive,\n  isZero,\n  negate,\n} from '@/lib/money';\nimport type { Expense, Person } from '@/types';\n\ninterface BalancesProps {\n  expenses: Expense[];\n  people: Person[];\n}\n\nexport function Balances({ expenses, people }: BalancesProps) {\n  const netBalances = calculateNetBalances(expenses, people);\n\n  if (expenses.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      {Array.from(netBalances.entries()).map(([personId, balance]) => {\n        const isOwed = isPositive(balance);\n        const owes = isNegative(balance);\n        const settled = isZero(balance);\n\n        return (\n          <div\n            key={personId}\n            className={`flex items-center justify-between rounded-lg border p-3 ${\n              isOwed\n                ? 'border-positive/20 bg-positive/[0.08]'\n                : owes\n                  ? 'border-destructive/20 bg-destructive/[0.08]'\n                  : 'border-border bg-muted'\n            }`}\n          >\n            <span className=\"text-sm font-medium text-foreground\">\n              {people.find(({ id }) => id === personId)?.name || 'Unknown'}\n            </span>\n            {settled ? (\n              <span className=\"flex items-center gap-1.5 text-sm text-text-muted\">\n                <Check className=\"h-3.5 w-3.5\" />\n                Settled\n              </span>\n            ) : isOwed ? (\n              <span className=\"flex items-center gap-1.5 text-sm font-semibold tabular-nums text-positive\">\n                <ArrowUp className=\"h-3.5 w-3.5\" />\n                {formatMoney(balance)}\n              </span>\n            ) : (\n              <span className=\"flex items-center gap-1.5 text-sm font-semibold tabular-nums text-destructive\">\n                <ArrowDown className=\"h-3.5 w-3.5\" />\n                {formatMoney(isNegative(balance) ? negate(balance) : balance)}\n              </span>\n            )}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/expense-list.tsx",
    "content": "import { ClipboardList } from 'lucide-react';\n\nimport { calculateShares, formatMoney, isPositive } from '@/lib/money';\nimport type { Expense, Person } from '@/types';\n\ninterface ExpenseListProps {\n  expenses: Expense[];\n  people: Person[];\n  onRemove(id: string): void;\n}\n\nexport function ExpenseList({ expenses, people, onRemove }: ExpenseListProps) {\n  function getPersonName(id: string) {\n    return people.find((person) => person.id === id)?.name || 'Unknown';\n  }\n\n  if (expenses.length === 0) {\n    return (\n      <div className=\"py-12 text-center\">\n        <div className=\"mb-4 inline-flex h-16 w-16 items-center justify-center rounded-xl bg-muted\">\n          <ClipboardList className=\"h-8 w-8 text-text-muted\" />\n        </div>\n        <p className=\"text-sm text-text-muted\">No expenses yet</p>\n        <p className=\"mt-1 text-xs text-text-muted\">\n          Add your first expense above\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-3\">\n      {expenses.map((expense) => {\n        const shares = calculateShares(expense, people);\n        const payer = getPersonName(expense.paidBy);\n\n        return (\n          <div\n            key={expense.id}\n            className=\"group rounded-lg border border-border bg-muted p-4\"\n          >\n            <div className=\"flex items-start justify-between gap-4\">\n              <div className=\"min-w-0 flex-1\">\n                <h3 className=\"truncate text-sm font-semibold text-foreground\">\n                  {expense.description}\n                </h3>\n                <p className=\"mt-0.5 text-xs text-text-muted\">\n                  Paid by{' '}\n                  <span className=\"font-medium text-text-secondary\">\n                    {payer}\n                  </span>\n                </p>\n              </div>\n              <div className=\"shrink-0 text-right\">\n                <p className=\"text-lg font-bold tabular-nums text-foreground\">\n                  {formatMoney(expense.amount)}\n                </p>\n                <button\n                  onClick={() => onRemove(expense.id)}\n                  aria-label={`Remove expense: ${expense.description}`}\n                  className=\"mt-1 rounded text-xs text-text-muted transition-colors hover:text-destructive focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-muted focus-visible:outline-none sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100\"\n                >\n                  Remove\n                </button>\n              </div>\n            </div>\n\n            <div className=\"mt-3 border-t border-border pt-3\">\n              <div className=\"mb-2 flex items-center gap-2\">\n                <span className=\"text-[10px] uppercase tracking-wider text-text-muted\">\n                  Split\n                </span>\n                <span className=\"rounded-full bg-card px-2 py-0.5 text-[10px] text-text-muted\">\n                  {expense.splitType}\n                </span>\n              </div>\n              <div className=\"flex flex-wrap gap-2\">\n                {Array.from(shares.entries())\n                  .filter(([_, amount]) => isPositive(amount))\n                  .map(([personId, amount]) => (\n                    <span\n                      key={personId}\n                      className=\"inline-flex items-center gap-1.5 rounded-lg bg-card px-3 py-1.5 text-xs\"\n                    >\n                      <span className=\"text-text-muted\">\n                        {getPersonName(personId)}\n                      </span>\n                      <span className=\"font-medium text-foreground\">\n                        {formatMoney(amount)}\n                      </span>\n                    </span>\n                  ))}\n              </div>\n            </div>\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/index.ts",
    "content": "export * from './add-expense';\nexport * from './add-person';\nexport * from './balances';\nexport * from './expense-list';\nexport * from './person-list';\nexport * from './settlements';\n"
  },
  {
    "path": "examples/expense-splitter/src/components/person-list.tsx",
    "content": "import { Users, X } from 'lucide-react';\n\nimport type { Person } from '@/types';\n\ninterface PersonListProps {\n  people: Person[];\n  onRemove(id: string): void;\n}\n\nexport function PersonList({ people, onRemove }: PersonListProps) {\n  if (people.length === 0) {\n    return (\n      <div className=\"py-8 text-center\">\n        <div className=\"mb-4 inline-flex h-16 w-16 items-center justify-center rounded-xl bg-muted\">\n          <Users className=\"h-8 w-8 text-text-muted\" />\n        </div>\n        <p className=\"text-sm text-text-muted\">\n          Add people to split expenses with\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex flex-wrap gap-2\">\n      {people.map((person) => (\n        <div\n          key={person.id}\n          className=\"group inline-flex items-center gap-2 rounded-full border border-border bg-card px-4 py-2 text-sm font-medium text-text-secondary\"\n        >\n          <span className=\"font-medium\">{person.name}</span>\n          <button\n            onClick={() => onRemove(person.id)}\n            className=\"rounded-full opacity-60 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none focus-visible:opacity-100\"\n            aria-label={`Remove ${person.name}`}\n          >\n            <X className=\"h-3.5 w-3.5\" />\n          </button>\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/components/settlements.tsx",
    "content": "import { ArrowRight, Check } from 'lucide-react';\n\nimport { calculateSettlements, formatMoney } from '@/lib/money';\nimport type { Expense, Person } from '@/types';\n\ninterface SettlementsProps {\n  expenses: Expense[];\n  people: Person[];\n}\n\nexport function Settlements({ expenses, people }: SettlementsProps) {\n  const settlements = calculateSettlements(expenses, people);\n\n  function getPersonName(id: string) {\n    return people.find((person) => person.id === id)?.name || 'Unknown';\n  }\n\n  if (expenses.length === 0) {\n    return null;\n  }\n\n  if (settlements.length === 0) {\n    return (\n      <div className=\"py-8 text-center\">\n        <div className=\"mb-4 inline-flex h-16 w-16 items-center justify-center rounded-xl bg-positive/[0.08]\">\n          <Check className=\"h-8 w-8 text-positive\" />\n        </div>\n        <p className=\"text-lg font-semibold text-positive\">All settled up!</p>\n        <p className=\"mt-1 text-sm text-text-muted\">No payments needed</p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-3\">\n      <p className=\"text-xs text-text-muted\">\n        <span className=\"font-medium text-foreground\">\n          {settlements.length}\n        </span>{' '}\n        payment{settlements.length !== 1 ? 's' : ''} to settle:\n      </p>\n      {settlements.map((settlement) => (\n        <div\n          key={`${settlement.from}-${settlement.to}`}\n          className=\"rounded-lg border border-border bg-muted p-4\"\n        >\n          <div className=\"flex items-center justify-between gap-4\">\n            <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n              <span className=\"text-sm font-medium text-foreground\">\n                {getPersonName(settlement.from)}\n              </span>\n              <ArrowRight className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n              <span className=\"text-sm font-medium text-foreground\">\n                {getPersonName(settlement.to)}\n              </span>\n            </div>\n            <span className=\"shrink-0 text-lg font-bold tabular-nums text-primary\">\n              {formatMoney(settlement.amount)}\n            </span>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #ededef;\n  --card: #131416;\n  --card-foreground: #ededef;\n  --popover: #131416;\n  --popover-foreground: #ededef;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #131416;\n  --secondary-foreground: #ededef;\n  --muted: #08090a;\n  --muted-foreground: #a1a1a9;\n  --accent: #131416;\n  --accent-foreground: #ededef;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.08);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.5rem;\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.2);\n}\n\n/* Hide number input spinners */\ninput[type='number']::-webkit-outer-spin-button,\ninput[type='number']::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n\n/* Select dropdown arrow */\nselect {\n  appearance: none;\n  cursor: pointer;\n  background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236e6e76' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e\");\n  background-position: right 0.75rem center;\n  background-repeat: no-repeat;\n  background-size: 1.25em 1.25em;\n  padding-right: 2.5rem;\n}\n\n/* Checkbox styling */\ninput[type='checkbox'] {\n  width: 1.125rem;\n  height: 1.125rem;\n  border-radius: 0.25rem;\n  border: 1px solid var(--border);\n  background: var(--input);\n  cursor: pointer;\n  accent-color: var(--primary);\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/lib/money.ts",
    "content": "import type { Dinero } from 'dinero.js';\nimport {\n  dinero,\n  add,\n  subtract,\n  multiply,\n  allocate,\n  toDecimal,\n  toSnapshot,\n  isZero,\n  isPositive,\n  isNegative,\n  greaterThan,\n  compare,\n} from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nimport type { Expense, Person, Settlement } from '@/types';\n\nexport const currency = USD;\n\nconst formatter = new Intl.NumberFormat('en-US', {\n  style: 'currency',\n  currency: 'USD',\n});\n\nexport function zero(): Dinero<number> {\n  return dinero({ amount: 0, currency: USD });\n}\n\nexport function fromAmount(amount: number): Dinero<number> {\n  return dinero({ amount, currency: USD });\n}\n\n/**\n * Convert a decimal string (e.g. \"19.99\") to an integer amount in minor units.\n */\nexport function toMinorUnits(value: string): number {\n  return Math.round(parseFloat(value) * 100);\n}\n\nexport function snapshot(amount: Dinero<number>): number {\n  return toSnapshot(amount).amount;\n}\n\nexport function formatMoney(amount: Dinero<number>): string {\n  return toDecimal(amount, ({ value }) => {\n    return formatter.format(Number(value));\n  });\n}\n\nexport { isZero, isPositive, isNegative };\n\nexport function negate(amount: Dinero<number>): Dinero<number> {\n  return multiply(amount, -1);\n}\n\n/**\n * Calculate individual shares for an expense based on split type.\n */\nexport function calculateShares(\n  expense: Expense,\n  people: Person[]\n): Map<string, Dinero<number>> {\n  const shares = new Map<string, Dinero<number>>();\n\n  switch (expense.splitType) {\n    case 'equal': {\n      const participantIds =\n        expense.shares.length > 0\n          ? expense.shares.map(({ personId }) => personId)\n          : people.map(({ id }) => id);\n      const ratios = participantIds.map(() => 1);\n      const allocated = allocate(expense.amount, ratios);\n\n      participantIds.forEach((personId, index) => {\n        shares.set(personId, allocated[index]);\n      });\n\n      break;\n    }\n\n    case 'percentage': {\n      const ratios = expense.shares.map(({ value }) => value);\n      const allocated = allocate(expense.amount, ratios);\n\n      expense.shares.forEach((share, index) => {\n        shares.set(share.personId, allocated[index]);\n      });\n\n      break;\n    }\n  }\n\n  for (const person of people) {\n    if (!shares.has(person.id)) {\n      shares.set(person.id, zero());\n    }\n  }\n\n  return shares;\n}\n\n/**\n * Calculate what each person owes or is owed based on expenses.\n * Returns a map of net balance per `personId`.\n */\nexport function calculateNetBalances(\n  expenses: Expense[],\n  people: Person[]\n): Map<string, Dinero<number>> {\n  const balances = new Map<string, Dinero<number>>();\n\n  for (const person of people) {\n    balances.set(person.id, zero());\n  }\n\n  for (const expense of expenses) {\n    const shares = calculateShares(expense, people);\n\n    balances.set(\n      expense.paidBy,\n      add(balances.get(expense.paidBy)!, expense.amount)\n    );\n\n    for (const [personId, share] of shares) {\n      balances.set(personId, subtract(balances.get(personId)!, share));\n    }\n  }\n\n  return balances;\n}\n\n/**\n * Calculate optimal settlements to minimize transactions.\n * Uses a greedy algorithm: match the largest creditor with the largest debtor.\n */\nexport function calculateSettlements(\n  expenses: Expense[],\n  people: Person[]\n): Settlement[] {\n  const netBalances = calculateNetBalances(expenses, people);\n  const settlements: Settlement[] = [];\n\n  const creditors: { id: string; amount: Dinero<number> }[] = [];\n  const debtors: { id: string; amount: Dinero<number> }[] = [];\n\n  for (const [personId, balance] of netBalances) {\n    if (isPositive(balance)) {\n      creditors.push({ id: personId, amount: balance });\n    } else if (isNegative(balance)) {\n      debtors.push({ id: personId, amount: negate(balance) });\n    }\n  }\n\n  creditors.sort((a, b) => compare(b.amount, a.amount));\n  debtors.sort((a, b) => compare(b.amount, a.amount));\n\n  let i = 0;\n  let j = 0;\n\n  while (i < creditors.length && j < debtors.length) {\n    const creditor = creditors[i];\n    const debtor = debtors[j];\n\n    if (isZero(creditor.amount)) {\n      i++;\n\n      continue;\n    }\n\n    if (isZero(debtor.amount)) {\n      j++;\n\n      continue;\n    }\n\n    let settlementAmount: Dinero<number>;\n\n    if (greaterThan(creditor.amount, debtor.amount)) {\n      settlementAmount = debtor.amount;\n      creditor.amount = subtract(creditor.amount, debtor.amount);\n      debtor.amount = zero();\n\n      j++;\n    } else if (greaterThan(debtor.amount, creditor.amount)) {\n      settlementAmount = creditor.amount;\n      debtor.amount = subtract(debtor.amount, creditor.amount);\n      creditor.amount = zero();\n\n      i++;\n    } else {\n      settlementAmount = creditor.amount;\n      creditor.amount = zero();\n      debtor.amount = zero();\n\n      i++;\n      j++;\n    }\n\n    settlements.push({\n      from: debtor.id,\n      to: creditor.id,\n      amount: settlementAmount,\n    });\n  }\n\n  return settlements;\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/main.tsx",
    "content": "import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport './index.css';\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>\n);\n"
  },
  {
    "path": "examples/expense-splitter/src/types/index.ts",
    "content": "import type { Dinero } from 'dinero.js';\n\nexport type SplitType = 'equal' | 'percentage';\n\nexport interface Person {\n  id: string;\n  name: string;\n}\n\nexport interface ExpenseShare {\n  personId: string;\n  value: number;\n}\n\nexport interface Expense {\n  id: string;\n  description: string;\n  amount: Dinero<number>;\n  paidBy: string;\n  splitType: SplitType;\n  shares: ExpenseShare[];\n  createdAt: Date;\n}\n\nexport interface Settlement {\n  from: string;\n  to: string;\n  amount: Dinero<number>;\n}\n"
  },
  {
    "path": "examples/expense-splitter/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "examples/expense-splitter/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/expense-splitter/vite.config.ts",
    "content": "import path from 'node:path';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/expense-splitter/',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "examples/invoice-builder/.gitignore",
    "content": "node_modules\ndist\n"
  },
  {
    "path": "examples/invoice-builder/README.md",
    "content": "# Invoice Builder\n\nA React app that creates, calculates, and previews professional invoices with multi-currency support, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/invoice-builder/)\n\n## What you'll learn\n\n- Creating monetary values with `dinero`\n- Multiplying amounts with `multiply` for line item totals\n- Summing values with `add` for subtotals\n- Computing discounts and taxes with `allocate` for precise ratio-based distribution\n- Subtracting amounts with `subtract` for net calculations\n- Formatting money for display with `toDecimal`\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/invoice-builder\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/invoice-builder/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Dinero.js &amp; React | Invoice Builder</title>\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/invoice-builder/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-invoice-builder\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-react\": \"^0.475.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/invoice-builder/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/invoice-builder/src/App.tsx",
    "content": "import { useInvoice } from '@/hooks/use-invoice';\nimport { EditorPanel } from '@/components/editor-panel';\nimport { PreviewPanel } from '@/components/preview-panel';\n\nconst LOGO = (\n  <svg\n    viewBox=\"0 0 361.4 213.6\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"h-6 w-auto\"\n    aria-hidden=\"true\"\n  >\n    <path\n      fill=\"#4466ff\"\n      d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n    />\n    <path\n      fill=\"#fff\"\n      d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n    />\n    <path\n      fill=\"#f7a\"\n      d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n    />\n    <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n    <path\n      fill=\"#bcf\"\n      d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n    />\n    <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n  </svg>\n);\n\nexport default function App() {\n  const {\n    invoice,\n    updateField,\n    addLineItem,\n    removeLineItem,\n    updateLineItem,\n    setCurrency,\n    setDiscountType,\n  } = useInvoice();\n\n  return (\n    <div className=\"flex min-h-screen flex-col lg:h-screen lg:overflow-hidden\">\n      <header className=\"no-print flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\">\n        <div className=\"flex items-center gap-2 sm:gap-3 min-w-0\">\n          {LOGO}\n          <span className=\"text-sm font-semibold text-foreground truncate\">\n            Invoice Builder\n          </span>\n          <span className=\"hidden sm:inline rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary whitespace-nowrap\">\n            Built with Dinero.js\n          </span>\n        </div>\n        <a\n          href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/invoice-builder\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"shrink-0 text-xs text-text-muted transition-colors hover:text-foreground\"\n        >\n          GitHub\n          <span className=\"hidden sm:inline\"> source</span>\n        </a>\n      </header>\n      <div className=\"flex min-h-0 flex-1 flex-col lg:flex-row\">\n        <div className=\"no-print w-full border-b border-border lg:overflow-y-auto lg:w-120 lg:min-w-120 lg:border-r lg:border-b-0\">\n          <EditorPanel\n            invoice={invoice}\n            onFieldChange={updateField}\n            onAddLineItem={addLineItem}\n            onRemoveLineItem={removeLineItem}\n            onUpdateLineItem={updateLineItem}\n            onChangeCurrency={setCurrency}\n            onChangeDiscountType={setDiscountType}\n          />\n        </div>\n\n        <div className=\"flex-1 lg:overflow-y-auto bg-muted\">\n          <PreviewPanel invoice={invoice} />\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/components/editor-panel.tsx",
    "content": "import type {\n  InvoiceData,\n  CurrencyCode,\n  DiscountType,\n  LineItem,\n} from '@/lib/invoice-types';\nimport {\n  toMinorUnits,\n  minorUnitsToInputString,\n  lineTotal,\n  formatMoney,\n} from '@/lib/money';\nimport {\n  Plus,\n  Trash2,\n  ChevronDown,\n  Percent,\n  StickyNote,\n  Building2,\n} from 'lucide-react';\n\ntype EditorPanelProps = {\n  invoice: InvoiceData;\n  onFieldChange: <K extends keyof InvoiceData>(\n    field: K,\n    value: InvoiceData[K]\n  ) => void;\n  onAddLineItem: () => void;\n  onRemoveLineItem: (id: string) => void;\n  onUpdateLineItem: <K extends keyof LineItem>(\n    id: string,\n    field: K,\n    value: LineItem[K]\n  ) => void;\n  onChangeCurrency: (currency: CurrencyCode) => void;\n  onChangeDiscountType: (type: DiscountType) => void;\n};\n\nconst CURRENCIES: { value: CurrencyCode; label: string }[] = [\n  { value: 'USD', label: 'USD ($)' },\n  { value: 'EUR', label: 'EUR (\\u20AC)' },\n  { value: 'GBP', label: 'GBP (\\u00A3)' },\n  { value: 'JPY', label: 'JPY (\\u00A5)' },\n];\n\nexport function EditorPanel({\n  invoice,\n  onFieldChange,\n  onAddLineItem,\n  onRemoveLineItem,\n  onUpdateLineItem,\n  onChangeCurrency,\n  onChangeDiscountType,\n}: EditorPanelProps) {\n  function onMoneyInput(field: 'discountValue', value: string) {\n    if (invoice.discountType === 'percentage') {\n      const val = parseFloat(value) || 0;\n\n      onFieldChange(field, Math.max(0, Math.min(100, val)));\n    } else {\n      onFieldChange(field, toMinorUnits(value, invoice.currency));\n    }\n  }\n\n  return (\n    <div className=\"flex flex-col gap-4 p-5\">\n      <div className=\"flex items-start justify-between gap-4\">\n        <div>\n          <h2 className=\"text-base font-bold text-foreground\">\n            Invoice Editor\n          </h2>\n          <p className=\"text-xs text-text-muted mt-0.5\">\n            {invoice.invoiceNumber}\n          </p>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          <select\n            value={invoice.currency}\n            onChange={(e) => onChangeCurrency(e.target.value as CurrencyCode)}\n            aria-label=\"Currency\"\n            name=\"currency\"\n            autoComplete=\"off\"\n            className=\"rounded-md border border-border bg-card px-2 py-1.5 text-xs text-foreground transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n          >\n            {CURRENCIES.map((c) => (\n              <option key={c.value} value={c.value}>\n                {c.label}\n              </option>\n            ))}\n          </select>\n        </div>\n      </div>\n\n      <div className=\"flex items-center gap-3 rounded-lg bg-[rgba(255,255,255,0.03)] px-4 py-3 border border-border\">\n        <div className=\"flex h-8 w-8 items-center justify-center rounded-md bg-primary/10\">\n          <Building2 className=\"h-4 w-4 text-primary\" aria-hidden=\"true\" />\n        </div>\n        <div className=\"flex-1 min-w-0\">\n          <p className=\"text-sm font-semibold text-foreground truncate\">\n            {invoice.businessName}\n          </p>\n          <p className=\"text-xs text-text-muted truncate\">\n            {invoice.businessEmail}\n          </p>\n        </div>\n      </div>\n\n      <CollapsibleSection\n        title=\"Client Details\"\n        icon={ChevronDown}\n        defaultOpen={false}\n      >\n        <div className=\"flex flex-col gap-3\">\n          <div>\n            <FieldLabel htmlFor=\"client-name\">Name</FieldLabel>\n            <TextInput\n              id=\"client-name\"\n              name=\"client-name\"\n              value={invoice.clientName}\n              onChange={(v) => onFieldChange('clientName', v)}\n              placeholder=\"John Doe\"\n              autoComplete=\"off\"\n            />\n          </div>\n          <div>\n            <FieldLabel htmlFor=\"client-address\">Address</FieldLabel>\n            <TextInput\n              id=\"client-address\"\n              name=\"client-address\"\n              value={invoice.clientAddress}\n              onChange={(v) => onFieldChange('clientAddress', v)}\n              placeholder=\"456 Oak Ave, City, State\"\n              autoComplete=\"off\"\n            />\n          </div>\n          <div>\n            <FieldLabel htmlFor=\"client-email\">Email</FieldLabel>\n            <TextInput\n              id=\"client-email\"\n              name=\"client-email\"\n              value={invoice.clientEmail}\n              onChange={(v) => onFieldChange('clientEmail', v)}\n              placeholder=\"john@example.com\"\n              type=\"email\"\n              spellCheck={false}\n              autoComplete=\"off\"\n            />\n          </div>\n        </div>\n      </CollapsibleSection>\n\n      <div>\n        <div className=\"mb-3 flex items-center justify-between\">\n          <h3 className=\"text-sm font-semibold text-foreground\">Line Items</h3>\n          <span className=\"text-xs tabular-nums text-text-muted\">\n            {invoice.lineItems.length} item\n            {invoice.lineItems.length !== 1 ? 's' : ''}\n          </span>\n        </div>\n\n        <div className=\"flex flex-col gap-2\">\n          {invoice.lineItems.map((item, i) => (\n            <LineItemRow\n              key={item.id}\n              item={item}\n              index={i}\n              currency={invoice.currency}\n              onUpdate={(field, value) =>\n                onUpdateLineItem(item.id, field, value)\n              }\n              onRemove={() => onRemoveLineItem(item.id)}\n              canRemove={invoice.lineItems.length > 1}\n            />\n          ))}\n        </div>\n\n        <button\n          type=\"button\"\n          onClick={onAddLineItem}\n          className=\"mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border py-2.5 text-xs font-medium text-text-secondary transition-colors hover:border-primary hover:text-primary hover:bg-primary/5\"\n        >\n          <Plus className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n          Add line item\n        </button>\n      </div>\n\n      <div className=\"grid grid-cols-2 gap-3\">\n        <div>\n          <FieldLabel htmlFor=\"issue-date\">Issue Date</FieldLabel>\n          <input\n            id=\"issue-date\"\n            name=\"issue-date\"\n            type=\"date\"\n            value={invoice.issueDate}\n            onChange={(e) => onFieldChange('issueDate', e.target.value)}\n            className=\"w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none scheme-dark\"\n          />\n        </div>\n        <div>\n          <FieldLabel htmlFor=\"due-date\">Due Date</FieldLabel>\n          <input\n            id=\"due-date\"\n            name=\"due-date\"\n            type=\"date\"\n            value={invoice.dueDate}\n            onChange={(e) => onFieldChange('dueDate', e.target.value)}\n            className=\"w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none scheme-dark\"\n          />\n        </div>\n      </div>\n\n      <CollapsibleSection\n        title=\"Discount & Tax\"\n        icon={Percent}\n        defaultOpen={true}\n      >\n        <div className=\"flex flex-col gap-3\">\n          <div>\n            <FieldLabel>Discount</FieldLabel>\n            <div className=\"flex gap-2\">\n              <select\n                value={invoice.discountType}\n                onChange={(e) =>\n                  onChangeDiscountType(e.target.value as DiscountType)\n                }\n                aria-label=\"Discount type\"\n                name=\"discount-type\"\n                autoComplete=\"off\"\n                className=\"rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n              >\n                <option value=\"percentage\">%</option>\n                <option value=\"fixed\">Fixed</option>\n              </select>\n              <input\n                type=\"text\"\n                name=\"discount-value\"\n                value={\n                  invoice.discountType === 'percentage'\n                    ? invoice.discountValue === 0\n                      ? ''\n                      : String(invoice.discountValue)\n                    : minorUnitsToInputString(\n                        invoice.discountValue,\n                        invoice.currency\n                      )\n                }\n                onChange={(e) => onMoneyInput('discountValue', e.target.value)}\n                placeholder={\n                  invoice.discountType === 'percentage' ? '0' : '0.00'\n                }\n                aria-label=\"Discount value\"\n                autoComplete=\"off\"\n                className=\"flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground text-right placeholder:text-text-muted transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n              />\n            </div>\n          </div>\n          <div>\n            <FieldLabel htmlFor=\"tax-rate\">Tax Rate (%)</FieldLabel>\n            <input\n              id=\"tax-rate\"\n              type=\"text\"\n              name=\"tax-rate\"\n              value={invoice.taxRate === 0 ? '' : String(invoice.taxRate)}\n              onChange={(e) => {\n                const val = parseFloat(e.target.value) || 0;\n                onFieldChange('taxRate', Math.max(0, val));\n              }}\n              placeholder=\"0\"\n              aria-label=\"Tax rate percentage\"\n              autoComplete=\"off\"\n              className=\"w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground text-right placeholder:text-text-muted transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n            />\n          </div>\n        </div>\n      </CollapsibleSection>\n\n      <CollapsibleSection title=\"Notes\" icon={StickyNote} defaultOpen={false}>\n        <textarea\n          name=\"notes\"\n          value={invoice.notes}\n          onChange={(e) => onFieldChange('notes', e.target.value)}\n          placeholder=\"Payment terms, thank you message, etc.\"\n          rows={3}\n          className=\"w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-text-muted transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none resize-none\"\n        />\n      </CollapsibleSection>\n    </div>\n  );\n}\n\ntype FieldLabelProps = {\n  htmlFor?: string;\n} & React.PropsWithChildren;\n\nfunction FieldLabel({ htmlFor, children }: FieldLabelProps) {\n  return (\n    <label\n      htmlFor={htmlFor}\n      className=\"mb-1.5 block text-xs font-medium text-text-secondary\"\n    >\n      {children}\n    </label>\n  );\n}\n\ntype TextInputProps = {\n  id?: string;\n  name?: string;\n  value: string;\n  onChange: (value: string) => void;\n  placeholder?: string;\n  type?: string;\n  spellCheck?: boolean;\n  autoComplete?: string;\n};\n\nfunction TextInput({\n  id,\n  name,\n  value,\n  onChange,\n  placeholder,\n  type = 'text',\n  spellCheck,\n  autoComplete,\n}: TextInputProps) {\n  return (\n    <input\n      id={id}\n      name={name}\n      type={type}\n      value={value}\n      onChange={(e) => onChange(e.target.value)}\n      placeholder={placeholder}\n      spellCheck={spellCheck}\n      autoComplete={autoComplete}\n      className=\"w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-text-muted transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n    />\n  );\n}\n\ntype LineItemRowProps = {\n  item: LineItem;\n  index: number;\n  currency: CurrencyCode;\n  onUpdate: <K extends keyof LineItem>(field: K, value: LineItem[K]) => void;\n  onRemove: () => void;\n  canRemove: boolean;\n};\n\nfunction LineItemRow({\n  item,\n  index,\n  currency,\n  onUpdate,\n  onRemove,\n  canRemove,\n}: LineItemRowProps) {\n  return (\n    <div className=\"group rounded-lg border border-border bg-[rgba(255,255,255,0.02)] p-3 transition-colors hover:border-[rgba(255,255,255,0.12)]\">\n      <div className=\"flex items-start gap-3\">\n        <span className=\"mt-2 flex h-5 w-5 shrink-0 items-center justify-center rounded text-[10px] font-bold text-text-muted bg-[rgba(255,255,255,0.05)]\">\n          {index + 1}\n        </span>\n        <div className=\"flex-1 flex flex-col gap-2\">\n          <input\n            type=\"text\"\n            name={`item-${index}-description`}\n            value={item.description}\n            onChange={(e) => onUpdate('description', e.target.value)}\n            placeholder=\"Item description\"\n            aria-label={`Item ${index + 1} description`}\n            autoComplete=\"off\"\n            className=\"w-full rounded-md border-0 bg-transparent px-0 py-1 text-sm font-medium text-foreground placeholder:text-text-muted focus:ring-0 focus:outline-none focus-visible:ring-1 focus-visible:ring-primary\"\n          />\n          <div className=\"flex flex-wrap items-center gap-2 sm:gap-3\">\n            <div className=\"flex items-center gap-1.5\">\n              <span className=\"text-[10px] font-medium text-text-muted uppercase tracking-wider\">\n                Qty\n              </span>\n              <input\n                type=\"number\"\n                name={`item-${index}-quantity`}\n                min={1}\n                value={item.quantity}\n                onChange={(e) =>\n                  onUpdate(\n                    'quantity',\n                    Math.max(1, parseInt(e.target.value) || 1)\n                  )\n                }\n                aria-label={`Item ${index + 1} quantity`}\n                autoComplete=\"off\"\n                className=\"w-14 rounded-md border border-border bg-card px-2 py-1 text-xs text-foreground text-center transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n              />\n            </div>\n            <div className=\"flex items-center gap-1.5\">\n              <span className=\"text-[10px] font-medium text-text-muted uppercase tracking-wider\">\n                Price\n              </span>\n              <input\n                type=\"text\"\n                name={`item-${index}-price`}\n                value={minorUnitsToInputString(item.unitPriceCents, currency)}\n                onChange={(e) =>\n                  onUpdate(\n                    'unitPriceCents',\n                    toMinorUnits(e.target.value, currency)\n                  )\n                }\n                placeholder=\"0.00\"\n                aria-label={`Item ${index + 1} unit price`}\n                autoComplete=\"off\"\n                className=\"w-24 rounded-md border border-border bg-card px-2 py-1 text-xs text-foreground text-right placeholder:text-text-muted transition-colors focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none\"\n              />\n            </div>\n            <div className=\"ml-auto flex items-center gap-2\">\n              <span className=\"text-sm font-semibold text-foreground tabular-nums\">\n                {formatMoney(lineTotal(item, currency), currency)}\n              </span>\n              <button\n                type=\"button\"\n                onClick={onRemove}\n                disabled={!canRemove}\n                className=\"flex h-6 w-6 items-center justify-center rounded text-text-muted transition-all lg:opacity-0 lg:group-hover:opacity-100 hover:bg-destructive/10 hover:text-destructive disabled:opacity-0 disabled:cursor-not-allowed\"\n                aria-label=\"Remove line item\"\n              >\n                <Trash2 className=\"h-3 w-3\" aria-hidden=\"true\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\ntype CollapsibleSectionProps = {\n  title: string;\n  icon: React.ComponentType<{\n    className?: string;\n    'aria-hidden'?: boolean | 'true' | 'false';\n  }>;\n  defaultOpen?: boolean;\n} & React.PropsWithChildren;\n\nfunction CollapsibleSection({\n  title,\n  icon: Icon,\n  defaultOpen = false,\n  children,\n}: CollapsibleSectionProps) {\n  return (\n    <details open={defaultOpen} className=\"group/details\">\n      <summary className=\"flex cursor-pointer items-center gap-2 rounded-lg border border-border bg-card px-4 py-3 text-sm font-medium text-text-secondary transition-colors hover:text-foreground list-none [&::-webkit-details-marker]:hidden\">\n        <Icon className=\"h-3.5 w-3.5 text-text-muted\" aria-hidden=\"true\" />\n        <span>{title}</span>\n        <ChevronDown\n          className=\"ml-auto h-3.5 w-3.5 text-text-muted transition-transform group-open/details:rotate-180\"\n          aria-hidden=\"true\"\n        />\n      </summary>\n      <div className=\"mt-2 rounded-lg border border-border bg-card p-4 animate-fade-in-slide-down\">\n        {children}\n      </div>\n    </details>\n  );\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/components/preview-panel.tsx",
    "content": "import type { InvoiceData } from '@/lib/invoice-types';\nimport {\n  invoiceSubtotal,\n  discountAmount,\n  taxAmount,\n  grandTotal,\n  lineTotal,\n  formatMoney,\n  formatCents,\n} from '@/lib/money';\n\ninterface PreviewPanelProps {\n  invoice: InvoiceData;\n}\n\nexport function PreviewPanel({ invoice }: PreviewPanelProps) {\n  const { currency } = invoice;\n  const subtotal = invoiceSubtotal(invoice.lineItems, currency);\n  const discount = discountAmount(\n    subtotal,\n    invoice.discountType,\n    invoice.discountValue,\n    currency\n  );\n  const tax = taxAmount(subtotal, discount, invoice.taxRate, currency);\n  const total = grandTotal(subtotal, discount, tax);\n\n  const hasDiscount = invoice.discountValue > 0;\n  const hasTax = invoice.taxRate > 0;\n\n  return (\n    <div className=\"flex flex-col items-center gap-4 p-4 sm:p-6\">\n      <div\n        id=\"invoice-preview\"\n        className=\"w-full max-w-170 rounded-lg bg-white shadow-2xl shadow-black/20 print-only\"\n        style={{ color: '#1a1a2e' }}\n      >\n        <div className=\"p-5 sm:p-10\">\n          <div className=\"flex flex-col-reverse gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6\">\n            <div className=\"flex-1 min-w-0\">\n              {invoice.businessLogo && (\n                <img\n                  src={invoice.businessLogo}\n                  alt=\"Business logo\"\n                  width={140}\n                  height={48}\n                  className=\"mb-3 h-12 w-auto max-w-35 object-contain\"\n                />\n              )}\n              <h1 className=\"text-xl font-bold\" style={{ color: '#1a1a2e' }}>\n                {invoice.businessName || 'Your Company'}\n              </h1>\n              {invoice.businessAddress && (\n                <p className=\"mt-1 text-sm\" style={{ color: '#6b7280' }}>\n                  {invoice.businessAddress}\n                </p>\n              )}\n              {invoice.businessEmail && (\n                <p className=\"text-sm\" style={{ color: '#6b7280' }}>\n                  {invoice.businessEmail}\n                </p>\n              )}\n            </div>\n\n            <div className=\"sm:text-right\">\n              <h2\n                className=\"text-2xl sm:text-3xl font-extrabold tracking-tight uppercase\"\n                style={{ color: '#4466ff' }}\n              >\n                Invoice\n              </h2>\n              <p\n                className=\"mt-1 text-sm font-medium\"\n                style={{ color: '#6b7280' }}\n              >\n                {invoice.invoiceNumber}\n              </p>\n            </div>\n          </div>\n\n          <div className=\"mt-8 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6\">\n            <div>\n              <p\n                className=\"text-xs font-semibold uppercase tracking-wider\"\n                style={{ color: '#9ca3af' }}\n              >\n                Bill To\n              </p>\n              <p\n                className=\"mt-1 text-sm font-semibold\"\n                style={{ color: '#1a1a2e' }}\n              >\n                {invoice.clientName || 'Client Name'}\n              </p>\n              {invoice.clientAddress && (\n                <p className=\"text-sm\" style={{ color: '#6b7280' }}>\n                  {invoice.clientAddress}\n                </p>\n              )}\n              {invoice.clientEmail && (\n                <p className=\"text-sm\" style={{ color: '#6b7280' }}>\n                  {invoice.clientEmail}\n                </p>\n              )}\n            </div>\n\n            <div className=\"sm:text-right\">\n              <div className=\"flex flex-col gap-1\">\n                <div>\n                  <p\n                    className=\"text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af' }}\n                  >\n                    Issue Date\n                  </p>\n                  <p className=\"text-sm\" style={{ color: '#1a1a2e' }}>\n                    {formatDate(invoice.issueDate)}\n                  </p>\n                </div>\n                <div className=\"mt-2\">\n                  <p\n                    className=\"text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af' }}\n                  >\n                    Due Date\n                  </p>\n                  <p className=\"text-sm\" style={{ color: '#1a1a2e' }}>\n                    {formatDate(invoice.dueDate)}\n                  </p>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <div className=\"mt-8 -mx-5 px-5 overflow-x-auto sm:mx-0 sm:px-0\">\n            <table className=\"w-full text-sm min-w-80\">\n              <thead>\n                <tr style={{ borderBottom: '2px solid #e5e7eb' }}>\n                  <th\n                    className=\"pb-3 text-left text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af' }}\n                  >\n                    Description\n                  </th>\n                  <th\n                    className=\"pb-3 text-right text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af', width: '60px' }}\n                  >\n                    Qty\n                  </th>\n                  <th\n                    className=\"pb-3 text-right text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af', width: '110px' }}\n                  >\n                    Unit Price\n                  </th>\n                  <th\n                    className=\"pb-3 text-right text-xs font-semibold uppercase tracking-wider\"\n                    style={{ color: '#9ca3af', width: '110px' }}\n                  >\n                    Amount\n                  </th>\n                </tr>\n              </thead>\n              <tbody>\n                {invoice.lineItems.map((item, i) => (\n                  <tr\n                    key={item.id}\n                    style={{\n                      borderBottom: '1px solid #f3f4f6',\n                      backgroundColor: i % 2 === 0 ? 'transparent' : '#f9fafb',\n                    }}\n                  >\n                    <td className=\"py-3 pr-4\" style={{ color: '#1a1a2e' }}>\n                      {item.description || 'Untitled item'}\n                    </td>\n                    <td\n                      className=\"py-3 text-right tabular-nums\"\n                      style={{ color: '#4b5563' }}\n                    >\n                      {item.quantity}\n                    </td>\n                    <td\n                      className=\"py-3 text-right tabular-nums\"\n                      style={{ color: '#4b5563' }}\n                    >\n                      {formatCents(item.unitPriceCents, currency)}\n                    </td>\n                    <td\n                      className=\"py-3 text-right font-medium tabular-nums\"\n                      style={{ color: '#1a1a2e' }}\n                    >\n                      {formatMoney(lineTotal(item, currency), currency)}\n                    </td>\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n\n          <div className=\"mt-6 flex justify-end\">\n            <div className=\"w-full sm:w-64\">\n              <div\n                className=\"flex items-center justify-between py-2 text-sm\"\n                style={{ borderBottom: '1px solid #f3f4f6' }}\n              >\n                <span style={{ color: '#6b7280' }}>Subtotal</span>\n                <span\n                  className=\"font-medium tabular-nums\"\n                  style={{ color: '#1a1a2e' }}\n                >\n                  {formatMoney(subtotal, currency)}\n                </span>\n              </div>\n\n              {hasDiscount && (\n                <div\n                  className=\"flex items-center justify-between py-2 text-sm\"\n                  style={{ borderBottom: '1px solid #f3f4f6' }}\n                >\n                  <span style={{ color: '#6b7280' }}>\n                    Discount\n                    {invoice.discountType === 'percentage'\n                      ? ` (${invoice.discountValue}%)`\n                      : ''}\n                  </span>\n                  <span\n                    className=\"font-medium tabular-nums\"\n                    style={{ color: '#ef4444' }}\n                  >\n                    {'-'}\n                    {formatMoney(discount, currency)}\n                  </span>\n                </div>\n              )}\n\n              {hasTax && (\n                <div\n                  className=\"flex items-center justify-between py-2 text-sm\"\n                  style={{ borderBottom: '1px solid #f3f4f6' }}\n                >\n                  <span style={{ color: '#6b7280' }}>\n                    Tax ({invoice.taxRate}%)\n                  </span>\n                  <span\n                    className=\"font-medium tabular-nums\"\n                    style={{ color: '#1a1a2e' }}\n                  >\n                    {formatMoney(tax, currency)}\n                  </span>\n                </div>\n              )}\n\n              <div className=\"flex items-center justify-between py-3\">\n                <span\n                  className=\"text-sm font-bold\"\n                  style={{ color: '#1a1a2e' }}\n                >\n                  Total\n                </span>\n                <span\n                  className=\"text-xl font-extrabold tabular-nums\"\n                  style={{ color: '#4466ff' }}\n                >\n                  {formatMoney(total, currency)}\n                </span>\n              </div>\n            </div>\n          </div>\n\n          {invoice.notes && (\n            <div\n              className=\"mt-8 rounded-md p-4\"\n              style={{\n                backgroundColor: '#f9fafb',\n                borderLeft: '3px solid #4466ff',\n              }}\n            >\n              <p\n                className=\"text-xs font-semibold uppercase tracking-wider\"\n                style={{ color: '#9ca3af' }}\n              >\n                Notes\n              </p>\n              <p\n                className=\"mt-1 text-sm whitespace-pre-wrap\"\n                style={{ color: '#6b7280' }}\n              >\n                {invoice.notes}\n              </p>\n            </div>\n          )}\n\n          <div className=\"mt-10 text-center\">\n            <p className=\"text-xs\" style={{ color: '#d1d5db' }}>\n              Thank you for your business\n            </p>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction formatDate(value: string): string {\n  if (!value) {\n    return '';\n  }\n\n  const d = new Date(value + 'T00:00:00');\n\n  return new Intl.DateTimeFormat(undefined, {\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric',\n  }).format(d);\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/hooks/use-invoice.ts",
    "content": "import { useState, useCallback } from 'react';\nimport type {\n  InvoiceData,\n  LineItem,\n  CurrencyCode,\n  DiscountType,\n} from '@/lib/invoice-types';\n\nexport function useInvoice() {\n  const [invoice, setInvoice] = useState<InvoiceData>(createInitialInvoice);\n\n  const updateField = useCallback(\n    <K extends keyof InvoiceData>(field: K, value: InvoiceData[K]) => {\n      setInvoice((prev) => ({ ...prev, [field]: value }));\n    },\n    []\n  );\n\n  const addLineItem = useCallback(() => {\n    setInvoice((prev) => ({\n      ...prev,\n      lineItems: [...prev.lineItems, createEmptyLineItem()],\n    }));\n  }, []);\n\n  const removeLineItem = useCallback((id: string) => {\n    setInvoice((prev) => ({\n      ...prev,\n      lineItems:\n        prev.lineItems.length > 1\n          ? prev.lineItems.filter((item) => item.id !== id)\n          : prev.lineItems,\n    }));\n  }, []);\n\n  const updateLineItem = useCallback(\n    <K extends keyof LineItem>(id: string, field: K, value: LineItem[K]) => {\n      setInvoice((prev) => ({\n        ...prev,\n        lineItems: prev.lineItems.map((item) =>\n          item.id === id ? { ...item, [field]: value } : item\n        ),\n      }));\n    },\n    []\n  );\n\n  const setCurrency = useCallback((currency: CurrencyCode) => {\n    setInvoice((prev) => ({ ...prev, currency }));\n  }, []);\n\n  const setDiscountType = useCallback((discountType: DiscountType) => {\n    setInvoice((prev) => ({ ...prev, discountType, discountValue: 0 }));\n  }, []);\n\n  const setBusinessLogo = useCallback((logo: string | null) => {\n    setInvoice((prev) => ({ ...prev, businessLogo: logo }));\n  }, []);\n\n  return {\n    invoice,\n    updateField,\n    addLineItem,\n    removeLineItem,\n    updateLineItem,\n    setCurrency,\n    setDiscountType,\n    setBusinessLogo,\n  };\n}\n\nfunction generateInvoiceNumber(): string {\n  const now = new Date();\n  const y = now.getFullYear().toString().slice(-2);\n  const m = String(now.getMonth() + 1).padStart(2, '0');\n  const sequence = String(Math.floor(Math.random() * 9000) + 1000);\n\n  return `INV-${y}${m}-${sequence}`;\n}\n\nfunction todayISO(): string {\n  return new Date().toISOString().split('T')[0];\n}\n\nfunction dueDateISO(): string {\n  const d = new Date();\n  d.setDate(d.getDate() + 30);\n\n  return d.toISOString().split('T')[0];\n}\n\nfunction createEmptyLineItem(): LineItem {\n  return {\n    id: crypto.randomUUID(),\n    description: '',\n    quantity: 1,\n    unitPriceCents: 0,\n  };\n}\n\nfunction createInitialInvoice(): InvoiceData {\n  return {\n    businessName: 'Acme Corporation',\n    businessAddress: '100 Market St, San Francisco, CA 94105',\n    businessEmail: 'billing@acme.corp',\n    businessLogo: null,\n    clientName: 'Sarah Chen',\n    clientAddress: '42 Innovation Drive, Austin, TX 73301',\n    clientEmail: 'sarah@designstudio.io',\n    invoiceNumber: generateInvoiceNumber(),\n    issueDate: todayISO(),\n    dueDate: dueDateISO(),\n    currency: 'USD',\n    lineItems: [\n      {\n        id: crypto.randomUUID(),\n        description: 'Website Redesign',\n        quantity: 1,\n        unitPriceCents: 480000,\n      },\n      {\n        id: crypto.randomUUID(),\n        description: 'Logo & Brand Identity',\n        quantity: 1,\n        unitPriceCents: 240000,\n      },\n      {\n        id: crypto.randomUUID(),\n        description: 'SEO Optimization',\n        quantity: 3,\n        unitPriceCents: 45000,\n      },\n    ],\n    discountType: 'percentage',\n    discountValue: 5,\n    taxRate: 8.25,\n    notes:\n      'Payment due within 30 days. Please include the invoice number with your payment.',\n  };\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #ededef;\n  --card: #131416;\n  --card-foreground: #ededef;\n  --popover: #131416;\n  --popover-foreground: #ededef;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #131416;\n  --secondary-foreground: #ededef;\n  --muted: #08090a;\n  --muted-foreground: #a1a1a9;\n  --accent: #131416;\n  --accent-foreground: #ededef;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.08);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.5rem;\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n@keyframes fade-in-slide-down {\n  from {\n    opacity: 0;\n    transform: translateY(-4px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.animate-fade-in-slide-down {\n  animation: fade-in-slide-down 0.2s ease-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .animate-fade-in-slide-down {\n    animation: none;\n  }\n}\n\n@media print {\n  .no-print {\n    display: none !important;\n  }\n  .print-only {\n    display: block !important;\n  }\n  body {\n    background: white !important;\n    color: black !important;\n  }\n  @page {\n    margin: 0.5in;\n    size: A4;\n  }\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/lib/invoice-types.ts",
    "content": "export type CurrencyCode = 'USD' | 'EUR' | 'GBP' | 'JPY';\n\nexport interface LineItem {\n  id: string;\n  description: string;\n  quantity: number;\n  unitPriceCents: number;\n}\n\nexport type DiscountType = 'percentage' | 'fixed';\n\nexport interface InvoiceData {\n  businessName: string;\n  businessAddress: string;\n  businessEmail: string;\n  businessLogo: string | null;\n  clientName: string;\n  clientAddress: string;\n  clientEmail: string;\n  invoiceNumber: string;\n  issueDate: string;\n  dueDate: string;\n  currency: CurrencyCode;\n  lineItems: LineItem[];\n  discountType: DiscountType;\n  discountValue: number;\n  taxRate: number;\n  notes: string;\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/lib/money.ts",
    "content": "import {\n  dinero,\n  add,\n  subtract,\n  multiply,\n  allocate,\n  toDecimal,\n} from 'dinero.js';\nimport type { Dinero, DineroCurrency } from 'dinero.js';\nimport { USD, EUR, GBP, JPY } from 'dinero.js/currencies';\n\nimport type { CurrencyCode, DiscountType, LineItem } from '@/lib/invoice-types';\n\nconst CURRENCIES = {\n  USD,\n  EUR,\n  GBP,\n  JPY,\n} as const;\n\nconst CURRENCY_LOCALES: Record<CurrencyCode, string> = {\n  USD: 'en-US',\n  EUR: 'de-DE',\n  GBP: 'en-GB',\n  JPY: 'ja-JP',\n};\n\nconst NON_NUMERIC = /[^0-9.-]/g;\n\n/**\n * Convert a decimal string (e.g. \"19.99\") to an integer amount in minor units.\n * Uses the currency base and exponent so JPY (exponent 0) stays in major units.\n */\nexport function toMinorUnits<TCurrency extends CurrencyCode>(\n  value: string,\n  code: TCurrency\n): number {\n  const cleaned = value.replace(NON_NUMERIC, '');\n\n  if (cleaned === '' || cleaned === '-' || cleaned === '.') {\n    return 0;\n  }\n\n  const { base, exponent } = CURRENCIES[code];\n  const factor = (typeof base === 'number' ? base : base[0]) ** exponent;\n\n  return Math.round(parseFloat(cleaned) * factor);\n}\n\n/**\n * Convert an integer amount to a display string for input fields.\n * Uses the currency exponent so JPY returns the value as-is.\n */\nexport function minorUnitsToInputString<TCurrency extends CurrencyCode>(\n  amount: number,\n  code: TCurrency\n): string {\n  if (amount === 0) {\n    return '';\n  }\n\n  const { exponent } = CURRENCIES[code];\n\n  if (exponent === 0) {\n    return String(amount);\n  }\n\n  return (amount / 10 ** exponent).toFixed(exponent);\n}\n\n/**\n * Compute the line total as a Dinero object.\n */\nexport function lineTotal<TCurrency extends CurrencyCode>(\n  item: LineItem,\n  code: TCurrency\n): Dinero<number, TCurrency> {\n  const price = dinero({\n    amount: item.unitPriceCents,\n    currency: currencyFor(code),\n  });\n\n  return multiply(price, item.quantity);\n}\n\n/**\n * Sum of all line totals.\n */\nexport function invoiceSubtotal<TCurrency extends CurrencyCode>(\n  items: LineItem[],\n  code: TCurrency\n): Dinero<number, TCurrency> {\n  return items.reduce<Dinero<number, TCurrency>>(\n    (sum, item) => add(sum, lineTotal(item, code)),\n    zero(code)\n  );\n}\n\n/**\n * Scale a float percentage (e.g. 8.25) to an integer ratio pair.\n * 8.25 → [825, 9175] (out of 10000), 5 → [500, 9500], 5.5 → [550, 9450].\n */\nfunction percentageRatios(percentage: number): [number, number] {\n  const scaled = Math.round(percentage * 100);\n\n  return [scaled, 10000 - scaled];\n}\n\n/**\n * Compute the discount amount as a Dinero object.\n */\nexport function discountAmount<TCurrency extends CurrencyCode>(\n  sub: Dinero<number, TCurrency>,\n  discountType: DiscountType,\n  discountValue: number,\n  code: TCurrency\n): Dinero<number, TCurrency> {\n  if (discountValue <= 0) {\n    return zero(code);\n  }\n\n  if (discountType === 'percentage') {\n    const [discounted] = allocate(sub, percentageRatios(discountValue));\n\n    return discounted;\n  }\n\n  return dinero({ amount: discountValue, currency: currencyFor(code) });\n}\n\n/**\n * Compute the tax amount as a Dinero object.\n */\nexport function taxAmount<TCurrency extends CurrencyCode>(\n  sub: Dinero<number, TCurrency>,\n  disc: Dinero<number, TCurrency>,\n  taxRate: number,\n  code: TCurrency\n): Dinero<number, TCurrency> {\n  if (taxRate <= 0) {\n    return zero(code);\n  }\n\n  const taxable = subtract(sub, disc);\n  const [taxPortion] = allocate(taxable, percentageRatios(taxRate));\n\n  return taxPortion;\n}\n\n/**\n * Grand total: subtotal - discount + tax.\n */\nexport function grandTotal<TCurrency extends CurrencyCode>(\n  sub: Dinero<number, TCurrency>,\n  discount: Dinero<number, TCurrency>,\n  tax: Dinero<number, TCurrency>\n): Dinero<number, TCurrency> {\n  return add(subtract(sub, discount), tax);\n}\n\n/**\n * Format a Dinero object for display (e.g. \"$19.99\").\n */\nexport function formatMoney<TCurrency extends CurrencyCode>(\n  amount: Dinero<number, TCurrency>,\n  code: TCurrency\n): string {\n  const locale = CURRENCY_LOCALES[code];\n\n  return toDecimal(amount, ({ value, currency }) => {\n    return Number(value).toLocaleString(locale, {\n      style: 'currency',\n      currency: currency.code,\n    });\n  });\n}\n\n/**\n * Format a raw minor-unit amount for display (used in preview for unit prices).\n */\nexport function formatCents<TCurrency extends CurrencyCode>(\n  minorUnits: number,\n  code: TCurrency\n): string {\n  const d = dinero({ amount: minorUnits, currency: currencyFor(code) });\n\n  return formatMoney(d, code);\n}\n\nfunction zero<TCurrency extends CurrencyCode>(\n  code: TCurrency\n): Dinero<number, TCurrency> {\n  return dinero({ amount: 0, currency: currencyFor(code) });\n}\n\nfunction currencyFor<TCurrency extends CurrencyCode>(\n  code: TCurrency\n): DineroCurrency<number, TCurrency> {\n  return CURRENCIES[code] as DineroCurrency<number, TCurrency>;\n}\n"
  },
  {
    "path": "examples/invoice-builder/src/main.tsx",
    "content": "import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport './index.css';\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>\n);\n"
  },
  {
    "path": "examples/invoice-builder/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "examples/invoice-builder/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/invoice-builder/vite.config.ts",
    "content": "import path from 'node:path';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/invoice-builder/',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "examples/portfolio-tracker/README.md",
    "content": "# Portfolio Tracker\n\nA React app that tracks multi-currency investment holdings and converts them to a base currency, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/portfolio-tracker/)\n\n## What you'll learn\n\n- Creating monetary values with `dinero`\n- Multiplying amounts with `multiply` for holding valuations\n- Summing values with `add` for portfolio totals\n- Converting currencies with `convert` and scaled exchange rates\n- Formatting money for display with `toDecimal`\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/portfolio-tracker\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/portfolio-tracker/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Portfolio Tracker — Dinero.js Example</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/portfolio-tracker/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-portfolio-tracker\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-react\": \"^0.475.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/portfolio-tracker/src/App.tsx",
    "content": "import { usePortfolio } from '@/hooks/use-portfolio';\nimport { HoldingsEditor } from '@/components/holdings-editor';\nimport { Dashboard } from '@/components/dashboard';\n\nconst LOGO = (\n  <svg\n    viewBox=\"0 0 361.4 213.6\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"h-6 w-auto\"\n    aria-hidden=\"true\"\n  >\n    <path\n      fill=\"#4466ff\"\n      d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n    />\n    <path\n      fill=\"#fff\"\n      d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n    />\n    <path\n      fill=\"#f7a\"\n      d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n    />\n    <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n    <path\n      fill=\"#bcf\"\n      d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n    />\n    <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n  </svg>\n);\n\nexport default function App() {\n  const portfolio = usePortfolio();\n\n  return (\n    <div className=\"flex min-h-screen flex-col lg:h-screen lg:overflow-hidden\">\n      <header className=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\">\n        <div className=\"flex min-w-0 items-center gap-2 sm:gap-3\">\n          {LOGO}\n          <h1 className=\"truncate text-sm font-semibold text-foreground\">\n            Portfolio Tracker\n          </h1>\n          <span className=\"hidden whitespace-nowrap rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline\">\n            Built with Dinero.js\n          </span>\n        </div>\n        <a\n          href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/portfolio-tracker\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"shrink-0 rounded-sm text-xs text-text-muted transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary/50\"\n        >\n          GitHub\n          <span className=\"hidden sm:inline\"> source</span>\n        </a>\n      </header>\n\n      <div className=\"flex min-h-0 flex-1 flex-col lg:flex-row\">\n        <div className=\"w-full border-b border-border lg:w-120 lg:min-w-120 lg:overflow-y-auto lg:border-b-0 lg:border-r\">\n          <div className=\"p-5\">\n            <HoldingsEditor\n              baseCurrency={portfolio.baseCurrency}\n              onBaseCurrencyChange={portfolio.setBaseCurrency}\n              holdings={portfolio.holdings}\n              onAddHolding={portfolio.addHolding}\n              onRemoveHolding={portfolio.removeHolding}\n              onUpdateHolding={portfolio.updateHolding}\n            />\n          </div>\n        </div>\n\n        <div className=\"relative flex-1 bg-muted lg:overflow-y-auto\">\n          <div className=\"pointer-events-none absolute right-0 top-0 h-150 w-150 bg-[radial-gradient(ellipse_at_top_right,rgba(68,102,255,0.03),transparent_70%)]\" />\n\n          <div\n            className=\"pointer-events-none absolute inset-0 opacity-[0.015]\"\n            style={{\n              backgroundImage:\n                'radial-gradient(circle, rgba(255,255,255,0.8) 1px, transparent 1px)',\n              backgroundSize: '24px 24px',\n            }}\n          />\n\n          <div className=\"relative p-4 sm:p-5\">\n            <Dashboard\n              totalValueFormatted={portfolio.totalValueFormatted}\n              totalValueNumber={portfolio.totalValueNumber}\n              totalChange={portfolio.totalChange}\n              bestPerformer={portfolio.bestPerformer}\n              currencyExposure={portfolio.currencyExposure}\n              holdingsWithValues={portfolio.holdingsWithValues}\n              currencyBreakdown={portfolio.currencyBreakdown}\n              categoryBreakdown={portfolio.categoryBreakdown}\n              baseCurrency={portfolio.baseCurrency}\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/breakdown-bar.tsx",
    "content": "import type { CurrencyCode } from '@/lib/types';\n\ninterface BreakdownItem {\n  label: string;\n  value: number;\n  percent: number;\n  color: string;\n}\n\ninterface BreakdownBarProps {\n  title: string;\n  items: BreakdownItem[];\n  baseCurrency: CurrencyCode;\n}\n\nfunction formatValue(amount: number, currency: CurrencyCode): string {\n  const locale = currency === 'JPY' ? 'ja-JP' : 'en-US';\n  return amount.toLocaleString(locale, {\n    style: 'currency',\n    currency,\n    minimumFractionDigits: currency === 'JPY' ? 0 : 2,\n    maximumFractionDigits: currency === 'JPY' ? 0 : 2,\n  });\n}\n\nexport function BreakdownBar({\n  title,\n  items,\n  baseCurrency,\n}: BreakdownBarProps) {\n  if (items.length === 0) return null;\n\n  return (\n    <div className=\"rounded-lg border border-border bg-card p-4 shadow-xl shadow-black/20\">\n      <h3 className=\"mb-3 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n        {title}\n      </h3>\n\n      {/* Stacked bar */}\n      <div className=\"mb-4 flex h-3 overflow-hidden rounded-full bg-white/[0.04]\">\n        {items.map((item) => (\n          <div\n            key={item.label}\n            className=\"h-full transition-[width] duration-500 first:rounded-l-full last:rounded-r-full\"\n            style={{\n              width: `${Math.max(item.percent, 0.5)}%`,\n              backgroundColor: item.color,\n            }}\n          />\n        ))}\n      </div>\n\n      {/* Legend */}\n      <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n        {items.map((item) => (\n          <div\n            key={item.label}\n            className=\"flex items-center justify-between gap-2 rounded-md bg-surface-raised px-3 py-2\"\n          >\n            <div className=\"flex min-w-0 items-center gap-2\">\n              <div\n                className=\"h-2.5 w-2.5 shrink-0 rounded-full\"\n                style={{ backgroundColor: item.color }}\n              />\n              <span className=\"truncate text-xs font-medium text-foreground\">\n                {item.label}\n              </span>\n            </div>\n            <div className=\"flex shrink-0 items-center gap-2\">\n              <span className=\"hidden text-xs tabular-nums text-muted-foreground sm:inline\">\n                {formatValue(item.value, baseCurrency)}\n              </span>\n              <span\n                className=\"rounded-md px-1.5 py-0.5 text-[10px] font-bold tabular-nums\"\n                style={{\n                  backgroundColor: `${item.color}15`,\n                  color: item.color,\n                }}\n              >\n                {item.percent.toFixed(1)}%\n              </span>\n            </div>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/dashboard.tsx",
    "content": "import type { CurrencyCode, Category } from '@/lib/types';\nimport { CATEGORY_COLORS, CURRENCY_COLORS } from '@/lib/types';\nimport { SummaryCards } from '@/components/summary-cards';\nimport { HoldingsTable } from '@/components/holdings-table';\nimport { BreakdownBar } from '@/components/breakdown-bar';\n\nimport type { HoldingWithValue } from '@/hooks/use-portfolio';\n\ninterface DashboardProps {\n  totalValueFormatted: string;\n  totalValueNumber: number;\n  totalChange: { amount: number; percent: number };\n  bestPerformer: { name: string; changePercent: number } | null;\n  currencyExposure: number;\n  holdingsWithValues: HoldingWithValue[];\n  currencyBreakdown: {\n    currency: CurrencyCode;\n    value: number;\n    percent: number;\n  }[];\n  categoryBreakdown: {\n    category: Category;\n    value: number;\n    percent: number;\n  }[];\n  baseCurrency: CurrencyCode;\n}\n\nexport function Dashboard({\n  totalValueFormatted,\n  totalValueNumber,\n  totalChange,\n  bestPerformer,\n  currencyExposure,\n  holdingsWithValues,\n  currencyBreakdown,\n  categoryBreakdown,\n  baseCurrency,\n}: DashboardProps) {\n  const currencyItems = currencyBreakdown.map((item) => ({\n    label: item.currency,\n    value: item.value,\n    percent: item.percent,\n    color: CURRENCY_COLORS[item.currency] ?? '#9ca3af',\n  }));\n\n  const categoryItems = categoryBreakdown.map((item) => ({\n    label: item.category,\n    value: item.value,\n    percent: item.percent,\n    color: CATEGORY_COLORS[item.category] ?? '#9ca3af',\n  }));\n\n  return (\n    <div className=\"flex flex-col gap-5\">\n      <SummaryCards\n        totalValueFormatted={totalValueFormatted}\n        totalChange={totalChange}\n        bestPerformer={bestPerformer}\n        currencyExposure={currencyExposure}\n        holdingsCount={holdingsWithValues.length}\n        baseCurrency={baseCurrency}\n      />\n\n      <HoldingsTable\n        holdings={holdingsWithValues}\n        totalValueNumber={totalValueNumber}\n        baseCurrency={baseCurrency}\n      />\n\n      <BreakdownBar\n        title=\"Currency Breakdown\"\n        items={currencyItems}\n        baseCurrency={baseCurrency}\n      />\n\n      <BreakdownBar\n        title=\"Category Breakdown\"\n        items={categoryItems}\n        baseCurrency={baseCurrency}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/exchange-rates.tsx",
    "content": "import { RefreshCw } from 'lucide-react';\n\nimport type { CurrencyCode } from '@/lib/types';\nimport { CURRENCIES } from '@/lib/types';\nimport { getRateDisplay } from '@/lib/money';\n\ninterface ExchangeRatesCardProps {\n  baseCurrency: CurrencyCode;\n}\n\nexport function ExchangeRatesCard({ baseCurrency }: ExchangeRatesCardProps) {\n  const otherCurrencies = CURRENCIES.filter((c) => c !== baseCurrency);\n\n  return (\n    <div className=\"rounded-lg border border-border bg-card p-4 shadow-xl shadow-black/20\">\n      <div className=\"mb-3 flex items-center justify-between\">\n        <h3 className=\"text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n          Exchange Rates\n        </h3>\n        <button\n          type=\"button\"\n          className=\"rounded-md p-1.5 text-muted-foreground transition-[background-color,color] duration-150 hover:bg-white/4 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n          aria-label=\"Refresh rates\"\n        >\n          <RefreshCw className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n        </button>\n      </div>\n      <div className=\"space-y-2\">\n        {otherCurrencies.map((currency) => (\n          <div\n            key={currency}\n            className=\"flex items-center justify-between gap-3 rounded-md bg-surface-raised px-3 py-2\"\n          >\n            <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n              {baseCurrency} / {currency}\n            </span>\n            <span className=\"min-w-0 truncate text-xs font-semibold tabular-nums text-foreground\">\n              {getRateDisplay(baseCurrency, currency)}\n            </span>\n          </div>\n        ))}\n      </div>\n      <p className=\"mt-3 text-[10px] text-muted-foreground\">\n        Last updated: Mar 1, 2026 09:30 UTC\n      </p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/holding-card.tsx",
    "content": "import { Trash2 } from 'lucide-react';\n\nimport type { CurrencyCode, Category, Holding } from '@/lib/types';\nimport { CURRENCIES, CATEGORIES, CATEGORY_COLORS } from '@/lib/types';\nimport { toMinorUnits, minorUnitsToInputString } from '@/lib/money';\n\ninterface HoldingCardProps {\n  holding: Holding;\n  onUpdate: (id: string, updates: Partial<Omit<Holding, 'id'>>) => void;\n  onRemove: (id: string) => void;\n}\n\nexport function HoldingCard({ holding, onUpdate, onRemove }: HoldingCardProps) {\n  const borderColor = CATEGORY_COLORS[holding.category];\n\n  return (\n    <div className=\"group relative overflow-hidden rounded-lg border border-border bg-card shadow-xl shadow-black/20\">\n      <div\n        className=\"absolute bottom-0 left-0 top-0 w-0.75\"\n        style={{ backgroundColor: borderColor }}\n      />\n\n      <div className=\"p-4 pl-5\">\n        <div className=\"mb-3 flex items-center gap-2\">\n          <label className=\"sr-only\" htmlFor={`name-${holding.id}`}>\n            Asset name\n          </label>\n          <input\n            id={`name-${holding.id}`}\n            type=\"text\"\n            name=\"asset-name\"\n            autoComplete=\"off\"\n            value={holding.name}\n            onChange={(e) => onUpdate(holding.id, { name: e.target.value })}\n            placeholder=\"Asset name\"\n            className=\"flex-1 rounded-md border border-white/8 bg-white/4 px-3 py-1.5 text-sm text-foreground transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n          />\n          <button\n            type=\"button\"\n            onClick={() => onRemove(holding.id)}\n            className=\"rounded-md p-1.5 text-muted-foreground opacity-100 transition-[background-color,color,opacity] duration-150 hover:bg-destructive/10 hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 lg:opacity-0 lg:group-hover:opacity-100\"\n            aria-label={`Delete ${holding.name}`}\n          >\n            <Trash2 className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n          </button>\n        </div>\n\n        <div className=\"mb-3\">\n          <label\n            htmlFor={`category-${holding.id}`}\n            className=\"mb-1 block text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\"\n          >\n            Category\n          </label>\n          <select\n            id={`category-${holding.id}`}\n            name=\"category\"\n            value={holding.category}\n            onChange={(e) =>\n              onUpdate(holding.id, { category: e.target.value as Category })\n            }\n            className=\"w-full appearance-none rounded-md border border-white/8 bg-white/4 px-3 py-1.5 text-sm text-foreground transition-[border-color,box-shadow] duration-150 focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n          >\n            {CATEGORIES.map((cat) => (\n              <option key={cat} value={cat} className=\"bg-card text-foreground\">\n                {cat}\n              </option>\n            ))}\n          </select>\n        </div>\n\n        <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n          <div>\n            <label\n              htmlFor={`quantity-${holding.id}`}\n              className=\"mb-1 block text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\"\n            >\n              Quantity\n            </label>\n            <input\n              id={`quantity-${holding.id}`}\n              type=\"number\"\n              name=\"quantity\"\n              value={holding.quantity || ''}\n              onChange={(e) =>\n                onUpdate(holding.id, {\n                  quantity: parseFloat(e.target.value) || 0,\n                })\n              }\n              placeholder=\"0\"\n              step=\"any\"\n              className=\"w-full rounded-md border border-white/8 bg-white/4 px-3 py-1.5 text-sm tabular-nums text-foreground transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n            />\n          </div>\n          <div>\n            <label\n              htmlFor={`unit-price-${holding.id}`}\n              className=\"mb-1 block text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\"\n            >\n              Unit Price\n            </label>\n            <input\n              id={`unit-price-${holding.id}`}\n              type=\"text\"\n              name=\"unit-price\"\n              inputMode=\"decimal\"\n              autoComplete=\"off\"\n              value={minorUnitsToInputString(\n                holding.unitPriceCents,\n                holding.currency\n              )}\n              onChange={(e) =>\n                onUpdate(holding.id, {\n                  unitPriceCents: toMinorUnits(\n                    e.target.value,\n                    holding.currency\n                  ),\n                })\n              }\n              placeholder=\"0.00\"\n              className=\"w-full rounded-md border border-white/8 bg-white/4 px-3 py-1.5 text-sm tabular-nums text-foreground transition-[border-color,box-shadow] duration-150 placeholder:text-muted-foreground focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n            />\n          </div>\n          <div className=\"col-span-2 sm:col-span-1\">\n            <label\n              htmlFor={`currency-${holding.id}`}\n              className=\"mb-1 block text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\"\n            >\n              Currency\n            </label>\n            <select\n              id={`currency-${holding.id}`}\n              name=\"currency\"\n              value={holding.currency}\n              onChange={(e) =>\n                onUpdate(holding.id, {\n                  currency: e.target.value as CurrencyCode,\n                })\n              }\n              className=\"w-full appearance-none rounded-md border border-white/8 bg-white/4 px-3 py-1.5 text-sm text-foreground transition-[border-color,box-shadow] duration-150 focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n            >\n              {CURRENCIES.map((c) => (\n                <option key={c} value={c} className=\"bg-card text-foreground\">\n                  {c}\n                </option>\n              ))}\n            </select>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/holdings-editor.tsx",
    "content": "import { Plus } from 'lucide-react';\n\nimport type { CurrencyCode, Holding } from '@/lib/types';\nimport { CURRENCIES } from '@/lib/types';\nimport { ExchangeRatesCard } from '@/components/exchange-rates';\nimport { HoldingCard } from '@/components/holding-card';\n\ninterface HoldingsEditorProps {\n  baseCurrency: CurrencyCode;\n  onBaseCurrencyChange: (currency: CurrencyCode) => void;\n  holdings: Holding[];\n  onAddHolding: () => void;\n  onRemoveHolding: (id: string) => void;\n  onUpdateHolding: (id: string, updates: Partial<Omit<Holding, 'id'>>) => void;\n}\n\nexport function HoldingsEditor({\n  baseCurrency,\n  onBaseCurrencyChange,\n  holdings,\n  onAddHolding,\n  onRemoveHolding,\n  onUpdateHolding,\n}: HoldingsEditorProps) {\n  return (\n    <div className=\"flex flex-col gap-6\">\n      <div className=\"rounded-lg border border-border bg-card p-4 shadow-xl shadow-black/20\">\n        <label\n          htmlFor=\"base-currency\"\n          className=\"mb-2 block text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\"\n        >\n          Base Currency\n        </label>\n        <select\n          id=\"base-currency\"\n          name=\"base-currency\"\n          value={baseCurrency}\n          onChange={(e) => onBaseCurrencyChange(e.target.value as CurrencyCode)}\n          className=\"w-full appearance-none rounded-md border border-white/8 bg-white/4 px-3 py-2 text-sm font-medium text-foreground transition-[border-color,box-shadow] duration-150 focus-visible:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n        >\n          {CURRENCIES.map((c) => (\n            <option key={c} value={c} className=\"bg-card text-foreground\">\n              {c}\n            </option>\n          ))}\n        </select>\n      </div>\n\n      <ExchangeRatesCard baseCurrency={baseCurrency} />\n\n      <div>\n        <h3 className=\"mb-3 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n          Holdings ({holdings.length})\n        </h3>\n        <div className=\"flex flex-col gap-3\">\n          {holdings.map((holding) => (\n            <HoldingCard\n              key={holding.id}\n              holding={holding}\n              onUpdate={onUpdateHolding}\n              onRemove={onRemoveHolding}\n            />\n          ))}\n        </div>\n\n        <button\n          type=\"button\"\n          onClick={onAddHolding}\n          className=\"mt-3 flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-white/12 px-4 py-2.5 text-sm text-muted-foreground transition-[border-color,background-color,color,transform] duration-150 hover:scale-[1.01] hover:border-primary/40 hover:bg-primary/4 hover:text-foreground focus-visible:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30\"\n        >\n          <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n          Add Holding\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/holdings-table.tsx",
    "content": "import type { CurrencyCode, Category } from '@/lib/types';\nimport { CATEGORY_COLORS } from '@/lib/types';\nimport { formatCents } from '@/lib/money';\n\ninterface HoldingRow {\n  id: string;\n  name: string;\n  category: Category;\n  quantity: number;\n  unitPriceCents: number;\n  currency: CurrencyCode;\n  baseValueFormatted: string;\n  baseValueNumber: number;\n  changePercent: number;\n}\n\ninterface HoldingsTableProps {\n  holdings: HoldingRow[];\n  totalValueNumber: number;\n  baseCurrency: CurrencyCode;\n}\n\nexport function HoldingsTable({\n  holdings,\n  totalValueNumber,\n  baseCurrency,\n}: HoldingsTableProps) {\n  return (\n    <div className=\"overflow-hidden rounded-lg border border-border bg-card shadow-xl shadow-black/20\">\n      <div className=\"border-b border-border px-4 py-3\">\n        <h3 className=\"text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n          Holdings Overview\n        </h3>\n      </div>\n      <div className=\"overflow-x-auto\">\n        <table className=\"w-full min-w-120 text-sm\">\n          <thead>\n            <tr className=\"border-b border-border\">\n              <th className=\"px-4 py-2.5 text-left text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\">\n                Asset\n              </th>\n              <th className=\"hidden px-4 py-2.5 text-left text-[10px] font-semibold uppercase tracking-widest text-muted-foreground md:table-cell\">\n                Category\n              </th>\n              <th className=\"px-4 py-2.5 text-right text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\">\n                Qty\n              </th>\n              <th className=\"hidden px-4 py-2.5 text-right text-[10px] font-semibold uppercase tracking-widest text-muted-foreground sm:table-cell\">\n                Unit Price\n              </th>\n              <th className=\"px-4 py-2.5 text-right text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\">\n                Value ({baseCurrency})\n              </th>\n              <th className=\"px-4 py-2.5 text-right text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\">\n                Alloc %\n              </th>\n              <th className=\"px-4 py-2.5 text-right text-[10px] font-semibold uppercase tracking-widest text-muted-foreground\">\n                Change\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {holdings.map((h) => {\n              const allocationPercent =\n                totalValueNumber > 0\n                  ? (h.baseValueNumber / totalValueNumber) * 100\n                  : 0;\n              const isPositive = h.changePercent >= 0;\n\n              return (\n                <tr\n                  key={h.id}\n                  className=\"border-b border-border transition-colors duration-100 last:border-b-0 hover:bg-white/2\"\n                >\n                  <td className=\"px-4 py-3\">\n                    <div className=\"flex items-center gap-2\">\n                      <div\n                        className=\"h-1.5 w-1.5 shrink-0 rounded-full\"\n                        style={{\n                          backgroundColor: CATEGORY_COLORS[h.category],\n                        }}\n                      />\n                      <span className=\"max-w-35 truncate font-medium text-foreground\">\n                        {h.name || 'Unnamed'}\n                      </span>\n                    </div>\n                  </td>\n\n                  <td className=\"hidden px-4 py-3 md:table-cell\">\n                    <span\n                      className=\"inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold\"\n                      style={{\n                        backgroundColor: `${CATEGORY_COLORS[h.category]}15`,\n                        color: CATEGORY_COLORS[h.category],\n                      }}\n                    >\n                      {h.category}\n                    </span>\n                  </td>\n\n                  <td className=\"px-4 py-3 text-right tabular-nums text-foreground\">\n                    {formatQuantity(h.quantity)}\n                  </td>\n\n                  <td className=\"hidden px-4 py-3 text-right tabular-nums text-muted-foreground sm:table-cell\">\n                    {formatCents(h.unitPriceCents, h.currency)}\n                  </td>\n\n                  <td className=\"px-4 py-3 text-right font-semibold tabular-nums text-foreground\">\n                    {h.baseValueFormatted}\n                  </td>\n\n                  <td className=\"px-4 py-3 text-right\">\n                    <div className=\"flex items-center justify-end gap-2\">\n                      <div className=\"hidden h-1.5 w-16 overflow-hidden rounded-full bg-white/6 lg:block\">\n                        <div\n                          className=\"h-full rounded-full transition-[width] duration-500\"\n                          style={{\n                            width: `${allocationPercent}%`,\n                            backgroundColor: CATEGORY_COLORS[h.category],\n                          }}\n                        />\n                      </div>\n                      <span className=\"text-xs tabular-nums text-muted-foreground\">\n                        {allocationPercent.toFixed(1)}%\n                      </span>\n                    </div>\n                  </td>\n\n                  <td className=\"px-4 py-3 text-right\">\n                    <span\n                      className=\"text-xs font-semibold tabular-nums transition-colors duration-300\"\n                      style={{\n                        color: isPositive ? 'var(--positive)' : 'var(--loss)',\n                      }}\n                    >\n                      {isPositive ? '+' : ''}\n                      {h.changePercent.toFixed(2)}%\n                    </span>\n                  </td>\n                </tr>\n              );\n            })}\n          </tbody>\n        </table>\n      </div>\n      {holdings.length === 0 && (\n        <div className=\"px-4 py-8 text-center text-sm text-muted-foreground\">\n          No holdings yet.\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction formatQuantity(num: number): string {\n  return new Intl.NumberFormat('en-US', {\n    minimumFractionDigits: num % 1 === 0 ? 0 : 2,\n    maximumFractionDigits: num % 1 === 0 ? 0 : 2,\n  }).format(num);\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/components/summary-cards.tsx",
    "content": "import { TrendingUp, TrendingDown, Wallet, Globe, Trophy } from 'lucide-react';\n\nimport type { CurrencyCode } from '@/lib/types';\n\ninterface SummaryCardsProps {\n  totalValueFormatted: string;\n  totalChange: { amount: number; percent: number };\n  bestPerformer: { name: string; changePercent: number } | null;\n  currencyExposure: number;\n  holdingsCount: number;\n  baseCurrency: CurrencyCode;\n}\n\nexport function SummaryCards({\n  totalValueFormatted,\n  totalChange,\n  bestPerformer,\n  currencyExposure,\n  holdingsCount,\n  baseCurrency,\n}: SummaryCardsProps) {\n  const isPositive = totalChange.amount >= 0;\n\n  return (\n    <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n      <div className=\"relative overflow-hidden rounded-lg border border-border p-4 shadow-xl shadow-black/20 backdrop-blur-xl bg-white/3\">\n        <div className=\"pointer-events-none absolute inset-0 bg-linear-to-br from-primary/4 to-transparent\" />\n        <div className=\"relative\">\n          <div className=\"mb-2 flex items-center gap-1.5 min-w-0\">\n            <Wallet\n              className=\"h-3.5 w-3.5 shrink-0 text-primary\"\n              aria-hidden=\"true\"\n            />\n            <span className=\"truncate text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n              Total Value\n            </span>\n          </div>\n          <p className=\"text-xl font-extrabold tabular-nums tracking-tight text-foreground sm:text-2xl lg:text-3xl\">\n            {totalValueFormatted}\n          </p>\n          <p className=\"mt-1 text-[11px] text-muted-foreground\">\n            across {holdingsCount} holding{holdingsCount !== 1 ? 's' : ''}\n          </p>\n        </div>\n      </div>\n\n      <div className=\"relative overflow-hidden rounded-lg border border-border p-4 shadow-xl shadow-black/20 backdrop-blur-xl bg-white/3\">\n        <div className=\"relative\">\n          <div className=\"mb-2 flex items-center gap-1.5 min-w-0\">\n            {isPositive ? (\n              <TrendingUp\n                className=\"h-3.5 w-3.5 shrink-0 text-positive\"\n                aria-hidden=\"true\"\n              />\n            ) : (\n              <TrendingDown\n                className=\"h-3.5 w-3.5 shrink-0 text-destructive\"\n                aria-hidden=\"true\"\n              />\n            )}\n            <span className=\"truncate text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n              Today{'\\u2019'}s Change\n            </span>\n          </div>\n          <p\n            className=\"text-xl font-extrabold tabular-nums tracking-tight transition-colors duration-300 sm:text-2xl lg:text-3xl\"\n            style={{ color: isPositive ? 'var(--positive)' : 'var(--loss)' }}\n          >\n            {isPositive ? '+' : ''}\n            {formatChangeAmount(totalChange.amount, baseCurrency)}\n          </p>\n          <p\n            className=\"mt-1 text-[11px] font-semibold tabular-nums\"\n            style={{ color: isPositive ? 'var(--positive)' : 'var(--loss)' }}\n          >\n            {isPositive ? '+' : ''}\n            {totalChange.percent.toFixed(2)}%\n          </p>\n        </div>\n      </div>\n\n      <div className=\"relative overflow-hidden rounded-lg border border-border p-4 shadow-xl shadow-black/20 backdrop-blur-xl bg-white/3\">\n        <div className=\"relative\">\n          <div className=\"mb-2 flex items-center gap-1.5 min-w-0\">\n            <Trophy\n              className=\"h-3.5 w-3.5 shrink-0 text-warning\"\n              aria-hidden=\"true\"\n            />\n            <span className=\"truncate text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n              Best Performer\n            </span>\n          </div>\n          {bestPerformer ? (\n            <>\n              <p className=\"truncate text-lg font-bold text-foreground\">\n                {bestPerformer.name || 'Unnamed'}\n              </p>\n              <p\n                className=\"mt-1 text-[11px] font-semibold tabular-nums\"\n                style={{\n                  color:\n                    bestPerformer.changePercent >= 0\n                      ? 'var(--positive)'\n                      : 'var(--loss)',\n                }}\n              >\n                {bestPerformer.changePercent >= 0 ? '+' : ''}\n                {bestPerformer.changePercent.toFixed(2)}% today\n              </p>\n            </>\n          ) : (\n            <p className=\"text-sm text-muted-foreground\">No holdings</p>\n          )}\n        </div>\n      </div>\n\n      <div className=\"relative overflow-hidden rounded-lg border border-border p-4 shadow-xl shadow-black/20 backdrop-blur-xl bg-white/3\">\n        <div className=\"relative\">\n          <div className=\"mb-2 flex items-center gap-1.5 min-w-0\">\n            <Globe\n              className=\"h-3.5 w-3.5 shrink-0 text-primary\"\n              aria-hidden=\"true\"\n            />\n            <span className=\"truncate text-[11px] font-semibold uppercase tracking-widest text-muted-foreground\">\n              Currencies\n            </span>\n          </div>\n          <p className=\"text-xl font-extrabold tabular-nums tracking-tight text-foreground sm:text-2xl lg:text-3xl\">\n            {currencyExposure}\n          </p>\n          <p className=\"mt-1 text-[11px] text-muted-foreground\">\n            currency exposure{currencyExposure !== 1 ? 's' : ''}\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction formatChangeAmount(amount: number, currency: CurrencyCode): string {\n  const locale = currency === 'JPY' ? 'ja-JP' : 'en-US';\n\n  return Math.abs(amount).toLocaleString(locale, {\n    style: 'currency',\n    currency,\n    minimumFractionDigits: currency === 'JPY' ? 0 : 2,\n    maximumFractionDigits: currency === 'JPY' ? 0 : 2,\n  });\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/hooks/use-portfolio.ts",
    "content": "import { useState, useMemo, useCallback } from 'react';\nimport type { Dinero } from 'dinero.js';\n\nimport type { CurrencyCode, Category, Holding } from '@/lib/types';\nimport {\n  holdingValue,\n  convertToBase,\n  sumDineros,\n  formatMoney,\n  zero,\n} from '@/lib/money';\n\nconst DEFAULT_HOLDINGS: Holding[] = [\n  {\n    id: '1',\n    name: 'Apple Inc.',\n    category: 'Stocks',\n    quantity: 15,\n    unitPriceCents: 19850,\n    currency: 'USD',\n  },\n  {\n    id: '2',\n    name: 'Bitcoin',\n    category: 'Crypto',\n    quantity: 0.5,\n    unitPriceCents: 7843200,\n    currency: 'EUR',\n  },\n  {\n    id: '3',\n    name: 'Euro Savings',\n    category: 'Cash',\n    quantity: 10000,\n    unitPriceCents: 100,\n    currency: 'EUR',\n  },\n  {\n    id: '4',\n    name: 'UK Govt Bonds',\n    category: 'Bonds',\n    quantity: 50,\n    unitPriceCents: 10275,\n    currency: 'GBP',\n  },\n];\n\nconst MOCK_CHANGES: Record<string, number> = {\n  '1': 1.2,\n  '2': -2.4,\n  '3': 0.01,\n  '4': 0.35,\n};\n\nlet nextId = 5;\n\nexport interface HoldingWithValue extends Holding {\n  originalValue: Dinero<number, CurrencyCode>;\n  baseValue: Dinero<number, CurrencyCode>;\n  baseValueFormatted: string;\n  changePercent: number;\n  baseValueNumber: number;\n}\n\nexport function usePortfolio() {\n  const [baseCurrency, setBaseCurrency] = useState<CurrencyCode>('USD');\n  const [holdings, setHoldings] = useState<Holding[]>(DEFAULT_HOLDINGS);\n\n  const addHolding = useCallback(() => {\n    const newHolding: Holding = {\n      id: String(nextId++),\n      name: '',\n      category: 'Stocks',\n      quantity: 0,\n      unitPriceCents: 0,\n      currency: baseCurrency,\n    };\n    setHoldings((prev) => [...prev, newHolding]);\n  }, [baseCurrency]);\n\n  const removeHolding = useCallback((id: string) => {\n    setHoldings((prev) => prev.filter((h) => h.id !== id));\n  }, []);\n\n  const updateHolding = useCallback(\n    (id: string, updates: Partial<Omit<Holding, 'id'>>) => {\n      setHoldings((prev) =>\n        prev.map((h) => (h.id === id ? { ...h, ...updates } : h))\n      );\n    },\n    []\n  );\n\n  const holdingsWithValues: HoldingWithValue[] = useMemo(() => {\n    return holdings.map((h) => {\n      const originalValue = holdingValue(\n        h.unitPriceCents,\n        h.quantity,\n        h.currency\n      );\n      const baseValue = convertToBase(originalValue, h.currency, baseCurrency);\n      const baseValueFormatted = formatMoney(baseValue, baseCurrency);\n\n      const baseValueNumber = parseFloat(\n        baseValueFormatted.replace(/[^0-9.-]/g, '')\n      );\n\n      const changePercent =\n        MOCK_CHANGES[h.id] ?? Math.round((Math.random() * 4 - 2) * 100) / 100;\n      if (!MOCK_CHANGES[h.id]) {\n        MOCK_CHANGES[h.id] = changePercent;\n      }\n\n      return {\n        ...h,\n        originalValue,\n        baseValue,\n        baseValueFormatted,\n        changePercent,\n        baseValueNumber,\n      };\n    });\n  }, [holdings, baseCurrency]);\n\n  const totalValueDinero = useMemo(() => {\n    if (holdingsWithValues.length === 0) {\n      return zero(baseCurrency);\n    }\n\n    return sumDineros(\n      holdingsWithValues.map((h) => h.baseValue),\n      baseCurrency\n    );\n  }, [holdingsWithValues, baseCurrency]);\n\n  const totalValueFormatted = useMemo(\n    () => formatMoney(totalValueDinero, baseCurrency),\n    [totalValueDinero, baseCurrency]\n  );\n\n  const totalValueNumber = useMemo(\n    () => holdingsWithValues.reduce((sum, h) => sum + h.baseValueNumber, 0),\n    [holdingsWithValues]\n  );\n\n  const totalChange = useMemo(() => {\n    const changeAmount = holdingsWithValues.reduce(\n      (sum, h) => sum + h.baseValueNumber * (h.changePercent / 100),\n      0\n    );\n\n    const percent =\n      totalValueNumber > 0 ? (changeAmount / totalValueNumber) * 100 : 0;\n\n    return { amount: changeAmount, percent };\n  }, [holdingsWithValues, totalValueNumber]);\n\n  const bestPerformer = useMemo(() => {\n    if (holdingsWithValues.length === 0) {\n      return null;\n    }\n\n    return holdingsWithValues.reduce((best, h) =>\n      h.changePercent > best.changePercent ? h : best\n    );\n  }, [holdingsWithValues]);\n\n  const currencyExposure = new Set(holdings.map((h) => h.currency)).size;\n\n  const currencyBreakdown = useMemo(() => {\n    const breakdown: Record<string, number> = {};\n\n    holdingsWithValues.forEach((h) => {\n      breakdown[h.currency] = (breakdown[h.currency] || 0) + h.baseValueNumber;\n    });\n\n    return Object.entries(breakdown)\n      .map(([currency, value]) => ({\n        currency: currency as CurrencyCode,\n        value,\n        percent: totalValueNumber > 0 ? (value / totalValueNumber) * 100 : 0,\n      }))\n      .toSorted((a, b) => b.value - a.value);\n  }, [holdingsWithValues, totalValueNumber]);\n\n  const categoryBreakdown = useMemo(() => {\n    const breakdown: Record<string, number> = {};\n\n    holdingsWithValues.forEach((h) => {\n      breakdown[h.category] = (breakdown[h.category] || 0) + h.baseValueNumber;\n    });\n\n    return Object.entries(breakdown)\n      .map(([category, value]) => ({\n        category: category as Category,\n        value,\n        percent: totalValueNumber > 0 ? (value / totalValueNumber) * 100 : 0,\n      }))\n      .toSorted((a, b) => b.value - a.value);\n  }, [holdingsWithValues, totalValueNumber]);\n\n  const sortedHoldings = useMemo(\n    () =>\n      holdingsWithValues.toSorted(\n        (a, b) => b.baseValueNumber - a.baseValueNumber\n      ),\n    [holdingsWithValues]\n  );\n\n  return {\n    baseCurrency,\n    setBaseCurrency,\n    holdings,\n    addHolding,\n    removeHolding,\n    updateHolding,\n    holdingsWithValues: sortedHoldings,\n    totalValueFormatted,\n    totalValueNumber,\n    totalChange,\n    bestPerformer,\n    currencyExposure,\n    currencyBreakdown,\n    categoryBreakdown,\n  };\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #e8eaed;\n  --card: #13151a;\n  --card-foreground: #e8eaed;\n  --popover: #13151a;\n  --popover-foreground: #e8eaed;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #1a1c22;\n  --secondary-foreground: #e8eaed;\n  --muted: #08090a;\n  --muted-foreground: #6b7280;\n  --accent: #4466ff;\n  --accent-foreground: #ffffff;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.06);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.625rem;\n  --surface: #111318;\n  --surface-raised: rgba(255, 255, 255, 0.03);\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n  --gain: #34d399;\n  --loss: #f87171;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-surface: var(--surface);\n  --color-surface-raised: var(--surface-raised);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.2);\n}\n\n/* Hide number input spinners */\ninput[type='number']::-webkit-outer-spin-button,\ninput[type='number']::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type='number'] {\n  -moz-appearance: textfield;\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/lib/money.ts",
    "content": "import { dinero, add, multiply, toDecimal, convert } from 'dinero.js';\nimport type { Dinero, DineroCurrency, DineroRates } from 'dinero.js';\nimport { USD, EUR, GBP, JPY } from 'dinero.js/currencies';\n\nimport type { CurrencyCode } from '@/lib/types';\n\nconst CURRENCIES_MAP = { USD, EUR, GBP, JPY } as const;\n\nconst CURRENCY_LOCALES: Record<CurrencyCode, string> = {\n  USD: 'en-US',\n  EUR: 'de-DE',\n  GBP: 'en-GB',\n  JPY: 'ja-JP',\n};\n\nconst NON_NUMERIC = /[^0-9.-]/g;\n\n/**\n * Hardcoded realistic exchange rates (base: USD).\n * 1 USD = X of target currency.\n */\nconst BASE_RATES_FROM_USD: Record<CurrencyCode, number> = {\n  USD: 1,\n  EUR: 0.92,\n  GBP: 0.79,\n  JPY: 149.5,\n};\n\nexport function currencyFor<TCode extends CurrencyCode>(\n  code: TCode\n): DineroCurrency<number, TCode> {\n  return CURRENCIES_MAP[code] as DineroCurrency<number, TCode>;\n}\n\nfunction scaleFor(code: CurrencyCode): number {\n  const { base, exponent } = currencyFor(code);\n\n  return (typeof base === 'number' ? base : base[0]) ** exponent;\n}\n\n/**\n * Convert a decimal string (e.g. \"198.50\") to an integer amount in minor units.\n */\nexport function toMinorUnits(value: string, code: CurrencyCode): number {\n  const cleaned = value.replace(NON_NUMERIC, '');\n\n  if (cleaned === '' || cleaned === '-' || cleaned === '.') {\n    return 0;\n  }\n\n  return Math.round(parseFloat(cleaned) * scaleFor(code));\n}\n\n/**\n * Convert minor-unit amount back to a display string for input fields.\n */\nexport function minorUnitsToInputString(\n  amount: number,\n  code: CurrencyCode\n): string {\n  if (amount === 0) {\n    return '';\n  }\n\n  const { exponent } = currencyFor(code);\n\n  if (exponent === 0) {\n    return String(amount);\n  }\n\n  return (amount / 10 ** exponent).toFixed(exponent);\n}\n\nexport function zero<TCode extends CurrencyCode>(\n  code: TCode\n): Dinero<number, TCode> {\n  return dinero({ amount: 0, currency: currencyFor(code) });\n}\n\nexport function fromMinorUnits<TCode extends CurrencyCode>(\n  amount: number,\n  code: TCode\n): Dinero<number, TCode> {\n  return dinero({ amount, currency: currencyFor(code) });\n}\n\n/**\n * Count the number of decimal places in a number.\n */\nfunction countDecimals(n: number): number {\n  const str = String(n);\n  const dotIndex = str.indexOf('.');\n\n  return dotIndex === -1 ? 0 : str.length - dotIndex - 1;\n}\n\n/**\n * Compute the total value of a holding: quantity * unitPrice.\n * `unitPriceCents` is in minor units; `quantity` can be fractional.\n * Uses a scaled amount for `multiply` to avoid non-integer intermediate results.\n */\nexport function holdingValue<TCode extends CurrencyCode>(\n  unitPriceCents: number,\n  quantity: number,\n  code: TCode\n): Dinero<number, TCode> {\n  const price = fromMinorUnits(unitPriceCents, code);\n  const scale = countDecimals(quantity);\n\n  return multiply(price, { amount: Math.round(quantity * 10 ** scale), scale });\n}\n\n/**\n * Build a Dinero.js-compatible Rates object for converting from `from` to `to`.\n */\nfunction buildRates<TFrom extends CurrencyCode, TTo extends CurrencyCode>(\n  from: TFrom,\n  to: TTo\n): DineroRates<number> {\n  const fromToUsd = 1 / BASE_RATES_FROM_USD[from];\n  const usdToTarget = BASE_RATES_FROM_USD[to];\n  const rate = fromToUsd * usdToTarget;\n\n  const toCurrency = currencyFor(to);\n  const scale = scaleFor(to);\n  const scaledRate = Math.round(rate * scale);\n\n  return {\n    [toCurrency.code]: {\n      amount: scaledRate,\n      scale: toCurrency.exponent,\n    },\n  } as DineroRates<number>;\n}\n\n/**\n * Convert a Dinero object from one currency to another.\n */\nexport function convertToBase<\n  TFrom extends CurrencyCode,\n  TTo extends CurrencyCode,\n>(amount: Dinero<number, TFrom>, from: TFrom, to: TTo): Dinero<number, TTo> {\n  if (from === (to as string)) {\n    return amount as unknown as Dinero<number, TTo>;\n  }\n\n  return convert(amount, currencyFor(to), buildRates(from, to));\n}\n\n/**\n * Sum an array of Dinero objects in the same currency.\n */\nexport function sumDineros<TCode extends CurrencyCode>(\n  items: Dinero<number, TCode>[],\n  code: TCode\n): Dinero<number, TCode> {\n  return items.reduce((sum, item) => add(sum, item), zero(code));\n}\n\n/**\n * Get the raw exchange rate number from one currency to another.\n */\nexport function getRate(from: CurrencyCode, to: CurrencyCode): number {\n  if (from === to) {\n    return 1;\n  }\n\n  const fromToUsd = 1 / BASE_RATES_FROM_USD[from];\n\n  return fromToUsd * BASE_RATES_FROM_USD[to];\n}\n\n/**\n * Display string for exchange rates: \"1 USD = 0.9200 EUR\"\n */\nexport function getRateDisplay(from: CurrencyCode, to: CurrencyCode): string {\n  const rate = getRate(from, to);\n  const decimals = to === 'JPY' ? 2 : 4;\n\n  return `1 ${from} = ${rate.toFixed(decimals)} ${to}`;\n}\n\n/**\n * Format a Dinero object for display (e.g. \"$2,977.50\").\n */\nexport function formatMoney<TCode extends CurrencyCode>(\n  amount: Dinero<number, TCode>,\n  code: TCode\n): string {\n  const locale = CURRENCY_LOCALES[code];\n\n  return toDecimal(amount, ({ value, currency }) =>\n    Number(value).toLocaleString(locale, {\n      style: 'currency',\n      currency: currency.code,\n    })\n  );\n}\n\n/**\n * Format a raw minor-unit amount for display.\n */\nexport function formatCents<TCode extends CurrencyCode>(\n  minorUnits: number,\n  code: TCode\n): string {\n  return formatMoney(fromMinorUnits(minorUnits, code), code);\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/src/lib/types.ts",
    "content": "export type CurrencyCode = 'USD' | 'EUR' | 'GBP' | 'JPY';\n\nexport type Category =\n  | 'Stocks'\n  | 'Crypto'\n  | 'Cash'\n  | 'Bonds'\n  | 'Real Estate'\n  | 'Other';\n\nexport interface Holding {\n  id: string;\n  name: string;\n  category: Category;\n  quantity: number;\n  unitPriceCents: number;\n  currency: CurrencyCode;\n}\n\nexport const CURRENCIES: CurrencyCode[] = ['USD', 'EUR', 'GBP', 'JPY'];\n\nexport const CATEGORIES: Category[] = [\n  'Stocks',\n  'Crypto',\n  'Cash',\n  'Bonds',\n  'Real Estate',\n  'Other',\n];\n\nexport const CATEGORY_COLORS: Record<Category, string> = {\n  Stocks: '#60a5fa',\n  Crypto: '#fb923c',\n  Cash: '#34d399',\n  Bonds: '#c084fc',\n  'Real Estate': '#fbbf24',\n  Other: '#9ca3af',\n};\n\nexport const CURRENCY_COLORS: Record<CurrencyCode, string> = {\n  USD: '#60a5fa',\n  EUR: '#34d399',\n  GBP: '#fb923c',\n  JPY: '#f472b6',\n};\n"
  },
  {
    "path": "examples/portfolio-tracker/src/main.tsx",
    "content": "import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport './index.css';\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>\n);\n"
  },
  {
    "path": "examples/portfolio-tracker/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/portfolio-tracker/vite.config.ts",
    "content": "import path from 'node:path';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/portfolio-tracker/',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "examples/pricing-react/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n*.tsbuildinfo\n"
  },
  {
    "path": "examples/pricing-react/README.md",
    "content": "# Pricing Page\n\nA React app that displays tiered pricing plans with annual discounts and per-seat pricing, powered by [Dinero.js](https://dinerojs.com).\n\n[**Live demo →**](https://dinerojs.com/examples/pricing-react/)\n\n## What you'll learn\n\n- Creating monetary values with `dinero`\n- Multiplying amounts with `multiply` for per-seat pricing\n- Computing discounts with `allocate` for precise ratio-based distribution\n- Subtracting amounts with `subtract` for discounted prices\n- Checking values with `isZero` and `hasSubUnits` for display logic\n- Formatting money for display with `toDecimal`\n\n## Getting started\n\n1. Install dependencies from the **repository root**:\n\n   ```sh\n   npm install\n   ```\n\n2. Navigate to the example:\n\n   ```sh\n   cd examples/pricing-react\n   ```\n\n3. Start the dev server:\n\n   ```sh\n   npm run dev\n   ```\n\n## Learn more\n\nCheck out the [Dinero.js documentation](https://dinerojs.com) to explore the full API.\n"
  },
  {
    "path": "examples/pricing-react/index.html",
    "content": "<!doctype html>\n<html lang=\"en\" style=\"color-scheme: dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta name=\"theme-color\" content=\"#0c0d0f\" />\n    <title>Pricing Plans — Dinero.js Example</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <script\n      src=\"https://cdn.usefathom.com/script.js\"\n      data-site=\"PSUFDDGC\"\n      defer\n    ></script>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/pricing-react/package.json",
    "content": "{\n  \"name\": \"@dinero.js/example-pricing-react\",\n  \"version\": \"2.0.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"dinero.js\": \"2.0.2\",\n    \"lucide-react\": \"^0.475.0\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.0\",\n    \"@types/react\": \"^19.0.0\",\n    \"@types/react-dom\": \"^19.0.0\",\n    \"@vitejs/plugin-react\": \"^4.3.0\",\n    \"postcss\": \"^8.4.0\",\n    \"tailwindcss\": \"^4.0.0\",\n    \"typescript\": \"^5.7.0\",\n    \"vite\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/pricing-react/postcss.config.js",
    "content": "export default {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n};\n"
  },
  {
    "path": "examples/pricing-react/src/App.tsx",
    "content": "import { useState } from 'react';\n\nimport { PricingToggle } from '@/components/pricing-toggle';\nimport { SeatSlider } from '@/components/seat-slider';\nimport { TierCard } from '@/components/tier-card';\nimport { tiers } from '@/data';\nimport { fromMinorUnits, multiply } from '@/lib/money';\n\nconst LOGO = (\n  <svg\n    viewBox=\"0 0 361.4 213.6\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className=\"h-6 w-auto\"\n    aria-hidden=\"true\"\n  >\n    <path\n      fill=\"#4466ff\"\n      d=\"M361.4 147.8h-41.1v-8.2c0-2.8-.1-5.5-.3-8.2h41.4V115h-43.5c-3.4-16.7-10.2-32.1-19.6-45.6 8.7-21 7.3-45.3-4.2-65.3-1.5-2.5-4.2-4.1-7.2-4.1-20 0-39 8.2-52.7 22.1-11.7-3.7-24.2-5.7-37.1-5.7h-32.8c-12.9 0-25.4 2-37.1 5.7C113.5 8.2 94.5 0 74.5 0c-2.9 0-5.6 1.6-7.1 4.1-11.5 20-12.9 44.3-4.2 65.3C53.8 82.9 47 98.3 43.6 115H0v16.4h41.4c-.2 2.7-.3 5.5-.3 8.2v8.2H0v16.4h41.1v49.3h279.3v-49.3h41.1v-16.4h-.1z\"\n    />\n    <path\n      fill=\"#fff\"\n      d=\"M197.1 32.9h-32.8c-58.9 0-106.8 47.9-106.8 106.8v57.5h246.3v-57.5c.1-58.9-47.8-106.8-106.7-106.8z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M96.7 115c-3.3 0-5.3-3.7-3.4-6.4 3.2-4.6 9.5-10 21.7-10 17 0 26.2 9.8 30.3 15.9 1.1 1.6-.3 3.6-2.2 3.2-4.8-1.2-13.6-2.6-28.1-2.6H96.7v-.1z\"\n    />\n    <path\n      fill=\"#f7a\"\n      d=\"M180.7 123.7c11 0 16.4 0 16.4 5.5 0 4-8.7 8-13.5 9.9-1.9.8-4 .8-5.9 0-4.7-1.9-13.5-5.9-13.5-9.8.1-5.6 5.6-5.6 16.5-5.6z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M118.7 148.3c-.7-1.3 1-2.7 2.1-1.7 5 4.5 13.6 9.4 27.1 9.4 16.4 0 24.6-8.2 32.9-8.2 8.2 0 16.4 8.2 32.9 8.2 13.5 0 22.1-5 27.1-9.4 1.1-1 2.8.3 2.1 1.7-4.5 8.5-13.5 20.1-29.2 20.1-16.4 0-24.6-8.2-32.8-8.2-8.2 0-16.4 8.2-32.8 8.2-15.9 0-24.9-11.6-29.4-20.1z\"\n    />\n    <circle cx=\"82.1\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n    <path\n      fill=\"#bcf\"\n      d=\"M197.1 32.9h-32.8c-1 0-1.9.1-2.8.1-2.9 16.1 9.6 35.3 29.9 29.2 10.7-3.2 27.8-7.1 36.7 2.5 7.8 8.5 1.5 22.7 6.8 33 11.4 21.8 42.9 24.2 66.8 20.2-10.1-48.5-53.2-85-104.6-85z\"\n    />\n    <path\n      fill=\"#4466ff\"\n      d=\"M264.7 115c3.3 0 5.3-3.7 3.4-6.4-3.2-4.6-9.5-10-21.7-10-17 0-26.2 9.8-30.3 15.9-1 1.6.3 3.6 2.2 3.2 4.8-1.2 13.6-2.6 28.1-2.6h18.3v-.1z\"\n    />\n    <circle cx=\"279.3\" cy=\"147.8\" r=\"16.4\" fill=\"#bcf\" />\n  </svg>\n);\n\nfunction App() {\n  const [monthlyBilling, setMonthlyBilling] = useState(true);\n  const [seats, setSeats] = useState(1);\n\n  return (\n    <div className=\"min-h-screen\">\n      <header className=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-5\">\n        <div className=\"flex min-w-0 items-center gap-2 sm:gap-3\">\n          {LOGO}\n          <h1 className=\"truncate text-sm font-semibold text-foreground\">\n            Pricing Plans\n          </h1>\n          <span className=\"hidden whitespace-nowrap rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary sm:inline\">\n            Built with Dinero.js\n          </span>\n        </div>\n        <a\n          href=\"https://github.com/dinerojs/dinero.js/tree/main/examples/pricing-react\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"shrink-0 rounded-sm text-xs text-text-muted transition-colors hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-primary/50\"\n        >\n          GitHub\n          <span className=\"hidden sm:inline\"> source</span>\n        </a>\n      </header>\n      <div className=\"mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8\">\n        <div className=\"flex flex-col items-center\">\n          <h2 className=\"text-center text-5xl font-bold text-wrap-balance text-foreground\">\n            Pricing Plans\n          </h2>\n          <p className=\"mt-5 text-center text-xl text-text-secondary\">\n            One tool for your whole team.\n          </p>\n          <PricingToggle\n            monthlyBilling={monthlyBilling}\n            onToggle={setMonthlyBilling}\n          />\n          <SeatSlider seats={seats} onChange={setSeats} />\n        </div>\n        <div className=\"mt-16 grid grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-4\">\n          {tiers.map((tier) => {\n            const isActive = !tier.individual || seats === 1;\n            const price = multiply(\n              fromMinorUnits(tier.monthlyPrice),\n              tier.individual ? 1 : seats\n            );\n\n            return (\n              <TierCard\n                key={tier.name}\n                name={tier.name}\n                description={tier.description}\n                featuresHeading={tier.featuresHeading}\n                features={tier.features}\n                price={price}\n                monthlyBilling={monthlyBilling}\n                isActive={isActive}\n              />\n            );\n          })}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/pricing-react/src/components/pricing-toggle.tsx",
    "content": "const DISCOUNT_RATE = 10;\n\ninterface PricingToggleProps {\n  monthlyBilling: boolean;\n  onToggle: (monthly: boolean) => void;\n}\n\nexport function PricingToggle({\n  monthlyBilling,\n  onToggle,\n}: PricingToggleProps) {\n  return (\n    <div\n      role=\"radiogroup\"\n      aria-label=\"Billing period\"\n      className=\"relative mt-6 flex gap-0.5 self-center rounded-lg bg-muted p-0.5 sm:mt-8\"\n    >\n      <button\n        type=\"button\"\n        role=\"radio\"\n        aria-checked={monthlyBilling}\n        onClick={() => onToggle(true)}\n        className={`relative rounded-md px-8 py-2 text-sm font-medium transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none ${\n          monthlyBilling\n            ? 'bg-card text-foreground shadow-sm'\n            : 'text-text-secondary hover:bg-card/50 hover:text-foreground'\n        }`}\n      >\n        Monthly\n      </button>\n      <button\n        type=\"button\"\n        role=\"radio\"\n        aria-checked={!monthlyBilling}\n        onClick={() => onToggle(false)}\n        className={`relative rounded-md px-8 py-2 text-sm font-medium transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none ${\n          !monthlyBilling\n            ? 'bg-card text-foreground shadow-sm'\n            : 'text-text-secondary hover:bg-card/50 hover:text-foreground'\n        }`}\n      >\n        Yearly\n        <span\n          className={`ml-2 -mr-4 rounded-full px-2 py-0.5 text-xs ${\n            !monthlyBilling\n              ? 'bg-primary/20 text-primary'\n              : 'bg-primary/10 text-primary/70'\n          }`}\n        >\n          -{DISCOUNT_RATE}%\n        </span>\n      </button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/pricing-react/src/components/seat-slider.tsx",
    "content": "interface SeatSliderProps {\n  seats: number;\n  onChange: (seats: number) => void;\n}\n\nexport function SeatSlider({ seats, onChange }: SeatSliderProps) {\n  const percentage = ((seats - 1) / 99) * 100;\n  const label = `${seats}\\u00a0user${seats > 1 ? 's' : ''}`;\n\n  return (\n    <div className=\"relative mt-14 px-4 sm:px-16 lg:px-40 xl:px-24\">\n      <div className=\"relative\">\n        <div\n          className=\"absolute -top-10 -translate-x-1/2 rounded bg-foreground px-2 py-1 text-xs font-medium text-background\"\n          style={{ left: `${percentage}%` }}\n          aria-hidden=\"true\"\n        >\n          {label}\n        </div>\n        <input\n          type=\"range\"\n          min={1}\n          max={100}\n          value={seats}\n          onChange={(e) => onChange(Number(e.target.value))}\n          aria-label=\"Number of users\"\n          aria-valuetext={label}\n          className=\"range-slider w-full touch-manipulation\"\n          style={{ '--progress': `${percentage}%` } as React.CSSProperties}\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/pricing-react/src/components/tier-card.tsx",
    "content": "import type { Dinero } from 'dinero.js';\nimport { Check } from 'lucide-react';\n\nimport { multiply, allocate, subtract, isZero, formatMoney } from '@/lib/money';\n\nconst DISCOUNT_RATE = 10;\n\ninterface TierCardProps {\n  name: string;\n  description: string;\n  featuresHeading: string;\n  features: string[];\n  price: Dinero<number>;\n  monthlyBilling: boolean;\n  isActive: boolean;\n}\n\nexport function TierCard({\n  name,\n  description,\n  featuresHeading,\n  features,\n  price,\n  monthlyBilling,\n  isActive,\n}: TierCardProps) {\n  return (\n    <div\n      className={`flex h-full flex-col rounded-lg border border-border bg-card transition-opacity duration-150 ${\n        isActive ? '' : 'opacity-40'\n      }`}\n    >\n      <div className=\"flex flex-col p-6\">\n        <h2 className=\"text-lg font-semibold text-foreground\">{name}</h2>\n        <p className=\"mt-2 text-sm text-text-secondary\">{description}</p>\n        <p className=\"mt-8 flex items-baseline gap-1\">\n          {monthlyBilling ? (\n            <MonthlyPrice price={price} />\n          ) : (\n            <YearlyPrice price={price} />\n          )}\n        </p>\n        <button\n          type=\"button\"\n          disabled={!isActive}\n          className=\"mt-8 block w-full rounded-md border border-primary bg-primary px-4 py-2 text-center text-sm font-semibold text-primary-foreground transition-colors duration-150 hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n        >\n          Buy {name}\n        </button>\n      </div>\n      <div className=\"border-t border-border px-6 pt-6 pb-8 flex-1\">\n        <h3 className=\"text-xs font-medium tracking-wide text-foreground uppercase\">\n          {featuresHeading}\n        </h3>\n        <ul className=\"mt-6 space-y-4\">\n          {features.map((feature) => (\n            <li key={feature} className=\"flex min-w-0 items-center gap-3\">\n              <div className=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-positive/10\">\n                <Check className=\"size-3 text-positive\" aria-hidden=\"true\" />\n              </div>\n              <span className=\"text-sm text-text-secondary\">{feature}</span>\n            </li>\n          ))}\n        </ul>\n      </div>\n    </div>\n  );\n}\n\nfunction MonthlyPrice({ price }: { price: Dinero<number> }) {\n  return (\n    <>\n      <span className=\"text-3xl font-bold tabular-nums text-foreground\">\n        {formatMoney(price)}\n      </span>\n      <span className=\"flex flex-col text-xs font-medium text-text-muted\">\n        <span>per month</span>\n      </span>\n    </>\n  );\n}\n\nfunction YearlyPrice({ price }: { price: Dinero<number> }) {\n  const fullPrice = multiply(price, 12);\n  const [discount] = allocate(fullPrice, [DISCOUNT_RATE, 100 - DISCOUNT_RATE]);\n  const discountedPrice = subtract(fullPrice, discount);\n\n  return (\n    <>\n      <span className=\"text-3xl font-bold tabular-nums text-foreground\">\n        {formatMoney(discountedPrice)}\n      </span>\n      <span className=\"flex flex-col text-xs font-medium text-text-muted\">\n        {!isZero(fullPrice) && (\n          <span className=\"tabular-nums line-through\">\n            {formatMoney(fullPrice)}\n          </span>\n        )}\n        <span>per year</span>\n      </span>\n    </>\n  );\n}\n"
  },
  {
    "path": "examples/pricing-react/src/data/index.ts",
    "content": "import type { PricingTier } from '@/types';\n\nimport items from './items.json';\n\nexport const tiers: PricingTier[] = items;\n"
  },
  {
    "path": "examples/pricing-react/src/data/items.json",
    "content": "[\n  {\n    \"name\": \"Personal\",\n    \"monthlyPrice\": 0,\n    \"individual\": true,\n    \"description\": \"For organizing every corner of your life.\",\n    \"featuresHeading\": \"Free for individuals\",\n    \"features\": [\n      \"Single user\",\n      \"Unlimited pages & blocks\",\n      \"Share with 5 guests\",\n      \"Sync across devices\"\n    ]\n  },\n  {\n    \"name\": \"Freelancer\",\n    \"monthlyPrice\": 400,\n    \"individual\": true,\n    \"description\": \"For power users who want to do even more.\",\n    \"featuresHeading\": \"Everything in Personal, plus\",\n    \"features\": [\n      \"Single user\",\n      \"Unlimited file uploads\",\n      \"Unlimited guests\",\n      \"Version history\"\n    ]\n  },\n  {\n    \"name\": \"Team\",\n    \"monthlyPrice\": 800,\n    \"individual\": false,\n    \"description\": \"For teams who want to work together in one place.\",\n    \"featuresHeading\": \"Everything in Pro, plus\",\n    \"features\": [\n      \"Unlimited team members\",\n      \"Collaborative workspace\",\n      \"Advanced permissions\",\n      \"Admin tools\"\n    ]\n  },\n  {\n    \"name\": \"Enterprise\",\n    \"monthlyPrice\": 1600,\n    \"individual\": false,\n    \"description\": \"Controls and support to run your company.\",\n    \"featuresHeading\": \"Everything in Team, plus\",\n    \"features\": [\n      \"SAML SSO\",\n      \"User provisioning (SCIM)\",\n      \"Advanced security\",\n      \"Dedicated manager\",\n      \"Custom contract\"\n    ]\n  }\n]\n"
  },
  {
    "path": "examples/pricing-react/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --background: #0c0d0f;\n  --foreground: #ededef;\n  --card: #131416;\n  --card-foreground: #ededef;\n  --popover: #131416;\n  --popover-foreground: #ededef;\n  --primary: #4466ff;\n  --primary-foreground: #ffffff;\n  --secondary: #131416;\n  --secondary-foreground: #ededef;\n  --muted: #08090a;\n  --muted-foreground: #a1a1a9;\n  --accent: #131416;\n  --accent-foreground: #ededef;\n  --destructive: #f87171;\n  --destructive-foreground: #ffffff;\n  --border: rgba(255, 255, 255, 0.08);\n  --input: rgba(255, 255, 255, 0.08);\n  --ring: #4466ff;\n  --radius: 0.5rem;\n  --text-secondary: #a1a1a9;\n  --text-muted: #6e6e76;\n  --positive: #34d399;\n  --warning: #fbbf24;\n}\n\n@theme inline {\n  --font-sans:\n    'Plus Jakarta Sans', 'Plus Jakarta Sans Fallback', system-ui, sans-serif;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-destructive-foreground: var(--destructive-foreground);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-positive: var(--positive);\n  --color-warning: var(--warning);\n  --color-text-secondary: var(--text-secondary);\n  --color-text-muted: var(--text-muted);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n    font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';\n  }\n}\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n::-webkit-scrollbar-thumb {\n  background: rgba(255, 255, 255, 0.1);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: rgba(255, 255, 255, 0.2);\n}\n\n/* Range slider */\n.range-slider {\n  --progress: 0%;\n  -webkit-appearance: none;\n  appearance: none;\n  background: transparent;\n  cursor: pointer;\n  height: 24px;\n}\n\n.range-slider::-webkit-slider-runnable-track {\n  height: 6px;\n  border-radius: 3px;\n  background: linear-gradient(\n    to right,\n    var(--primary) var(--progress),\n    rgba(255, 255, 255, 0.12) var(--progress)\n  );\n}\n\n.range-slider::-moz-range-track {\n  height: 6px;\n  border-radius: 3px;\n  background: rgba(255, 255, 255, 0.12);\n}\n\n.range-slider::-moz-range-progress {\n  height: 6px;\n  border-radius: 3px;\n  background: var(--primary);\n}\n\n.range-slider::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: var(--primary);\n  border: 3px solid var(--background);\n  margin-top: -9px;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n}\n\n.range-slider::-moz-range-thumb {\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: var(--primary);\n  border: 3px solid var(--background);\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n}\n\n.range-slider:focus-visible {\n  outline: none;\n}\n\n.range-slider:focus-visible::-webkit-slider-thumb {\n  box-shadow:\n    0 1px 3px rgba(0, 0, 0, 0.4),\n    0 0 0 3px var(--ring);\n}\n\n.range-slider:focus-visible::-moz-range-thumb {\n  box-shadow:\n    0 1px 3px rgba(0, 0, 0, 0.4),\n    0 0 0 3px var(--ring);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .range-slider::-webkit-slider-thumb {\n    transition: none;\n  }\n  .range-slider::-moz-range-thumb {\n    transition: none;\n  }\n}\n"
  },
  {
    "path": "examples/pricing-react/src/lib/money.ts",
    "content": "import {\n  dinero,\n  multiply,\n  allocate,\n  subtract,\n  isZero,\n  toDecimal,\n} from 'dinero.js';\nimport type { Dinero } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nexport function fromMinorUnits(amount: number): Dinero<number> {\n  return dinero({ amount, currency: USD });\n}\n\nexport function formatMoney(amount: Dinero<number>): string {\n  return toDecimal(amount, ({ value, currency }) =>\n    Number(value).toLocaleString('en-US', {\n      style: 'currency',\n      currency: currency.code,\n    })\n  );\n}\n\nexport { multiply, allocate, subtract, isZero };\n"
  },
  {
    "path": "examples/pricing-react/src/main.tsx",
    "content": "import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport App from './App';\n\nimport './index.css';\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>\n);\n"
  },
  {
    "path": "examples/pricing-react/src/types/index.ts",
    "content": "export interface PricingTier {\n  name: string;\n  monthlyPrice: number;\n  individual: boolean;\n  description: string;\n  featuresHeading: string;\n  features: string[];\n}\n"
  },
  {
    "path": "examples/pricing-react/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/pricing-react/vite.config.ts",
    "content": "import path from 'node:path';\nimport react from '@vitejs/plugin-react';\nimport { defineConfig } from 'vite';\n\nexport default defineConfig({\n  base: '/examples/pricing-react/',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      '@': path.resolve(__dirname, './src'),\n    },\n  },\n});\n"
  },
  {
    "path": "global.d.ts",
    "content": "declare const __DEV__: boolean;\ndeclare const __TEST__: boolean;\n"
  },
  {
    "path": "lint-staged.config.cjs",
    "content": "module.exports = {\n  'packages/**/*.ts': () => 'npx tsc -p tsconfig.json --noEmit',\n  '!(docs/)**/*.(js|mjs|ts)': (filenames) => `oxlint ${filenames.join(' ')}`,\n  '!(docs/)**/*.(js|mjs|jsx|ts|tsx)': (filenames) =>\n    `npm run format -- ${filenames.join(' ')}`,\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@dinero.js/monorepo\",\n  \"version\": \"2.0.2\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"packageManager\": \"npm@11.11.0\",\n  \"type\": \"module\",\n  \"workspaces\": [\n    \"packages/*\",\n    \"examples/*\",\n    \"docs\"\n  ],\n  \"scripts\": {\n    \"build:clean\": \"turbo build:clean\",\n    \"build\": \"turbo build --filter=./packages/*\",\n    \"format\": \"prettier --write .\",\n    \"lint\": \"oxlint packages/ test/\",\n    \"prepare\": \"husky && npm run build\",\n    \"release\": \"shipjs prepare\",\n    \"test:size\": \"size-limit\",\n    \"test:types\": \"tsc -p tsconfig.json --noEmit\",\n    \"test\": \"vitest\",\n    \"docs:dev\": \"npm run dev -w @dinero.js/docs\",\n    \"docs:build\": \"npm run build -w @dinero.js/docs\",\n    \"docs:preview\": \"npm run preview -w @dinero.js/docs\"\n  },\n  \"devDependencies\": {\n    \"@size-limit/file\": \"^12.0.0\",\n    \"@types/big.js\": \"6.2.2\",\n    \"@vitest/coverage-v8\": \"^4.0.18\",\n    \"big.js\": \"^7.0.1\",\n    \"fast-check\": \"^4.5.3\",\n    \"husky\": \"^9.1.7\",\n    \"lint-staged\": \"^16.2.7\",\n    \"oxlint\": \"^1.42.0\",\n    \"prettier\": \"^3.4.2\",\n    \"rimraf\": \"^6.0.1\",\n    \"shipjs\": \"^0.28.0\",\n    \"size-limit\": \"^12.0.0\",\n    \"turbo\": \"^2.8.14\",\n    \"typescript\": \"^5.7.3\",\n    \"vite\": \"^7.3.1\",\n    \"vitest\": \"^4.0.18\"\n  }\n}\n"
  },
  {
    "path": "packages/dinero.js/README.md",
    "content": "# dinero.js\n\n> Create, calculate, and format money in JavaScript and TypeScript\n\n**This package exports Dinero.js.** It works out of the box with `number` types, but you can adapt it for custom types or third-party libraries by implementing a custom calculator.\n\n## 📦 Install\n\n```sh\nnpm install dinero.js\n\n# or\n\nyarn add dinero.js\n```\n\n## ⚡️ Quick start\n\n`Dinero` objects are minimal. Every function in `dinero.js` is side-effect free, allowing you only to bundle exactly what you use.\n\n```js\nimport { dinero, add } from 'dinero.js';\nimport { USD } from 'dinero.js/currencies';\n\nconst d1 = dinero({ amount: 500, currency: USD });\nconst d2 = dinero({ amount: 800, currency: USD });\n\nadd(d1, d2);\n```\n\n## 📚 Documentation\n\nFor full documentation, visit the [online documentation](https://dinerojs.com).\n"
  },
  {
    "path": "packages/dinero.js/package.json",
    "content": "{\n  \"name\": \"dinero.js\",\n  \"version\": \"2.0.2\",\n  \"description\": \"Create, calculate, and format money in JavaScript and TypeScript\",\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"keywords\": [\n    \"money\",\n    \"monetary\",\n    \"amount\",\n    \"immutable\",\n    \"pure\",\n    \"side-effect free\",\n    \"currency\",\n    \"finance\",\n    \"typescript\",\n    \"formatting\",\n    \"bigint\",\n    \"decimal\"\n  ],\n  \"homepage\": \"https://dinerojs.com\",\n  \"bugs\": \"https://github.com/dinerojs/dinero.js/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dinerojs/dinero.js.git\"\n  },\n  \"type\": \"module\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"Sarah Dayan\",\n    \"url\": \"https://sarahdayan.dev\"\n  },\n  \"sideEffects\": false,\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/esm/index.d.ts\",\n      \"default\": \"./dist/esm/index.js\"\n    },\n    \"./currencies\": {\n      \"types\": \"./dist/esm/currencies/index.d.ts\",\n      \"default\": \"./dist/esm/currencies/index.js\"\n    },\n    \"./bigint/currencies\": {\n      \"types\": \"./dist/esm/bigint/currencies/index.d.ts\",\n      \"default\": \"./dist/esm/bigint/currencies/index.js\"\n    },\n    \"./bigint\": {\n      \"types\": \"./dist/esm/bigint/index.d.ts\",\n      \"default\": \"./dist/esm/bigint/index.js\"\n    }\n  },\n  \"main\": \"dist/esm/index.js\",\n  \"umd:main\": \"dist/umd/index.production.js\",\n  \"jsdelivr\": \"dist/umd/index.production.js\",\n  \"unpkg\": \"dist/umd/index.production.js\",\n  \"module\": \"dist/esm/index.js\",\n  \"source\": \"src/index.ts\",\n  \"types\": \"dist/esm/index.d.ts\",\n  \"files\": [\n    \"dist/\"\n  ],\n  \"scripts\": {\n    \"build:clean\": \"rimraf ./dist\",\n    \"build\": \"tsdown\",\n    \"lint\": \"oxlint .\",\n    \"test\": \"vitest --config ../../vitest.config.ts --dir src\"\n  },\n  \"devDependencies\": {\n    \"tsdown\": \"^0.20.1\"\n  }\n}\n"
  },
  {
    "path": "packages/dinero.js/src/__tests__/currency-safety.typetest.ts",
    "content": "/**\n * Type-level tests for currency safety.\n *\n * These tests don't run at runtime. They are validated by `npm run test:types`.\n * Lines marked with `@ts-expect-error` must produce a type error.\n * If they don't, tsc will report an \"unused @ts-expect-error\" error, failing\n * the type check.\n */\n\nimport { USD, EUR } from 'dinero.js/currencies';\n\nimport {\n  dinero,\n  add,\n  subtract,\n  greaterThan,\n  greaterThanOrEqual,\n  lessThan,\n  lessThanOrEqual,\n  equal,\n  compare,\n  minimum,\n  maximum,\n  haveSameAmount,\n  normalizeScale,\n  allocate,\n  multiply,\n  trimScale,\n  transformScale,\n  convert,\n  toSnapshot,\n  toDecimal,\n  toUnits,\n  haveSameCurrency,\n} from 'dinero.js';\n\nconst dineroUSD = dinero({ amount: 1000, currency: USD });\nconst dineroEUR = dinero({ amount: 1000, currency: EUR });\n\n/**\n * Same-currency operations should compile\n */\n\nadd(dineroUSD, dineroUSD);\nsubtract(dineroUSD, dineroUSD);\ngreaterThan(dineroUSD, dineroUSD);\ngreaterThanOrEqual(dineroUSD, dineroUSD);\nlessThan(dineroUSD, dineroUSD);\nlessThanOrEqual(dineroUSD, dineroUSD);\nequal(dineroUSD, dineroUSD);\ncompare(dineroUSD, dineroUSD);\nminimum([dineroUSD, dineroUSD]);\nmaximum([dineroUSD, dineroUSD]);\nhaveSameAmount([dineroUSD, dineroUSD]);\nnormalizeScale([dineroUSD, dineroUSD]);\n\n/**\n * Cross-currency operations should NOT compile\n */\n\n// @ts-expect-error - Different currencies\nadd(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\nsubtract(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\ngreaterThan(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\ngreaterThanOrEqual(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\nlessThan(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\nlessThanOrEqual(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\nequal(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\ncompare(dineroUSD, dineroEUR);\n\n// @ts-expect-error - Different currencies\nminimum([dineroUSD, dineroEUR]);\n\n// @ts-expect-error - Different currencies\nmaximum([dineroUSD, dineroEUR]);\n\n// @ts-expect-error - Different currencies\nhaveSameAmount([dineroUSD, dineroEUR]);\n\n// @ts-expect-error - Different currencies\nnormalizeScale([dineroUSD, dineroEUR]);\n\n/**\n * Unary operations preserve currency\n */\n\nconst allocated = allocate(dineroUSD, [50, 50]);\n// Each allocation result should still be USD-typed\nadd(allocated[0], dineroUSD);\n// @ts-expect-error - Allocated USD + EUR\nadd(allocated[0], dineroEUR);\n\nconst multiplied = multiply(dineroUSD, 2);\nadd(multiplied, dineroUSD);\n// @ts-expect-error - Multiplied USD + EUR\nadd(multiplied, dineroEUR);\n\nconst trimmed = trimScale(dineroUSD);\nadd(trimmed, dineroUSD);\n// @ts-expect-error - Trimmed USD + EUR\nadd(trimmed, dineroEUR);\n\nconst transformed = transformScale(dineroUSD, 4);\nadd(transformed, dineroUSD);\n// @ts-expect-error - Transformed USD + EUR\nadd(transformed, dineroEUR);\n\n/**\n * `convert` changes currency type\n */\n\nconst converted = convert(dineroUSD, EUR, { EUR: { amount: 89, scale: 2 } });\n// Converted object should be EUR-typed\nadd(converted, dineroEUR);\n// @ts-expect-error - Converted EUR + USD\nadd(converted, dineroUSD);\n\n/**\n * `toSnapshot` preserves currency type\n */\n\nconst snapshot = toSnapshot(dineroUSD);\n// Snapshot currency should be compatible with USD\nconst _usdCode: 'USD' = snapshot.currency.code;\n\n/**\n * `toDecimal` transformer receives typed currency\n */\n\ntoDecimal(dineroUSD, ({ currency }) => {\n  const _code: 'USD' = currency.code;\n\n  return _code;\n});\n\n/**\n * `toUnits` transformer receives typed currency\n */\n\ntoUnits(dineroUSD, ({ currency }) => {\n  const _code: 'USD' = currency.code;\n\n  return _code;\n});\n\n/**\n * `haveSameCurrency` accepts different currencies (intentional — it's a runtime check)\n */\n\nhaveSameCurrency([dineroUSD, dineroEUR]);\n\n/**\n * Untyped currencies (plain string) still work (backward compatibility)\n */\n\nconst untypedCurrency = { code: 'XYZ', base: 10, exponent: 2 };\nconst dineroUntyped1 = dinero({ amount: 100, currency: untypedCurrency });\nconst dineroUntyped2 = dinero({ amount: 200, currency: untypedCurrency });\n\n// Two untyped Dinero objects can be added (both are string)\nadd(dineroUntyped1, dineroUntyped2);\n"
  },
  {
    "path": "packages/dinero.js/src/__tests__/dinero.test.ts",
    "content": "import { USD } from '../currencies';\n\nimport { toSnapshot, dinero } from '..';\n\ndescribe('dinero', () => {\n  it('creates a Dinero object', () => {\n    const d = dinero({ amount: 50000, currency: USD, scale: 4 });\n\n    const snapshot = toSnapshot(d);\n\n    expect(snapshot).toMatchObject({ amount: 50000, currency: USD, scale: 4 });\n  });\n  it(\"uses the currency's exponent as scale when not provided\", () => {\n    const d = dinero({ amount: 500, currency: USD });\n\n    const snapshot = toSnapshot(d);\n\n    expect(snapshot).toMatchObject({ amount: 500, currency: USD, scale: 2 });\n  });\n  it('cleans up unwanted properties from the options', () => {\n    const d = dinero({\n      amount: 500,\n      // @ts-expect-error\n      currency: { code: 'USD', exponent: 2, base: 10, _extraProperty: 123 },\n      _extraProperty: 123,\n    });\n\n    const snapshot = toSnapshot(d);\n\n    expect(snapshot).toStrictEqual({\n      amount: 500,\n      currency: USD,\n      scale: 2,\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/add.test.ts",
    "content": "import { EUR, USD, MGA, MRU } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { add, equal, subtract, toSnapshot } from '..';\n\ndescribe('add', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 100, currency: USD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 600,\n          currency: USD,\n          scale: 2,\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: -500, currency: USD });\n        const d2 = dinero({ amount: -100, currency: USD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: -600,\n          currency: USD,\n          scale: 2,\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 6000,\n          currency: USD,\n          scale: 3,\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 100, currency: EUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: 8, currency: MGA });\n        const d2 = dinero({ amount: 3, currency: MGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 11,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: -8, currency: MGA });\n        const d2 = dinero({ amount: -3, currency: MGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: -11,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 8, currency: MGA });\n        const d2 = dinero({ amount: 10, currency: MGA, scale: 2 });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 50,\n          currency: MGA,\n          scale: 2,\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 8, currency: MRU });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n    const bigintMGA = castToBigintCurrency(MGA);\n    const bigintMRU = castToBigintCurrency(MRU);\n\n    describe('decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 100n, currency: bigintUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 600n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('adds up positive Dinero objects with large integers', () => {\n        const d1 = dinero({\n          amount: 1000000000000000050n,\n          currency: bigintUSD,\n        });\n        const d2 = dinero({ amount: 10n, currency: bigintUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 1000000000000000060n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: -500n, currency: bigintUSD });\n        const d2 = dinero({ amount: -100n, currency: bigintUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: -600n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('adds up negative Dinero objects with large integers', () => {\n        const d1 = dinero({\n          amount: -1000000000000000050n,\n          currency: bigintUSD,\n        });\n        const d2 = dinero({ amount: -10n, currency: bigintUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: -1000000000000000060n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 1000n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 6000n,\n          currency: bigintUSD,\n          scale: 3n,\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 100n, currency: bigintEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMGA });\n        const d2 = dinero({ amount: 3n, currency: bigintMGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 11n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: -8n, currency: bigintMGA });\n        const d2 = dinero({ amount: -3n, currency: bigintMGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: -11n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMGA });\n        const d2 = dinero({ amount: 10n, currency: bigintMGA, scale: 2n });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: 50n,\n          currency: bigintMGA,\n          scale: 2n,\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMRU });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n    const bigjsMRU = castToBigjsCurrency(MRU);\n\n    describe('decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(600),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('adds up positive Dinero objects with large integers', () => {\n        const d1 = dinero({\n          amount: new Big('1000000000000000050'),\n          currency: bigjsUSD,\n        });\n        const d2 = dinero({ amount: new Big(10), currency: bigjsUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big('1000000000000000060'),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: new Big(-500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(-100), currency: bigjsUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(-600),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('adds up negative Dinero objects with large integers', () => {\n        const d1 = dinero({\n          amount: new Big('-1000000000000000050'),\n          currency: bigjsUSD,\n        });\n        const d2 = dinero({ amount: new Big(-10), currency: bigjsUSD });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big('-1000000000000000060'),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({\n          amount: new Big(1000),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(6000),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(100), currency: bigjsEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('adds up positive Dinero objects', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(3), currency: bigjsMGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(11),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('adds up negative Dinero objects', () => {\n        const d1 = dinero({ amount: new Big(-8), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(-3), currency: bigjsMGA });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(-11),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMGA });\n        const d2 = dinero({\n          amount: new Big(10),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n\n        const snapshot = toSnapshot(add(d1, d2));\n\n        expect(snapshot).toEqual({\n          amount: new Big(50),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMRU });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          add(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n\n    it('is commutative: add(a, b) equals add(b, a)', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(equal(add(d1, d2), add(d2, d1))).toBe(true);\n        })\n      );\n    });\n    it('is associative: add(add(a, b), c) equals add(a, add(b, c))', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, safeAmount, (a, b, c) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n          const d3 = dinero({ amount: c, currency: USD });\n\n          expect(equal(add(add(d1, d2), d3), add(d1, add(d2, d3)))).toBe(true);\n        })\n      );\n    });\n    it('has zero as identity: add(a, 0) equals a', () => {\n      fc.assert(\n        fc.property(safeAmount, (a) => {\n          const d = dinero({ amount: a, currency: USD });\n          const zero = dinero({ amount: 0, currency: USD });\n\n          expect(equal(add(d, zero), d)).toBe(true);\n        })\n      );\n    });\n    it('is the inverse of subtract: subtract(add(a, b), b) equals a', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(equal(subtract(add(d1, d2), d2), d1)).toBe(true);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/allocate.test.ts",
    "content": "import { USD, MGA } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { allocate, toSnapshot } from '..';\n\ndescribe('allocate', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: 1003, currency: USD });\n        const shares = allocate(d, [50, 50]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 502,\n          currency: USD,\n          scale: 2,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 501,\n          currency: USD,\n          scale: 2,\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: 100, currency: USD });\n        const shares = allocate(d, [1, 3]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 25,\n          currency: USD,\n          scale: 2,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 75,\n          currency: USD,\n          scale: 2,\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: 1003, currency: USD });\n        const shares = allocate(d, [0, 50, 50]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 0,\n          currency: USD,\n          scale: 2,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 502,\n          currency: USD,\n          scale: 2,\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: 501,\n          currency: USD,\n          scale: 2,\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: 100, currency: USD });\n        const shares = allocate(d, [\n          { amount: 505, scale: 1 },\n          { amount: 495, scale: 1 },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 505,\n          currency: USD,\n          scale: 3,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 495,\n          currency: USD,\n          scale: 3,\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: 100, currency: USD });\n        const shares = allocate(d, [\n          { amount: 5050, scale: 2 },\n          { amount: 495, scale: 1 },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 5050,\n          currency: USD,\n          scale: 4,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 4950,\n          currency: USD,\n          scale: 4,\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: 100, currency: USD });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: 100, currency: USD });\n\n        expect(() => {\n          allocate(d, [-50, -50]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: 100, currency: USD });\n\n        expect(() => {\n          allocate(d, [0, 0]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const shares = allocate(d, [50, 50]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 3,\n          currency: MGA,\n          scale: 1,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 2,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const shares = allocate(d, [1, 3]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 1,\n          currency: MGA,\n          scale: 1,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 4,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const shares = allocate(d, [0, 50, 50]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 0,\n          currency: MGA,\n          scale: 1,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 3,\n          currency: MGA,\n          scale: 1,\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: 2,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const shares = allocate(d, [\n          { amount: 505, scale: 1 },\n          { amount: 495, scale: 1 },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 13,\n          currency: MGA,\n          scale: 2,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 12,\n          currency: MGA,\n          scale: 2,\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const shares = allocate(d, [\n          { amount: 5050, scale: 2 },\n          { amount: 495, scale: 1 },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 64,\n          currency: MGA,\n          scale: 3,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 61,\n          currency: MGA,\n          scale: 3,\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n\n        expect(() => {\n          allocate(d, [-50, -50]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n\n        expect(() => {\n          allocate(d, [0, 0]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintMGA = castToBigintCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: 1003n, currency: bigintUSD });\n        const shares = allocate(d, [50n, 50n]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 502n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 501n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('allocates to percentages with large integers', () => {\n        const d = dinero({ amount: 1000000000000000025n, currency: bigintUSD });\n        const shares = allocate(d, [50n, 50n]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 500000000000000013n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 500000000000000012n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n        const shares = allocate(d, [1n, 3n]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 25n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 75n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: 1003n, currency: bigintUSD });\n        const shares = allocate(d, [0n, 50n, 50n]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 0n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 502n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: 501n,\n          currency: bigintUSD,\n          scale: 2n,\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n        const shares = allocate(d, [\n          { amount: 505n, scale: 1n },\n          { amount: 495n, scale: 1n },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 505n,\n          currency: bigintUSD,\n          scale: 3n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 495n,\n          currency: bigintUSD,\n          scale: 3n,\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n        const shares = allocate(d, [\n          { amount: 5050n, scale: 2n },\n          { amount: 495n, scale: 1n },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 5050n,\n          currency: bigintUSD,\n          scale: 4n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 4950n,\n          currency: bigintUSD,\n          scale: 4n,\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n\n        expect(() => {\n          allocate(d, [-50n, -50n]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: 100n, currency: bigintUSD });\n\n        expect(() => {\n          allocate(d, [0n, 0n]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const shares = allocate(d, [50n, 50n]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 3n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 2n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const shares = allocate(d, [1n, 3n]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 1n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 4n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const shares = allocate(d, [0n, 50n, 50n]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 0n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 3n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: 2n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const shares = allocate(d, [\n          { amount: 505n, scale: 1n },\n          { amount: 495n, scale: 1n },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 13n,\n          currency: bigintMGA,\n          scale: 2n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 12n,\n          currency: bigintMGA,\n          scale: 2n,\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const shares = allocate(d, [\n          { amount: 5050n, scale: 2n },\n          { amount: 495n, scale: 1n },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: 64n,\n          currency: bigintMGA,\n          scale: 3n,\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: 61n,\n          currency: bigintMGA,\n          scale: 3n,\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(() => {\n          allocate(d, [-50n, -50n]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(() => {\n          allocate(d, [0n, 0n]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: new Big(1003), currency: bigjsUSD });\n        const shares = allocate(d, [new Big(50), new Big(50)]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(502),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(501),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n        const shares = allocate(d, [new Big(1), new Big(3)]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(25),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(75),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: new Big(1003), currency: bigjsUSD });\n        const shares = allocate(d, [new Big(0), new Big(50), new Big(50)]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(0),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(502),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: new Big(501),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n        const shares = allocate(d, [\n          { amount: new Big(505), scale: new Big(1) },\n          { amount: new Big(495), scale: new Big(1) },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(505),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(495),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n        const shares = allocate(d, [\n          { amount: new Big(5050), scale: new Big(2) },\n          { amount: new Big(495), scale: new Big(1) },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(5050),\n          currency: bigjsUSD,\n          scale: new Big(4),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(4950),\n          currency: bigjsUSD,\n          scale: new Big(4),\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n        expect(() => {\n          allocate(d, [new Big(-50), new Big(-50)]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n        expect(() => {\n          allocate(d, [new Big(0), new Big(0)]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('allocates to percentages', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const shares = allocate(d, [new Big(50), new Big(50)]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(3),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(2),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('allocates to percentages with large integers', () => {\n        const d = dinero({\n          amount: new Big('1000000000000000025'),\n          currency: bigjsUSD,\n        });\n        const shares = allocate(d, [new Big(50), new Big(50)]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big('500000000000000013'),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big('500000000000000012'),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n      });\n      it('allocates to ratios', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const shares = allocate(d, [new Big(1), new Big(3)]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        // Remainder goes to largest ratio (3) per #776\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(1),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(4),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('ignores zero ratios', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const shares = allocate(d, [new Big(0), new Big(50), new Big(50)]);\n        const [firstAllocated, secondAllocated, thirdAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(0),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(3),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n        expect(toSnapshot(thirdAllocated)).toEqual({\n          amount: new Big(2),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('converts the allocated amounts to the safest scale', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const shares = allocate(d, [\n          { amount: new Big(505), scale: new Big(1) },\n          { amount: new Big(495), scale: new Big(1) },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(13),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(12),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n      });\n      it('converts the ratios to the same scale before allocating', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const shares = allocate(d, [\n          { amount: new Big(5050), scale: new Big(2) },\n          { amount: new Big(495), scale: new Big(1) },\n        ]);\n        const [firstAllocated, secondAllocated] = shares;\n\n        expect(toSnapshot(firstAllocated)).toEqual({\n          amount: new Big(64),\n          currency: bigjsMGA,\n          scale: new Big(3),\n        });\n        expect(toSnapshot(secondAllocated)).toEqual({\n          amount: new Big(61),\n          currency: bigjsMGA,\n          scale: new Big(3),\n        });\n      });\n      it('throws when using empty ratios', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(() => {\n          allocate(d, []);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using negative ratios', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(() => {\n          allocate(d, [new Big(-50), new Big(-50)]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n      it('throws when using only zero ratios', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(() => {\n          allocate(d, [new Big(0), new Big(0)]);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Ratios are invalid.]`\n        );\n      });\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n    const positiveRatios = fc.array(fc.integer({ min: 1, max: 100 }), {\n      minLength: 1,\n      maxLength: 10,\n    });\n\n    it('preserves the total: sum of shares equals the original amount', () => {\n      fc.assert(\n        fc.property(safeAmount, positiveRatios, (amount, ratios) => {\n          const d = dinero({ amount, currency: USD });\n          const shares = allocate(d, ratios);\n          const total = shares.reduce(\n            (sum, share) => sum + toSnapshot(share).amount,\n            0\n          );\n\n          expect(total).toBe(amount);\n        })\n      );\n    });\n    it('distributes non-negatively for positive amounts', () => {\n      fc.assert(\n        fc.property(\n          fc.integer({ min: 0, max: 100000 }),\n          positiveRatios,\n          (amount, ratios) => {\n            const d = dinero({ amount, currency: USD });\n            const shares = allocate(d, ratios);\n\n            shares.forEach((share) => {\n              expect(toSnapshot(share).amount).toBeGreaterThanOrEqual(0);\n            });\n          }\n        )\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/compare.test.ts",
    "content": "import { EUR, USD, MGA } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { compare, equal, lessThanOrEqual, greaterThanOrEqual } from '..';\n\ndescribe('compare', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 800, currency: USD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\n        const d2 = dinero({ amount: 800, currency: USD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 500, currency: EUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 8, currency: MGA });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 25, currency: MGA, scale: 2 });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n    const bigintMGA = castToBigintCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('correctly compares large integers', () => {\n        const d1 = dinero({\n          amount: 1000000000000000050n,\n          currency: bigintUSD,\n        });\n        const d2 = dinero({\n          amount: 1000000000000000060n,\n          currency: bigintUSD,\n        });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n        const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMGA });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 25n, currency: bigintMGA, scale: 2n });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('correctly compares large integers', () => {\n        const d1 = dinero({\n          amount: new Big('1000000000000000050'),\n          currency: bigjsUSD,\n        });\n        const d2 = dinero({\n          amount: new Big('1000000000000000060'),\n          currency: bigjsUSD,\n        });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({\n          amount: new Big(5000),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns -1 when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('returns 0 when amounts are equal', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(compare(d1, d2)).toBe(0);\n      });\n      it('returns 1 when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(compare(d1, d2)).toBe(1);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({\n          amount: new Big(25),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        expect(compare(d1, d2)).toBe(-1);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          compare(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n\n    it('is antisymmetric: compare(a, b) equals -compare(b, a)', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(compare(d1, d2)).toBe(-compare(d2, d1));\n        })\n      );\n    });\n    it('is transitive: if a <= b and b <= c, then a <= c', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, safeAmount, (a, b, c) => {\n          const sorted = [a, b, c].sort((x, y) => x - y);\n          const d1 = dinero({ amount: sorted[0], currency: USD });\n          const d2 = dinero({ amount: sorted[1], currency: USD });\n          const d3 = dinero({ amount: sorted[2], currency: USD });\n\n          expect(lessThanOrEqual(d1, d2)).toBe(true);\n          expect(lessThanOrEqual(d2, d3)).toBe(true);\n          expect(lessThanOrEqual(d1, d3)).toBe(true);\n        })\n      );\n    });\n    it('is consistent with equal: compare(a, b) === 0 iff equal(a, b)', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(compare(d1, d2) === 0).toBe(equal(d1, d2));\n        })\n      );\n    });\n    it('is total: for any a, b either a <= b or b <= a', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(lessThanOrEqual(d1, d2) || greaterThanOrEqual(d1, d2)).toBe(\n            true\n          );\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/convert.test.ts",
    "content": "import { EUR, IQD, USD, MGA, MRU } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { convert, toSnapshot } from '..';\n\ndescribe('convert', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: 500, currency: USD });\n\n        const converted = convert(d, EUR, {\n          EUR: {\n            amount: 89,\n            scale: 2,\n          },\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 44500,\n          currency: EUR,\n          scale: 4,\n        });\n      });\n      it(\"uses the destination currency's exponent as scale\", () => {\n        const d = dinero({ amount: 500, currency: USD });\n\n        const converted = convert(d, IQD, {\n          IQD: 1199,\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 5995000,\n          currency: IQD,\n          scale: 3,\n        });\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: 1, currency: MRU });\n\n        const converted = convert(d, MGA, {\n          MGA: 108,\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 108,\n          currency: MGA,\n          scale: 1,\n        });\n      });\n      it('throws when converting between different bases', () => {\n        const d = dinero({ amount: 1000, currency: USD });\n\n        expect(() => {\n          convert(d, MGA, {\n            MGA: {\n              amount: 3912566,\n              scale: 3,\n            },\n          });\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency base.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n    const bigintIQD = castToBigintCurrency(IQD);\n    const bigintMGA = castToBigintCurrency(MGA);\n    const bigintMRU = castToBigintCurrency(MRU);\n\n    describe('decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: 500n, currency: bigintUSD });\n\n        const converted = convert(d, bigintEUR, {\n          EUR: {\n            amount: 89n,\n            scale: 2n,\n          },\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 44500n,\n          currency: bigintEUR,\n          scale: 4n,\n        });\n      });\n      it(\"uses the destination currency's exponent as scale\", () => {\n        const d = dinero({ amount: 500n, currency: bigintUSD });\n\n        const converted = convert(d, bigintIQD, {\n          IQD: 1199n,\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 5995000n,\n          currency: bigintIQD,\n          scale: 3n,\n        });\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: 1n, currency: bigintMRU });\n\n        const converted = convert(d, bigintMGA, {\n          MGA: 108n,\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: 108n,\n          currency: bigintMGA,\n          scale: 1n,\n        });\n      });\n      it('throws when converting between different bases', () => {\n        const d = dinero({ amount: 1000n, currency: bigintUSD });\n\n        expect(() => {\n          convert(d, bigintMGA, {\n            MGA: {\n              amount: 3912566n,\n              scale: 3n,\n            },\n          });\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency base.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n    const bigjsIQD = castToBigjsCurrency(IQD);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n    const bigjsMRU = castToBigjsCurrency(MRU);\n\n    describe('decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        const converted = convert(d, bigjsEUR, {\n          EUR: {\n            amount: new Big(89),\n            scale: new Big(2),\n          },\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: new Big(44500),\n          currency: bigjsEUR,\n          scale: new Big(4),\n        });\n      });\n      it(\"uses the destination currency's exponent as scale\", () => {\n        const d = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        const converted = convert(d, bigjsIQD, {\n          IQD: new Big(1199),\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: new Big(5995000),\n          currency: bigjsIQD,\n          scale: new Big(3),\n        });\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('converts a Dinero object to another currency', () => {\n        const d = dinero({ amount: new Big(1), currency: bigjsMRU });\n\n        const converted = convert(d, bigjsMGA, {\n          MGA: new Big(108),\n        });\n\n        expect(toSnapshot(converted)).toEqual({\n          amount: new Big(108),\n          currency: bigjsMGA,\n          scale: new Big(1),\n        });\n      });\n      it('throws when converting between different bases', () => {\n        const d = dinero({ amount: new Big(1000), currency: bigjsUSD });\n\n        expect(() => {\n          convert(d, bigjsMGA, {\n            MGA: {\n              amount: new Big(3912566),\n              scale: new Big(3),\n            },\n          });\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency base.]`\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/equal.test.ts",
    "content": "import { EUR, MGA, USD } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { equal } from '..';\n\ndescribe('equal', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 800, currency: USD });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: EUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 800, currency: EUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD, scale: 3 });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: MGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 25, currency: MGA, scale: 2 });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: 25, currency: MGA });\n        const d2 = dinero({ amount: 25, currency: MGA, scale: 2 });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n    const bigintMGA = castToBigintCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintEUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 800n, currency: bigintEUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD, scale: 3n });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintMGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 25n, currency: bigintMGA, scale: 2n });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: 25n, currency: bigintMGA });\n        const d2 = dinero({ amount: 25n, currency: bigintMGA, scale: 2n });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsEUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(800), currency: bigjsEUR });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({\n          amount: new Big(5000),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({\n          amount: new Big(500),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns true when amounts and currencies are equal', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when currencies are not equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsMGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts and currencies are not equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        // @ts-expect-error different currencies\n        expect(equal(d1, d2)).toBe(false);\n      });\n      it('returns true when amounts are equal after normalization', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({\n          amount: new Big(25),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n\n        expect(equal(d1, d2)).toBe(true);\n      });\n      it('returns false when amounts are not equal after normalization', () => {\n        const d1 = dinero({ amount: new Big(25), currency: bigjsMGA });\n        const d2 = dinero({\n          amount: new Big(25),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n\n        expect(equal(d1, d2)).toBe(false);\n      });\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n\n    it('is reflexive: equal(a, a) is always true', () => {\n      fc.assert(\n        fc.property(safeAmount, (a) => {\n          const d = dinero({ amount: a, currency: USD });\n\n          expect(equal(d, d)).toBe(true);\n        })\n      );\n    });\n    it('is symmetric: equal(a, b) equals equal(b, a)', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(equal(d1, d2)).toBe(equal(d2, d1));\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/greaterThan.test.ts",
    "content": "import { EUR, MGA, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { greaterThan } from '..';\n\ndescribe('greaterThan', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 800, currency: USD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 500, currency: USD });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800, currency: USD });\n        const d2 = dinero({ amount: 500, currency: EUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 8, currency: MGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: 5, currency: MGA });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 8, currency: MGA });\n        const d2 = dinero({ amount: 5, currency: MGA });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 8, currency: MGA });\n        const d2 = dinero({ amount: 25, currency: MGA, scale: 2 });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 500, currency: USD });\n        const d2 = dinero({ amount: 500, currency: MGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n    const bigintMGA = castToBigintCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 800n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 8n, currency: bigintMGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: 5n, currency: bigintMGA });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMGA });\n        const d2 = dinero({ amount: 5n, currency: bigintMGA });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: 8n, currency: bigintMGA });\n        const d2 = dinero({ amount: 25n, currency: bigintMGA, scale: 2n });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: 500n, currency: bigintUSD });\n        const d2 = dinero({ amount: 500n, currency: bigintMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n\n    describe('decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({\n          amount: new Big(5000),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsEUR });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when the first amount is less than the other', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(8), currency: bigjsMGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns false when amounts are equal', () => {\n        const d1 = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(greaterThan(d1, d2)).toBe(false);\n      });\n      it('returns true when the first amount is greater than the other', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMGA });\n        const d2 = dinero({ amount: new Big(5), currency: bigjsMGA });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('normalizes the result to the highest scale', () => {\n        const d1 = dinero({ amount: new Big(8), currency: bigjsMGA });\n        const d2 = dinero({\n          amount: new Big(25),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n\n        expect(greaterThan(d1, d2)).toBe(true);\n      });\n      it('throws when using different currencies', () => {\n        const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n        const d2 = dinero({ amount: new Big(500), currency: bigjsMGA });\n\n        expect(() => {\n          // @ts-expect-error different currencies\n          greaterThan(d1, d2);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Objects must have the same currency.]`\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/greaterThanOrEqual.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { greaterThanOrEqual } from '..';\n\ndescribe('greaterThanOrEqual', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns false when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 800, currency: USD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 5000, currency: USD, scale: 3 });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 500, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        greaterThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns false when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        greaterThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns false when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(5000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      expect(greaterThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        greaterThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/hasSubUnits.test.ts",
    "content": "import { MGA, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { hasSubUnits } from '..';\n\ndescribe('hasSubUnits', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n    const GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\n\n    describe('decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: 1100, currency: USD });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a custom scale', () => {\n        const d = dinero({ amount: 1100, currency: USD, scale: 3 });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: 1150, currency: USD });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a custom scale', () => {\n        const d = dinero({ amount: 1150, currency: USD, scale: 1 });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: 10, currency: MGA });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: 11, currency: MGA });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a multi-base', () => {\n        const d = dinero({ amount: 240, currency: GBP });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a multi-base', () => {\n        const d = dinero({ amount: 267, currency: GBP });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintMGA = castToBigintCurrency(MGA);\n    const bigintGBP = { code: 'GBP', base: [20n, 12n], exponent: 1n };\n\n    describe('decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: 1100n, currency: bigintUSD });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a custom scale', () => {\n        const d = dinero({ amount: 1100n, currency: bigintUSD, scale: 3n });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: 1150n, currency: bigintUSD });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a custom scale', () => {\n        const d = dinero({ amount: 1150n, currency: bigintUSD, scale: 1n });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: 10n, currency: bigintMGA });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: 11n, currency: bigintMGA });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a multi-base', () => {\n        const d = dinero({ amount: 240n, currency: bigintGBP });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a multi-base', () => {\n        const d = dinero({ amount: 267n, currency: bigintGBP });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n    const bigjsGBP = {\n      code: 'GBP',\n      base: [new Big(20), new Big(12)],\n      exponent: new Big(1),\n    };\n\n    describe('decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: new Big(1100), currency: bigjsUSD });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a custom scale', () => {\n        const d = dinero({\n          amount: new Big(1100),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: new Big(1150), currency: bigjsUSD });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a custom scale', () => {\n        const d = dinero({\n          amount: new Big(1150),\n          currency: bigjsUSD,\n          scale: new Big(1),\n        });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns false when there are no sub-units', () => {\n        const d = dinero({ amount: new Big(10), currency: bigjsMGA });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units', () => {\n        const d = dinero({ amount: new Big(11), currency: bigjsMGA });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n      it('returns false when there are no sub-units based on a multi-base', () => {\n        const d = dinero({ amount: new Big(240), currency: bigjsGBP });\n\n        expect(hasSubUnits(d)).toBe(false);\n      });\n      it('returns true when there are sub-units based on a multi-base', () => {\n        const d = dinero({ amount: new Big(267), currency: bigjsGBP });\n\n        expect(hasSubUnits(d)).toBe(true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/haveSameAmount.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { haveSameAmount } from '..';\n\ndescribe('haveSameAmount', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 1000, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: USD });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal', () => {\n      const d1 = dinero({ amount: 1000, currency: USD });\n      const d2 = dinero({ amount: 2000, currency: USD });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n    it('returns true when amounts are equal once normalized', () => {\n      const d1 = dinero({ amount: 1000, currency: USD });\n      const d2 = dinero({ amount: 10000, currency: USD, scale: 3 });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal once normalized', () => {\n      const d1 = dinero({ amount: 10000, currency: USD });\n      const d2 = dinero({ amount: 10000, currency: USD, scale: 3 });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 1000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal', () => {\n      const d1 = dinero({ amount: 1000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 2000n, currency: bigintUSD });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n    it('returns true when amounts are equal once normalized', () => {\n      const d1 = dinero({ amount: 1000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 10000n, currency: bigintUSD, scale: 3n });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal once normalized', () => {\n      const d1 = dinero({ amount: 10000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 10000n, currency: bigintUSD, scale: 3n });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal', () => {\n      const d1 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(2000), currency: bigjsUSD });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n    it('returns true when amounts are equal once normalized', () => {\n      const d1 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(10000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      expect(haveSameAmount([d1, d2])).toBe(true);\n    });\n    it('returns false when amounts are not equal once normalized', () => {\n      const d1 = dinero({ amount: new Big(10000), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(10000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      expect(haveSameAmount([d1, d2])).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/haveSameCurrency.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { haveSameCurrency } from '..';\n\ndescribe('haveSameCurrency', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when currencies are equal', () => {\n      const d1 = dinero({ amount: 2000, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: USD });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns false when currencies are not equal', () => {\n      const d1 = dinero({ amount: 1000, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: EUR });\n\n      expect(haveSameCurrency([d1, d2])).toBe(false);\n    });\n    it('returns true when currencies are structurally equal', () => {\n      const d1 = dinero({\n        amount: 2000,\n        currency: {\n          code: 'USD',\n          base: 10,\n          exponent: 2,\n        },\n      });\n      const d2 = dinero({\n        amount: 1000,\n        currency: {\n          code: 'USD',\n          base: 10,\n          exponent: 2,\n        },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies are structurally equal', () => {\n      const GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\n      const d1 = dinero({ amount: 240, currency: GBP });\n      const d2 = dinero({ amount: 240, currency: GBP });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies compute to the same base', () => {\n      const d1 = dinero({\n        amount: 240,\n        currency: { code: 'GBP', base: [20, 12], exponent: 1 },\n      });\n      const d2 = dinero({\n        amount: 240,\n        currency: { code: 'GBP', base: 240, exponent: 1 },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns true when currencies are equal', () => {\n      const d1 = dinero({ amount: 2000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns false when currencies are not equal', () => {\n      const d1 = dinero({ amount: 1000n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintEUR });\n\n      expect(haveSameCurrency([d1, d2])).toBe(false);\n    });\n    it('returns true when currencies are structurally equal', () => {\n      const d1 = dinero({\n        amount: 2000n,\n        currency: {\n          code: 'USD',\n          base: 10n,\n          exponent: 2n,\n        },\n      });\n      const d2 = dinero({\n        amount: 1000n,\n        currency: {\n          code: 'USD',\n          base: 10n,\n          exponent: 2n,\n        },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies are structurally equal', () => {\n      const GBP = { code: 'GBP', base: [20n, 12n], exponent: 1n };\n      const d1 = dinero({ amount: 240n, currency: GBP });\n      const d2 = dinero({ amount: 240n, currency: GBP });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies compute to the same base', () => {\n      const d1 = dinero({\n        amount: 240n,\n        currency: { code: 'GBP', base: [20n, 12n], exponent: 1n },\n      });\n      const d2 = dinero({\n        amount: 240n,\n        currency: { code: 'GBP', base: 240n, exponent: 1n },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns true when currencies are equal', () => {\n      const d1 = dinero({ amount: new Big(2000), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns false when currencies are not equal', () => {\n      const d1 = dinero({ amount: new Big(1000), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(1000), currency: bigjsEUR });\n\n      expect(haveSameCurrency([d1, d2])).toBe(false);\n    });\n    it('returns true when currencies are structurally equal', () => {\n      const d1 = dinero({\n        amount: new Big(2000),\n        currency: {\n          code: 'USD',\n          base: new Big(10),\n          exponent: new Big(2),\n        },\n      });\n      const d2 = dinero({\n        amount: new Big(1000),\n        currency: {\n          code: 'USD',\n          base: new Big(10),\n          exponent: new Big(2),\n        },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies are structurally equal', () => {\n      const GBP = {\n        code: 'GBP',\n        base: [new Big(20), new Big(20)],\n        exponent: new Big(1),\n      };\n      const d1 = dinero({ amount: new Big(240), currency: GBP });\n      const d2 = dinero({ amount: new Big(240), currency: GBP });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n    it('returns true when multi-base currencies compute to the same base', () => {\n      const d1 = dinero({\n        amount: new Big(240),\n        currency: {\n          code: 'GBP',\n          base: [new Big(20), new Big(12)],\n          exponent: new Big(1),\n        },\n      });\n      const d2 = dinero({\n        amount: new Big(240),\n        currency: { code: 'GBP', base: new Big(240), exponent: new Big(1) },\n      });\n\n      expect(haveSameCurrency([d1, d2])).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/isNegative.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { isNegative } from '..';\n\ndescribe('isNegative', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when amount is less than 0', () => {\n      const d = dinero({ amount: -100, currency: USD });\n\n      expect(isNegative(d)).toBe(true);\n    });\n    it('returns false when amount is greater than 0', () => {\n      const d = dinero({ amount: 100, currency: USD });\n\n      expect(isNegative(d)).toBe(false);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: 0, currency: USD });\n      const d2 = dinero({ amount: -0, currency: USD });\n\n      expect(isNegative(d1)).toBe(false);\n      expect(isNegative(d2)).toBe(false);\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns true when amount is less than 0', () => {\n      const d = dinero({ amount: -100n, currency: bigintUSD });\n\n      expect(isNegative(d)).toBe(true);\n    });\n    it('returns false when amount is greater than 0', () => {\n      const d = dinero({ amount: 100n, currency: bigintUSD });\n\n      expect(isNegative(d)).toBe(false);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: 0n, currency: bigintUSD });\n      const d2 = dinero({ amount: -0n, currency: bigintUSD });\n\n      expect(isNegative(d1)).toBe(false);\n      expect(isNegative(d2)).toBe(false);\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns true when amount is less than 0', () => {\n      const d = dinero({ amount: new Big(-100), currency: bigjsUSD });\n\n      expect(isNegative(d)).toBe(true);\n    });\n    it('returns false when amount is greater than 0', () => {\n      const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n      expect(isNegative(d)).toBe(false);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: new Big(0), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(-0), currency: bigjsUSD });\n\n      expect(isNegative(d1)).toBe(false);\n      expect(isNegative(d2)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/isPositive.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { isPositive } from '..';\n\ndescribe('isPositive', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns false when amount is less than 0', () => {\n      const d = dinero({ amount: -100, currency: USD });\n\n      expect(isPositive(d)).toBe(false);\n    });\n    it('returns true when amount is greater than 0', () => {\n      const d = dinero({ amount: 100, currency: USD });\n\n      expect(isPositive(d)).toBe(true);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: 0, currency: USD });\n      const d2 = dinero({ amount: -0, currency: USD });\n\n      expect(isPositive(d1)).toBe(false);\n      expect(isPositive(d2)).toBe(false);\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns false when amount is less than 0', () => {\n      const d = dinero({ amount: -100n, currency: bigintUSD });\n\n      expect(isPositive(d)).toBe(false);\n    });\n    it('returns true when amount is greater than 0', () => {\n      const d = dinero({ amount: 100n, currency: bigintUSD });\n\n      expect(isPositive(d)).toBe(true);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: 0n, currency: bigintUSD });\n      const d2 = dinero({ amount: -0n, currency: bigintUSD });\n\n      expect(isPositive(d1)).toBe(false);\n      expect(isPositive(d2)).toBe(false);\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns false when amount is less than 0', () => {\n      const d = dinero({ amount: new Big(-100), currency: bigjsUSD });\n\n      expect(isPositive(d)).toBe(false);\n    });\n    it('returns true when amount is greater than 0', () => {\n      const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n      expect(isPositive(d)).toBe(true);\n    });\n    it('returns false when amount is equal to 0', () => {\n      const d1 = dinero({ amount: new Big(0), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(-0), currency: bigjsUSD });\n\n      expect(isPositive(d1)).toBe(false);\n      expect(isPositive(d2)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/isZero.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { isZero } from '..';\n\ndescribe('isZero', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when amount is equal to 0', () => {\n      const d = dinero({ amount: 0, currency: USD });\n\n      expect(isZero(d)).toBe(true);\n    });\n    it('returns false when amount is not equal to 0', () => {\n      const d = dinero({ amount: 100, currency: USD });\n\n      expect(isZero(d)).toBe(false);\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns true when amount is equal to 0', () => {\n      const d = dinero({ amount: 0n, currency: bigintUSD });\n\n      expect(isZero(d)).toBe(true);\n    });\n    it('returns false when amount is not equal to 0', () => {\n      const d = dinero({ amount: 100n, currency: bigintUSD });\n\n      expect(isZero(d)).toBe(false);\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns true when amount is equal to 0', () => {\n      const d = dinero({ amount: new Big(0), currency: bigjsUSD });\n\n      expect(isZero(d)).toBe(true);\n    });\n    it('returns false when amount is not equal to 0', () => {\n      const d = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n      expect(isZero(d)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/lessThan.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { lessThan } from '..';\n\ndescribe('lessThan', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 800, currency: USD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('returns false when amounts are equal', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\n      const d2 = dinero({ amount: 800, currency: USD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 500, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThan(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('returns false when amounts are equal', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n      const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThan(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('returns false when amounts are equal', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(lessThan(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({\n        amount: new Big(5000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n      expect(lessThan(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThan(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/lessThanOrEqual.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { lessThanOrEqual } from '..';\n\ndescribe('lessThanOrEqual', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 800, currency: USD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800, currency: USD });\n      const d2 = dinero({ amount: 500, currency: USD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 5000, currency: USD, scale: 3 });\n      const d2 = dinero({ amount: 800, currency: USD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 800, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: 800n, currency: bigintUSD });\n      const d2 = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 5000n, currency: bigintUSD, scale: 3n });\n      const d2 = dinero({ amount: 800n, currency: bigintUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 800n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns true when the first amount is less than the other', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns true when amounts are equal', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('returns false when the first amount is greater than the other', () => {\n      const d1 = dinero({ amount: new Big(800), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(false);\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({\n        amount: new Big(5000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsUSD });\n\n      expect(lessThanOrEqual(d1, d2)).toBe(true);\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(800), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        lessThanOrEqual(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/maximum.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { maximum, toSnapshot } from '..';\n\ndescribe('maximum', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns the greatest from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: 150, currency: USD });\n      const d2 = dinero({ amount: 50, currency: USD });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 150,\n        currency: USD,\n        scale: 2,\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 5000,\n        currency: USD,\n        scale: 3,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 150, currency: USD });\n      const d2 = dinero({ amount: 50, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        maximum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns the greatest from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: 150n, currency: bigintUSD });\n      const d2 = dinero({ amount: 50n, currency: bigintUSD });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 150n,\n        currency: bigintUSD,\n        scale: 2n,\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD, scale: 3n });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 5000n,\n        currency: bigintUSD,\n        scale: 3n,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 150n, currency: bigintUSD });\n      const d2 = dinero({ amount: 50n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        maximum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns the greatest from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: new Big(150), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(50), currency: bigjsUSD });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: new Big(150),\n        currency: bigjsUSD,\n        scale: new Big(2),\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      const snapshot = toSnapshot(maximum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: new Big(5000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(150), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(50), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        maximum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/minimum.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { minimum, toSnapshot } from '..';\n\ndescribe('minimum', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns the lower from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: 150, currency: USD });\n      const d2 = dinero({ amount: 50, currency: USD });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 50,\n        currency: USD,\n        scale: 2,\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 1000,\n        currency: USD,\n        scale: 3,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 150, currency: USD });\n      const d2 = dinero({ amount: 50, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        minimum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('returns the lower from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: 150n, currency: bigintUSD });\n      const d2 = dinero({ amount: 50n, currency: bigintUSD });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 50n,\n        currency: bigintUSD,\n        scale: 2n,\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD, scale: 3n });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: 1000n,\n        currency: bigintUSD,\n        scale: 3n,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 150n, currency: bigintUSD });\n      const d2 = dinero({ amount: 50n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        minimum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('returns the lower from a set of Dinero objects', () => {\n      const d1 = dinero({ amount: new Big(150), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(50), currency: bigjsUSD });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: new Big(50),\n        currency: bigjsUSD,\n        scale: new Big(2),\n      });\n    });\n    it('returns the greatest from a set of Dinero objects after normalization', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      const snapshot = toSnapshot(minimum([d1, d2]));\n\n      expect(snapshot).toEqual({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(150), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(50), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        minimum([d1, d2]);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/multiply.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { equal, multiply, toSnapshot } from '..';\n\ndescribe('multiply', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('multiplies positive Dinero objects', () => {\n      const d = dinero({ amount: 400, currency: USD });\n\n      expect(toSnapshot(multiply(d, 4))).toEqual({\n        amount: 1600,\n        scale: 2,\n        currency: USD,\n      });\n      expect(toSnapshot(multiply(d, -1))).toEqual({\n        amount: -400,\n        scale: 2,\n        currency: USD,\n      });\n    });\n    it('multiplies negative Dinero objects', () => {\n      const d = dinero({ amount: -400, currency: USD });\n\n      expect(toSnapshot(multiply(d, 4))).toEqual({\n        amount: -1600,\n        scale: 2,\n        currency: USD,\n      });\n      expect(toSnapshot(multiply(d, 1))).toEqual({\n        amount: -400,\n        scale: 2,\n        currency: USD,\n      });\n    });\n    it('converts the multiplied amount to the safest scale', () => {\n      const d = dinero({ amount: 401, currency: USD });\n\n      const snapshot = toSnapshot(multiply(d, { amount: 2001, scale: 3 }));\n\n      expect(snapshot).toEqual({\n        amount: 802401,\n        scale: 5,\n        currency: USD,\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('multiplies positive Dinero objects', () => {\n      const d = dinero({ amount: 400n, currency: bigintUSD });\n\n      expect(toSnapshot(multiply(d, 4n))).toEqual({\n        amount: 1600n,\n        scale: 2n,\n        currency: bigintUSD,\n      });\n      expect(toSnapshot(multiply(d, -1n))).toEqual({\n        amount: -400n,\n        scale: 2n,\n        currency: bigintUSD,\n      });\n    });\n    it('multiplies negative Dinero objects', () => {\n      const d = dinero({ amount: -400n, currency: bigintUSD });\n\n      expect(toSnapshot(multiply(d, 4n))).toEqual({\n        amount: -1600n,\n        scale: 2n,\n        currency: bigintUSD,\n      });\n      expect(toSnapshot(multiply(d, 1n))).toEqual({\n        amount: -400n,\n        scale: 2n,\n        currency: bigintUSD,\n      });\n    });\n    it('converts the multiplied amount to the safest scale', () => {\n      const d = dinero({ amount: 401n, currency: bigintUSD });\n\n      const snapshot = toSnapshot(multiply(d, { amount: 2001n, scale: 3n }));\n\n      expect(snapshot).toEqual({\n        amount: 802401n,\n        scale: 5n,\n        currency: bigintUSD,\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('multiplies positive Dinero objects', () => {\n      const d = dinero({ amount: new Big(400), currency: bigjsUSD });\n\n      expect(toSnapshot(multiply(d, new Big(4)))).toEqual({\n        amount: new Big(1600),\n        scale: new Big(2),\n        currency: bigjsUSD,\n      });\n      expect(toSnapshot(multiply(d, new Big(-1)))).toEqual({\n        amount: new Big(-400),\n        scale: new Big(2),\n        currency: bigjsUSD,\n      });\n    });\n    it('multiplies negative Dinero objects', () => {\n      const d = dinero({ amount: new Big(-400), currency: bigjsUSD });\n\n      expect(toSnapshot(multiply(d, new Big(4)))).toEqual({\n        amount: new Big(-1600),\n        scale: new Big(2),\n        currency: bigjsUSD,\n      });\n      expect(toSnapshot(multiply(d, new Big(1)))).toEqual({\n        amount: new Big(-400),\n        scale: new Big(2),\n        currency: bigjsUSD,\n      });\n    });\n    it('converts the multiplied amount to the safest scale', () => {\n      const d = dinero({ amount: new Big(401), currency: bigjsUSD });\n\n      const snapshot = toSnapshot(\n        multiply(d, { amount: new Big(2001), scale: new Big(3) })\n      );\n\n      expect(snapshot).toEqual({\n        amount: new Big(802401),\n        scale: new Big(5),\n        currency: bigjsUSD,\n      });\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n\n    it('has 1 as identity: multiply(a, 1) equals a', () => {\n      fc.assert(\n        fc.property(safeAmount, (a) => {\n          const d = dinero({ amount: a, currency: USD });\n\n          expect(equal(multiply(d, 1), d)).toBe(true);\n        })\n      );\n    });\n    it('by zero yields zero amount', () => {\n      fc.assert(\n        fc.property(safeAmount, (a) => {\n          const d = dinero({ amount: a, currency: USD });\n          const result = toSnapshot(multiply(d, 0)).amount;\n\n          // JavaScript produces -0 for negative * 0, so compare with ==\n          expect(result == 0).toBe(true);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/normalizeScale.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { normalizeScale, toSnapshot } from '..';\n\ndescribe('normalizeScale', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns an array of Dinero objects with normalized scale and converted amount', () => {\n      const d1 = dinero({ amount: 100, currency: USD, scale: 2 });\n      const d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\n      const [firstDineroObject, secondDineroObject] = normalizeScale([d1, d2]);\n\n      expect(toSnapshot(firstDineroObject)).toEqual({\n        amount: 1000,\n        currency: USD,\n        scale: 3,\n      });\n      expect(toSnapshot(secondDineroObject)).toEqual({\n        amount: 1000,\n        currency: USD,\n        scale: 3,\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns an array of Dinero objects with normalized scale and converted amount', () => {\n      const d1 = dinero({ amount: 100n, currency: bigintUSD, scale: 2n });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD, scale: 3n });\n\n      const [firstDineroObject, secondDineroObject] = normalizeScale([d1, d2]);\n\n      expect(toSnapshot(firstDineroObject)).toEqual({\n        amount: 1000n,\n        currency: bigintUSD,\n        scale: 3n,\n      });\n      expect(toSnapshot(secondDineroObject)).toEqual({\n        amount: 1000n,\n        currency: bigintUSD,\n        scale: 3n,\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns an array of Dinero objects with normalized scale and converted amount', () => {\n      const d1 = dinero({\n        amount: new Big(100),\n        currency: bigjsUSD,\n        scale: new Big(2),\n      });\n      const d2 = dinero({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      const [firstDineroObject, secondDineroObject] = normalizeScale([d1, d2]);\n\n      expect(toSnapshot(firstDineroObject)).toEqual({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n      expect(toSnapshot(secondDineroObject)).toEqual({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/subtract.test.ts",
    "content": "import { EUR, USD } from '../../currencies';\nimport Big from 'big.js';\nimport * as fc from 'fast-check';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { add, equal, subtract, toSnapshot } from '..';\n\ndescribe('subtract', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('subtracts positive Dinero objects', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 100, currency: USD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: 400,\n        currency: USD,\n        scale: 2,\n      });\n    });\n    it('subtracts negative Dinero objects', () => {\n      const d1 = dinero({ amount: -500, currency: USD });\n      const d2 = dinero({ amount: -100, currency: USD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: -400,\n        currency: USD,\n        scale: 2,\n      });\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 1000, currency: USD, scale: 3 });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: 4000,\n        currency: USD,\n        scale: 3,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 500, currency: USD });\n      const d2 = dinero({ amount: 100, currency: EUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        subtract(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintEUR = castToBigintCurrency(EUR);\n\n    it('subtracts positive Dinero objects', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 100n, currency: bigintUSD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: 400n,\n        currency: bigintUSD,\n        scale: 2n,\n      });\n    });\n    it('subtracts negative Dinero objects', () => {\n      const d1 = dinero({ amount: -500n, currency: bigintUSD });\n      const d2 = dinero({ amount: -100n, currency: bigintUSD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: -400n,\n        currency: bigintUSD,\n        scale: 2n,\n      });\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 1000n, currency: bigintUSD, scale: 3n });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: 4000n,\n        currency: bigintUSD,\n        scale: 3n,\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: 500n, currency: bigintUSD });\n      const d2 = dinero({ amount: 100n, currency: bigintEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        subtract(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsEUR = castToBigjsCurrency(EUR);\n\n    it('subtracts positive Dinero objects', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(100), currency: bigjsUSD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: new Big(400),\n        currency: bigjsUSD,\n        scale: new Big(2),\n      });\n    });\n    it('subtracts negative Dinero objects', () => {\n      const d1 = dinero({ amount: new Big(-500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(-100), currency: bigjsUSD });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: new Big(-400),\n        currency: bigjsUSD,\n        scale: new Big(2),\n      });\n    });\n    it('normalizes the result to the highest scale', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({\n        amount: new Big(1000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n\n      const snapshot = toSnapshot(subtract(d1, d2));\n\n      expect(snapshot).toEqual({\n        amount: new Big(4000),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n    });\n    it('throws when using different currencies', () => {\n      const d1 = dinero({ amount: new Big(500), currency: bigjsUSD });\n      const d2 = dinero({ amount: new Big(100), currency: bigjsEUR });\n\n      expect(() => {\n        // @ts-expect-error different currencies\n        subtract(d1, d2);\n      }).toThrowErrorMatchingInlineSnapshot(\n        `[Error: [Dinero.js] Objects must have the same currency.]`\n      );\n    });\n  });\n  describe('properties', () => {\n    const dinero = createNumberDinero;\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n\n    it('self-subtraction yields zero: subtract(a, a) has amount 0', () => {\n      fc.assert(\n        fc.property(safeAmount, (a) => {\n          const d = dinero({ amount: a, currency: USD });\n\n          expect(toSnapshot(subtract(d, d)).amount).toBe(0);\n        })\n      );\n    });\n    it('is the inverse of add: add(subtract(a, b), b) equals a', () => {\n      fc.assert(\n        fc.property(safeAmount, safeAmount, (a, b) => {\n          const d1 = dinero({ amount: a, currency: USD });\n          const d2 = dinero({ amount: b, currency: USD });\n\n          expect(equal(add(subtract(d1, d2), d2), d1)).toBe(true);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/toDecimal.test.ts",
    "content": "import { JPY, MGA, USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { toDecimal } from '..';\n\ndescribe('toDecimal', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns the amount in decimal format', () => {\n        const d = dinero({ amount: 1050, currency: USD });\n\n        expect(toDecimal(d)).toBe('10.50');\n      });\n      it('returns the amount in decimal format based on a custom scale', () => {\n        const d = dinero({ amount: 10545, currency: USD, scale: 3 });\n\n        expect(toDecimal(d)).toBe('10.545');\n      });\n      it('returns the amount in decimal format with trailing zeros', () => {\n        const d = dinero({ amount: 1000, currency: USD });\n\n        expect(toDecimal(d)).toBe('10.00');\n      });\n      it('returns the amount in decimal format with leading zeros', () => {\n        const d = dinero({ amount: 1005, currency: USD });\n\n        expect(toDecimal(d)).toBe('10.05');\n      });\n      it('returns the amount in decimal format and pads the decimal part', () => {\n        const d = dinero({ amount: 500, currency: USD });\n\n        expect(toDecimal(d)).toBe('5.00');\n      });\n      it('returns the negative amount in decimal format', () => {\n        const d = dinero({ amount: -1050, currency: USD });\n\n        expect(toDecimal(d)).toBe('-10.50');\n      });\n      it('returns the negative amount with a leading zero in decimal format', () => {\n        const d = dinero({ amount: -1, currency: USD });\n\n        expect(toDecimal(d)).toBe('-0.01');\n      });\n      it('returns negative zero amount as a positive value in decimal format', () => {\n        const d = dinero({ amount: -0, currency: USD });\n\n        expect(toDecimal(d)).toBe('0.00');\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: 1050, currency: USD });\n\n        expect(\n          toDecimal(d, ({ value, currency }) => `${currency.code} ${value}`)\n        ).toBe('USD 10.50');\n      });\n      it('returns whole numbers when scale is zero', () => {\n        const d = dinero({ amount: 100, currency: JPY });\n        expect(toDecimal(d)).toBe('100');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('throws when passing a Dinero object using a non-decimal currency', () => {\n        const d = dinero({ amount: 13, currency: MGA });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n      it('throws when passing a Dinero object using a multi-base currency which compiles to a multiple of 10', () => {\n        const d = dinero({\n          amount: 13,\n          currency: { code: 'ABC', exponent: 1, base: [5, 2] },\n        });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintMGA = castToBigintCurrency(MGA);\n    const bigintJPY = castToBigintCurrency(JPY);\n\n    describe('decimal currencies', () => {\n      it('returns the amount in decimal format', () => {\n        const d = dinero({ amount: 1050n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('10.50');\n      });\n      it('returns the amount in decimal format with large integers', () => {\n        const d = dinero({ amount: 1000000000000000050n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('10000000000000000.50');\n      });\n      it('returns the amount in decimal format based on a custom scale', () => {\n        const d = dinero({ amount: 10545n, currency: bigintUSD, scale: 3n });\n\n        expect(toDecimal(d)).toBe('10.545');\n      });\n      it('returns the amount in decimal format with trailing zeros', () => {\n        const d = dinero({ amount: 1000n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('10.00');\n      });\n      it('returns the amount in decimal format with leading zeros', () => {\n        const d = dinero({ amount: 1005n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('10.05');\n      });\n      it('returns the amount in decimal format and pads the decimal part', () => {\n        const d = dinero({ amount: 500n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('5.00');\n      });\n      it('returns the negative amount in decimal format', () => {\n        const d = dinero({ amount: -1050n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('-10.50');\n      });\n      it('returns the negative amount with a leading zero in decimal format', () => {\n        const d = dinero({ amount: -1n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('-0.01');\n      });\n      it('returns negative zero amount as a positive value in decimal format', () => {\n        const d = dinero({ amount: -0n, currency: bigintUSD });\n\n        expect(toDecimal(d)).toBe('0.00');\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: 1050n, currency: bigintUSD });\n\n        expect(\n          toDecimal(d, ({ value, currency }) => `${currency.code} ${value}`)\n        ).toBe('USD 10.50');\n      });\n      it('returns whole numbers when scale is zero', () => {\n        const d = dinero({ amount: 100n, currency: bigintJPY });\n        expect(toDecimal(d)).toBe('100');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('throws when passing a Dinero object using a non-decimal currency', () => {\n        const d = dinero({ amount: 13n, currency: bigintMGA });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n      it('throws when passing a Dinero object using a multi-base currency which compiles to a multiple of 10', () => {\n        const d = dinero({\n          amount: 13n,\n          currency: { code: 'ABC', exponent: 1n, base: [5n, 2n] },\n        });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n    const bigjsJPY = castToBigjsCurrency(JPY);\n\n    describe('decimal currencies', () => {\n      it('returns the amount in decimal format', () => {\n        const d = dinero({ amount: new Big(1050), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('10.50');\n      });\n      it('returns the amount in decimal format with large integers', () => {\n        const d = dinero({\n          amount: new Big('1000000000000000050'),\n          currency: bigjsUSD,\n        });\n\n        expect(toDecimal(d)).toBe('10000000000000000.50');\n      });\n      it('returns the amount in decimal format based on a custom scale', () => {\n        const d = dinero({\n          amount: new Big(10545),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(toDecimal(d)).toBe('10.545');\n      });\n      it('returns the amount in decimal format with trailing zeros', () => {\n        const d = dinero({ amount: new Big(1000), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('10.00');\n      });\n      it('returns the amount in decimal format with leading zeros', () => {\n        const d = dinero({ amount: new Big(1005), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('10.05');\n      });\n      it('returns the amount in decimal format and pads the decimal part', () => {\n        const d = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('5.00');\n      });\n      it('returns the negative amount in decimal format', () => {\n        const d = dinero({ amount: new Big(-1005), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('-10.05');\n      });\n      it('returns the negative amount with a leading zero in decimal format', () => {\n        const d = dinero({ amount: new Big(-1), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('-0.01');\n      });\n      it('returns negative zero amount as a positive value in decimal format', () => {\n        const d = dinero({ amount: new Big(-0), currency: bigjsUSD });\n\n        expect(toDecimal(d)).toBe('0.00');\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: new Big(1050), currency: bigjsUSD });\n\n        expect(\n          toDecimal(d, ({ value, currency }) => `${currency.code} ${value}`)\n        ).toBe('USD 10.50');\n      });\n      it('returns whole numbers when scale is zero', () => {\n        const d = dinero({ amount: new Big(100), currency: bigjsJPY });\n        expect(toDecimal(d)).toBe('100');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('throws when passing a Dinero object using a non-decimal currency', () => {\n        const d = dinero({ amount: new Big(13), currency: bigjsMGA });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n      it('throws when passing a Dinero object using a multi-base currency which compiles to a multiple of 10', () => {\n        const d = dinero({\n          amount: new Big(13),\n          currency: {\n            code: 'ABC',\n            exponent: new Big(1),\n            base: [new Big(5), new Big(2)],\n          },\n        });\n\n        expect(() => {\n          toDecimal(d);\n        }).toThrowErrorMatchingInlineSnapshot(\n          `[Error: [Dinero.js] Currency is not decimal.]`\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/toSnapshot.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { toSnapshot } from '..';\n\ndescribe('toSnapshot', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it('returns an object literal with the right data', () => {\n      const d = dinero({ amount: 500, currency: USD });\n\n      expect(toSnapshot(d)).toEqual({\n        amount: 500,\n        currency: {\n          code: 'USD',\n          base: 10,\n          exponent: 2,\n        },\n        scale: 2,\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it('returns an object literal with the right data', () => {\n      const d = dinero({ amount: 500n, currency: bigintUSD });\n\n      expect(toSnapshot(d)).toEqual({\n        amount: 500n,\n        currency: {\n          code: 'USD',\n          base: 10n,\n          exponent: 2n,\n        },\n        scale: 2n,\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it('returns an object literal with the right data', () => {\n      const d = dinero({ amount: new Big(500), currency: bigjsUSD });\n\n      expect(toSnapshot(d)).toEqual({\n        amount: new Big(500),\n        currency: {\n          code: 'USD',\n          base: new Big(10),\n          exponent: new Big(2),\n        },\n        scale: new Big(2),\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/toUnits.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\n\nimport { toUnits } from '..';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from '../../../../../test/utils';\n\ndescribe('toUnits', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const d = dinero({ amount: 1050, currency: USD });\n\n        expect(toUnits(d)).toEqual([10, 50]);\n      });\n      it('returns the amount in currency units, based on a custom scale', () => {\n        const d = dinero({ amount: 10545, currency: USD, scale: 3 });\n\n        expect(toUnits(d)).toEqual([10, 545]);\n      });\n      it('returns the amount in currency units, with a single trailing zero', () => {\n        const d = dinero({ amount: 1000, currency: USD });\n\n        expect(toUnits(d)).toEqual([10, 0]);\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: 1050, currency: USD });\n\n        expect(\n          toUnits(\n            d,\n            ({ value, currency }) => `${value.join('.')} ${currency.code}`\n          )\n        ).toBe('10.50 USD');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const GRD = { code: 'GRD', base: 6, exponent: 1 };\n        const d = dinero({ amount: 9, currency: GRD });\n\n        expect(toUnits(d)).toEqual([1, 3]);\n      });\n      it('handles currencies with multiple subdivisions', () => {\n        const GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\n        const d = dinero({ amount: 267, currency: GBP });\n\n        expect(toUnits(d)).toEqual([1, 2, 3]);\n      });\n      it('handles intermediary zero values', () => {\n        const GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\n        const d = dinero({ amount: 2, currency: GBP });\n\n        expect(toUnits(d)).toEqual([0, 0, 2]);\n      });\n      it('uses a custom transformer', () => {\n        const GBP = { code: 'GBP', base: [20, 12], exponent: 1 };\n        const d = dinero({ amount: 267, currency: GBP });\n        const labels = ['pounds', 'shillings', 'pence'];\n\n        expect(\n          toUnits(d, ({ value }) =>\n            value\n              .filter((amount) => amount > 0)\n              .map((amount, index) => `${amount} ${labels[index]}`)\n              .join(', ')\n          )\n        ).toBe('1 pounds, 2 shillings, 3 pence');\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    describe('decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const d = dinero({ amount: 1050n, currency: bigintUSD });\n\n        expect(toUnits(d)).toEqual([10n, 50n]);\n      });\n      it('returns the amount in currency units, based on a custom scale', () => {\n        const d = dinero({ amount: 10545n, currency: bigintUSD, scale: 3n });\n\n        expect(toUnits(d)).toEqual([10n, 545n]);\n      });\n      it('returns the amount in currency units, with a single trailing zero', () => {\n        const d = dinero({ amount: 1000n, currency: bigintUSD });\n\n        expect(toUnits(d)).toEqual([10n, 0n]);\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: 1050n, currency: bigintUSD });\n\n        expect(\n          toUnits(\n            d,\n            ({ value, currency }) => `${value.join('.')} ${currency.code}`\n          )\n        ).toBe('10.50 USD');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const GRD = { code: 'GRD', base: 6n, exponent: 1n };\n        const d = dinero({ amount: 9n, currency: GRD });\n\n        expect(toUnits(d)).toEqual([1n, 3n]);\n      });\n      it('handles currencies with multiple subdivisions', () => {\n        const GBP = { code: 'GBP', base: [20n, 12n], exponent: 1n };\n        const d = dinero({ amount: 267n, currency: GBP });\n\n        expect(toUnits(d)).toEqual([1n, 2n, 3n]);\n      });\n      it('handles intermediary zero values', () => {\n        const GBP = { code: 'GBP', base: [20n, 12n], exponent: 1n };\n        const d = dinero({ amount: 2n, currency: GBP });\n\n        expect(toUnits(d)).toEqual([0n, 0n, 2n]);\n      });\n      it('uses a custom transformer', () => {\n        const GBP = { code: 'GBP', base: [20n, 12n], exponent: 1n };\n        const d = dinero({ amount: 267n, currency: GBP });\n        const labels = ['pounds', 'shillings', 'pence'];\n\n        expect(\n          toUnits(d, ({ value }) =>\n            value\n              .filter((amount) => amount > 0n)\n              .map((amount, index) => `${amount} ${labels[index]}`)\n              .join(', ')\n          )\n        ).toBe('1 pounds, 2 shillings, 3 pence');\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    describe('decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const d = dinero({ amount: new Big(1050), currency: bigjsUSD });\n\n        expect(toUnits(d)).toEqual([new Big(10), new Big(50)]);\n      });\n      it('returns the amount in currency units, based on a custom scale', () => {\n        const d = dinero({\n          amount: new Big(10545),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(toUnits(d)).toEqual([new Big(10), new Big(545)]);\n      });\n      it('returns the amount in currency units, with a single trailing zero', () => {\n        const d = dinero({ amount: new Big(1000), currency: bigjsUSD });\n\n        expect(toUnits(d)).toEqual([new Big(10), new Big(0)]);\n      });\n      it('uses a custom transformer', () => {\n        const d = dinero({ amount: new Big(1050), currency: bigjsUSD });\n\n        expect(\n          toUnits(\n            d,\n            ({ value, currency }) => `${value.join('.')} ${currency.code}`\n          )\n        ).toBe('10.50 USD');\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns the amount in currency units', () => {\n        const GRD = { code: 'GRD', base: new Big(6), exponent: new Big(1) };\n        const d = dinero({ amount: new Big(9), currency: GRD });\n\n        expect(toUnits(d)).toEqual([new Big(1), new Big(3)]);\n      });\n      it('handles currencies with multiple subdivisions', () => {\n        const GBP = {\n          code: 'GBP',\n          base: [new Big(20), new Big(12)],\n          exponent: new Big(1),\n        };\n        const d = dinero({ amount: new Big(267), currency: GBP });\n\n        expect(toUnits(d)).toEqual([new Big(1), new Big(2), new Big(3)]);\n      });\n      it('handles intermediary zero values', () => {\n        const GBP = {\n          code: 'GBP',\n          base: [new Big(20), new Big(12)],\n          exponent: new Big(1),\n        };\n        const d = dinero({ amount: new Big(2), currency: GBP });\n\n        expect(toUnits(d)).toEqual([new Big(0), new Big(0), new Big(2)]);\n      });\n      it('uses a custom transformer', () => {\n        const GBP = {\n          code: 'GBP',\n          base: [new Big(20), new Big(12)],\n          exponent: new Big(1),\n        };\n        const d = dinero({ amount: new Big(267), currency: GBP });\n        const labels = ['pounds', 'shillings', 'pence'];\n\n        expect(\n          toUnits(d, ({ value }) =>\n            value\n              .filter((amount) => amount > new Big(0))\n              .map((amount, index) => `${amount} ${labels[index]}`)\n              .join(', ')\n          )\n        ).toBe('1 pounds, 2 shillings, 3 pence');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/transformScale.test.ts",
    "content": "import {\n  down,\n  halfAwayFromZero,\n  halfDown,\n  halfEven,\n  halfOdd,\n  halfTowardsZero,\n  halfUp,\n  up,\n} from '../../core';\nimport { USD, MGA } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { toSnapshot, transformScale } from '..';\n\nconst ABC = { code: 'ABC', base: 6, exponent: 1 };\n\ntype DineroDivideOperation = Parameters<typeof transformScale>[2];\n\ndescribe('transformScale', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    describe('decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({ amount: 500, currency: USD, scale: 2 });\n        const snapshot = toSnapshot(transformScale(d, 4));\n\n        expect(snapshot).toMatchObject({ amount: 50000, scale: 4 });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({ amount: 14270, currency: USD, scale: 2 });\n        const snapshot = toSnapshot(transformScale(d, 0));\n\n        expect(snapshot).toMatchObject({ amount: 142, scale: 0 });\n      });\n      it('converts between scales correctly', () => {\n        const d = dinero({ amount: 333336, currency: USD, scale: 5 });\n        const snapshot = toSnapshot(transformScale(d, 2));\n\n        expect(snapshot).toMatchObject({ amount: 333, scale: 2 });\n      });\n      it('converts from long initial scales correctly', () => {\n        const d = dinero({ amount: 3333333336, currency: USD, scale: 9 });\n        const snapshot = toSnapshot(transformScale(d, 2));\n\n        expect(snapshot).toMatchObject({ amount: 333, scale: 2 });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(transformScale(d, 2, up));\n\n        expect(snapshot).toMatchObject({ amount: 1046, scale: 2 });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(transformScale(d, 2, down));\n\n        expect(snapshot).toMatchObject({ amount: 1045, scale: 2 });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({ amount: 10415, currency: USD, scale: 3 });\n        const d2 = dinero({ amount: 10425, currency: USD, scale: 3 });\n\n        expect(toSnapshot(transformScale(d1, 2, halfOdd))).toMatchObject({\n          amount: 1041,\n          scale: 2,\n        });\n        expect(toSnapshot(transformScale(d2, 2, halfOdd))).toMatchObject({\n          amount: 1043,\n          scale: 2,\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({ amount: 10425, currency: USD, scale: 3 });\n        const d2 = dinero({ amount: 10435, currency: USD, scale: 3 });\n\n        expect(toSnapshot(transformScale(d1, 2, halfEven))).toMatchObject({\n          amount: 1042,\n          scale: 2,\n        });\n        expect(toSnapshot(transformScale(d2, 2, halfEven))).toMatchObject({\n          amount: 1044,\n          scale: 2,\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({ amount: 10455, currency: USD, scale: 3 });\n        const d2 = dinero({ amount: 10456, currency: USD, scale: 3 });\n\n        expect(toSnapshot(transformScale(d1, 2, halfDown))).toMatchObject({\n          amount: 1045,\n          scale: 2,\n        });\n        expect(toSnapshot(transformScale(d2, 2, halfDown))).toMatchObject({\n          amount: 1046,\n          scale: 2,\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({ amount: 10454, currency: USD, scale: 3 });\n        const d2 = dinero({ amount: 10455, currency: USD, scale: 3 });\n\n        expect(toSnapshot(transformScale(d1, 2, halfUp))).toMatchObject({\n          amount: 1045,\n          scale: 2,\n        });\n        expect(toSnapshot(transformScale(d2, 2, halfUp))).toMatchObject({\n          amount: 1046,\n          scale: 2,\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({ amount: 10415, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(transformScale(d1, 2, halfTowardsZero));\n\n        expect(snapshot).toMatchObject({\n          amount: 1041,\n          scale: 2,\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({ amount: 10415, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(transformScale(d1, 2, halfAwayFromZero));\n\n        expect(snapshot).toMatchObject({\n          amount: 1042,\n          scale: 2,\n        });\n      });\n      it('uses a custom divide function', () => {\n        const divideFn = vi.fn(() => 1045) as DineroDivideOperation;\n        const d = dinero({ amount: 10455, currency: USD, scale: 3 });\n\n        const snapshot = toSnapshot(transformScale(d, 2, divideFn));\n\n        expect(snapshot).toMatchObject({ amount: 1045, scale: 2 });\n        expect(divideFn).toHaveBeenNthCalledWith(\n          1,\n          10455,\n          10,\n          expect.objectContaining({\n            add: expect.any(Function),\n            compare: expect.any(Function),\n            decrement: expect.any(Function),\n            increment: expect.any(Function),\n            integerDivide: expect.any(Function),\n            modulo: expect.any(Function),\n            multiply: expect.any(Function),\n            power: expect.any(Function),\n            subtract: expect.any(Function),\n            zero: expect.any(Function),\n          })\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({ amount: 5, currency: MGA });\n        const snapshot = toSnapshot(transformScale(d, 2));\n\n        expect(snapshot).toMatchObject({ amount: 25, scale: 2 });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({ amount: 26, currency: MGA, scale: 2 });\n        const snapshot = toSnapshot(transformScale(d, 1));\n\n        expect(snapshot).toMatchObject({ amount: 5, scale: 1 });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const snapshot = toSnapshot(transformScale(d, 1, up));\n\n        expect(snapshot).toMatchObject({ amount: 6, scale: 1 });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const snapshot = toSnapshot(transformScale(d, 1, down));\n\n        expect(snapshot).toMatchObject({ amount: 5, scale: 1 });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(toSnapshot(transformScale(d1, 1, halfOdd))).toMatchObject({\n          amount: 5,\n          scale: 1,\n        });\n        expect(toSnapshot(transformScale(d2, 1, halfOdd))).toMatchObject({\n          amount: 7,\n          scale: 1,\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(toSnapshot(transformScale(d1, 1, halfEven))).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n        expect(toSnapshot(transformScale(d2, 1, halfEven))).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(toSnapshot(transformScale(d1, 1, halfDown))).toMatchObject({\n          amount: 5,\n          scale: 1,\n        });\n        expect(toSnapshot(transformScale(d2, 1, halfDown))).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(toSnapshot(transformScale(d1, 1, halfUp))).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n        expect(toSnapshot(transformScale(d2, 1, halfUp))).toMatchObject({\n          amount: 7,\n          scale: 1,\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(\n          toSnapshot(transformScale(d1, 1, halfTowardsZero))\n        ).toMatchObject({\n          amount: 5,\n          scale: 1,\n        });\n        expect(\n          toSnapshot(transformScale(d2, 1, halfTowardsZero))\n        ).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({ amount: 33, currency: ABC, scale: 2 });\n        const d2 = dinero({ amount: 39, currency: ABC, scale: 2 });\n\n        expect(\n          toSnapshot(transformScale(d1, 1, halfAwayFromZero))\n        ).toMatchObject({\n          amount: 6,\n          scale: 1,\n        });\n        expect(\n          toSnapshot(transformScale(d2, 1, halfAwayFromZero))\n        ).toMatchObject({\n          amount: 7,\n          scale: 1,\n        });\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n    const bigintMGA = castToBigintCurrency(MGA);\n    const bigintABC = castToBigintCurrency(ABC);\n\n    describe('decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({ amount: 500n, currency: bigintUSD, scale: 2n });\n        const snapshot = toSnapshot(transformScale(d, 4n));\n\n        expect(snapshot).toMatchObject({ amount: 50000n, scale: 4n });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({ amount: 14270n, currency: bigintUSD, scale: 2n });\n        const snapshot = toSnapshot(transformScale(d, 0n));\n\n        expect(snapshot).toMatchObject({ amount: 142n, scale: 0n });\n      });\n      it('converts between scales correctly', () => {\n        const d = dinero({ amount: 333336n, currency: bigintUSD, scale: 5n });\n        const snapshot = toSnapshot(transformScale(d, 2n));\n\n        expect(snapshot).toMatchObject({ amount: 333n, scale: 2n });\n      });\n      it('converts from long initial scales correctly', () => {\n        const d = dinero({\n          amount: 3333333336n,\n          currency: bigintUSD,\n          scale: 9n,\n        });\n        const snapshot = toSnapshot(transformScale(d, 2n));\n\n        expect(snapshot).toMatchObject({ amount: 333n, scale: 2n });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({ amount: 10455n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(transformScale(d, 2n, up));\n\n        expect(snapshot).toMatchObject({ amount: 1046n, scale: 2n });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({ amount: 10455n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(transformScale(d, 2n, down));\n\n        expect(snapshot).toMatchObject({ amount: 1045n, scale: 2n });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({ amount: 10415n, currency: bigintUSD, scale: 3n });\n        const d2 = dinero({ amount: 10425n, currency: bigintUSD, scale: 3n });\n\n        expect(toSnapshot(transformScale(d1, 2n, halfOdd))).toMatchObject({\n          amount: 1041n,\n          scale: 2n,\n        });\n        expect(toSnapshot(transformScale(d2, 2n, halfOdd))).toMatchObject({\n          amount: 1043n,\n          scale: 2n,\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({ amount: 10425n, currency: bigintUSD, scale: 3n });\n        const d2 = dinero({ amount: 10435n, currency: bigintUSD, scale: 3n });\n\n        expect(toSnapshot(transformScale(d1, 2n, halfEven))).toMatchObject({\n          amount: 1042n,\n          scale: 2n,\n        });\n        expect(toSnapshot(transformScale(d2, 2n, halfEven))).toMatchObject({\n          amount: 1044n,\n          scale: 2n,\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({ amount: 10455n, currency: bigintUSD, scale: 3n });\n        const d2 = dinero({ amount: 10456n, currency: bigintUSD, scale: 3n });\n\n        expect(toSnapshot(transformScale(d1, 2n, halfDown))).toMatchObject({\n          amount: 1045n,\n          scale: 2n,\n        });\n        expect(toSnapshot(transformScale(d2, 2n, halfDown))).toMatchObject({\n          amount: 1046n,\n          scale: 2n,\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({ amount: 10454n, currency: bigintUSD, scale: 3n });\n        const d2 = dinero({ amount: 10455n, currency: bigintUSD, scale: 3n });\n\n        expect(toSnapshot(transformScale(d1, 2n, halfUp))).toMatchObject({\n          amount: 1045n,\n          scale: 2n,\n        });\n        expect(toSnapshot(transformScale(d2, 2n, halfUp))).toMatchObject({\n          amount: 1046n,\n          scale: 2n,\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({ amount: 10415n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(transformScale(d1, 2n, halfTowardsZero));\n\n        expect(snapshot).toMatchObject({\n          amount: 1041n,\n          scale: 2n,\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({ amount: 10415n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(transformScale(d1, 2n, halfAwayFromZero));\n\n        expect(snapshot).toMatchObject({\n          amount: 1042n,\n          scale: 2n,\n        });\n      });\n      it('uses a custom divide function', () => {\n        const divideFn = vi.fn(() => 1045n) as DineroDivideOperation;\n        const d = dinero({ amount: 10455n, currency: bigintUSD, scale: 3n });\n\n        const snapshot = toSnapshot(transformScale(d, 2n, divideFn));\n\n        expect(snapshot).toMatchObject({ amount: 1045n, scale: 2n });\n        expect(divideFn).toHaveBeenNthCalledWith(\n          1,\n          10455n,\n          10n,\n          expect.objectContaining({\n            add: expect.any(Function),\n            compare: expect.any(Function),\n            decrement: expect.any(Function),\n            increment: expect.any(Function),\n            integerDivide: expect.any(Function),\n            modulo: expect.any(Function),\n            multiply: expect.any(Function),\n            power: expect.any(Function),\n            subtract: expect.any(Function),\n            zero: expect.any(Function),\n          })\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({ amount: 5n, currency: bigintMGA });\n        const snapshot = toSnapshot(transformScale(d, 2n));\n\n        expect(snapshot).toMatchObject({ amount: 25n, scale: 2n });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({ amount: 26n, currency: bigintMGA, scale: 2n });\n        const snapshot = toSnapshot(transformScale(d, 1n));\n\n        expect(snapshot).toMatchObject({ amount: 5n, scale: 1n });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const snapshot = toSnapshot(transformScale(d, 1n, up));\n\n        expect(snapshot).toMatchObject({ amount: 6n, scale: 1n });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const snapshot = toSnapshot(transformScale(d, 1n, down));\n\n        expect(snapshot).toMatchObject({ amount: 5n, scale: 1n });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(toSnapshot(transformScale(d1, 1n, halfOdd))).toMatchObject({\n          amount: 5n,\n          scale: 1n,\n        });\n        expect(toSnapshot(transformScale(d2, 1n, halfOdd))).toMatchObject({\n          amount: 7n,\n          scale: 1n,\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(toSnapshot(transformScale(d1, 1n, halfEven))).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n        expect(toSnapshot(transformScale(d2, 1n, halfEven))).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(toSnapshot(transformScale(d1, 1n, halfDown))).toMatchObject({\n          amount: 5n,\n          scale: 1n,\n        });\n        expect(toSnapshot(transformScale(d2, 1n, halfDown))).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(toSnapshot(transformScale(d1, 1n, halfUp))).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n        expect(toSnapshot(transformScale(d2, 1n, halfUp))).toMatchObject({\n          amount: 7n,\n          scale: 1n,\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(\n          toSnapshot(transformScale(d1, 1n, halfTowardsZero))\n        ).toMatchObject({\n          amount: 5n,\n          scale: 1n,\n        });\n        expect(\n          toSnapshot(transformScale(d2, 1n, halfTowardsZero))\n        ).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({ amount: 33n, currency: bigintABC, scale: 2n });\n        const d2 = dinero({ amount: 39n, currency: bigintABC, scale: 2n });\n\n        expect(\n          toSnapshot(transformScale(d1, 1n, halfAwayFromZero))\n        ).toMatchObject({\n          amount: 6n,\n          scale: 1n,\n        });\n        expect(\n          toSnapshot(transformScale(d2, 1n, halfAwayFromZero))\n        ).toMatchObject({\n          amount: 7n,\n          scale: 1n,\n        });\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n    const bigjsMGA = castToBigjsCurrency(MGA);\n    const bigjsABC = castToBigjsCurrency(ABC);\n\n    describe('decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({\n          amount: new Big(500),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(4)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(50000),\n          scale: new Big(4),\n        });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({\n          amount: new Big(14270),\n          currency: bigjsUSD,\n          scale: new Big(2),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(0)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(142),\n          scale: new Big(0),\n        });\n      });\n      it('converts between scales correctly', () => {\n        const d = dinero({\n          amount: new Big(333336),\n          currency: bigjsUSD,\n          scale: new Big(5),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(2)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(333),\n          scale: new Big(2),\n        });\n      });\n      it('converts from long initial scales correctly', () => {\n        const d = dinero({\n          amount: new Big(3333333336),\n          currency: bigjsUSD,\n          scale: new Big(9),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(2)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(333),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({\n          amount: new Big(10455),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(transformScale(d, new Big(2), up));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(1046),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({\n          amount: new Big(10455),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(transformScale(d, new Big(2), down));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(1045),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10415),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        const d2 = dinero({\n          amount: new Big(10425),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(2), halfOdd))\n        ).toMatchObject({\n          amount: new Big(1041),\n          scale: new Big(2),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(2), halfOdd))\n        ).toMatchObject({\n          amount: new Big(1043),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10425),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        const d2 = dinero({\n          amount: new Big(10435),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(2), halfEven))\n        ).toMatchObject({\n          amount: new Big(1042),\n          scale: new Big(2),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(2), halfEven))\n        ).toMatchObject({\n          amount: new Big(1044),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10455),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        const d2 = dinero({\n          amount: new Big(10456),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(2), halfDown))\n        ).toMatchObject({\n          amount: new Big(1045),\n          scale: new Big(2),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(2), halfDown))\n        ).toMatchObject({\n          amount: new Big(1046),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10454),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n        const d2 = dinero({\n          amount: new Big(10455),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(2), halfUp))\n        ).toMatchObject({\n          amount: new Big(1045),\n          scale: new Big(2),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(2), halfUp))\n        ).toMatchObject({\n          amount: new Big(1046),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10415),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(\n          transformScale(d1, new Big(2), halfTowardsZero)\n        );\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(1041),\n          scale: new Big(2),\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(10415),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(\n          transformScale(d1, new Big(2), halfAwayFromZero)\n        );\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(1042),\n          scale: new Big(2),\n        });\n      });\n      it('uses a custom divide function', () => {\n        const divideFn = vi.fn(() => new Big(1045)) as DineroDivideOperation;\n        const d = dinero({\n          amount: new Big(10455),\n          currency: bigjsUSD,\n          scale: new Big(3),\n        });\n\n        const snapshot = toSnapshot(transformScale(d, new Big(2), divideFn));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(1045),\n          scale: new Big(2),\n        });\n        expect(divideFn).toHaveBeenNthCalledWith(\n          1,\n          new Big(10455),\n          new Big(10),\n          expect.objectContaining({\n            add: expect.any(Function),\n            compare: expect.any(Function),\n            decrement: expect.any(Function),\n            increment: expect.any(Function),\n            integerDivide: expect.any(Function),\n            modulo: expect.any(Function),\n            multiply: expect.any(Function),\n            power: expect.any(Function),\n            subtract: expect.any(Function),\n            zero: expect.any(Function),\n          })\n        );\n      });\n    });\n    describe('non-decimal currencies', () => {\n      it('returns a new Dinero object with a new scale and a converted amount', () => {\n        const d = dinero({ amount: new Big(5), currency: bigjsMGA });\n        const snapshot = toSnapshot(transformScale(d, new Big(2)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(25),\n          scale: new Big(2),\n        });\n      });\n      it('returns a new Dinero object with a new scale and a converted, rounded down amount', () => {\n        const d = dinero({\n          amount: new Big(26),\n          currency: bigjsMGA,\n          scale: new Big(2),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(1)));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(5),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `up` divide function', () => {\n        const d = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(1), up));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `down` divide function', () => {\n        const d = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const snapshot = toSnapshot(transformScale(d, new Big(1), down));\n\n        expect(snapshot).toMatchObject({\n          amount: new Big(5),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfOdd` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfOdd))\n        ).toMatchObject({\n          amount: new Big(5),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfOdd))\n        ).toMatchObject({\n          amount: new Big(7),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfEven` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfEven))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfEven))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfDown` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfDown))\n        ).toMatchObject({\n          amount: new Big(5),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfDown))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfUp` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfUp))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfUp))\n        ).toMatchObject({\n          amount: new Big(7),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfTowardsZero` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfTowardsZero))\n        ).toMatchObject({\n          amount: new Big(5),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfTowardsZero))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n      });\n      it('uses the provided `halfAwayFromZero` divide function', () => {\n        const d1 = dinero({\n          amount: new Big(33),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n        const d2 = dinero({\n          amount: new Big(39),\n          currency: bigjsABC,\n          scale: new Big(2),\n        });\n\n        expect(\n          toSnapshot(transformScale(d1, new Big(1), halfAwayFromZero))\n        ).toMatchObject({\n          amount: new Big(6),\n          scale: new Big(1),\n        });\n        expect(\n          toSnapshot(transformScale(d2, new Big(1), halfAwayFromZero))\n        ).toMatchObject({\n          amount: new Big(7),\n          scale: new Big(1),\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/__tests__/trimScale.test.ts",
    "content": "import { USD } from '../../currencies';\nimport Big from 'big.js';\nimport {\n  castToBigintCurrency,\n  castToBigjsCurrency,\n  createNumberDinero,\n  createBigintDinero,\n  createBigjsDinero,\n} from 'test-utils';\n\nimport { toSnapshot, trimScale } from '..';\n\ndescribe('trimScale', () => {\n  describe('number', () => {\n    const dinero = createNumberDinero;\n\n    it(\"trims a Dinero object down to its currency exponent's scale\", () => {\n      const d = dinero({ amount: 500000, currency: USD, scale: 5 });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 500, scale: 2 });\n    });\n    it('trims a Dinero object down to the safest possible scale', () => {\n      const d = dinero({ amount: 55550, currency: USD, scale: 4 });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 5555, scale: 3 });\n    });\n    it(\"doesn't trim the scale when there's nothing to trim\", () => {\n      const d = dinero({ amount: 5555, currency: USD, scale: 3 });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 5555, scale: 3 });\n    });\n    it(\"doesn't crash on zero amounts\", () => {\n      const d = dinero({ amount: 0, currency: USD });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: 0,\n        scale: 2,\n        currency: USD,\n      });\n    });\n  });\n  describe('bigint', () => {\n    const dinero = createBigintDinero;\n    const bigintUSD = castToBigintCurrency(USD);\n\n    it(\"trims a Dinero object down to its currency exponent's scale\", () => {\n      const d = dinero({ amount: 500000n, currency: bigintUSD, scale: 5n });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 500n, scale: 2n });\n    });\n    it('trims a Dinero object down to the safest possible scale', () => {\n      const d = dinero({ amount: 55550n, currency: bigintUSD, scale: 4n });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 5555n, scale: 3n });\n    });\n    it(\"doesn't trim the scale when there's nothing to trim\", () => {\n      const d = dinero({ amount: 5555n, currency: bigintUSD, scale: 3n });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({ amount: 5555n, scale: 3n });\n    });\n    it(\"doesn't crash on zero amounts\", () => {\n      const d = dinero({ amount: 0n, currency: bigintUSD });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: 0n,\n        scale: 2n,\n        currency: bigintUSD,\n      });\n    });\n  });\n  describe('Big.js', () => {\n    const dinero = createBigjsDinero;\n    const bigjsUSD = castToBigjsCurrency(USD);\n\n    it(\"trims a Dinero object down to its currency exponent's scale\", () => {\n      const d = dinero({\n        amount: new Big(500000),\n        currency: bigjsUSD,\n        scale: new Big(5),\n      });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: new Big(500),\n        scale: new Big(2),\n      });\n    });\n    it('trims a Dinero object down to the safest possible scale', () => {\n      const d = dinero({\n        amount: new Big(55550),\n        currency: bigjsUSD,\n        scale: new Big(4),\n      });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: new Big(5555),\n        scale: new Big(3),\n      });\n    });\n    it(\"doesn't trim the scale when there's nothing to trim\", () => {\n      const d = dinero({\n        amount: new Big(5555),\n        currency: bigjsUSD,\n        scale: new Big(3),\n      });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: new Big(5555),\n        scale: new Big(3),\n      });\n    });\n    it(\"doesn't crash on zero amounts\", () => {\n      const d = dinero({ amount: new Big(0), currency: bigjsUSD });\n      const snapshot = toSnapshot(trimScale(d));\n\n      expect(snapshot).toMatchObject({\n        amount: new Big(0),\n        scale: new Big(2),\n        currency: bigjsUSD,\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/api/add.ts",
    "content": "import { safeAdd } from '../core';\nimport type { AddParams } from '../core';\n\n/**\n * Add up the passed Dinero objects.\n *\n * @param augend - The Dinero object to add to.\n * @param addend - The Dinero object to add.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function add<TAmount, TCurrency extends string>(\n  ...[augend, addend]: AddParams<TAmount, TCurrency>\n) {\n  const { calculator } = augend;\n  const addFn = safeAdd(calculator);\n\n  return addFn(augend, addend);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/allocate.ts",
    "content": "import { safeAllocate } from '../core';\nimport type { AllocateParams } from '../core';\n\n/**\n * Distribute the amount of a Dinero object across a list of ratios.\n *\n * @param dineroObject - The Dinero object to allocate from.\n * @param ratios - The ratios to allocate the amount to.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function allocate<TAmount, TCurrency extends string>(\n  ...[dineroObject, ratios]: AllocateParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const allocateFn = safeAllocate(calculator);\n\n  return allocateFn(dineroObject, ratios);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/compare.ts",
    "content": "import { safeCompare } from '../core';\nimport type { CompareParams } from '../core';\n\n/**\n * Compare the value of a Dinero object relative to another.\n *\n * @param dineroObject - The Dinero object to compare.\n * @param comparator - The Dinero object to compare to.\n *\n * @returns One of -1, 0, or 1 depending on whether the first Dinero object is less than, equal to, or greater than the other.\n *\n * @public\n */\nexport function compare<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: CompareParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const compareFn = safeCompare(calculator);\n\n  return compareFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/convert.ts",
    "content": "import { convert as coreConvert } from '../core';\nimport type { ConvertParams } from '../core';\n\n/**\n * Convert a Dinero object to another currency.\n *\n * @param dineroObject - The Dinero object to format.\n * @param newCurrency - The currency to convert to.\n * @param rates - The rates to convert with.\n *\n * @returns A converted Dinero object.\n *\n * @public\n */\nexport function convert<\n  TAmount,\n  TCurrency extends string,\n  TNewCurrency extends string,\n>(\n  ...[dineroObject, newCurrency, rates]: ConvertParams<\n    TAmount,\n    TCurrency,\n    TNewCurrency\n  >\n) {\n  const { calculator } = dineroObject;\n  const converter = coreConvert(calculator);\n\n  return converter(dineroObject, newCurrency, rates);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/equal.ts",
    "content": "import { equal as coreEqual } from '../core';\nimport type { EqualParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is equal to another.\n *\n * @param dineroObject - The first Dinero object to compare.\n * @param comparator - The second Dinero object to compare.\n *\n * @returns Whether the Dinero objects are equal.\n *\n * @public\n */\nexport function equal<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: EqualParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const equalFn = coreEqual(calculator);\n\n  return equalFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/greaterThan.ts",
    "content": "import { safeGreaterThan } from '../core';\nimport type { GreaterThanParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is greater than another.\n *\n * @param dineroObject - The Dinero object to compare.\n * @param comparator - The Dinero object to compare to.\n *\n * @returns Whether the Dinero to compare is greater than the other.\n *\n * @public\n */\nexport function greaterThan<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: GreaterThanParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const greaterThanFn = safeGreaterThan(calculator);\n\n  return greaterThanFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/greaterThanOrEqual.ts",
    "content": "import { safeGreaterThanOrEqual } from '../core';\nimport type { GreaterThanOrEqualParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is greater than or equal another.\n *\n * @param dineroObject - The Dinero object to compare.\n * @param comparator - The Dinero object to compare to.\n *\n * @returns Whether the Dinero to compare is greater than or equal the other.\n *\n * @public\n */\nexport function greaterThanOrEqual<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: GreaterThanOrEqualParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const greaterThanOrEqualFn = safeGreaterThanOrEqual(calculator);\n\n  return greaterThanOrEqualFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/hasSubUnits.ts",
    "content": "import { hasSubUnits as coreHasSubUnits } from '../core';\nimport type { HasSubUnitsParams } from '../core';\n\n/**\n * Check whether a Dinero object has minor currency units.\n *\n * @param dineroObject - The Dinero object to check.\n *\n * @returns Whether the Dinero object has minor currency units.\n *\n * @public\n */\nexport function hasSubUnits<TAmount>(\n  ...[dineroObject]: HasSubUnitsParams<TAmount>\n) {\n  const { calculator } = dineroObject;\n  const hasSubUnitsFn = coreHasSubUnits(calculator);\n\n  return hasSubUnitsFn(dineroObject);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/haveSameAmount.ts",
    "content": "import { haveSameAmount as coreHaveSameAmount } from '../core';\nimport type { HaveSameAmountParams } from '../core';\n\n/**\n * Check whether a set of Dinero objects have the same amount.\n *\n * @param dineroObjects - The Dinero objects to compare.\n *\n * @returns Whether the Dinero objects have the same amount.\n *\n * @public\n */\nexport function haveSameAmount<TAmount, TCurrency extends string>(\n  ...[dineroObjects]: HaveSameAmountParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObjects[0];\n  const haveSameAmountFn = coreHaveSameAmount(calculator);\n\n  return haveSameAmountFn(dineroObjects);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/haveSameCurrency.ts",
    "content": "import { haveSameCurrency as coreHaveSameCurrency } from '../core';\n\n/**\n * Check whether a set of Dinero objects have the same currency.\n *\n * @param dineroObjects - The Dinero objects to compare.\n *\n * @returns Whether the Dinero objects have the same currency.\n *\n * @public\n */\nexport const haveSameCurrency = coreHaveSameCurrency;\n"
  },
  {
    "path": "packages/dinero.js/src/api/index.ts",
    "content": "export * from './add';\nexport * from './allocate';\nexport * from './compare';\nexport * from './convert';\nexport * from './equal';\nexport * from './greaterThan';\nexport * from './greaterThanOrEqual';\nexport * from './hasSubUnits';\nexport * from './haveSameAmount';\nexport * from './haveSameCurrency';\nexport * from './isNegative';\nexport * from './isPositive';\nexport * from './isZero';\nexport * from './lessThan';\nexport * from './lessThanOrEqual';\nexport * from './maximum';\nexport * from './minimum';\nexport * from './multiply';\nexport * from './normalizeScale';\nexport * from './subtract';\nexport * from './toDecimal';\nexport * from './toSnapshot';\nexport * from './toUnits';\nexport * from './transformScale';\nexport * from './trimScale';\n"
  },
  {
    "path": "packages/dinero.js/src/api/isNegative.ts",
    "content": "import { isNegative as coreIsNegative } from '../core';\nimport type { IsNegativeParams } from '../core';\n\n/**\n * Check whether a Dinero object is negative.\n *\n * @param dineroObject - The Dinero object to check.\n *\n * @returns Whether the Dinero object is negative.\n *\n * @public\n */\nexport function isNegative<TAmount>(\n  ...[dineroObject]: IsNegativeParams<TAmount>\n) {\n  const { calculator } = dineroObject;\n  const isNegativeFn = coreIsNegative(calculator);\n\n  return isNegativeFn(dineroObject);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/isPositive.ts",
    "content": "import { isPositive as coreIsPositive } from '../core';\nimport type { IsPositiveParams } from '../core';\n\n/**\n * Check whether a Dinero object is positive.\n *\n * @param dineroObject - The Dinero object to check.\n *\n * @returns Whether the Dinero object is positive.\n *\n * @public\n */\nexport function isPositive<TAmount>(\n  ...[dineroObject]: IsPositiveParams<TAmount>\n) {\n  const { calculator } = dineroObject;\n  const isPositiveFn = coreIsPositive(calculator);\n\n  return isPositiveFn(dineroObject);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/isZero.ts",
    "content": "import { isZero as coreIsZero } from '../core';\nimport type { IsZeroParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is zero.\n *\n * @param dineroObject - The Dinero object to check.\n *\n * @returns Whether the value of a Dinero object is zero.\n *\n * @public\n */\nexport function isZero<TAmount>(...[dineroObject]: IsZeroParams<TAmount>) {\n  const { calculator } = dineroObject;\n  const isZeroFn = coreIsZero(calculator);\n\n  return isZeroFn(dineroObject);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/lessThan.ts",
    "content": "import { safeLessThan } from '../core';\nimport type { LessThanParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is lesser than another.\n *\n * @param dineroObject - The Dinero object to compare.\n * @param comparator - The Dinero object to compare to.\n *\n * @returns Whether the Dinero to compare is lesser than the other.\n *\n * @public\n */\nexport function lessThan<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: LessThanParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const lessThanFn = safeLessThan(calculator);\n\n  return lessThanFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/lessThanOrEqual.ts",
    "content": "import { safeLessThanOrEqual } from '../core';\nimport type { LessThanOrEqualParams } from '../core';\n\n/**\n * Check whether the value of a Dinero object is lesser than or equal to another.\n *\n * @param dineroObject - The Dinero object to compare.\n * @param comparator - The Dinero object to compare to.\n *\n * @returns Whether the Dinero to compare is lesser than or equal to the other.\n *\n * @public\n */\nexport function lessThanOrEqual<TAmount, TCurrency extends string>(\n  ...[dineroObject, comparator]: LessThanOrEqualParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const lessThanOrEqualFn = safeLessThanOrEqual(calculator);\n\n  return lessThanOrEqualFn(dineroObject, comparator);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/maximum.ts",
    "content": "import { safeMaximum } from '../core';\nimport type { MaximumParams } from '../core';\n\n/**\n * Get the greatest of the passed Dinero objects.\n *\n * @param dineroObjects - The Dinero objects to maximum.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function maximum<TAmount, TCurrency extends string>(\n  ...[dineroObjects]: MaximumParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObjects[0];\n  const maximumFn = safeMaximum(calculator);\n\n  return maximumFn(dineroObjects);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/minimum.ts",
    "content": "import { safeMinimum } from '../core';\nimport type { MinimumParams } from '../core';\n\n/**\n * Get the lowest of the passed Dinero objects.\n *\n * @param dineroObjects - The Dinero objects to minimum.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function minimum<TAmount, TCurrency extends string>(\n  ...[dineroObjects]: MinimumParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObjects[0];\n  const minimumFn = safeMinimum(calculator);\n\n  return minimumFn(dineroObjects);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/multiply.ts",
    "content": "import { multiply as coreMultiply } from '../core';\nimport type { MultiplyParams } from '../core';\n\n/**\n * Multiply the passed Dinero object.\n *\n * @param multiplicand - The Dinero object to multiply.\n * @param multiplier - The number to multiply with.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function multiply<TAmount, TCurrency extends string>(\n  ...[multiplicand, multiplier]: MultiplyParams<TAmount, TCurrency>\n) {\n  const { calculator } = multiplicand;\n  const multiplyFn = coreMultiply(calculator);\n\n  return multiplyFn(multiplicand, multiplier);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/normalizeScale.ts",
    "content": "import { normalizeScale as coreNormalizeScale } from '../core';\nimport type { NormalizeScaleParams } from '../core';\n\n/**\n * Normalize a set of Dinero objects to the highest scale of the set.\n *\n * @param dineroObjects - The Dinero objects to normalize.\n *\n * @returns A new set of Dinero objects.\n *\n * @public\n */\nexport function normalizeScale<TAmount, TCurrency extends string>(\n  ...[dineroObjects]: NormalizeScaleParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObjects[0];\n  const normalizeScaleFn = coreNormalizeScale(calculator);\n\n  return normalizeScaleFn(dineroObjects);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/subtract.ts",
    "content": "import { safeSubtract } from '../core';\nimport type { SubtractParams } from '../core';\n\n/**\n * Subtract the passed Dinero objects.\n *\n * @param minuend - The Dinero object to subtract from.\n * @param subtrahend - The Dinero object to subtract.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function subtract<TAmount, TCurrency extends string>(\n  ...[minuend, subtrahend]: SubtractParams<TAmount, TCurrency>\n) {\n  const { calculator } = minuend;\n  const subtractFn = safeSubtract(calculator);\n\n  return subtractFn(minuend, subtrahend);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/toDecimal.ts",
    "content": "import { toDecimal as coreToDecimal } from '../core';\nimport type { ToDecimalParams, Dinero, DineroTransformer } from '../core';\n\nexport function toDecimal<TAmount, TCurrency extends string>(\n  dineroObject: Dinero<TAmount, TCurrency>\n): string;\n\nexport function toDecimal<TAmount, TOutput, TCurrency extends string>(\n  dineroObject: Dinero<TAmount, TCurrency>,\n  transformer: DineroTransformer<TAmount, TOutput, string, TCurrency>\n): TOutput;\n\n/**\n * Get the amount of a Dinero object in decimal form.\n *\n * @param dineroObject - The Dinero object to format.\n * @param transformer - A transformer function.\n *\n * @returns The amount in decimal form.\n *\n * @public\n */\nexport function toDecimal<TAmount, TOutput, TCurrency extends string>(\n  ...[dineroObject, transformer]: ToDecimalParams<TAmount, TOutput, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const toDecimalFn = coreToDecimal<TAmount, TOutput>(calculator);\n\n  return toDecimalFn(dineroObject, transformer);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/toSnapshot.ts",
    "content": "import { toSnapshot as coreToSnapshot } from '../core';\n\n/**\n * Get a snapshot of a Dinero object.\n *\n * @param dineroObject - The Dinero object to format.\n * @param transformer - A transformer function.\n *\n * @returns A snapshot of the object.\n *\n * @public\n */\nexport const toSnapshot = coreToSnapshot;\n"
  },
  {
    "path": "packages/dinero.js/src/api/toUnits.ts",
    "content": "import { toUnits as coreToUnits } from '../core';\nimport type { ToUnitsParams, Dinero, DineroTransformer } from '../core';\n\nexport function toUnits<TAmount, TCurrency extends string>(\n  dineroObject: Dinero<TAmount, TCurrency>\n): readonly TAmount[];\n\nexport function toUnits<TAmount, TOutput, TCurrency extends string>(\n  dineroObject: Dinero<TAmount, TCurrency>,\n  transformer: DineroTransformer<\n    TAmount,\n    TOutput,\n    readonly TAmount[],\n    TCurrency\n  >\n): TOutput;\n\n/**\n * Get the amount of a Dinero object in units.\n *\n * @param dineroObject - The Dinero object to format.\n * @param transformer - A transformer function.\n *\n * @returns The amount in units.\n *\n * @public\n */\nexport function toUnits<TAmount, TOutput, TCurrency extends string>(\n  ...[dineroObject, transformer]: ToUnitsParams<TAmount, TOutput, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const toUnitsFn = coreToUnits<TAmount, TOutput>(calculator);\n\n  return toUnitsFn(dineroObject, transformer);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/transformScale.ts",
    "content": "import { transformScale as coreTransformScale } from '../core';\nimport type { TransformScaleParams } from '../core';\n\n/**\n * Transform a Dinero object to a new scale.\n *\n * @param dineroObject - The Dinero object to transform.\n * @param newScale - The new scale.\n * @param divide - A custom divide function.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function transformScale<TAmount, TCurrency extends string>(\n  ...[dineroObject, newScale, divide]: TransformScaleParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const transformScaleFn = coreTransformScale(calculator);\n\n  return transformScaleFn(dineroObject, newScale, divide);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/api/trimScale.ts",
    "content": "import { trimScale as coreTrimScale } from '../core';\nimport type { TrimScaleParams } from '../core';\n\n/**\n * Trim a Dinero object's scale as much as possible, down to the currency exponent.\n *\n * @param dineroObject - The Dinero object which scale to trim.\n *\n * @returns A new Dinero object.\n *\n * @public\n */\nexport function trimScale<TAmount, TCurrency extends string>(\n  ...[dineroObject]: TrimScaleParams<TAmount, TCurrency>\n) {\n  const { calculator } = dineroObject;\n  const trimScaleFn = coreTrimScale(calculator);\n\n  return trimScaleFn(dineroObject);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/bigint/currencies/index.ts",
    "content": "export * from './iso4217';\nexport type { DineroCurrency } from '../../currencies/types';\n"
  },
  {
    "path": "packages/dinero.js/src/bigint/currencies/iso4217.ts",
    "content": "/**\n * ISO 4217 currency definitions.\n *\n * These track the latest ISO 4217 standard. DineroCurrency properties may change\n * when you upgrade Dinero.js. If you need stability, pin your package version.\n *\n * @see https://www.six-group.com/en/products-services/financial-information/data-standards.html\n */\n\nimport type { DineroCurrency } from '../../currencies/types';\n\n/**\n * United Arab Emirates dirham.\n */\nexport const AED: DineroCurrency<bigint> = {\n  code: 'AED',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Afghan afghani.\n */\nexport const AFN: DineroCurrency<bigint> = {\n  code: 'AFN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Albanian lek.\n */\nexport const ALL: DineroCurrency<bigint> = {\n  code: 'ALL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Armenian dram.\n */\nexport const AMD: DineroCurrency<bigint> = {\n  code: 'AMD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Angolan kwanza.\n */\nexport const AOA: DineroCurrency<bigint> = {\n  code: 'AOA',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Argentine peso.\n */\nexport const ARS: DineroCurrency<bigint> = {\n  code: 'ARS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Australian dollar.\n */\nexport const AUD: DineroCurrency<bigint> = {\n  code: 'AUD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Aruban florin.\n */\nexport const AWG: DineroCurrency<bigint> = {\n  code: 'AWG',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Azerbaijani manat.\n */\nexport const AZN: DineroCurrency<bigint> = {\n  code: 'AZN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bosnia and Herzegovina convertible mark.\n */\nexport const BAM: DineroCurrency<bigint> = {\n  code: 'BAM',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Barbados dollar.\n */\nexport const BBD: DineroCurrency<bigint> = {\n  code: 'BBD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bangladeshi taka.\n */\nexport const BDT: DineroCurrency<bigint> = {\n  code: 'BDT',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bulgarian lev.\n */\nexport const BGN: DineroCurrency<bigint> = {\n  code: 'BGN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bahraini dinar.\n */\nexport const BHD: DineroCurrency<bigint> = {\n  code: 'BHD',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Burundian franc.\n */\nexport const BIF: DineroCurrency<bigint> = {\n  code: 'BIF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Bermudian dollar.\n */\nexport const BMD: DineroCurrency<bigint> = {\n  code: 'BMD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Brunei dollar.\n */\nexport const BND: DineroCurrency<bigint> = {\n  code: 'BND',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bolivian boliviano.\n */\nexport const BOB: DineroCurrency<bigint> = {\n  code: 'BOB',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bolivian Mvdol.\n */\nexport const BOV: DineroCurrency<bigint> = {\n  code: 'BOV',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Brazilian real.\n */\nexport const BRL: DineroCurrency<bigint> = {\n  code: 'BRL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bahamian dollar.\n */\nexport const BSD: DineroCurrency<bigint> = {\n  code: 'BSD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Bhutanese ngultrum.\n */\nexport const BTN: DineroCurrency<bigint> = {\n  code: 'BTN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Botswana pula.\n */\nexport const BWP: DineroCurrency<bigint> = {\n  code: 'BWP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Belarusian ruble.\n */\nexport const BYN: DineroCurrency<bigint> = {\n  code: 'BYN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Belize dollar.\n */\nexport const BZD: DineroCurrency<bigint> = {\n  code: 'BZD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Canadian dollar.\n */\nexport const CAD: DineroCurrency<bigint> = {\n  code: 'CAD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Congolese franc.\n */\nexport const CDF: DineroCurrency<bigint> = {\n  code: 'CDF',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * WIR Euro.\n */\nexport const CHE: DineroCurrency<bigint> = {\n  code: 'CHE',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Swiss franc.\n */\nexport const CHF: DineroCurrency<bigint> = {\n  code: 'CHF',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * WIR Franc.\n */\nexport const CHW: DineroCurrency<bigint> = {\n  code: 'CHW',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Unidad de Fomento.\n */\nexport const CLF: DineroCurrency<bigint> = {\n  code: 'CLF',\n  base: 10n,\n  exponent: 4n,\n};\n\n/**\n * Chilean peso.\n */\nexport const CLP: DineroCurrency<bigint> = {\n  code: 'CLP',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Renminbi (Chinese) yuan.\n */\nexport const CNY: DineroCurrency<bigint> = {\n  code: 'CNY',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Colombian peso.\n */\nexport const COP: DineroCurrency<bigint> = {\n  code: 'COP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Unidad de Valor Real.\n */\nexport const COU: DineroCurrency<bigint> = {\n  code: 'COU',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Costa Rican colón.\n */\nexport const CRC: DineroCurrency<bigint> = {\n  code: 'CRC',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Cuban peso.\n */\nexport const CUP: DineroCurrency<bigint> = {\n  code: 'CUP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Cape Verdean escudo.\n */\nexport const CVE: DineroCurrency<bigint> = {\n  code: 'CVE',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Czech koruna.\n */\nexport const CZK: DineroCurrency<bigint> = {\n  code: 'CZK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Djiboutian franc.\n */\nexport const DJF: DineroCurrency<bigint> = {\n  code: 'DJF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Danish krone.\n */\nexport const DKK: DineroCurrency<bigint> = {\n  code: 'DKK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Dominican peso.\n */\nexport const DOP: DineroCurrency<bigint> = {\n  code: 'DOP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Algerian dinar.\n */\nexport const DZD: DineroCurrency<bigint> = {\n  code: 'DZD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Egyptian pound.\n */\nexport const EGP: DineroCurrency<bigint> = {\n  code: 'EGP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Eritrean nakfa.\n */\nexport const ERN: DineroCurrency<bigint> = {\n  code: 'ERN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Ethiopian birr.\n */\nexport const ETB: DineroCurrency<bigint> = {\n  code: 'ETB',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Euro.\n */\nexport const EUR: DineroCurrency<bigint> = {\n  code: 'EUR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Fiji dollar.\n */\nexport const FJD: DineroCurrency<bigint> = {\n  code: 'FJD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Falkland Islands pound.\n */\nexport const FKP: DineroCurrency<bigint> = {\n  code: 'FKP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Pound sterling.\n */\nexport const GBP: DineroCurrency<bigint> = {\n  code: 'GBP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Georgian lari.\n */\nexport const GEL: DineroCurrency<bigint> = {\n  code: 'GEL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Ghanaian cedi.\n */\nexport const GHS: DineroCurrency<bigint> = {\n  code: 'GHS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Gibraltar pound.\n */\nexport const GIP: DineroCurrency<bigint> = {\n  code: 'GIP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Gambian dalasi.\n */\nexport const GMD: DineroCurrency<bigint> = {\n  code: 'GMD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Guinean franc.\n */\nexport const GNF: DineroCurrency<bigint> = {\n  code: 'GNF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Guatemalan quetzal.\n */\nexport const GTQ: DineroCurrency<bigint> = {\n  code: 'GTQ',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Guyanese dollar.\n */\nexport const GYD: DineroCurrency<bigint> = {\n  code: 'GYD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Hong Kong dollar.\n */\nexport const HKD: DineroCurrency<bigint> = {\n  code: 'HKD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Honduran lempira.\n */\nexport const HNL: DineroCurrency<bigint> = {\n  code: 'HNL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Haitian gourde.\n */\nexport const HTG: DineroCurrency<bigint> = {\n  code: 'HTG',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Hungarian forint.\n */\nexport const HUF: DineroCurrency<bigint> = {\n  code: 'HUF',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Indonesian rupiah.\n */\nexport const IDR: DineroCurrency<bigint> = {\n  code: 'IDR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Israeli new shekel.\n */\nexport const ILS: DineroCurrency<bigint> = {\n  code: 'ILS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Indian rupee.\n */\nexport const INR: DineroCurrency<bigint> = {\n  code: 'INR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Iraqi dinar.\n */\nexport const IQD: DineroCurrency<bigint> = {\n  code: 'IQD',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Iranian rial.\n */\nexport const IRR: DineroCurrency<bigint> = {\n  code: 'IRR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Icelandic króna.\n */\nexport const ISK: DineroCurrency<bigint> = {\n  code: 'ISK',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Jamaican dollar.\n */\nexport const JMD: DineroCurrency<bigint> = {\n  code: 'JMD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Jordanian dinar.\n */\nexport const JOD: DineroCurrency<bigint> = {\n  code: 'JOD',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Japanese yen.\n */\nexport const JPY: DineroCurrency<bigint> = {\n  code: 'JPY',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Kenyan shilling.\n */\nexport const KES: DineroCurrency<bigint> = {\n  code: 'KES',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Kyrgyzstani som.\n */\nexport const KGS: DineroCurrency<bigint> = {\n  code: 'KGS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Cambodian riel.\n */\nexport const KHR: DineroCurrency<bigint> = {\n  code: 'KHR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Comoro franc.\n */\nexport const KMF: DineroCurrency<bigint> = {\n  code: 'KMF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * North Korean won.\n */\nexport const KPW: DineroCurrency<bigint> = {\n  code: 'KPW',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * South Korean won.\n */\nexport const KRW: DineroCurrency<bigint> = {\n  code: 'KRW',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Kuwaiti dinar.\n */\nexport const KWD: DineroCurrency<bigint> = {\n  code: 'KWD',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Cayman Islands dollar.\n */\nexport const KYD: DineroCurrency<bigint> = {\n  code: 'KYD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Kazakhstani tenge.\n */\nexport const KZT: DineroCurrency<bigint> = {\n  code: 'KZT',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Lao kip.\n */\nexport const LAK: DineroCurrency<bigint> = {\n  code: 'LAK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Lebanese pound.\n */\nexport const LBP: DineroCurrency<bigint> = {\n  code: 'LBP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Sri Lankan rupee.\n */\nexport const LKR: DineroCurrency<bigint> = {\n  code: 'LKR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Liberian dollar.\n */\nexport const LRD: DineroCurrency<bigint> = {\n  code: 'LRD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Lesotho loti.\n */\nexport const LSL: DineroCurrency<bigint> = {\n  code: 'LSL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Libyan dinar.\n */\nexport const LYD: DineroCurrency<bigint> = {\n  code: 'LYD',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Moroccan dirham.\n */\nexport const MAD: DineroCurrency<bigint> = {\n  code: 'MAD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Moldovan leu.\n */\nexport const MDL: DineroCurrency<bigint> = {\n  code: 'MDL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Malagasy ariary.\n */\nexport const MGA: DineroCurrency<bigint> = {\n  code: 'MGA',\n  base: 5n,\n  exponent: 1n,\n};\n\n/**\n * Macedonian denar.\n */\nexport const MKD: DineroCurrency<bigint> = {\n  code: 'MKD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Myanmar kyat.\n */\nexport const MMK: DineroCurrency<bigint> = {\n  code: 'MMK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Mongolian tögrög.\n */\nexport const MNT: DineroCurrency<bigint> = {\n  code: 'MNT',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Macanese pataca.\n */\nexport const MOP: DineroCurrency<bigint> = {\n  code: 'MOP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Mauritanian ouguiya.\n */\nexport const MRU: DineroCurrency<bigint> = {\n  code: 'MRU',\n  base: 5n,\n  exponent: 1n,\n};\n\n/**\n * Mauritian rupee.\n */\nexport const MUR: DineroCurrency<bigint> = {\n  code: 'MUR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Maldivian rufiyaa.\n */\nexport const MVR: DineroCurrency<bigint> = {\n  code: 'MVR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Malawian kwacha.\n */\nexport const MWK: DineroCurrency<bigint> = {\n  code: 'MWK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Mexican peso.\n */\nexport const MXN: DineroCurrency<bigint> = {\n  code: 'MXN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Mexican Unidad de Inversion.\n */\nexport const MXV: DineroCurrency<bigint> = {\n  code: 'MXV',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Malaysian ringgit.\n */\nexport const MYR: DineroCurrency<bigint> = {\n  code: 'MYR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Mozambican metical.\n */\nexport const MZN: DineroCurrency<bigint> = {\n  code: 'MZN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Namibian dollar.\n */\nexport const NAD: DineroCurrency<bigint> = {\n  code: 'NAD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Nigerian naira.\n */\nexport const NGN: DineroCurrency<bigint> = {\n  code: 'NGN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Nicaraguan córdoba.\n */\nexport const NIO: DineroCurrency<bigint> = {\n  code: 'NIO',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Norwegian krone.\n */\nexport const NOK: DineroCurrency<bigint> = {\n  code: 'NOK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Nepalese rupee.\n */\nexport const NPR: DineroCurrency<bigint> = {\n  code: 'NPR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * New Zealand dollar.\n */\nexport const NZD: DineroCurrency<bigint> = {\n  code: 'NZD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Omani rial.\n */\nexport const OMR: DineroCurrency<bigint> = {\n  code: 'OMR',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Panamanian balboa.\n */\nexport const PAB: DineroCurrency<bigint> = {\n  code: 'PAB',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Peruvian sol.\n */\nexport const PEN: DineroCurrency<bigint> = {\n  code: 'PEN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Papua New Guinean kina.\n */\nexport const PGK: DineroCurrency<bigint> = {\n  code: 'PGK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Philippine peso.\n */\nexport const PHP: DineroCurrency<bigint> = {\n  code: 'PHP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Pakistani rupee.\n */\nexport const PKR: DineroCurrency<bigint> = {\n  code: 'PKR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Polish złoty.\n */\nexport const PLN: DineroCurrency<bigint> = {\n  code: 'PLN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Paraguayan guaraní.\n */\nexport const PYG: DineroCurrency<bigint> = {\n  code: 'PYG',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Qatari riyal.\n */\nexport const QAR: DineroCurrency<bigint> = {\n  code: 'QAR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Romanian leu.\n */\nexport const RON: DineroCurrency<bigint> = {\n  code: 'RON',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Serbian dinar.\n */\nexport const RSD: DineroCurrency<bigint> = {\n  code: 'RSD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Russian ruble.\n */\nexport const RUB: DineroCurrency<bigint> = {\n  code: 'RUB',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Rwandan franc.\n */\nexport const RWF: DineroCurrency<bigint> = {\n  code: 'RWF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Saudi riyal.\n */\nexport const SAR: DineroCurrency<bigint> = {\n  code: 'SAR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Solomon Islands dollar.\n */\nexport const SBD: DineroCurrency<bigint> = {\n  code: 'SBD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Seychelles rupee.\n */\nexport const SCR: DineroCurrency<bigint> = {\n  code: 'SCR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Sudanese pound.\n */\nexport const SDG: DineroCurrency<bigint> = {\n  code: 'SDG',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Swedish krona.\n */\nexport const SEK: DineroCurrency<bigint> = {\n  code: 'SEK',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Singapore dollar.\n */\nexport const SGD: DineroCurrency<bigint> = {\n  code: 'SGD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Saint Helena pound.\n */\nexport const SHP: DineroCurrency<bigint> = {\n  code: 'SHP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Sierra Leonean leone.\n */\nexport const SLE: DineroCurrency<bigint> = {\n  code: 'SLE',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Somali shilling.\n */\nexport const SOS: DineroCurrency<bigint> = {\n  code: 'SOS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Surinamese dollar.\n */\nexport const SRD: DineroCurrency<bigint> = {\n  code: 'SRD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * South Sudanese pound.\n */\nexport const SSP: DineroCurrency<bigint> = {\n  code: 'SSP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * São Tomé and Príncipe dobra.\n */\nexport const STN: DineroCurrency<bigint> = {\n  code: 'STN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Salvadoran colón.\n */\nexport const SVC: DineroCurrency<bigint> = {\n  code: 'SVC',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Syrian pound.\n */\nexport const SYP: DineroCurrency<bigint> = {\n  code: 'SYP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Swazi lilangeni.\n */\nexport const SZL: DineroCurrency<bigint> = {\n  code: 'SZL',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Thai baht.\n */\nexport const THB: DineroCurrency<bigint> = {\n  code: 'THB',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Tajikistani somoni.\n */\nexport const TJS: DineroCurrency<bigint> = {\n  code: 'TJS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Turkmenistan manat.\n */\nexport const TMT: DineroCurrency<bigint> = {\n  code: 'TMT',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Tunisian dinar.\n */\nexport const TND: DineroCurrency<bigint> = {\n  code: 'TND',\n  base: 10n,\n  exponent: 3n,\n};\n\n/**\n * Tongan paʻanga.\n */\nexport const TOP: DineroCurrency<bigint> = {\n  code: 'TOP',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Turkish lira.\n */\nexport const TRY: DineroCurrency<bigint> = {\n  code: 'TRY',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Trinidad and Tobago dollar.\n */\nexport const TTD: DineroCurrency<bigint> = {\n  code: 'TTD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * New Taiwan dollar.\n */\nexport const TWD: DineroCurrency<bigint> = {\n  code: 'TWD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Tanzanian shilling.\n */\nexport const TZS: DineroCurrency<bigint> = {\n  code: 'TZS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Ukrainian hryvnia.\n */\nexport const UAH: DineroCurrency<bigint> = {\n  code: 'UAH',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Ugandan shilling.\n */\nexport const UGX: DineroCurrency<bigint> = {\n  code: 'UGX',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * United States dollar.\n */\nexport const USD: DineroCurrency<bigint> = {\n  code: 'USD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * United States dollar (next day).\n */\nexport const USN: DineroCurrency<bigint> = {\n  code: 'USN',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Uruguay Peso en Unidades Indexadas.\n */\nexport const UYI: DineroCurrency<bigint> = {\n  code: 'UYI',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Uruguayan peso.\n */\nexport const UYU: DineroCurrency<bigint> = {\n  code: 'UYU',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Unidad previsional.\n */\nexport const UYW: DineroCurrency<bigint> = {\n  code: 'UYW',\n  base: 10n,\n  exponent: 4n,\n};\n\n/**\n * Uzbekistani soʻm.\n */\nexport const UZS: DineroCurrency<bigint> = {\n  code: 'UZS',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Venezuelan digital bolívar.\n */\nexport const VED: DineroCurrency<bigint> = {\n  code: 'VED',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Venezuelan bolívar.\n */\nexport const VES: DineroCurrency<bigint> = {\n  code: 'VES',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Vietnamese đồng.\n */\nexport const VND: DineroCurrency<bigint> = {\n  code: 'VND',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Vanuatu vatu.\n */\nexport const VUV: DineroCurrency<bigint> = {\n  code: 'VUV',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Samoan tālā.\n */\nexport const WST: DineroCurrency<bigint> = {\n  code: 'WST',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Arab Accounting Dinar.\n */\nexport const XAD: DineroCurrency<bigint> = {\n  code: 'XAD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Central African CFA franc.\n */\nexport const XAF: DineroCurrency<bigint> = {\n  code: 'XAF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * East Caribbean dollar.\n */\nexport const XCD: DineroCurrency<bigint> = {\n  code: 'XCD',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Caribbean guilder.\n */\nexport const XCG: DineroCurrency<bigint> = {\n  code: 'XCG',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * West African CFA franc.\n */\nexport const XOF: DineroCurrency<bigint> = {\n  code: 'XOF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * CFP franc.\n */\nexport const XPF: DineroCurrency<bigint> = {\n  code: 'XPF',\n  base: 10n,\n  exponent: 0n,\n};\n\n/**\n * Yemeni rial.\n */\nexport const YER: DineroCurrency<bigint> = {\n  code: 'YER',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * South African rand.\n */\nexport const ZAR: DineroCurrency<bigint> = {\n  code: 'ZAR',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Zambian kwacha.\n */\nexport const ZMW: DineroCurrency<bigint> = {\n  code: 'ZMW',\n  base: 10n,\n  exponent: 2n,\n};\n\n/**\n * Zimbabwe Gold.\n */\nexport const ZWG: DineroCurrency<bigint> = {\n  code: 'ZWG',\n  base: 10n,\n  exponent: 2n,\n};\n"
  },
  {
    "path": "packages/dinero.js/src/bigint/dinero.ts",
    "content": "import { calculator } from '../calculator/bigint';\nimport { createDinero } from '../core';\n\n/**\n * Create a Dinero object with bigint amounts.\n *\n * @param options.amount - The amount in minor currency units as a bigint.\n * @param options.currency - The currency.\n * @param options.scale - The number of decimal places to represent.\n *\n * @returns The created Dinero object.\n *\n * @public\n */\nexport const dinero = createDinero({\n  calculator,\n});\n"
  },
  {
    "path": "packages/dinero.js/src/bigint/index.ts",
    "content": "export * from '../api';\nexport * from './currencies';\nexport type {\n  CreateDineroOptions,\n  Dinero,\n  DineroBinaryOperation,\n  DineroCalculator,\n  DineroComparisonOperator,\n  DineroDivideOperation,\n  DineroFactory,\n  DineroFormatter,\n  DineroOptions,\n  DineroRate,\n  DineroRates,\n  DineroScaledAmount,\n  DineroSnapshot,\n  DineroTransformer,\n  DineroUnaryOperation,\n  TransformerOptions,\n} from '../core';\nexport {\n  createDinero,\n  down,\n  halfAwayFromZero,\n  halfDown,\n  halfEven,\n  halfOdd,\n  halfTowardsZero,\n  halfUp,\n  up,\n} from '../core';\nexport type { DineroCurrency } from '../currencies';\nexport { calculator } from '../calculator/bigint';\nexport * from './dinero';\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/add.test.ts",
    "content": "import { add } from '../add';\n\ndescribe('add', () => {\n  it('adds up positive numbers', () => {\n    expect(add(2n, 3n)).toBe(5n);\n  });\n  it('adds up negative numbers', () => {\n    expect(add(-1n, -2n)).toBe(-3n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/compare.test.ts",
    "content": "import { DineroComparisonOperator } from '../../../../core';\n\nimport { compare } from '../compare';\n\ndescribe('compare', () => {\n  describe('DineroComparisonOperator.LT', () => {\n    it('returns `DineroComparisonOperator.LT` with positive numbers', () => {\n      expect(compare(1n, 2n)).toBe(DineroComparisonOperator.LT);\n    });\n    it('returns `DineroComparisonOperator.LT` with negative numbers', () => {\n      expect(compare(-2n, -1n)).toBe(DineroComparisonOperator.LT);\n    });\n  });\n  describe('DineroComparisonOperator.GT', () => {\n    it('returns `DineroComparisonOperator.GT` with positive numbers', () => {\n      expect(compare(2n, 1n)).toBe(DineroComparisonOperator.GT);\n    });\n    it('returns `DineroComparisonOperator.GT` with negative numbers', () => {\n      expect(compare(-1n, -2n)).toBe(DineroComparisonOperator.GT);\n    });\n  });\n  describe('DineroComparisonOperator.EQ', () => {\n    it('returns `DineroComparisonOperator.EQ` with positive numbers', () => {\n      expect(compare(2n, 2n)).toBe(DineroComparisonOperator.EQ);\n    });\n    it('returns `DineroComparisonOperator.EQ` with negative numbers', () => {\n      expect(compare(-2n, -2n)).toBe(DineroComparisonOperator.EQ);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/decrement.test.ts",
    "content": "import { decrement } from '../decrement';\n\ndescribe('decrement', () => {\n  it('decrements positive numbers', () => {\n    expect(decrement(2n)).toBe(1n);\n  });\n  it('decrements negative numbers', () => {\n    expect(decrement(-2n)).toBe(-3n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/increment.test.ts",
    "content": "import { increment } from '../increment';\n\ndescribe('increment', () => {\n  it('increments positive numbers', () => {\n    expect(increment(2n)).toBe(3n);\n  });\n  it('increments negative numbers', () => {\n    expect(increment(-2n)).toBe(-1n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/integerDivide.test.ts",
    "content": "import { integerDivide } from '../integerDivide';\n\ndescribe('integerDivide', () => {\n  it('divides positive numbers', () => {\n    expect(integerDivide(8n, 2n)).toBe(4n);\n  });\n  it('divides negative numbers', () => {\n    expect(integerDivide(-8n, -2n)).toBe(4n);\n  });\n  it('rounds positive numbers towards zero', () => {\n    expect(integerDivide(3n, 2n)).toBe(1n);\n  });\n  it('rounds negative numbers towards zero', () => {\n    expect(integerDivide(-3n, 2n)).toBe(-1n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/modulo.test.ts",
    "content": "import { modulo } from '../modulo';\n\ndescribe('modulo', () => {\n  it('performs a modulo with positive numbers', () => {\n    expect(modulo(5n, 3n)).toBe(2n);\n  });\n  it('performs a modulo with negative numbers', () => {\n    expect(modulo(-5n, -4n)).toBe(-1n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/multiply.test.ts",
    "content": "import { multiply } from '../multiply';\n\ndescribe('multiply', () => {\n  it('multiplies positive numbers', () => {\n    expect(multiply(10n, 20n)).toBe(200n);\n  });\n  it('multiplies negative numbers', () => {\n    expect(multiply(-10n, -20n)).toBe(200n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/power.test.ts",
    "content": "import { power } from '../power';\n\ndescribe('power', () => {\n  it('raises a positive number to the power of an exponent', () => {\n    expect(power(2n, 3n)).toBe(8n);\n  });\n  it('raises a negative number to the power of an exponent', () => {\n    expect(power(-2n, 3n)).toBe(-8n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/subtract.test.ts",
    "content": "import { subtract } from '../subtract';\n\ndescribe('subtract', () => {\n  it('subtracts positive numbers', () => {\n    expect(subtract(1n, 2n)).toBe(-1n);\n  });\n  it('subtracts negative numbers', () => {\n    expect(subtract(-1n, -2n)).toBe(1n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/__tests__/zero.test.ts",
    "content": "import { zero } from '../zero';\n\ndescribe('zero', () => {\n  it('returns zero', () => {\n    expect(zero()).toBe(0n);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/add.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the sum of two bigints.\n *\n * @param augend - The bigint to add to.\n * @param addend - The bigint to add.\n *\n * @returns The sum of the two bigints.\n */\nexport const add: DineroBinaryOperation<bigint> = (augend, addend) => {\n  return augend + addend;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/compare.ts",
    "content": "import { DineroComparisonOperator } from '../../../core';\nimport type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Compare two bigints.\n *\n * @param a - The first bigint to compare.\n * @param b - The second bigint to compare.\n *\n * @returns Whether the two bigints are equal, or whether the first one is greater or less than the other.\n */\nexport const compare: DineroBinaryOperation<\n  bigint,\n  DineroComparisonOperator\n> = (a, b) => {\n  if (a < b) {\n    return DineroComparisonOperator.LT;\n  }\n  if (a > b) {\n    return DineroComparisonOperator.GT;\n  }\n  return DineroComparisonOperator.EQ;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/decrement.ts",
    "content": "import type { DineroUnaryOperation } from '../../../core';\n\n/**\n * Returns an decremented bigint.\n *\n * @param value - The bigint to decrement.\n *\n * @returns The decremented bigint.\n */\nexport const decrement: DineroUnaryOperation<bigint> = (value) => {\n  return value - 1n;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/increment.ts",
    "content": "import type { DineroUnaryOperation } from '../../../core';\n\n/**\n * Returns an incremented bigint.\n *\n * @param value - The bigint to increment.\n *\n * @returns The incremented bigint.\n */\nexport const increment: DineroUnaryOperation<bigint> = (value) => {\n  return value + 1n;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/index.ts",
    "content": "export * from './add';\nexport * from './compare';\nexport * from './decrement';\nexport * from './increment';\nexport * from './integerDivide';\nexport * from './modulo';\nexport * from './multiply';\nexport * from './power';\nexport * from './subtract';\nexport * from './zero';\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/integerDivide.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the quotient of two bigints with no fractional part.\n *\n * @param dividend - The bigint to divide.\n * @param divisor - The bigint to divide with.\n *\n * @returns The quotient of the two bigints.\n */\nexport const integerDivide: DineroBinaryOperation<bigint> = (\n  dividend,\n  divisor\n) => {\n  return dividend / divisor;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/modulo.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the remainder of two bigints.\n *\n * @param dividend - The bigint to divide.\n * @param divisor - The bigint to divide with.\n *\n * @returns The remainder of the two bigints.\n */\nexport const modulo: DineroBinaryOperation<bigint> = (dividend, divisor) => {\n  return dividend % divisor;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/multiply.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the product of two bigints.\n *\n * @param multiplicand - The bigint to multiply.\n * @param multiplier - The bigint to multiply with.\n *\n * @returns The product of the two bigints.\n */\nexport const multiply: DineroBinaryOperation<bigint> = (\n  multiplicand,\n  multiplier\n) => {\n  return multiplicand * multiplier;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/power.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns an bigint to the power of an exponent.\n *\n * @param base - The base bigint.\n * @param exponent - The exponent to raise the base to.\n *\n * @returns The base to the power of the exponent.\n */\nexport const power: DineroBinaryOperation<bigint> = (base, exponent) => {\n  return base ** exponent;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/subtract.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the difference between two bigints.\n *\n * @param minuend - The bigint to subtract from.\n * @param subtrahend - The bigint to subtract.\n *\n * @returns The difference of the two bigints.\n */\nexport const subtract: DineroBinaryOperation<bigint> = (\n  minuend,\n  subtrahend\n) => {\n  return minuend - subtrahend;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/api/zero.ts",
    "content": "/**\n * Return zero as a bigint.\n *\n * @returns Zero as a bigint.\n */\nexport function zero() {\n  return 0n;\n}\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/calculator.ts",
    "content": "import {\n  add,\n  compare,\n  decrement,\n  increment,\n  integerDivide,\n  modulo,\n  multiply,\n  power,\n  subtract,\n  zero,\n} from './api';\n\nexport const calculator = {\n  add,\n  compare,\n  decrement,\n  increment,\n  integerDivide,\n  modulo,\n  multiply,\n  power,\n  subtract,\n  zero,\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/bigint/index.ts",
    "content": "export * from './api';\nexport * from './calculator';\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/add.test.ts",
    "content": "import { add } from '../add';\n\ndescribe('add', () => {\n  it('adds up positive numbers', () => {\n    expect(add(2, 3)).toBe(5);\n  });\n  it('adds up negative numbers', () => {\n    expect(add(-1, -2)).toBe(-3);\n  });\n  it('adds up floats', () => {\n    expect(add(1.5, 2.5)).toBe(4);\n  });\n  it('adds up numbers in scientific notation', () => {\n    expect(add(1e5, 2e5)).toBe(300000);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/compare.test.ts",
    "content": "import { DineroComparisonOperator } from '../../../../core';\n\nimport { compare } from '../compare';\n\ndescribe('compare', () => {\n  describe('DineroComparisonOperator.LT', () => {\n    it('returns `DineroComparisonOperator.LT` with positive numbers', () => {\n      expect(compare(1, 2)).toBe(DineroComparisonOperator.LT);\n    });\n    it('returns `DineroComparisonOperator.LT` with negative numbers', () => {\n      expect(compare(-2, -1)).toBe(DineroComparisonOperator.LT);\n    });\n    it('returns `DineroComparisonOperator.LT` with positive floats', () => {\n      expect(compare(1.2, 2.2)).toBe(DineroComparisonOperator.LT);\n    });\n    it('returns `DineroComparisonOperator.LT` with negative floats', () => {\n      expect(compare(-2.2, -1.2)).toBe(DineroComparisonOperator.LT);\n    });\n    it('returns `DineroComparisonOperator.LT` with a numbers in scientific notation', () => {\n      expect(compare(1e5, 2e5)).toBe(DineroComparisonOperator.LT);\n    });\n  });\n  describe('DineroComparisonOperator.GT', () => {\n    it('returns `DineroComparisonOperator.GT` with positive numbers', () => {\n      expect(compare(2, 1)).toBe(DineroComparisonOperator.GT);\n    });\n    it('returns `DineroComparisonOperator.GT` with negative numbers', () => {\n      expect(compare(-1, -2)).toBe(DineroComparisonOperator.GT);\n    });\n    it('returns `DineroComparisonOperator.GT` with positive floats', () => {\n      expect(compare(2.2, 1.2)).toBe(DineroComparisonOperator.GT);\n    });\n    it('returns `DineroComparisonOperator.GT` with negative floats', () => {\n      expect(compare(-1.2, -2.2)).toBe(DineroComparisonOperator.GT);\n    });\n    it('returns `DineroComparisonOperator.GT` with a numbers in scientific notation', () => {\n      expect(compare(2e5, 1e5)).toBe(DineroComparisonOperator.GT);\n    });\n  });\n  describe('DineroComparisonOperator.EQ', () => {\n    it('returns `DineroComparisonOperator.EQ` with positive numbers', () => {\n      expect(compare(2, 2)).toBe(DineroComparisonOperator.EQ);\n    });\n    it('returns `DineroComparisonOperator.EQ` with negative numbers', () => {\n      expect(compare(-2, -2)).toBe(DineroComparisonOperator.EQ);\n    });\n    it('returns `DineroComparisonOperator.EQ` with positive floats', () => {\n      expect(compare(2.2, 2.2)).toBe(DineroComparisonOperator.EQ);\n    });\n    it('returns `DineroComparisonOperator.EQ` with negative floats', () => {\n      expect(compare(-2.2, -2.2)).toBe(DineroComparisonOperator.EQ);\n    });\n    it('returns `DineroComparisonOperator.EQ` with a numbers in scientific notation', () => {\n      expect(compare(2e5, 2e5)).toBe(DineroComparisonOperator.EQ);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/decrement.test.ts",
    "content": "import { decrement } from '../decrement';\n\ndescribe('decrement', () => {\n  it('decrements positive numbers', () => {\n    expect(decrement(2)).toBe(1);\n  });\n  it('decrements negative numbers', () => {\n    expect(decrement(-2)).toBe(-3);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/increment.test.ts",
    "content": "import { increment } from '../increment';\n\ndescribe('increment', () => {\n  it('increments positive numbers', () => {\n    expect(increment(2)).toBe(3);\n  });\n  it('increments negative numbers', () => {\n    expect(increment(-2)).toBe(-1);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/integerDivide.test.ts",
    "content": "import { integerDivide } from '../integerDivide';\n\ndescribe('integerDivide', () => {\n  it('divides positive numbers', () => {\n    expect(integerDivide(8, 2)).toBe(4);\n  });\n  it('divides negative numbers', () => {\n    expect(integerDivide(-8, -2)).toBe(4);\n  });\n  it('divides floats', () => {\n    expect(integerDivide(10.5, 2.5)).toBe(4);\n  });\n  it('divides numbers in scientific notation', () => {\n    expect(integerDivide(3e5, 2e5)).toBe(1);\n  });\n  it('rounds positive numbers towards zero', () => {\n    expect(integerDivide(3, 2)).toBe(1);\n  });\n  it('rounds negative numbers towards zero', () => {\n    expect(integerDivide(-3, 2)).toBe(-1);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/modulo.test.ts",
    "content": "import { modulo } from '../modulo';\n\ndescribe('modulo', () => {\n  it('performs a modulo with positive numbers', () => {\n    expect(modulo(5, 3)).toBe(2);\n  });\n  it('performs a modulo with negative numbers', () => {\n    expect(modulo(-5, -4)).toBe(-1);\n  });\n  it('performs a modulo with floats', () => {\n    expect(modulo(10.5, 2.5)).toBe(0.5);\n  });\n  it('performs a modulo with numbers in scientific notation', () => {\n    expect(modulo(4e5, 3e5)).toBe(100000);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/multiply.test.ts",
    "content": "import { multiply } from '../multiply';\n\ndescribe('multiply', () => {\n  it('multiplies positive numbers', () => {\n    expect(multiply(10, 20)).toBe(200);\n  });\n  it('multiplies negative numbers', () => {\n    expect(multiply(-10, -20)).toBe(200);\n  });\n  it('multiplies numbers in scientific notation', () => {\n    expect(multiply(1e5, 2e5)).toBe(20000000000);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/power.test.ts",
    "content": "import { power } from '../power';\n\ndescribe('power', () => {\n  it('raises a positive number to the power of an exponent', () => {\n    expect(power(2, 3)).toBe(8);\n  });\n  it('raises a negative number to the power of an exponent', () => {\n    expect(power(-2, 3)).toBe(-8);\n  });\n  it('raises a float to the power of an exponent', () => {\n    expect(power(1.5, 3)).toBe(3.375);\n  });\n  it('raises a number in scientific notation to the power of an exponent', () => {\n    expect(power(1e5, 3)).toBe(1000000000000000);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/subtract.test.ts",
    "content": "import { subtract } from '../subtract';\n\ndescribe('subtract', () => {\n  it('subtracts positive numbers', () => {\n    expect(subtract(1, 2)).toBe(-1);\n  });\n  it('subtracts negative numbers', () => {\n    expect(subtract(-1, -2)).toBe(1);\n  });\n  it('subtracts floats', () => {\n    expect(subtract(1.5, 2.5)).toBe(-1);\n  });\n  it('subtracts numbers in scientific notation', () => {\n    expect(subtract(1e5, 2e5)).toBe(-100000);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/__tests__/zero.test.ts",
    "content": "import { zero } from '../zero';\n\ndescribe('zero', () => {\n  it('returns zero', () => {\n    expect(zero()).toBe(0);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/add.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the sum of two numbers.\n *\n * @param augend - The number to add to.\n * @param addend - The number to add.\n *\n * @returns The sum of the two numbers.\n */\nexport const add: DineroBinaryOperation<number> = (augend, addend) => {\n  return augend + addend;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/compare.ts",
    "content": "import { DineroComparisonOperator } from '../../../core';\nimport type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Compare two numbers.\n *\n * @param a - The first number to compare.\n * @param b - The second number to compare.\n *\n * @returns Whether the two numbers are equal, or whether the first one is greater or less than the other.\n */\nexport const compare: DineroBinaryOperation<\n  number,\n  DineroComparisonOperator\n> = (a, b) => {\n  if (a < b) {\n    return DineroComparisonOperator.LT;\n  }\n  if (a > b) {\n    return DineroComparisonOperator.GT;\n  }\n  return DineroComparisonOperator.EQ;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/decrement.ts",
    "content": "import type { DineroUnaryOperation } from '../../../core';\n\n/**\n * Returns an decremented number.\n *\n * @param value - The number to decrement.\n *\n * @returns The decremented number.\n */\nexport const decrement: DineroUnaryOperation<number> = (value) => {\n  return value - 1;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/increment.ts",
    "content": "import type { DineroUnaryOperation } from '../../../core';\n\n/**\n * Returns an incremented number.\n *\n * @param value - The number to increment.\n *\n * @returns The incremented number.\n */\nexport const increment: DineroUnaryOperation<number> = (value) => {\n  return value + 1;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/index.ts",
    "content": "export * from './add';\nexport * from './compare';\nexport * from './decrement';\nexport * from './increment';\nexport * from './integerDivide';\nexport * from './modulo';\nexport * from './multiply';\nexport * from './power';\nexport * from './subtract';\nexport * from './zero';\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/integerDivide.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the quotient of two numbers with no fractional part.\n *\n * @param dividend - The number to divide.\n * @param divisor - The number to divide with.\n *\n * @returns The quotient of the two numbers.\n */\nexport const integerDivide: DineroBinaryOperation<number> = (\n  dividend,\n  divisor\n) => {\n  return Math.trunc(dividend / divisor);\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/modulo.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the remainder of two numbers.\n *\n * @param dividend - The number to divide.\n * @param divisor - The number to divide with.\n *\n * @returns The remainder of the two numbers.\n */\nexport const modulo: DineroBinaryOperation<number> = (dividend, divisor) => {\n  return dividend % divisor;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/multiply.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the product of two numbers.\n *\n * @param multiplicand - The number to multiply.\n * @param multiplier - The number to multiply with.\n *\n * @returns The product of the two numbers.\n */\nexport const multiply: DineroBinaryOperation<number> = (\n  multiplicand,\n  multiplier\n) => {\n  return multiplicand * multiplier;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/power.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns an number to the power of an exponent.\n *\n * @param base - The base number.\n * @param exponent - The exponent to raise the base to.\n *\n * @returns The base to the power of the exponent.\n */\nexport const power: DineroBinaryOperation<number> = (base, exponent) => {\n  return base ** exponent;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/subtract.ts",
    "content": "import type { DineroBinaryOperation } from '../../../core';\n\n/**\n * Returns the difference between two numbers.\n *\n * @param minuend - The number to subtract from.\n * @param subtrahend - The number to subtract.\n *\n * @returns The difference of the two numbers.\n */\nexport const subtract: DineroBinaryOperation<number> = (\n  minuend,\n  subtrahend\n) => {\n  return minuend - subtrahend;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/api/zero.ts",
    "content": "/**\n * Return zero as a number.\n *\n * @returns Zero as a number.\n */\nexport function zero() {\n  return 0;\n}\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/calculator.ts",
    "content": "import {\n  add,\n  compare,\n  decrement,\n  increment,\n  integerDivide,\n  modulo,\n  multiply,\n  power,\n  subtract,\n  zero,\n} from './api';\n\nexport const calculator = {\n  add,\n  compare,\n  decrement,\n  increment,\n  integerDivide,\n  modulo,\n  multiply,\n  power,\n  subtract,\n  zero,\n};\n"
  },
  {
    "path": "packages/dinero.js/src/calculator/number/index.ts",
    "content": "export * from './api';\nexport * from './calculator';\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/add.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type AddParams<TAmount, TCurrency extends string = string> = readonly [\n  augend: Dinero<TAmount, TCurrency>,\n  addend: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeAdd<TAmount>(calculator: DineroCalculator<TAmount>) {\n  return function add<TCurrency extends string>(\n    ...[augend, addend]: AddParams<TAmount, TCurrency>\n  ) {\n    const { amount: augendAmount, currency, scale } = augend.toJSON();\n    const { amount: addendAmount } = addend.toJSON();\n\n    const amount = calculator.add(augendAmount, addendAmount);\n\n    return augend.create({\n      amount,\n      currency,\n      scale,\n    });\n  };\n}\n\nexport function safeAdd<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const addFn = unsafeAdd(calculator);\n\n  return function add<TCurrency extends string>(\n    ...[augend, addend]: AddParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([augend, addend]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [newAugend, newAddend] = normalizeFn([augend, addend]);\n\n    return addFn(newAugend, newAddend);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/allocate.ts",
    "content": "import { INVALID_RATIOS_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero, DineroScaledAmount } from '../types';\nimport {\n  distribute,\n  equal,\n  getAmountAndScale,\n  greaterThan,\n  greaterThanOrEqual,\n  maximum,\n} from '../utils';\n\nimport { transformScale } from './transformScale';\n\ntype UnsafeAllocateParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  ratios: ReadonlyArray<DineroScaledAmount<TAmount>>,\n];\n\nfunction unsafeAllocate<TAmount>(calculator: DineroCalculator<TAmount>) {\n  return function allocate<TCurrency extends string>(\n    ...[dineroObject, ratios]: UnsafeAllocateParams<TAmount, TCurrency>\n  ) {\n    const { amount, currency, scale } = dineroObject.toJSON();\n    const distributeFn = distribute(calculator);\n    const shares = distributeFn(\n      amount,\n      ratios.map((ratio) => ratio.amount)\n    );\n\n    return shares.map((share) => {\n      return dineroObject.create({\n        amount: share,\n        currency,\n        scale,\n      });\n    });\n  };\n}\n\nexport type AllocateParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  ratios: ReadonlyArray<DineroScaledAmount<TAmount> | TAmount>,\n];\n\nexport function safeAllocate<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const allocateFn = unsafeAllocate(calculator);\n  const greaterThanOrEqualFn = greaterThanOrEqual(calculator);\n  const greaterThanFn = greaterThan(calculator);\n  const convertScaleFn = transformScale(calculator);\n  const maximumFn = maximum(calculator);\n  const equalFn = equal(calculator);\n  const zero = calculator.zero();\n  const ten = new Array(10).fill(null).reduce(calculator.increment, zero);\n\n  return function allocate<TCurrency extends string>(\n    ...[dineroObject, ratios]: AllocateParams<TAmount, TCurrency>\n  ) {\n    const hasRatios = ratios.length > 0;\n    const scaledRatios = ratios.map((ratio) => getAmountAndScale(ratio, zero));\n    const highestRatioScale = hasRatios\n      ? maximumFn(scaledRatios.map(({ scale }) => scale))\n      : zero;\n    const normalizedRatios = scaledRatios.map(({ amount, scale }) => {\n      const factor = equalFn(scale, highestRatioScale)\n        ? zero\n        : calculator.subtract(highestRatioScale, scale);\n\n      return {\n        amount: calculator.multiply(amount, calculator.power(ten, factor)),\n        scale,\n      };\n    });\n    const hasOnlyPositiveRatios = normalizedRatios.every(({ amount }) =>\n      greaterThanOrEqualFn(amount, zero)\n    );\n    const hasOneNonZeroRatio = normalizedRatios.some(({ amount }) =>\n      greaterThanFn(amount, zero)\n    );\n\n    const condition = hasRatios && hasOnlyPositiveRatios && hasOneNonZeroRatio;\n    assert(condition, INVALID_RATIOS_MESSAGE);\n\n    const { scale } = dineroObject.toJSON();\n    const newScale = calculator.add(scale, highestRatioScale);\n\n    return allocateFn(convertScaleFn(dineroObject, newScale), normalizedRatios);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/compare.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { compare as cmp } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type CompareParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeCompare<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const compareFn = cmp(calculator);\n\n  return function compare<TCurrency extends string>(\n    ...[dineroObject, comparator]: CompareParams<TAmount, TCurrency>\n  ) {\n    const dineroObjects = [dineroObject, comparator];\n\n    const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {\n      const { amount } = d.toJSON();\n\n      return amount;\n    });\n\n    return compareFn(subjectAmount, comparatorAmount);\n  };\n}\n\nexport function safeCompare<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const compareFn = unsafeCompare(calculator);\n\n  return function compare<TCurrency extends string>(\n    ...[dineroObject, comparator]: CompareParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([dineroObject, comparator]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [subjectAmount, comparatorAmount] = normalizeFn([\n      dineroObject,\n      comparator,\n    ]);\n\n    return compareFn(subjectAmount, comparatorAmount);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/convert.ts",
    "content": "import type { DineroCurrency } from '../../currencies';\n\nimport { MISMATCHED_BASES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero, DineroRates } from '../types';\nimport { computeBase, equal, getAmountAndScale, maximum } from '../utils';\n\nimport { transformScale } from './transformScale';\n\nexport type ConvertParams<\n  TAmount,\n  TCurrency extends string = string,\n  TNewCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  newCurrency: DineroCurrency<TAmount, TNewCurrency>,\n  rates: DineroRates<TAmount>,\n];\n\nexport function convert<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const convertScaleFn = transformScale(calculator);\n  const maximumFn = maximum(calculator);\n  const computeBaseFn = computeBase(calculator);\n  const equalFn = equal(calculator);\n  const zero = calculator.zero();\n\n  return function convertFn<\n    TCurrency extends string,\n    TNewCurrency extends string,\n  >(\n    ...[dineroObject, newCurrency, rates]: ConvertParams<\n      TAmount,\n      TCurrency,\n      TNewCurrency\n    >\n  ) {\n    const rate = rates[newCurrency.code];\n    const { amount, currency, scale } = dineroObject.toJSON();\n\n    const sourceBase = computeBaseFn(currency.base);\n    const targetBase = computeBaseFn(newCurrency.base);\n\n    assert(equalFn(sourceBase, targetBase), MISMATCHED_BASES_MESSAGE);\n\n    const { amount: rateAmount, scale: rateScale } = getAmountAndScale(\n      rate,\n      zero\n    );\n\n    const newScale = calculator.add(scale, rateScale);\n\n    return convertScaleFn(\n      dineroObject.create({\n        amount: calculator.multiply(amount, rateAmount),\n        currency: newCurrency,\n        scale: newScale,\n      }),\n      maximumFn([newScale, newCurrency.exponent])\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/equal.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\n\nimport { haveSameAmount } from './haveSameAmount';\nimport { haveSameCurrency } from './haveSameCurrency';\n\nexport type EqualParams<TAmount, TCurrency extends string = string> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nexport function equal<TAmount>(calculator: DineroCalculator<TAmount>) {\n  return function _equal<TCurrency extends string>(\n    ...[dineroObject, comparator]: EqualParams<TAmount, TCurrency>\n  ) {\n    return (\n      haveSameAmount(calculator)([dineroObject, comparator]) &&\n      haveSameCurrency([dineroObject, comparator])\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/greaterThan.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { greaterThan as gt } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type GreaterThanParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeGreaterThan<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const greaterThanFn = gt(calculator);\n\n  return function greaterThan<TCurrency extends string>(\n    ...[dineroObject, comparator]: GreaterThanParams<TAmount, TCurrency>\n  ) {\n    const dineroObjects = [dineroObject, comparator];\n\n    const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {\n      const { amount } = d.toJSON();\n\n      return amount;\n    });\n\n    return greaterThanFn(subjectAmount, comparatorAmount);\n  };\n}\n\nexport function safeGreaterThan<TAmount>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const normalizeFn = normalizeScale(calculator);\n  const greaterThanFn = unsafeGreaterThan(calculator);\n\n  return function greaterThan<TCurrency extends string>(\n    ...[dineroObject, comparator]: GreaterThanParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([dineroObject, comparator]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [subjectAmount, comparatorAmount] = normalizeFn([\n      dineroObject,\n      comparator,\n    ]);\n\n    return greaterThanFn(subjectAmount, comparatorAmount);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/greaterThanOrEqual.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { greaterThanOrEqual as gte } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type GreaterThanOrEqualParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeGreaterThanOrEqual<TAmount>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const greaterThanOrEqualFn = gte(calculator);\n\n  return function greaterThanOrEqual<TCurrency extends string>(\n    ...[dineroObject, comparator]: GreaterThanOrEqualParams<TAmount, TCurrency>\n  ) {\n    const dineroObjects = [dineroObject, comparator];\n\n    const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {\n      const { amount } = d.toJSON();\n\n      return amount;\n    });\n\n    return greaterThanOrEqualFn(subjectAmount, comparatorAmount);\n  };\n}\n\nexport function safeGreaterThanOrEqual<TAmount>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const normalizeFn = normalizeScale(calculator);\n  const greaterThanOrEqualFn = unsafeGreaterThanOrEqual(calculator);\n\n  return function greaterThanOrEqual<TCurrency extends string>(\n    ...[dineroObject, comparator]: GreaterThanOrEqualParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([dineroObject, comparator]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [subjectAmount, comparatorAmount] = normalizeFn([\n      dineroObject,\n      comparator,\n    ]);\n\n    return greaterThanOrEqualFn(subjectAmount, comparatorAmount);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/hasSubUnits.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { computeBase, equal } from '../utils';\n\nexport type HasSubUnitsParams<TAmount> = readonly [\n  dineroObject: Dinero<TAmount>,\n];\n\nexport function hasSubUnits<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n  const computeBaseFn = computeBase(calculator);\n\n  return function _hasSubUnits(...[dineroObject]: HasSubUnitsParams<TAmount>) {\n    const { amount, currency, scale } = dineroObject.toJSON();\n    const base = computeBaseFn(currency.base);\n\n    return !equalFn(\n      calculator.modulo(amount, calculator.power(base, scale)),\n      calculator.zero()\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/haveSameAmount.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { equal } from '../utils';\n\nimport { normalizeScale } from './normalizeScale';\n\nexport type HaveSameAmountParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObjects: readonly [\n    Dinero<TAmount, TCurrency>,\n    ...Dinero<TAmount, NoInfer<TCurrency>>[],\n  ],\n];\n\nexport function haveSameAmount<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const equalFn = equal(calculator);\n\n  return function _haveSameAmount<TCurrency extends string>(\n    ...[dineroObjects]: HaveSameAmountParams<TAmount, TCurrency>\n  ) {\n    const [firstDinero, ...otherDineros] = normalizeFn(dineroObjects);\n    const { amount: comparatorAmount } = firstDinero.toJSON();\n\n    return otherDineros.every((d) => {\n      const { amount: subjectAmount } = d.toJSON();\n\n      return equalFn(subjectAmount, comparatorAmount);\n    });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/haveSameCurrency.ts",
    "content": "import type { Dinero } from '../types';\nimport { computeBase, equal } from '../utils';\n\nexport function haveSameCurrency<TAmount>(\n  dineroObjects: ReadonlyArray<Dinero<TAmount>>\n) {\n  const [firstDinero, ...otherDineros] = dineroObjects;\n  const computeBaseFn = computeBase(firstDinero.calculator);\n\n  const { currency: comparator } = firstDinero.toJSON();\n  const equalFn = equal(firstDinero.calculator);\n  const comparatorBase = computeBaseFn(comparator.base);\n\n  return otherDineros.every((d) => {\n    const { currency: subject } = d.toJSON();\n    const subjectBase = computeBaseFn(subject.base);\n\n    return (\n      subject.code === comparator.code &&\n      equalFn(subjectBase, comparatorBase) &&\n      equalFn(subject.exponent, comparator.exponent)\n    );\n  });\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/index.ts",
    "content": "export * from './add';\nexport * from './allocate';\nexport * from './compare';\nexport * from './convert';\nexport * from './equal';\nexport * from './greaterThan';\nexport * from './greaterThanOrEqual';\nexport * from './hasSubUnits';\nexport * from './haveSameAmount';\nexport * from './haveSameCurrency';\nexport * from './isNegative';\nexport * from './isPositive';\nexport * from './isZero';\nexport * from './lessThan';\nexport * from './lessThanOrEqual';\nexport * from './maximum';\nexport * from './minimum';\nexport * from './multiply';\nexport * from './normalizeScale';\nexport * from './subtract';\nexport * from './toDecimal';\nexport * from './toSnapshot';\nexport * from './toUnits';\nexport * from './transformScale';\nexport * from './trimScale';\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/isNegative.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { lessThan } from '../utils';\n\nexport type IsNegativeParams<TAmount> = readonly [\n  dineroObject: Dinero<TAmount>,\n];\n\nexport function isNegative<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const lessThanFn = lessThan(calculator);\n\n  return function _isNegative(...[dineroObject]: IsNegativeParams<TAmount>) {\n    const { amount } = dineroObject.toJSON();\n\n    return lessThanFn(amount, calculator.zero());\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/isPositive.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { greaterThan } from '../utils';\n\nexport type IsPositiveParams<TAmount> = readonly [\n  dineroObject: Dinero<TAmount>,\n];\n\nexport function isPositive<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const greaterThanFn = greaterThan(calculator);\n\n  return function _isPositive(...[dineroObject]: IsPositiveParams<TAmount>) {\n    const { amount } = dineroObject.toJSON();\n\n    return greaterThanFn(amount, calculator.zero());\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/isZero.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { equal } from '../utils';\n\nexport type IsZeroParams<TAmount> = readonly [dineroObject: Dinero<TAmount>];\n\nexport function isZero<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n\n  return function _isZero(...[dineroObject]: IsZeroParams<TAmount>) {\n    const { amount } = dineroObject.toJSON();\n\n    return equalFn(amount, calculator.zero());\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/lessThan.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { lessThan as lt } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type LessThanParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeLessThan<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const lessThanFn = lt(calculator);\n\n  return function lessThan<TCurrency extends string>(\n    ...[dineroObject, comparator]: LessThanParams<TAmount, TCurrency>\n  ) {\n    const dineroObjects = [dineroObject, comparator];\n\n    const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {\n      const { amount } = d.toJSON();\n\n      return amount;\n    });\n\n    return lessThanFn(subjectAmount, comparatorAmount);\n  };\n}\n\nexport function safeLessThan<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const lessThanFn = unsafeLessThan(calculator);\n\n  return function lessThan<TCurrency extends string>(\n    ...[dineroObject, comparator]: LessThanParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([dineroObject, comparator]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [subjectAmount, comparatorAmount] = normalizeFn([\n      dineroObject,\n      comparator,\n    ]);\n\n    return lessThanFn(subjectAmount, comparatorAmount);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/lessThanOrEqual.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { lessThanOrEqual as lte } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type LessThanOrEqualParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  comparator: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeLessThanOrEqual<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const lessThanOrEqualFn = lte(calculator);\n\n  return function lessThanOrEqual<TCurrency extends string>(\n    ...[dineroObject, comparator]: LessThanOrEqualParams<TAmount, TCurrency>\n  ) {\n    const dineroObjects = [dineroObject, comparator];\n\n    const [subjectAmount, comparatorAmount] = dineroObjects.map((d) => {\n      const { amount } = d.toJSON();\n\n      return amount;\n    });\n\n    return lessThanOrEqualFn(subjectAmount, comparatorAmount);\n  };\n}\n\nexport function safeLessThanOrEqual<TAmount>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const normalizeFn = normalizeScale(calculator);\n  const lessThanOrEqualFn = unsafeLessThanOrEqual(calculator);\n\n  return function lessThanOrEqual<TCurrency extends string>(\n    ...[dineroObject, comparator]: LessThanOrEqualParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([dineroObject, comparator]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [subjectAmount, comparatorAmount] = normalizeFn([\n      dineroObject,\n      comparator,\n    ]);\n\n    return lessThanOrEqualFn(subjectAmount, comparatorAmount);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/maximum.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { maximum as max } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type MaximumParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObjects: readonly [\n    Dinero<TAmount, TCurrency>,\n    ...Dinero<TAmount, NoInfer<TCurrency>>[],\n  ],\n];\n\nfunction unsafeMaximum<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const maxFn = max(calculator);\n\n  return function maximum<TCurrency extends string>(\n    dineroObjects: ReadonlyArray<Dinero<TAmount, TCurrency>>\n  ) {\n    const [firstDinero] = dineroObjects;\n    const { currency, scale } = firstDinero.toJSON();\n\n    const amount = maxFn(\n      dineroObjects.map((subject) => {\n        const { amount: subjectAmount } = subject.toJSON();\n\n        return subjectAmount;\n      })\n    );\n\n    return firstDinero.create({\n      amount,\n      currency,\n      scale,\n    });\n  };\n}\n\nexport function safeMaximum<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const maxFn = unsafeMaximum(calculator);\n\n  return function maximum<TCurrency extends string>(\n    ...[dineroObjects]: MaximumParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency(dineroObjects);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const normalizedDineroObjects = normalizeFn([...dineroObjects]);\n\n    return maxFn(normalizedDineroObjects);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/minimum.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\nimport { minimum as min } from '../utils';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type MinimumParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObjects: readonly [\n    Dinero<TAmount, TCurrency>,\n    ...Dinero<TAmount, NoInfer<TCurrency>>[],\n  ],\n];\n\nfunction unsafeMinimum<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const minFn = min(calculator);\n\n  return function minimum<TCurrency extends string>(\n    dineroObjects: ReadonlyArray<Dinero<TAmount, TCurrency>>\n  ) {\n    const [firstDinero] = dineroObjects;\n    const { currency, scale } = firstDinero.toJSON();\n\n    const amount = minFn(\n      dineroObjects.map((subject) => {\n        const { amount: subjectAmount } = subject.toJSON();\n\n        return subjectAmount;\n      })\n    );\n\n    return firstDinero.create({\n      amount,\n      currency,\n      scale,\n    });\n  };\n}\n\nexport function safeMinimum<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const minFn = unsafeMinimum(calculator);\n\n  return function minimum<TCurrency extends string>(\n    ...[dineroObjects]: MinimumParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency(dineroObjects);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const normalizedDineroObjects = normalizeFn([...dineroObjects]);\n\n    return minFn(normalizedDineroObjects);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/multiply.ts",
    "content": "import type { DineroCalculator, Dinero, DineroScaledAmount } from '../types';\nimport { getAmountAndScale } from '../utils';\n\nimport { transformScale } from './transformScale';\n\nexport type MultiplyParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  multiplicand: Dinero<TAmount, TCurrency>,\n  multiplier: DineroScaledAmount<TAmount> | TAmount,\n];\n\nexport function multiply<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const convertScaleFn = transformScale(calculator);\n  const zero = calculator.zero();\n\n  return function multiplyFn<TCurrency extends string>(\n    ...[multiplicand, multiplier]: MultiplyParams<TAmount, TCurrency>\n  ) {\n    const { amount, currency, scale } = multiplicand.toJSON();\n    const { amount: multiplierAmount, scale: multiplierScale } =\n      getAmountAndScale(multiplier, zero);\n\n    const newScale = calculator.add(scale, multiplierScale);\n\n    return convertScaleFn(\n      multiplicand.create({\n        amount: calculator.multiply(amount, multiplierAmount),\n        currency,\n        scale: newScale,\n      }),\n      newScale\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/normalizeScale.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { equal, maximum } from '../utils';\n\nimport { transformScale } from './transformScale';\n\nexport type NormalizeScaleParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObjects: readonly [\n    Dinero<TAmount, TCurrency>,\n    ...Dinero<TAmount, NoInfer<TCurrency>>[],\n  ],\n];\n\nexport function normalizeScale<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const maximumFn = maximum(calculator);\n  const convertScaleFn = transformScale(calculator);\n  const equalFn = equal(calculator);\n\n  return function _normalizeScale<TCurrency extends string>(\n    ...[dineroObjects]: NormalizeScaleParams<TAmount, TCurrency>\n  ) {\n    const highestScale = dineroObjects.reduce((highest, current) => {\n      const { scale } = current.toJSON();\n\n      return maximumFn([highest, scale]);\n    }, calculator.zero());\n\n    return dineroObjects.map((d) => {\n      const { scale } = d.toJSON();\n\n      return !equalFn(scale, highestScale)\n        ? convertScaleFn(d, highestScale)\n        : d;\n    });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/subtract.ts",
    "content": "import { UNEQUAL_CURRENCIES_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type { DineroCalculator, Dinero } from '../types';\n\nimport { haveSameCurrency } from './haveSameCurrency';\nimport { normalizeScale } from './normalizeScale';\n\nexport type SubtractParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  minuend: Dinero<TAmount, TCurrency>,\n  subtrahend: Dinero<TAmount, NoInfer<TCurrency>>,\n];\n\nfunction unsafeSubtract<TAmount>(calculator: DineroCalculator<TAmount>) {\n  return function subtract<TCurrency extends string>(\n    ...[minuend, subtrahend]: SubtractParams<TAmount, TCurrency>\n  ) {\n    const { amount: minuendAmount, currency, scale } = minuend.toJSON();\n    const { amount: subtrahendAmount } = subtrahend.toJSON();\n\n    const amount = calculator.subtract(minuendAmount, subtrahendAmount);\n\n    return minuend.create({\n      amount,\n      currency,\n      scale,\n    });\n  };\n}\n\nexport function safeSubtract<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const normalizeFn = normalizeScale(calculator);\n  const subtractFn = unsafeSubtract(calculator);\n\n  return function subtract<TCurrency extends string>(\n    ...[minuend, subtrahend]: SubtractParams<TAmount, TCurrency>\n  ) {\n    const condition = haveSameCurrency([minuend, subtrahend]);\n    assert(condition, UNEQUAL_CURRENCIES_MESSAGE);\n\n    const [newMinuend, newSubtrahend] = normalizeFn([minuend, subtrahend]);\n\n    return subtractFn(newMinuend, newSubtrahend);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/toDecimal.ts",
    "content": "import { NON_DECIMAL_CURRENCY_MESSAGE } from '../checks';\nimport { assert } from '../helpers';\nimport type {\n  DineroCalculator,\n  Dinero,\n  DineroFormatter,\n  DineroTransformer,\n} from '../types';\nimport { absolute, computeBase, equal, isArray, lessThan } from '../utils';\n\nimport { toUnits } from './toUnits';\n\nexport type ToDecimalParams<\n  TAmount,\n  TOutput,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  transformer?: DineroTransformer<TAmount, TOutput, string, TCurrency>,\n];\n\nexport function toDecimal<TAmount, TOutput>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const toUnitsFn = toUnits<TAmount, readonly TAmount[]>(calculator);\n  const computeBaseFn = computeBase(calculator);\n  const equalFn = equal(calculator);\n\n  return function toDecimalFn<TCurrency extends string>(\n    ...[dineroObject, transformer]: ToDecimalParams<TAmount, TOutput, TCurrency>\n  ) {\n    const { currency, scale } = dineroObject.toJSON();\n\n    const base = computeBaseFn(currency.base);\n    const zero = calculator.zero();\n    const ten = new Array(10).fill(null).reduce(calculator.increment, zero);\n\n    const isMultiBase = isArray(currency.base);\n    const isBaseTen = equalFn(calculator.modulo(base, ten), zero);\n    const isDecimal = !isMultiBase && isBaseTen;\n\n    assert(isDecimal, NON_DECIMAL_CURRENCY_MESSAGE);\n\n    const units = toUnitsFn(dineroObject);\n\n    const getDecimalFn = getDecimal(calculator, dineroObject.formatter);\n    const value = getDecimalFn(units, scale);\n\n    if (!transformer) {\n      return value;\n    }\n\n    return transformer({ value, currency });\n  };\n}\n\nfunction getDecimal<TAmount>(\n  calculator: DineroCalculator<TAmount>,\n  formatter: DineroFormatter<TAmount>\n) {\n  const absoluteFn = absolute(calculator);\n  const equalFn = equal(calculator);\n  const lessThanFn = lessThan(calculator);\n  const zero = calculator.zero();\n\n  return (units: readonly TAmount[], scale: TAmount) => {\n    const whole = formatter.toString(units[0]);\n    const fractional = formatter.toString(absoluteFn(units[1]));\n\n    const scaleNumber = formatter.toNumber(scale);\n    const fractionalString =\n      scaleNumber > 0 ? `.${fractional.padStart(scaleNumber, '0')}` : '';\n    const decimal = `${whole}${fractionalString}`;\n\n    const leadsWithZero = equalFn(units[0], zero);\n    const isNegative = lessThanFn(units[1], zero);\n\n    // A leading negative zero is a special case because the `toString`\n    // formatter won't preserve its negative sign (since 0 === -0).\n    return leadsWithZero && isNegative ? `-${decimal}` : decimal;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/toSnapshot.ts",
    "content": "import type { Dinero } from '../types';\n\nexport function toSnapshot<TAmount, TCurrency extends string>(\n  dineroObject: Dinero<TAmount, TCurrency>\n) {\n  return dineroObject.toJSON();\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/toUnits.ts",
    "content": "import type { DineroCalculator, Dinero, DineroTransformer } from '../types';\nimport { isArray, getDivisors } from '../utils';\n\nexport type ToUnitsParams<\n  TAmount,\n  TOutput,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  transformer?: DineroTransformer<\n    TAmount,\n    TOutput,\n    readonly TAmount[],\n    TCurrency\n  >,\n];\n\nexport function toUnits<TAmount, TOutput>(\n  calculator: DineroCalculator<TAmount>\n) {\n  const getDivisorsFn = getDivisors(calculator);\n\n  return function toUnitsFn<TCurrency extends string>(\n    ...[dineroObject, transformer]: ToUnitsParams<TAmount, TOutput, TCurrency>\n  ) {\n    const { amount, currency, scale } = dineroObject.toJSON();\n    const { power, integerDivide, modulo } = calculator;\n\n    const bases = isArray(currency.base) ? currency.base : [currency.base];\n    const divisors = getDivisorsFn(bases.map((base) => power(base, scale)));\n    const value = divisors.reduce<readonly TAmount[]>(\n      (amounts, divisor, index) => {\n        const amountLeft = amounts[index];\n\n        const quotient = integerDivide(amountLeft, divisor);\n        const remainder = modulo(amountLeft, divisor);\n\n        return [...amounts.filter((_, i) => i !== index), quotient, remainder];\n      },\n      [amount]\n    );\n\n    if (!transformer) {\n      return value;\n    }\n\n    return transformer({ value, currency });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/transformScale.ts",
    "content": "import { down } from '../divide';\nimport type { DineroCalculator, Dinero, DineroDivideOperation } from '../types';\nimport { computeBase, greaterThan } from '../utils';\n\nexport type TransformScaleParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [\n  dineroObject: Dinero<TAmount, TCurrency>,\n  newScale: TAmount,\n  divide?: DineroDivideOperation,\n];\n\nexport function transformScale<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const greaterThanFn = greaterThan(calculator);\n  const computeBaseFn = computeBase(calculator);\n\n  return function transformScaleFn<TCurrency extends string>(\n    ...[dineroObject, newScale, divide = down]: TransformScaleParams<\n      TAmount,\n      TCurrency\n    >\n  ) {\n    const { amount, currency, scale } = dineroObject.toJSON();\n\n    const isLarger = greaterThanFn(newScale, scale);\n    const operation = isLarger ? calculator.multiply : divide;\n    const [a, b] = isLarger ? [newScale, scale] : [scale, newScale];\n    const base = computeBaseFn(currency.base);\n\n    const factor = calculator.power(base, calculator.subtract(a, b));\n\n    return dineroObject.create({\n      amount: operation(amount, factor, calculator),\n      currency,\n      scale: newScale,\n    });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/api/trimScale.ts",
    "content": "import type { DineroCalculator, Dinero } from '../types';\nimport { computeBase, countTrailingZeros, equal, maximum } from '../utils';\n\nimport { transformScale } from './transformScale';\n\nexport type TrimScaleParams<\n  TAmount,\n  TCurrency extends string = string,\n> = readonly [dineroObject: Dinero<TAmount, TCurrency>];\n\nexport function trimScale<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const countTrailingZerosFn = countTrailingZeros(calculator);\n  const equalFn = equal(calculator);\n  const maximumFn = maximum(calculator);\n  const transformScaleFn = transformScale(calculator);\n  const computeBaseFn = computeBase(calculator);\n\n  return function trimScaleFn<TCurrency extends string>(\n    ...[dineroObject]: TrimScaleParams<TAmount, TCurrency>\n  ) {\n    const { amount, currency, scale } = dineroObject.toJSON();\n    const base = computeBaseFn(currency.base);\n\n    const trailingZerosLength = countTrailingZerosFn(amount, base);\n    const difference = calculator.subtract(scale, trailingZerosLength);\n    const newScale = maximumFn([difference, currency.exponent]);\n\n    if (equalFn(newScale, scale)) {\n      return dineroObject;\n    }\n\n    return transformScaleFn(dineroObject, newScale);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/checks/index.ts",
    "content": "export * from './messages';\n"
  },
  {
    "path": "packages/dinero.js/src/core/checks/messages.ts",
    "content": "export const INVALID_AMOUNT_MESSAGE = 'Amount is invalid.';\nexport const INVALID_SCALE_MESSAGE = 'Scale is invalid.';\nexport const INVALID_RATIOS_MESSAGE = 'Ratios are invalid.';\nexport const UNEQUAL_SCALES_MESSAGE = 'Objects must have the same scale.';\nexport const UNEQUAL_CURRENCIES_MESSAGE =\n  'Objects must have the same currency.';\nexport const NON_DECIMAL_CURRENCY_MESSAGE = 'Currency is not decimal.';\nexport const MISMATCHED_BASES_MESSAGE =\n  'Objects must have the same currency base.';\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/down.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { down } from '../down';\n\ndescribe('down', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(down(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(down(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(down(0, 10, calculator)).toBe(0);\n    });\n    it('rounds down with a positive half quotient', () => {\n      expect(down(15, 10, calculator)).toBe(1);\n    });\n    it('rounds down with a negative half quotient', () => {\n      expect(down(-15, 10, calculator)).toBe(-2);\n    });\n    it('rounds down with any positive float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 9 }), (a) => {\n          expect(down(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds down with any negative float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -1 }), (a) => {\n          expect(down(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(down(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(down(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(down(0, 5, calculator)).toBe(0);\n    });\n    it('rounds down with a positive half quotient', () => {\n      expect(down(3, 2, calculator)).toBe(1);\n    });\n    it('rounds down with a negative half quotient', () => {\n      expect(down(-3, 2, calculator)).toBe(-2);\n    });\n    it('rounds down with any positive float', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(down(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds down with any negative float', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(down(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfAwayFromZero.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfAwayFromZero } from '../halfAwayFromZero';\n\ndescribe('halfAwayFromZero', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfAwayFromZero(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfAwayFromZero(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfAwayFromZero(0, 10, calculator)).toBe(0);\n    });\n    it('rounds to the nearest integer away from zero with a positive half quotient', () => {\n      expect(halfAwayFromZero(15, 10, calculator)).toBe(2);\n    });\n    it('rounds to the nearest integer away from zero with a negative half quotient', () => {\n      expect(halfAwayFromZero(-25, 10, calculator)).toBe(-3);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfAwayFromZero(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfAwayFromZero(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfAwayFromZero(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfAwayFromZero(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfAwayFromZero(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfAwayFromZero(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfAwayFromZero(0, 5, calculator)).toBe(0);\n    });\n    it('rounds to the nearest integer away from zero with a positive half quotient', () => {\n      expect(halfAwayFromZero(3, 2, calculator)).toBe(2);\n    });\n    it('rounds to the nearest integer away from zero with a negative half quotient', () => {\n      expect(halfAwayFromZero(-5, 2, calculator)).toBe(-3);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfAwayFromZero(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfAwayFromZero(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfAwayFromZero(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfAwayFromZero(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfDown.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfDown } from '../halfDown';\n\ndescribe('halfDown', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfDown(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfDown(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfDown(0, 10, calculator)).toBe(0);\n    });\n    it('rounds down with a positive half quotient', () => {\n      expect(halfDown(15, 10, calculator)).toBe(1);\n    });\n    it('rounds down with a negative half quotient', () => {\n      expect(halfDown(-15, 10, calculator)).toBe(-2);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfDown(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfDown(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfDown(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfDown(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfDown(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfDown(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfDown(0, 5, calculator)).toBe(0);\n    });\n    it('rounds down with a positive quotient below half', () => {\n      expect(halfDown(22, 5, calculator)).toBe(4);\n    });\n    it('rounds up with a negative quotient below half', () => {\n      expect(halfDown(-22, 5, calculator)).toBe(-4);\n    });\n    it('rounds down with a positive half quotient', () => {\n      expect(halfDown(3, 2, calculator)).toBe(1);\n    });\n    it('rounds down with a negative half quotient', () => {\n      expect(halfDown(-3, 2, calculator)).toBe(-2);\n    });\n    it('rounds up with a positive quotient above half', () => {\n      expect(halfDown(24, 5, calculator)).toBe(5);\n    });\n    it('rounds down with a negative quotient above half', () => {\n      expect(halfDown(-24, 5, calculator)).toBe(-5);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfDown(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfDown(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfDown(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfDown(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfEven.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfEven } from '../halfEven';\n\ndescribe('halfEven', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfEven(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfEven(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfEven(0, 10, calculator)).toBe(0);\n    });\n    it('rounds to nearest even integer with a positive half quotient rounding to an even integer', () => {\n      expect(halfEven(15, 10, calculator)).toBe(2);\n    });\n    it('rounds to nearest even integer with a positive half quotient rounding to an odd integer', () => {\n      expect(halfEven(25, 10, calculator)).toBe(2);\n    });\n    it('rounds to nearest even integer with a negative half quotient', () => {\n      expect(halfEven(-25, 10, calculator)).toBe(-2);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfEven(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfEven(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfEven(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfEven(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfEven(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfEven(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfEven(0, 5, calculator)).toBe(0);\n    });\n    it('rounds to nearest even integer with a positive half quotient rounding to an even integer', () => {\n      expect(halfEven(3, 2, calculator)).toBe(2);\n    });\n    it('rounds to nearest even integer with a positive half quotient rounding to an odd integer', () => {\n      expect(halfEven(5, 2, calculator)).toBe(2);\n    });\n    it('rounds to nearest even integer with a negative half quotient', () => {\n      expect(halfEven(-5, 2, calculator)).toBe(-2);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfEven(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfEven(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfEven(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfEven(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfOdd.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfOdd } from '../halfOdd';\n\ndescribe('halfOdd', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfOdd(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfOdd(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfOdd(0, 10, calculator)).toBe(0);\n    });\n    it('rounds to nearest odd integer with a positive half quotient rounding to an even integer', () => {\n      expect(halfOdd(15, 10, calculator)).toBe(1);\n    });\n    it('rounds to nearest odd integer with a positive half quotient rounding to an odd integer', () => {\n      expect(halfOdd(25, 10, calculator)).toBe(3);\n    });\n    it('rounds to nearest odd integer with a negative half quotient', () => {\n      expect(halfOdd(-25, 10, calculator)).toBe(-3);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfOdd(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfOdd(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfOdd(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfOdd(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfOdd(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfOdd(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfOdd(0, 5, calculator)).toBe(0);\n    });\n    it('rounds to nearest odd integer with a positive half quotient rounding to an even integer', () => {\n      expect(halfOdd(3, 2, calculator)).toBe(1);\n    });\n    it('rounds to nearest odd integer with a positive half quotient rounding to an odd integer', () => {\n      expect(halfOdd(5, 2, calculator)).toBe(3);\n    });\n    it('rounds to nearest odd integer with a negative half quotient', () => {\n      expect(halfOdd(-5, 2, calculator)).toBe(-3);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfOdd(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfOdd(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfOdd(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfOdd(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfTowardsZero.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfTowardsZero } from '../halfTowardsZero';\n\ndescribe('halfTowardsZero', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfTowardsZero(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfTowardsZero(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfTowardsZero(0, 10, calculator)).toBe(0);\n    });\n    it('rounds to the nearest integer towards zero with a positive half float', () => {\n      expect(halfTowardsZero(15, 10, calculator)).toBe(1);\n    });\n    it('rounds to the nearest integer towards zero with a negative half float', () => {\n      expect(halfTowardsZero(-25, 10, calculator)).toBe(-2);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfTowardsZero(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfTowardsZero(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfTowardsZero(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfTowardsZero(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfTowardsZero(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfTowardsZero(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfTowardsZero(0, 5, calculator)).toBe(0);\n    });\n    it('rounds to the nearest integer towards zero with a positive half float', () => {\n      expect(halfTowardsZero(3, 2, calculator)).toBe(1);\n    });\n    it('rounds to the nearest integer towards zero with a negative half float', () => {\n      expect(halfTowardsZero(-5, 2, calculator)).toBe(-2);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfTowardsZero(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfTowardsZero(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfTowardsZero(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfTowardsZero(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/halfUp.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { halfUp } from '../halfUp';\n\ndescribe('halfUp', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfUp(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfUp(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfUp(0, 10, calculator)).toBe(0);\n    });\n    it('rounds up with a positive half quotient', () => {\n      expect(halfUp(15, 10, calculator)).toBe(2);\n    });\n    it('rounds up with a negative half quotient', () => {\n      expect(halfUp(-15, 10, calculator)).toBe(-1);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 6, max: 9 }), (a) => {\n          expect(halfUp(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -6 }), (a) => {\n          expect(halfUp(a, 10, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(halfUp(a, 10, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(halfUp(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(halfUp(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(halfUp(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(halfUp(0, 5, calculator)).toBe(0);\n    });\n    it('rounds up with a positive half quotient', () => {\n      expect(halfUp(3, 2, calculator)).toBe(2);\n    });\n    it('rounds up with a negative half quotient', () => {\n      expect(halfUp(-3, 2, calculator)).toBe(-1);\n    });\n    it('rounds up with any positive float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 3, max: 4 }), (a) => {\n          expect(halfUp(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds down with any negative float quotient above half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -3 }), (a) => {\n          expect(halfUp(a, 5, calculator)).toBe(-1);\n        })\n      );\n    });\n    it('rounds down with any positive float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 2 }), (a) => {\n          expect(halfUp(a, 5, calculator)).toBe(0);\n        })\n      );\n    });\n    it('rounds up with any negative float quotient below half', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -2, max: -1 }), (a) => {\n          expect(halfUp(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/__tests__/up.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\nimport * as fc from 'fast-check';\n\nimport { up } from '../up';\n\ndescribe('up', () => {\n  describe('decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(up(20, 10, calculator)).toBe(2);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(up(-20, 10, calculator)).toBe(-2);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(up(0, 10, calculator)).toBe(0);\n    });\n    it('rounds up with a positive half quotient', () => {\n      expect(up(15, 10, calculator)).toBe(2);\n    });\n    it('rounds up with a negative half quotient', () => {\n      expect(up(-15, 10, calculator)).toBe(-1);\n    });\n    it('rounds up with any positive float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 9 }), (a) => {\n          expect(up(a, 10, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds up with any negative float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -9, max: -1 }), (a) => {\n          expect(up(a, 10, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n  describe('non-decimal factors', () => {\n    it('does not round with a positive integer quotient', () => {\n      expect(up(20, 5, calculator)).toBe(4);\n    });\n    it('does not round with a negative integer quotient', () => {\n      expect(up(-20, 5, calculator)).toBe(-4);\n    });\n    it('does not round with a zero quotient', () => {\n      expect(up(0, 5, calculator)).toBe(0);\n    });\n    it('rounds up with a positive half quotient', () => {\n      expect(up(3, 2, calculator)).toBe(2);\n    });\n    it('rounds up with a negative half quotient', () => {\n      expect(up(-3, 2, calculator)).toBe(-1);\n    });\n    it('rounds up with any positive float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: 1, max: 4 }), (a) => {\n          expect(up(a, 5, calculator)).toBe(1);\n        })\n      );\n    });\n    it('rounds up with any negative float quotient', () => {\n      fc.assert(\n        fc.property(fc.integer({ min: -4, max: -1 }), (a) => {\n          expect(up(a, 5, calculator)).toBe(-0);\n        })\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/down.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { equal, greaterThan } from '../utils';\n\n/**\n * Divide and round down.\n *\n * Rounding down happens whenever the quotient is not an integer.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const down: DineroDivideOperation = (amount, factor, calculator) => {\n  const greaterThanFn = greaterThan(calculator);\n  const equalFn = equal(calculator);\n\n  const zero = calculator.zero();\n  const isPositive = greaterThanFn(amount, zero);\n  const quotient = calculator.integerDivide(amount, factor);\n  const remainder = calculator.modulo(amount, factor);\n  const isInteger = equalFn(remainder, zero);\n\n  if (isPositive || isInteger) {\n    return quotient;\n  }\n\n  return calculator.decrement(quotient);\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfAwayFromZero.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { sign, isHalf, absolute } from '../utils';\n\nimport { halfUp, up } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round away from zero.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfAwayFromZero: DineroDivideOperation = (\n  amount,\n  factor,\n  calculator\n) => {\n  const signFn = sign(calculator);\n  const isHalfFn = isHalf(calculator);\n  const absoluteFn = absolute(calculator);\n\n  if (!isHalfFn(amount, factor)) {\n    return halfUp(amount, factor, calculator);\n  }\n\n  return calculator.multiply(\n    signFn(amount),\n    up(absoluteFn(amount), factor, calculator)\n  );\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfDown.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { isHalf } from '../utils';\n\nimport { down, halfUp } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round down.\n *\n * Rounding down happens when:\n * - The quotient is half (e.g., -1.5, 1.5).\n * - The quotient is positive and less than half (e.g., 1.4).\n * - The quotient is negative and greater than half (e.g., -1.6).\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfDown: DineroDivideOperation = (amount, factor, calculator) => {\n  const isHalfFn = isHalf(calculator);\n\n  if (isHalfFn(amount, factor)) {\n    return down(amount, factor, calculator);\n  }\n\n  return halfUp(amount, factor, calculator);\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfEven.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { isEven, isHalf } from '../utils';\n\nimport { halfUp } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round to the nearest even integer.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfEven: DineroDivideOperation = (amount, factor, calculator) => {\n  const isEvenFn = isEven(calculator);\n  const isHalfFn = isHalf(calculator);\n\n  const rounded = halfUp(amount, factor, calculator);\n\n  if (!isHalfFn(amount, factor)) {\n    return rounded;\n  }\n\n  return isEvenFn(rounded) ? rounded : calculator.decrement(rounded);\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfOdd.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { isEven, isHalf } from '../utils';\n\nimport { halfUp } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round to the nearest odd integer.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfOdd: DineroDivideOperation = (amount, factor, calculator) => {\n  const isEvenFn = isEven(calculator);\n  const isHalfFn = isHalf(calculator);\n\n  const rounded = halfUp(amount, factor, calculator);\n\n  if (!isHalfFn(amount, factor)) {\n    return rounded;\n  }\n\n  return isEvenFn(rounded) ? calculator.decrement(rounded) : rounded;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfTowardsZero.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { sign, isHalf, absolute } from '../utils';\n\nimport { halfUp, down } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round towards zero.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfTowardsZero: DineroDivideOperation = (\n  amount,\n  factor,\n  calculator\n) => {\n  const signFn = sign(calculator);\n  const isHalfFn = isHalf(calculator);\n  const absoluteFn = absolute(calculator);\n\n  if (!isHalfFn(amount, factor)) {\n    return halfUp(amount, factor, calculator);\n  }\n\n  return calculator.multiply(\n    signFn(amount),\n    down(absoluteFn(amount), factor, calculator)\n  );\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/halfUp.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { absolute, greaterThan, isHalf } from '../utils';\n\nimport { down, up } from '.';\n\n/**\n * Divide and round towards \"nearest neighbor\" unless both neighbors are\n * equidistant, in which case round up.\n *\n * Rounding up happens when:\n * - The quotient is half (e.g., -1.5, 1.5).\n * - The quotient is positive and greater than half (e.g., 1.6).\n * - The quotient is negative and less than half (e.g., -1.4).\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const halfUp: DineroDivideOperation = (amount, factor, calculator) => {\n  const greaterThanFn = greaterThan(calculator);\n  const isHalfFn = isHalf(calculator);\n  const absoluteFn = absolute(calculator);\n\n  const zero = calculator.zero();\n  const remainder = absoluteFn(calculator.modulo(amount, factor));\n  const difference = calculator.subtract(factor, remainder);\n  const isLessThanHalf = greaterThanFn(difference, remainder);\n  const isPositive = greaterThanFn(amount, zero);\n\n  if (\n    isHalfFn(amount, factor) ||\n    (isPositive && !isLessThanHalf) ||\n    (!isPositive && isLessThanHalf)\n  ) {\n    return up(amount, factor, calculator);\n  }\n\n  return down(amount, factor, calculator);\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/index.ts",
    "content": "export * from './down';\nexport * from './halfAwayFromZero';\nexport * from './halfDown';\nexport * from './halfEven';\nexport * from './halfOdd';\nexport * from './halfTowardsZero';\nexport * from './halfUp';\nexport * from './up';\n"
  },
  {
    "path": "packages/dinero.js/src/core/divide/up.ts",
    "content": "import type { DineroDivideOperation } from '..';\nimport { equal, greaterThan } from '../utils';\n\n/**\n * Divide and round up.\n *\n * Rounding up happens whenever the quotient is not an integer.\n *\n * @param amount - The amount to divide.\n * @param factor - The factor to divide by.\n * @param calculator - The calculator to use.\n *\n * @returns The rounded amount.\n */\nexport const up: DineroDivideOperation = (amount, factor, calculator) => {\n  const greaterThanFn = greaterThan(calculator);\n  const equalFn = equal(calculator);\n\n  const zero = calculator.zero();\n  const isPositive = greaterThanFn(amount, zero);\n  const quotient = calculator.integerDivide(amount, factor);\n  const remainder = calculator.modulo(amount, factor);\n  const isInteger = equalFn(remainder, zero);\n\n  if (!isInteger && isPositive) {\n    return calculator.increment(quotient);\n  }\n\n  return quotient;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/helpers/__tests__/assert.test.ts",
    "content": "import { assert } from '../assert';\n\nconst errorMessage = 'Some error message.';\n\ndescribe('assert', () => {\n  it(\"doesn't throw when the condition is met\", () => {\n    expect(() => assert(true, errorMessage)).not.toThrow(\n      new Error('[Dinero.js] Some error message.')\n    );\n  });\n  it(\"throws when the condition isn't met\", () => {\n    expect(() => assert(false, errorMessage)).toThrow(\n      new Error('[Dinero.js] Some error message.')\n    );\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/helpers/assert.ts",
    "content": "/**\n * Assert a condition.\n *\n * @param condition - The condition to verify.\n * @param message - The error message to throw.\n *\n * @throws If the condition isn't met.\n */\nexport function assert(condition: boolean, message: string) {\n  if (!condition) {\n    throw new Error(`[Dinero.js] ${message}`);\n  }\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/helpers/createDinero.ts",
    "content": "import type { DineroCurrency } from '../../currencies';\n\nimport type {\n  DineroCalculator,\n  Dinero,\n  DineroOptions,\n  DineroFormatter,\n} from '../types';\n\nexport type CreateDineroOptions<TAmount> = {\n  readonly calculator: DineroCalculator<TAmount>;\n  readonly formatter?: DineroFormatter<TAmount>;\n  readonly onCreate?: (options: DineroOptions<TAmount, string>) => void;\n};\n\nexport function createDinero<TAmount>({\n  calculator,\n  onCreate,\n  formatter = {\n    toNumber: Number,\n    toString: String,\n  },\n}: CreateDineroOptions<TAmount>) {\n  return function dinero<TCurrency extends string>({\n    amount,\n    currency: { code, base, exponent },\n    scale = exponent,\n  }: DineroOptions<TAmount, TCurrency>): Dinero<TAmount, TCurrency> {\n    const currency = { code, base, exponent } as DineroCurrency<\n      TAmount,\n      TCurrency\n    >;\n\n    onCreate?.({ amount, currency, scale });\n\n    return {\n      calculator,\n      formatter,\n      create: dinero,\n      toJSON() {\n        return {\n          amount,\n          currency,\n          scale,\n        };\n      },\n    };\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/helpers/index.ts",
    "content": "export * from './assert';\nexport * from './createDinero';\n"
  },
  {
    "path": "packages/dinero.js/src/core/index.ts",
    "content": "export * from './api';\nexport * from './checks';\nexport * from './helpers';\nexport * from './divide';\nexport * from './types';\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/Dinero.ts",
    "content": "import type {\n  DineroCalculator,\n  DineroFormatter,\n  DineroOptions,\n  DineroSnapshot,\n} from '.';\n\nexport type Dinero<TAmount, TCurrency extends string = string> = {\n  readonly calculator: DineroCalculator<TAmount>;\n  readonly formatter: DineroFormatter<TAmount>;\n  readonly create: <TC extends string = TCurrency>(\n    options: DineroOptions<TAmount, TC>\n  ) => Dinero<TAmount, TC>;\n  readonly toJSON: () => DineroSnapshot<TAmount, TCurrency>;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroBinaryOperation.ts",
    "content": "export type DineroBinaryOperation<TInput, TOutput = TInput> = (\n  a: TInput,\n  b: TInput\n) => TOutput;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroCalculator.ts",
    "content": "import type { DineroBinaryOperation, DineroUnaryOperation } from '.';\n\nexport enum DineroComparisonOperator {\n  LT = -1,\n  EQ = 0,\n  GT = 1,\n}\n\nexport type DineroCalculator<TInput> = {\n  readonly add: DineroBinaryOperation<TInput>;\n  readonly compare: DineroBinaryOperation<TInput, DineroComparisonOperator>;\n  readonly decrement: DineroUnaryOperation<TInput>;\n  readonly integerDivide: DineroBinaryOperation<TInput>;\n  readonly increment: DineroUnaryOperation<TInput>;\n  readonly modulo: DineroBinaryOperation<TInput>;\n  readonly multiply: DineroBinaryOperation<TInput>;\n  readonly power: DineroBinaryOperation<TInput>;\n  readonly subtract: DineroBinaryOperation<TInput>;\n  readonly zero: () => TInput;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroDivideOperation.ts",
    "content": "import type { DineroCalculator } from '.';\n\nexport type DineroDivideOperation = <TAmount>(\n  amount: TAmount,\n  factor: TAmount,\n  calculator: DineroCalculator<TAmount>\n) => TAmount;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroFactory.ts",
    "content": "import type { Dinero, DineroOptions } from '.';\n\nexport type DineroFactory<TAmount> = <TCurrency extends string>({\n  amount,\n  currency,\n  scale,\n}: DineroOptions<TAmount, TCurrency>) => Dinero<TAmount, TCurrency>;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroFormatter.ts",
    "content": "export type DineroFormatter<TAmount> = {\n  readonly toNumber: (value?: TAmount) => number;\n  readonly toString: (value?: TAmount) => string;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroOptions.ts",
    "content": "import type { DineroCurrency } from '../../currencies';\n\nexport type DineroOptions<TAmount, TCurrency extends string = string> = {\n  readonly amount: TAmount;\n  readonly currency: DineroCurrency<TAmount, TCurrency>;\n  readonly scale?: TAmount;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroRates.ts",
    "content": "import type { DineroScaledAmount } from './DineroScaledAmount';\n\nexport type DineroRate<TAmount> = DineroScaledAmount<TAmount> | TAmount;\n\nexport type DineroRates<TAmount> = Record<string, DineroRate<TAmount>>;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroScaledAmount.ts",
    "content": "export type DineroScaledAmount<TAmount> = {\n  readonly amount: TAmount;\n  readonly scale?: TAmount;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroSnapshot.ts",
    "content": "import type { DineroCurrency } from '../../currencies';\n\nexport type DineroSnapshot<TAmount, TCurrency extends string = string> = {\n  readonly amount: TAmount;\n  readonly currency: DineroCurrency<TAmount, TCurrency>;\n  readonly scale: TAmount;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroTransformer.ts",
    "content": "import type { DineroCurrency } from '../../currencies';\n\nexport type TransformerOptions<\n  TAmount,\n  TValue,\n  TCurrency extends string = string,\n> = {\n  readonly value: TValue;\n  readonly currency: DineroCurrency<TAmount, TCurrency>;\n};\n\nexport type DineroTransformer<\n  TAmount,\n  TOutput,\n  TValue,\n  TCurrency extends string = string,\n> = (options: TransformerOptions<TAmount, TValue, TCurrency>) => TOutput;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/DineroUnaryOperation.ts",
    "content": "export type DineroUnaryOperation<TInput, TOutput = TInput> = (\n  value: TInput\n) => TOutput;\n"
  },
  {
    "path": "packages/dinero.js/src/core/types/index.ts",
    "content": "export * from './DineroBinaryOperation';\nexport * from './DineroCalculator';\nexport * from './Dinero';\nexport * from './DineroFactory';\nexport * from './DineroOptions';\nexport * from './DineroSnapshot';\nexport * from './DineroDivideOperation';\nexport * from './DineroFormatter';\nexport * from './DineroRates';\nexport * from './DineroScaledAmount';\nexport * from './DineroTransformer';\nexport * from './DineroUnaryOperation';\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/absolute.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { absolute } from '../absolute';\n\nconst absoluteFn = absolute(calculator);\n\ndescribe('absolute', () => {\n  it('returns the value with positive values', () => {\n    expect(absoluteFn(5)).toBe(5);\n  });\n  it('returns the negation of the value with negative values', () => {\n    expect(absoluteFn(-5)).toBe(5);\n  });\n  it('returns the value with positive zero', () => {\n    expect(absoluteFn(0)).toBe(0);\n  });\n  it('returns the negation of the value with negative zero', () => {\n    expect(absoluteFn(-0)).toBe(0);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/compare.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { compare } from '../compare';\n\nconst compareFn = compare(calculator);\n\ndescribe('compare', () => {\n  describe('inferiority', () => {\n    it('returns -1 when the first number is less than the other with positive numbers', () => {\n      expect(compareFn(1, 2)).toBe(-1);\n    });\n    it('returns -1 when the first number is less than the other with negative numbers', () => {\n      expect(compareFn(-3, -2)).toBe(-1);\n    });\n    it('returns -1 when the first number is less than the other with floats', () => {\n      expect(compareFn(1.2, 2.2)).toBe(-1);\n    });\n    it('returns -1 when the first number is less than the other with numbers in scientific notation', () => {\n      expect(compareFn(2e5, 3e5)).toBe(-1);\n    });\n  });\n  describe('equality', () => {\n    it('returns 0 when the first number is equal to the other with positive numbers', () => {\n      expect(compareFn(4, 4)).toBe(0);\n    });\n    it('returns 0 when the first number is equal to the other with negative numbers', () => {\n      expect(compareFn(-2, -2)).toBe(0);\n    });\n    it('returns 0 when the first number is equal to the other with floats', () => {\n      expect(compareFn(3.2, 3.2)).toBe(0);\n    });\n    it('returns 0 when the first number is equal to the other with numbers in scientific notation', () => {\n      expect(compareFn(3e5, 3e5)).toBe(0);\n    });\n  });\n  describe('superiority', () => {\n    it('returns 1 when the first number is greater than the other with positive numbers', () => {\n      expect(compareFn(4, 3)).toBe(1);\n    });\n    it('returns 1 when the first number is greater than the other with negative numbers', () => {\n      expect(compareFn(-2, -3)).toBe(1);\n    });\n    it('returns 1 when the first number is greater than the other with floats', () => {\n      expect(compareFn(3.2, 2.2)).toBe(1);\n    });\n    it('returns 1 when the first number is greater than the other with numbers in scientific notation', () => {\n      expect(compareFn(3e5, 2e5)).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/computeBase.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { computeBase as createComputeBase } from '../computeBase';\n\nconst computeBase = createComputeBase(calculator);\n\ndescribe('computeBase', () => {\n  it('returns non-array values as is', () => {\n    expect(computeBase(100)).toBe(100);\n  });\n  it('computes array values', () => {\n    expect(computeBase([20, 12, 7])).toBe(1680);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/countTrailingZeros.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { countTrailingZeros } from '../countTrailingZeros';\n\nconst countTrailingZerosFn = countTrailingZeros(calculator);\n\ndescribe('countTrailingZeros', () => {\n  it('counts trailing zeros from a positive integer', () => {\n    expect(countTrailingZerosFn(1000, 10)).toBe(3);\n  });\n  it('counts trailing zeros from a negative integer', () => {\n    expect(countTrailingZerosFn(-1000, 10)).toBe(3);\n  });\n  it('counts trailing zeros from a positive integer in scientific notation', () => {\n    expect(countTrailingZerosFn(1e3, 10)).toBe(3);\n  });\n  it('counts trailing zeros from a negative integer in scientific notation', () => {\n    expect(countTrailingZerosFn(-1e3, 10)).toBe(3);\n  });\n  it(\"doesn't retrieve trailing zeros when there are none\", () => {\n    expect(countTrailingZerosFn(123, 10)).toBe(0);\n  });\n  it(\"doesn't retrieve trailing zeros from floats\", () => {\n    expect(countTrailingZerosFn(12.5, 10)).toBe(0);\n  });\n  it('correctly handles zero inputs', () => {\n    expect(countTrailingZerosFn(0, 10)).toBe(0);\n    expect(countTrailingZerosFn(0, 2)).toBe(0);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/distribute.test.ts",
    "content": "import * as fc from 'fast-check';\n\nimport { calculator } from '../../../calculator/number';\n\nimport { distribute } from '../distribute';\n\nconst distributeFn = distribute(calculator);\n\ndescribe('distribute', () => {\n  it('distributes to percentages', () => {\n    expect(distributeFn(1003, [50, 50])).toEqual([502, 501]);\n  });\n  it('distributes to ratios', () => {\n    expect(distributeFn(100, [1, 3])).toEqual([25, 75]);\n  });\n  it('distributes negative amounts', () => {\n    expect(distributeFn(-1003, [50, 50])).toEqual([-502, -501]);\n  });\n  it('distributes while ignoring zero ratios', () => {\n    expect(distributeFn(1003, [0, 50, 50])).toEqual([0, 502, 501]);\n  });\n  it('distributes to zero ratios', () => {\n    expect(distributeFn(1003, [0, 0])).toEqual([0, 0]);\n  });\n  it('distributes to negative ratios', () => {\n    expect(distributeFn(1003, [-50, -50])).toEqual([502, 501]);\n  });\n  it('distributes negative amounts to negative ratios', () => {\n    expect(distributeFn(-1003, [-50, -50])).toEqual([-502, -501]);\n  });\n  it('distributes to empty ratios', () => {\n    expect(distributeFn(1003, [])).toEqual([]);\n  });\n  it('distributes remainder to largest ratio first', () => {\n    expect(distributeFn(5, [100, 101, 100, 100])).toEqual([1, 2, 1, 1]);\n  });\n  it('distributes remainder to multiple largest ratios in order', () => {\n    expect(distributeFn(801, [1, 3])).toEqual([200, 601]);\n  });\n  it('does not hang with amounts larger than `MAX_SAFE_INTEGER`', () => {\n    const largeAmount = 337582417582417600000; // > Number.MAX_SAFE_INTEGER\n    const result = distributeFn(largeAmount, [50, 50]);\n\n    // We don't assert exact values since precision is lost,\n    // but the function should return without hanging\n    expect(result).toHaveLength(2);\n    expect(result[0] + result[1]).toBe(largeAmount);\n  });\n  describe('properties', () => {\n    const safeAmount = fc.integer({ min: -100000, max: 100000 });\n    const positiveRatios = fc.array(fc.integer({ min: 1, max: 100 }), {\n      minLength: 1,\n      maxLength: 10,\n    });\n\n    it('preserves the total: sum of shares equals the input', () => {\n      fc.assert(\n        fc.property(safeAmount, positiveRatios, (amount, ratios) => {\n          const shares = distributeFn(amount, ratios);\n\n          expect(shares.reduce((a, b) => a + b, 0)).toBe(amount);\n        })\n      );\n    });\n    it('distributes non-negatively for positive amounts', () => {\n      fc.assert(\n        fc.property(\n          fc.integer({ min: 0, max: 100000 }),\n          positiveRatios,\n          (amount, ratios) => {\n            const shares = distributeFn(amount, ratios);\n\n            shares.forEach((share) => {\n              expect(share).toBeGreaterThanOrEqual(0);\n            });\n          }\n        )\n      );\n    });\n    it('distributes non-positively for negative amounts', () => {\n      fc.assert(\n        fc.property(\n          fc.integer({ min: -100000, max: 0 }),\n          positiveRatios,\n          (amount, ratios) => {\n            const shares = distributeFn(amount, ratios);\n\n            shares.forEach((share) => {\n              expect(share).toBeLessThanOrEqual(0);\n            });\n          }\n        )\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/equal.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { equal } from '../equal';\n\nconst equalFn = equal(calculator);\n\ndescribe('equal', () => {\n  it('returns true with equal positive numbers', () => {\n    expect(equalFn(2, 2)).toBe(true);\n  });\n  it('returns true with equal negative numbers', () => {\n    expect(equalFn(-2, -2)).toBe(true);\n  });\n  it('returns true with equal floats numbers', () => {\n    expect(equalFn(1.2, 1.2)).toBe(true);\n  });\n  it('returns true with equal numbers in scientific notation', () => {\n    expect(equalFn(1e5, 1e5)).toBe(true);\n  });\n  it('returns false with unequal positive numbers', () => {\n    expect(equalFn(2, 3)).toBe(false);\n  });\n  it('returns false with unequal negative numbers', () => {\n    expect(equalFn(-2, -3)).toBe(false);\n  });\n  it('returns false with unequal floats numbers', () => {\n    expect(equalFn(1.2, 1.3)).toBe(false);\n  });\n  it('returns false with unequal numbers in scientific notation', () => {\n    expect(equalFn(1e5, 2e5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/getAmountAndScale.test.ts",
    "content": "import { getAmountAndScale } from '../getAmountAndScale';\n\ndescribe('getAmountAndScale', () => {\n  it('returns the amount and scale with scaled amounts', () => {\n    expect(getAmountAndScale({ amount: 100, scale: 2 }, 0)).toEqual({\n      amount: 100,\n      scale: 2,\n    });\n  });\n  it('returns a zero scale when unspecified', () => {\n    expect(getAmountAndScale({ amount: 100 }, 0)).toEqual({\n      amount: 100,\n      scale: 0,\n    });\n  });\n  it('returns the amount and a zero scale with amounts', () => {\n    expect(getAmountAndScale(100, 0)).toEqual({\n      amount: 100,\n      scale: 0,\n    });\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/getDivisors.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { getDivisors } from '../getDivisors';\n\nconst getDivisorsFn = getDivisors(calculator);\n\ndescribe('#getDivisors', () => {\n  it('returns the same divisor with one base', () => {\n    expect(getDivisorsFn([100])).toEqual([100]);\n  });\n  it('recursively computes divisors with two bases', () => {\n    expect(getDivisorsFn([20, 12])).toEqual([240, 12]);\n  });\n  it('recursively computes divisors with more than two bases', () => {\n    expect(getDivisorsFn([20, 12, 7])).toEqual([1680, 84, 7]);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/greaterThan.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { greaterThan } from '../greaterThan';\n\nconst greaterThanFn = greaterThan(calculator);\n\ndescribe('greaterThan', () => {\n  it('returns true when the first number is greater than the other with positive numbers', () => {\n    expect(greaterThanFn(4, 3)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with negative numbers', () => {\n    expect(greaterThanFn(-2, -3)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with floats', () => {\n    expect(greaterThanFn(2.2, 1.2)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with numbers in scientific notation', () => {\n    expect(greaterThanFn(2e5, 1e5)).toBe(true);\n  });\n  it('returns false when the first number is less than the other with positive numbers', () => {\n    expect(greaterThanFn(1, 2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with negative numbers', () => {\n    expect(greaterThanFn(-3, -2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with floats', () => {\n    expect(greaterThanFn(1.2, 2.2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with numbers in scientific notation', () => {\n    expect(greaterThanFn(1e5, 2e5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/greaterThanOrEqual.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { greaterThanOrEqual } from '../greaterThanOrEqual';\n\nconst greaterThanOrEqualFn = greaterThanOrEqual(calculator);\n\ndescribe('greaterThanOrEqual', () => {\n  it('returns true when the first number is greater than the other with positive numbers', () => {\n    expect(greaterThanOrEqualFn(4, 3)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with negative numbers', () => {\n    expect(greaterThanOrEqualFn(-2, -3)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with floats', () => {\n    expect(greaterThanOrEqualFn(2.2, 1.2)).toBe(true);\n  });\n  it('returns true when the first number is greater than the other with numbers in scientific notation', () => {\n    expect(greaterThanOrEqualFn(2e5, 1e5)).toBe(true);\n  });\n  it('returns true with equal positive numbers', () => {\n    expect(greaterThanOrEqualFn(2, 2)).toBe(true);\n  });\n  it('returns true with equal negative numbers', () => {\n    expect(greaterThanOrEqualFn(-2, -2)).toBe(true);\n  });\n  it('returns true with equal floats', () => {\n    expect(greaterThanOrEqualFn(2.2, 2.2)).toBe(true);\n  });\n  it('returns true with equal numbers in scientific notation', () => {\n    expect(greaterThanOrEqualFn(2e5, 2e5)).toBe(true);\n  });\n  it('returns false when the first number is less than the other with positive numbers', () => {\n    expect(greaterThanOrEqualFn(1, 2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with negative numbers', () => {\n    expect(greaterThanOrEqualFn(-3, -2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with floats', () => {\n    expect(greaterThanOrEqualFn(0.2, 1.2)).toBe(false);\n  });\n  it('returns false when the first number is less than the other with numbers in scientific notation', () => {\n    expect(greaterThanOrEqualFn(1e5, 2e5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/isArray.test.ts",
    "content": "import { isArray } from '..';\n\ndescribe('isArray', () => {\n  it('returns true with arrays', () => {\n    expect(isArray([])).toBe(true);\n  });\n  it('returns false with numbers', () => {\n    expect(isArray(5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/isEven.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { isEven } from '../isEven';\n\nconst isEvenFn = isEven(calculator);\n\ndescribe('isEven', () => {\n  it('returns true for a positive even integer', () => {\n    expect(isEvenFn(202)).toBe(true);\n  });\n  it('returns true for a negative even integer', () => {\n    expect(isEvenFn(-202)).toBe(true);\n  });\n  it('returns false for a positive odd integer', () => {\n    expect(isEvenFn(101)).toBe(false);\n  });\n  it('returns false for a negative odd integer', () => {\n    expect(isEvenFn(-101)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/isHalf.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { isHalf } from '../isHalf';\n\nconst isHalfFn = isHalf(calculator);\n\ndescribe('isHalf', () => {\n  it('returns true with a half number', () => {\n    expect(isHalfFn(5, 10)).toBe(true);\n  });\n  it('returns true with a negative half number', () => {\n    expect(isHalfFn(-5, 10)).toBe(true);\n  });\n  it('returns false with a non-half number', () => {\n    expect(isHalfFn(2, 10)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/isScaledAmount.test.ts",
    "content": "import { isScaledAmount } from '../isScaledAmount';\n\ndescribe('isScaledAmount', () => {\n  it('returns false with an integer', () => {\n    expect(isScaledAmount(100)).toBe(false);\n  });\n  it('returns true with a scaled amount', () => {\n    expect(isScaledAmount({ amount: 100, scale: 0 })).toBe(true);\n  });\n  it('returns true with a scaled amount without a scale', () => {\n    expect(isScaledAmount({ amount: 100 })).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/lessThan.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { lessThan } from '../lessThan';\n\nconst lessThanFn = lessThan(calculator);\n\ndescribe('lessThan', () => {\n  it('returns true when the first number is less than the other with positive numbers', () => {\n    expect(lessThanFn(1, 2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with negative numbers', () => {\n    expect(lessThanFn(-3, -2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with floats', () => {\n    expect(lessThanFn(1.2, 2.2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with numbers in scientific notation', () => {\n    expect(lessThanFn(2e5, 3e5)).toBe(true);\n  });\n  it('returns false when the first number is greater than the other with positive numbers', () => {\n    expect(lessThanFn(4, 3)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with negative numbers', () => {\n    expect(lessThanFn(-2, -3)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with floats', () => {\n    expect(lessThanFn(3.2, 2.2)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with numbers in scientific notation', () => {\n    expect(lessThanFn(3e5, 2e5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/lessThanOrEqual.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { lessThanOrEqual } from '../lessThanOrEqual';\n\nconst lessThanOrEqualFn = lessThanOrEqual(calculator);\n\ndescribe('lessThanOrEqual', () => {\n  it('returns true when the first number is less than the other with positive numbers', () => {\n    expect(lessThanOrEqualFn(1, 2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with negative numbers', () => {\n    expect(lessThanOrEqualFn(-3, -2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with floats', () => {\n    expect(lessThanOrEqualFn(1.2, 2.2)).toBe(true);\n  });\n  it('returns true when the first number is less than the other with numbers in scientific notation', () => {\n    expect(lessThanOrEqualFn(2e5, 3e5)).toBe(true);\n  });\n  it('returns true with equal positive numbers', () => {\n    expect(lessThanOrEqualFn(2, 2)).toBe(true);\n  });\n  it('returns true with equal negative numbers', () => {\n    expect(lessThanOrEqualFn(-2, -2)).toBe(true);\n  });\n  it('returns true with equal floats', () => {\n    expect(lessThanOrEqualFn(2.2, 2.2)).toBe(true);\n  });\n  it('returns true with equal numbers in scientific notation', () => {\n    expect(lessThanOrEqualFn(2e5, 2e5)).toBe(true);\n  });\n  it('returns false when the first number is greater than the other with positive numbers', () => {\n    expect(lessThanOrEqualFn(4, 3)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with negative numbers', () => {\n    expect(lessThanOrEqualFn(-2, -3)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with floats', () => {\n    expect(lessThanOrEqualFn(3.2, 2.2)).toBe(false);\n  });\n  it('returns false when the first number is greater than the other with numbers in scientific notation', () => {\n    expect(lessThanOrEqualFn(3e5, 2e5)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/maximum.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { maximum } from '../maximum';\n\nconst maximumFn = maximum(calculator);\n\ndescribe('maximum', () => {\n  it('gets the greatest from positive numbers', () => {\n    expect(maximumFn([5, 3, 2])).toBe(5);\n  });\n  it('gets the greatest from negative numbers', () => {\n    expect(maximumFn([-5, -4, -2])).toBe(-2);\n  });\n  it('gets the greatest from floats', () => {\n    expect(maximumFn([10.5, 2.5, 1.6])).toBe(10.5);\n  });\n  it('gets the greatest from numbers in scientific notation', () => {\n    expect(maximumFn([4e5, 3e5, 2e5])).toBe(4e5);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/minimum.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { minimum } from '../minimum';\n\nconst minimumFn = minimum(calculator);\n\ndescribe('minimum', () => {\n  it('gets the lowest from positive numbers', () => {\n    expect(minimumFn([5, 3, 2])).toBe(2);\n  });\n  it('gets the lowest from negative numbers', () => {\n    expect(minimumFn([-5, -4, -2])).toBe(-5);\n  });\n  it('gets the lowest from floats', () => {\n    expect(minimumFn([10.5, 2.5, 1.6])).toBe(1.6);\n  });\n  it('gets the lowest from numbers in scientific notation', () => {\n    expect(minimumFn([4e5, 3e5, 2e5])).toBe(2e5);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/__tests__/sign.test.ts",
    "content": "import { calculator } from '../../../calculator/number';\n\nimport { sign } from '../sign';\n\nconst signFn = sign(calculator);\n\ndescribe('sign', () => {\n  it('returns 0 with positive zero', () => {\n    expect(signFn(0)).toBe(0);\n  });\n  it('returns 0 with negative zero', () => {\n    expect(signFn(-0)).toBe(0);\n  });\n  it('returns 1 with positive values', () => {\n    expect(signFn(5)).toBe(1);\n  });\n  it('returns -1 with negative values', () => {\n    expect(signFn(-5)).toBe(-1);\n  });\n});\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/absolute.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\nimport { lessThan } from './lessThan';\n\nexport function absolute<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n  const lessThanFn = lessThan(calculator);\n  const zero = calculator.zero();\n\n  return (input: TAmount) => {\n    if (equalFn(input, zero)) {\n      return zero;\n    }\n\n    if (lessThanFn(input, zero)) {\n      const minusOne = calculator.decrement(zero);\n\n      return calculator.multiply(minusOne, input);\n    }\n\n    return input;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/compare.ts",
    "content": "import type { DineroCalculator } from '../types';\n\ntype ComparisonCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a compare function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The compare function.\n */\nexport function compare<TAmount>(calculator: ComparisonCalculator<TAmount>) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return calculator.compare(subject, comparator);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/computeBase.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { isArray } from './isArray';\n\nexport function computeBase<TAmount>(calculator: DineroCalculator<TAmount>) {\n  return (base: TAmount | readonly TAmount[]) => {\n    if (isArray(base)) {\n      return base.reduce((acc, curr) => calculator.multiply(acc, curr));\n    }\n\n    return base;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/countTrailingZeros.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\n\ntype CountTrailingZerosCalculator<TAmount> = DineroCalculator<TAmount>;\n\nexport function countTrailingZeros<TAmount>(\n  calculator: CountTrailingZerosCalculator<TAmount>\n) {\n  const equalFn = equal(calculator);\n\n  return (input: TAmount, base: TAmount) => {\n    const zero = calculator.zero();\n\n    if (equalFn(zero, input)) {\n      return calculator.zero();\n    }\n\n    let i = zero;\n    let temp = input;\n\n    while (equalFn(calculator.modulo(temp, base), zero)) {\n      temp = calculator.integerDivide(temp, base);\n      i = calculator.increment(i);\n    }\n\n    return i;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/distribute.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\nimport { greaterThan } from './greaterThan';\nimport { greaterThanOrEqual } from './greaterThanOrEqual';\nimport { lessThan } from './lessThan';\n\ntype DistributeCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a distribute function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The distribute function.\n */\nexport function distribute<TAmount>(calculator: DistributeCalculator<TAmount>) {\n  return (value: TAmount, ratios: readonly TAmount[]) => {\n    const equalFn = equal(calculator);\n    const greaterThanFn = greaterThan(calculator);\n    const lessThanFn = lessThan(calculator);\n    const greaterThanOrEqualFn = greaterThanOrEqual(calculator);\n\n    const zero = calculator.zero();\n    const one = calculator.increment(zero);\n\n    const total = ratios.reduce((a, b) => calculator.add(a, b), zero);\n\n    if (equalFn(total, zero)) {\n      return ratios;\n    }\n\n    let remainder = value;\n\n    const shares = ratios.map((ratio) => {\n      const share =\n        calculator.integerDivide(calculator.multiply(value, ratio), total) ||\n        zero;\n\n      remainder = calculator.subtract(remainder, share);\n\n      return share;\n    });\n\n    const isPositive = greaterThanOrEqualFn(value, zero);\n    const compare = isPositive ? greaterThanFn : lessThanFn;\n    const amount = isPositive ? one : calculator.decrement(zero);\n\n    // Create indices sorted by descending ratio for remainder distribution\n    // Indices with larger ratios receive remainder first\n    const sortedIndices = ratios\n      .map((ratio, index) => ({ ratio, index }))\n      .filter(({ ratio }) => !equalFn(ratio, zero))\n      .sort((a, b) => (greaterThanFn(a.ratio, b.ratio) ? -1 : 1))\n      .map(({ index }) => index);\n\n    let i = 0;\n\n    while (compare(remainder, zero)) {\n      const index = sortedIndices[i % sortedIndices.length];\n      shares[index] = calculator.add(shares[index], amount);\n\n      const newRemainder = calculator.subtract(remainder, amount);\n\n      // Guard against infinite loop due to floating-point precision loss.\n      // When using the number calculator with amounts larger than\n      // Number.MAX_SAFE_INTEGER, subtraction may have no effect.\n      if (equalFn(newRemainder, remainder)) {\n        break;\n      }\n\n      remainder = newRemainder;\n      i++;\n    }\n\n    return shares;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/equal.ts",
    "content": "import { DineroComparisonOperator } from '../types';\nimport type { DineroCalculator } from '../types';\n\ntype EqualCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns an equal function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The equal function.\n */\nexport function equal<TAmount>(calculator: EqualCalculator<TAmount>) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return (\n      calculator.compare(subject, comparator) === DineroComparisonOperator.EQ\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/getAmountAndScale.ts",
    "content": "import type { DineroScaledAmount } from '../types';\n\nimport { isScaledAmount } from './isScaledAmount';\n\nexport function getAmountAndScale<TAmount>(\n  value: DineroScaledAmount<TAmount> | TAmount,\n  zero: TAmount\n) {\n  if (isScaledAmount(value)) {\n    return { amount: value.amount, scale: value?.scale ?? zero };\n  }\n\n  return { amount: value, scale: zero };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/getDivisors.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nexport function getDivisors<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const { multiply } = calculator;\n\n  return (bases: readonly TAmount[]) => {\n    return bases.reduce<readonly TAmount[]>((divisors, _, i) => {\n      const divisor = bases.slice(i).reduce((acc, curr) => multiply(acc, curr));\n\n      return [...divisors, divisor];\n    }, []);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/greaterThan.ts",
    "content": "import { DineroComparisonOperator } from '../types';\nimport type { DineroCalculator } from '../types';\n\ntype GreaterThanCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a greaterThan function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The greaterThan function.\n */\nexport function greaterThan<TAmount>(\n  calculator: GreaterThanCalculator<TAmount>\n) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return (\n      calculator.compare(subject, comparator) === DineroComparisonOperator.GT\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/greaterThanOrEqual.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\nimport { greaterThan } from './greaterThan';\n\ntype GreaterThanOrEqualCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a greaterThanOrEqual function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The greaterThanOrEqual function.\n */\nexport function greaterThanOrEqual<TAmount>(\n  calculator: GreaterThanOrEqualCalculator<TAmount>\n) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return (\n      greaterThan(calculator)(subject, comparator) ||\n      equal(calculator)(subject, comparator)\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/index.ts",
    "content": "export * from './absolute';\nexport * from './compare';\nexport * from './computeBase';\nexport * from './countTrailingZeros';\nexport * from './distribute';\nexport * from './equal';\nexport * from './getAmountAndScale';\nexport * from './getDivisors';\nexport * from './greaterThan';\nexport * from './greaterThanOrEqual';\nexport * from './isArray';\nexport * from './isEven';\nexport * from './isHalf';\nexport * from './isScaledAmount';\nexport * from './lessThan';\nexport * from './lessThanOrEqual';\nexport * from './maximum';\nexport * from './minimum';\nexport * from './sign';\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/isArray.ts",
    "content": "export function isArray<TType>(\n  maybeArray: TType | readonly TType[]\n): maybeArray is readonly TType[] {\n  return Array.isArray(maybeArray);\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/isEven.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from '.';\n\nexport function isEven<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n  const zero = calculator.zero();\n  const two = calculator.increment(calculator.increment(zero));\n\n  return (input: TAmount) => {\n    return equalFn(calculator.modulo(input, two), zero);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/isHalf.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal, absolute } from '.';\n\nexport function isHalf<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n  const absoluteFn = absolute(calculator);\n\n  return (input: TAmount, total: TAmount) => {\n    const remainder = absoluteFn(calculator.modulo(input, total));\n    const difference = calculator.subtract(total, remainder);\n\n    return equalFn(difference, remainder);\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/isScaledAmount.ts",
    "content": "import type { DineroRate, DineroScaledAmount } from '../types';\n\nexport function isScaledAmount<TAmount>(\n  amount: DineroRate<TAmount>\n): amount is DineroScaledAmount<TAmount> {\n  return (amount as DineroScaledAmount<TAmount>)?.hasOwnProperty('amount');\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/lessThan.ts",
    "content": "import { DineroComparisonOperator } from '../types';\nimport type { DineroCalculator } from '../types';\n\ntype LessThanCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a lessThan function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The lessThan function.\n */\nexport function lessThan<TAmount>(calculator: LessThanCalculator<TAmount>) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return (\n      calculator.compare(subject, comparator) === DineroComparisonOperator.LT\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/lessThanOrEqual.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\nimport { lessThan } from './lessThan';\n\ntype LessThanOrEqualCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a lessThanOrEqual function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The lessThanOrEqual function.\n */\nexport function lessThanOrEqual<TAmount>(\n  calculator: LessThanOrEqualCalculator<TAmount>\n) {\n  return (subject: TAmount, comparator: TAmount) => {\n    return (\n      lessThan(calculator)(subject, comparator) ||\n      equal(calculator)(subject, comparator)\n    );\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/maximum.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { lessThan } from './lessThan';\n\ntype MaximumCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a maximum function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The maximum function.\n */\nexport function maximum<TAmount>(calculator: MaximumCalculator<TAmount>) {\n  const lessThanFn = lessThan(calculator);\n\n  return (values: readonly TAmount[]) => {\n    return values.reduce((acc, curr) => {\n      return lessThanFn(acc, curr) ? curr : acc;\n    });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/minimum.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { greaterThan } from './greaterThan';\n\ntype MinimumCalculator<TAmount> = DineroCalculator<TAmount>;\n\n/**\n * Returns a minimum function.\n *\n * @param calculator - The calculator to use.\n *\n * @returns The minimum function.\n */\nexport function minimum<TAmount>(calculator: MinimumCalculator<TAmount>) {\n  const greaterThanFn = greaterThan(calculator);\n\n  return (values: readonly TAmount[]) => {\n    return values.reduce((acc, curr) => {\n      return greaterThanFn(acc, curr) ? curr : acc;\n    });\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/core/utils/sign.ts",
    "content": "import type { DineroCalculator } from '../types';\n\nimport { equal } from './equal';\nimport { lessThan } from './lessThan';\n\nexport function sign<TAmount>(calculator: DineroCalculator<TAmount>) {\n  const equalFn = equal(calculator);\n  const lessThanFn = lessThan(calculator);\n  const zero = calculator.zero();\n\n  return (input: TAmount) => {\n    if (equalFn(input, zero)) {\n      return zero;\n    }\n\n    const one = calculator.increment(zero);\n    const minusOne = calculator.decrement(zero);\n\n    return lessThanFn(input, zero) ? minusOne : one;\n  };\n}\n"
  },
  {
    "path": "packages/dinero.js/src/currencies/index.ts",
    "content": "export * from './iso4217';\nexport * from './types';\n"
  },
  {
    "path": "packages/dinero.js/src/currencies/iso4217.ts",
    "content": "/**\n * ISO 4217 currency definitions.\n *\n * These track the latest ISO 4217 standard. DineroCurrency properties may change\n * when you upgrade Dinero.js. If you need stability, pin your package version.\n *\n * @see https://www.six-group.com/en/products-services/financial-information/data-standards.html\n */\n\nimport type { DineroCurrency } from './types';\n\n/**\n * United Arab Emirates dirham.\n */\nexport const AED = {\n  code: 'AED',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AED'>;\n\n/**\n * Afghan afghani.\n */\nexport const AFN = {\n  code: 'AFN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AFN'>;\n\n/**\n * Albanian lek.\n */\nexport const ALL = {\n  code: 'ALL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ALL'>;\n\n/**\n * Armenian dram.\n */\nexport const AMD = {\n  code: 'AMD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AMD'>;\n\n/**\n * Angolan kwanza.\n */\nexport const AOA = {\n  code: 'AOA',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AOA'>;\n\n/**\n * Argentine peso.\n */\nexport const ARS = {\n  code: 'ARS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ARS'>;\n\n/**\n * Australian dollar.\n */\nexport const AUD = {\n  code: 'AUD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AUD'>;\n\n/**\n * Aruban florin.\n */\nexport const AWG = {\n  code: 'AWG',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AWG'>;\n\n/**\n * Azerbaijani manat.\n */\nexport const AZN = {\n  code: 'AZN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'AZN'>;\n\n/**\n * Bosnia and Herzegovina convertible mark.\n */\nexport const BAM = {\n  code: 'BAM',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BAM'>;\n\n/**\n * Barbados dollar.\n */\nexport const BBD = {\n  code: 'BBD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BBD'>;\n\n/**\n * Bangladeshi taka.\n */\nexport const BDT = {\n  code: 'BDT',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BDT'>;\n\n/**\n * Bulgarian lev.\n */\nexport const BGN = {\n  code: 'BGN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BGN'>;\n\n/**\n * Bahraini dinar.\n */\nexport const BHD = {\n  code: 'BHD',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'BHD'>;\n\n/**\n * Burundian franc.\n */\nexport const BIF = {\n  code: 'BIF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'BIF'>;\n\n/**\n * Bermudian dollar.\n */\nexport const BMD = {\n  code: 'BMD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BMD'>;\n\n/**\n * Brunei dollar.\n */\nexport const BND = {\n  code: 'BND',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BND'>;\n\n/**\n * Bolivian boliviano.\n */\nexport const BOB = {\n  code: 'BOB',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BOB'>;\n\n/**\n * Bolivian Mvdol.\n */\nexport const BOV = {\n  code: 'BOV',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BOV'>;\n\n/**\n * Brazilian real.\n */\nexport const BRL = {\n  code: 'BRL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BRL'>;\n\n/**\n * Bahamian dollar.\n */\nexport const BSD = {\n  code: 'BSD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BSD'>;\n\n/**\n * Bhutanese ngultrum.\n */\nexport const BTN = {\n  code: 'BTN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BTN'>;\n\n/**\n * Botswana pula.\n */\nexport const BWP = {\n  code: 'BWP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BWP'>;\n\n/**\n * Belarusian ruble.\n */\nexport const BYN = {\n  code: 'BYN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BYN'>;\n\n/**\n * Belize dollar.\n */\nexport const BZD = {\n  code: 'BZD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'BZD'>;\n\n/**\n * Canadian dollar.\n */\nexport const CAD = {\n  code: 'CAD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CAD'>;\n\n/**\n * Congolese franc.\n */\nexport const CDF = {\n  code: 'CDF',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CDF'>;\n\n/**\n * WIR Euro.\n */\nexport const CHE = {\n  code: 'CHE',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CHE'>;\n\n/**\n * Swiss franc.\n */\nexport const CHF = {\n  code: 'CHF',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CHF'>;\n\n/**\n * WIR Franc.\n */\nexport const CHW = {\n  code: 'CHW',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CHW'>;\n\n/**\n * Unidad de Fomento.\n */\nexport const CLF = {\n  code: 'CLF',\n  base: 10,\n  exponent: 4,\n} as const satisfies DineroCurrency<number, 'CLF'>;\n\n/**\n * Chilean peso.\n */\nexport const CLP = {\n  code: 'CLP',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'CLP'>;\n\n/**\n * Renminbi (Chinese) yuan.\n */\nexport const CNY = {\n  code: 'CNY',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CNY'>;\n\n/**\n * Colombian peso.\n */\nexport const COP = {\n  code: 'COP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'COP'>;\n\n/**\n * Unidad de Valor Real.\n */\nexport const COU = {\n  code: 'COU',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'COU'>;\n\n/**\n * Costa Rican colón.\n */\nexport const CRC = {\n  code: 'CRC',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CRC'>;\n\n/**\n * Cuban peso.\n */\nexport const CUP = {\n  code: 'CUP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CUP'>;\n\n/**\n * Cape Verdean escudo.\n */\nexport const CVE = {\n  code: 'CVE',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CVE'>;\n\n/**\n * Czech koruna.\n */\nexport const CZK = {\n  code: 'CZK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'CZK'>;\n\n/**\n * Djiboutian franc.\n */\nexport const DJF = {\n  code: 'DJF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'DJF'>;\n\n/**\n * Danish krone.\n */\nexport const DKK = {\n  code: 'DKK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'DKK'>;\n\n/**\n * Dominican peso.\n */\nexport const DOP = {\n  code: 'DOP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'DOP'>;\n\n/**\n * Algerian dinar.\n */\nexport const DZD = {\n  code: 'DZD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'DZD'>;\n\n/**\n * Egyptian pound.\n */\nexport const EGP = {\n  code: 'EGP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'EGP'>;\n\n/**\n * Eritrean nakfa.\n */\nexport const ERN = {\n  code: 'ERN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ERN'>;\n\n/**\n * Ethiopian birr.\n */\nexport const ETB = {\n  code: 'ETB',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ETB'>;\n\n/**\n * Euro.\n */\nexport const EUR = {\n  code: 'EUR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'EUR'>;\n\n/**\n * Fiji dollar.\n */\nexport const FJD = {\n  code: 'FJD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'FJD'>;\n\n/**\n * Falkland Islands pound.\n */\nexport const FKP = {\n  code: 'FKP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'FKP'>;\n\n/**\n * Pound sterling.\n */\nexport const GBP = {\n  code: 'GBP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GBP'>;\n\n/**\n * Georgian lari.\n */\nexport const GEL = {\n  code: 'GEL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GEL'>;\n\n/**\n * Ghanaian cedi.\n */\nexport const GHS = {\n  code: 'GHS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GHS'>;\n\n/**\n * Gibraltar pound.\n */\nexport const GIP = {\n  code: 'GIP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GIP'>;\n\n/**\n * Gambian dalasi.\n */\nexport const GMD = {\n  code: 'GMD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GMD'>;\n\n/**\n * Guinean franc.\n */\nexport const GNF = {\n  code: 'GNF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'GNF'>;\n\n/**\n * Guatemalan quetzal.\n */\nexport const GTQ = {\n  code: 'GTQ',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GTQ'>;\n\n/**\n * Guyanese dollar.\n */\nexport const GYD = {\n  code: 'GYD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'GYD'>;\n\n/**\n * Hong Kong dollar.\n */\nexport const HKD = {\n  code: 'HKD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'HKD'>;\n\n/**\n * Honduran lempira.\n */\nexport const HNL = {\n  code: 'HNL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'HNL'>;\n\n/**\n * Haitian gourde.\n */\nexport const HTG = {\n  code: 'HTG',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'HTG'>;\n\n/**\n * Hungarian forint.\n */\nexport const HUF = {\n  code: 'HUF',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'HUF'>;\n\n/**\n * Indonesian rupiah.\n */\nexport const IDR = {\n  code: 'IDR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'IDR'>;\n\n/**\n * Israeli new shekel.\n */\nexport const ILS = {\n  code: 'ILS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ILS'>;\n\n/**\n * Indian rupee.\n */\nexport const INR = {\n  code: 'INR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'INR'>;\n\n/**\n * Iraqi dinar.\n */\nexport const IQD = {\n  code: 'IQD',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'IQD'>;\n\n/**\n * Iranian rial.\n */\nexport const IRR = {\n  code: 'IRR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'IRR'>;\n\n/**\n * Icelandic króna.\n */\nexport const ISK = {\n  code: 'ISK',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'ISK'>;\n\n/**\n * Jamaican dollar.\n */\nexport const JMD = {\n  code: 'JMD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'JMD'>;\n\n/**\n * Jordanian dinar.\n */\nexport const JOD = {\n  code: 'JOD',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'JOD'>;\n\n/**\n * Japanese yen.\n */\nexport const JPY = {\n  code: 'JPY',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'JPY'>;\n\n/**\n * Kenyan shilling.\n */\nexport const KES = {\n  code: 'KES',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KES'>;\n\n/**\n * Kyrgyzstani som.\n */\nexport const KGS = {\n  code: 'KGS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KGS'>;\n\n/**\n * Cambodian riel.\n */\nexport const KHR = {\n  code: 'KHR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KHR'>;\n\n/**\n * Comoro franc.\n */\nexport const KMF = {\n  code: 'KMF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'KMF'>;\n\n/**\n * North Korean won.\n */\nexport const KPW = {\n  code: 'KPW',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KPW'>;\n\n/**\n * South Korean won.\n */\nexport const KRW = {\n  code: 'KRW',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'KRW'>;\n\n/**\n * Kuwaiti dinar.\n */\nexport const KWD = {\n  code: 'KWD',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'KWD'>;\n\n/**\n * Cayman Islands dollar.\n */\nexport const KYD = {\n  code: 'KYD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KYD'>;\n\n/**\n * Kazakhstani tenge.\n */\nexport const KZT = {\n  code: 'KZT',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'KZT'>;\n\n/**\n * Lao kip.\n */\nexport const LAK = {\n  code: 'LAK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'LAK'>;\n\n/**\n * Lebanese pound.\n */\nexport const LBP = {\n  code: 'LBP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'LBP'>;\n\n/**\n * Sri Lankan rupee.\n */\nexport const LKR = {\n  code: 'LKR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'LKR'>;\n\n/**\n * Liberian dollar.\n */\nexport const LRD = {\n  code: 'LRD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'LRD'>;\n\n/**\n * Lesotho loti.\n */\nexport const LSL = {\n  code: 'LSL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'LSL'>;\n\n/**\n * Libyan dinar.\n */\nexport const LYD = {\n  code: 'LYD',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'LYD'>;\n\n/**\n * Moroccan dirham.\n */\nexport const MAD = {\n  code: 'MAD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MAD'>;\n\n/**\n * Moldovan leu.\n */\nexport const MDL = {\n  code: 'MDL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MDL'>;\n\n/**\n * Malagasy ariary.\n */\nexport const MGA = {\n  code: 'MGA',\n  base: 5,\n  exponent: 1,\n} as const satisfies DineroCurrency<number, 'MGA'>;\n\n/**\n * Macedonian denar.\n */\nexport const MKD = {\n  code: 'MKD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MKD'>;\n\n/**\n * Myanmar kyat.\n */\nexport const MMK = {\n  code: 'MMK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MMK'>;\n\n/**\n * Mongolian tögrög.\n */\nexport const MNT = {\n  code: 'MNT',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MNT'>;\n\n/**\n * Macanese pataca.\n */\nexport const MOP = {\n  code: 'MOP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MOP'>;\n\n/**\n * Mauritanian ouguiya.\n */\nexport const MRU = {\n  code: 'MRU',\n  base: 5,\n  exponent: 1,\n} as const satisfies DineroCurrency<number, 'MRU'>;\n\n/**\n * Mauritian rupee.\n */\nexport const MUR = {\n  code: 'MUR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MUR'>;\n\n/**\n * Maldivian rufiyaa.\n */\nexport const MVR = {\n  code: 'MVR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MVR'>;\n\n/**\n * Malawian kwacha.\n */\nexport const MWK = {\n  code: 'MWK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MWK'>;\n\n/**\n * Mexican peso.\n */\nexport const MXN = {\n  code: 'MXN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MXN'>;\n\n/**\n * Mexican Unidad de Inversion.\n */\nexport const MXV = {\n  code: 'MXV',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MXV'>;\n\n/**\n * Malaysian ringgit.\n */\nexport const MYR = {\n  code: 'MYR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MYR'>;\n\n/**\n * Mozambican metical.\n */\nexport const MZN = {\n  code: 'MZN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'MZN'>;\n\n/**\n * Namibian dollar.\n */\nexport const NAD = {\n  code: 'NAD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NAD'>;\n\n/**\n * Nigerian naira.\n */\nexport const NGN = {\n  code: 'NGN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NGN'>;\n\n/**\n * Nicaraguan córdoba.\n */\nexport const NIO = {\n  code: 'NIO',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NIO'>;\n\n/**\n * Norwegian krone.\n */\nexport const NOK = {\n  code: 'NOK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NOK'>;\n\n/**\n * Nepalese rupee.\n */\nexport const NPR = {\n  code: 'NPR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NPR'>;\n\n/**\n * New Zealand dollar.\n */\nexport const NZD = {\n  code: 'NZD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'NZD'>;\n\n/**\n * Omani rial.\n */\nexport const OMR = {\n  code: 'OMR',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'OMR'>;\n\n/**\n * Panamanian balboa.\n */\nexport const PAB = {\n  code: 'PAB',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PAB'>;\n\n/**\n * Peruvian sol.\n */\nexport const PEN = {\n  code: 'PEN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PEN'>;\n\n/**\n * Papua New Guinean kina.\n */\nexport const PGK = {\n  code: 'PGK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PGK'>;\n\n/**\n * Philippine peso.\n */\nexport const PHP = {\n  code: 'PHP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PHP'>;\n\n/**\n * Pakistani rupee.\n */\nexport const PKR = {\n  code: 'PKR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PKR'>;\n\n/**\n * Polish złoty.\n */\nexport const PLN = {\n  code: 'PLN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'PLN'>;\n\n/**\n * Paraguayan guaraní.\n */\nexport const PYG = {\n  code: 'PYG',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'PYG'>;\n\n/**\n * Qatari riyal.\n */\nexport const QAR = {\n  code: 'QAR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'QAR'>;\n\n/**\n * Romanian leu.\n */\nexport const RON = {\n  code: 'RON',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'RON'>;\n\n/**\n * Serbian dinar.\n */\nexport const RSD = {\n  code: 'RSD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'RSD'>;\n\n/**\n * Russian ruble.\n */\nexport const RUB = {\n  code: 'RUB',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'RUB'>;\n\n/**\n * Rwandan franc.\n */\nexport const RWF = {\n  code: 'RWF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'RWF'>;\n\n/**\n * Saudi riyal.\n */\nexport const SAR = {\n  code: 'SAR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SAR'>;\n\n/**\n * Solomon Islands dollar.\n */\nexport const SBD = {\n  code: 'SBD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SBD'>;\n\n/**\n * Seychelles rupee.\n */\nexport const SCR = {\n  code: 'SCR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SCR'>;\n\n/**\n * Sudanese pound.\n */\nexport const SDG = {\n  code: 'SDG',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SDG'>;\n\n/**\n * Swedish krona.\n */\nexport const SEK = {\n  code: 'SEK',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SEK'>;\n\n/**\n * Singapore dollar.\n */\nexport const SGD = {\n  code: 'SGD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SGD'>;\n\n/**\n * Saint Helena pound.\n */\nexport const SHP = {\n  code: 'SHP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SHP'>;\n\n/**\n * Sierra Leonean leone.\n */\nexport const SLE = {\n  code: 'SLE',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SLE'>;\n\n/**\n * Somali shilling.\n */\nexport const SOS = {\n  code: 'SOS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SOS'>;\n\n/**\n * Surinamese dollar.\n */\nexport const SRD = {\n  code: 'SRD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SRD'>;\n\n/**\n * South Sudanese pound.\n */\nexport const SSP = {\n  code: 'SSP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SSP'>;\n\n/**\n * São Tomé and Príncipe dobra.\n */\nexport const STN = {\n  code: 'STN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'STN'>;\n\n/**\n * Salvadoran colón.\n */\nexport const SVC = {\n  code: 'SVC',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SVC'>;\n\n/**\n * Syrian pound.\n */\nexport const SYP = {\n  code: 'SYP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SYP'>;\n\n/**\n * Swazi lilangeni.\n */\nexport const SZL = {\n  code: 'SZL',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'SZL'>;\n\n/**\n * Thai baht.\n */\nexport const THB = {\n  code: 'THB',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'THB'>;\n\n/**\n * Tajikistani somoni.\n */\nexport const TJS = {\n  code: 'TJS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TJS'>;\n\n/**\n * Turkmenistan manat.\n */\nexport const TMT = {\n  code: 'TMT',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TMT'>;\n\n/**\n * Tunisian dinar.\n */\nexport const TND = {\n  code: 'TND',\n  base: 10,\n  exponent: 3,\n} as const satisfies DineroCurrency<number, 'TND'>;\n\n/**\n * Tongan paʻanga.\n */\nexport const TOP = {\n  code: 'TOP',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TOP'>;\n\n/**\n * Turkish lira.\n */\nexport const TRY = {\n  code: 'TRY',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TRY'>;\n\n/**\n * Trinidad and Tobago dollar.\n */\nexport const TTD = {\n  code: 'TTD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TTD'>;\n\n/**\n * New Taiwan dollar.\n */\nexport const TWD = {\n  code: 'TWD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TWD'>;\n\n/**\n * Tanzanian shilling.\n */\nexport const TZS = {\n  code: 'TZS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'TZS'>;\n\n/**\n * Ukrainian hryvnia.\n */\nexport const UAH = {\n  code: 'UAH',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'UAH'>;\n\n/**\n * Ugandan shilling.\n */\nexport const UGX = {\n  code: 'UGX',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'UGX'>;\n\n/**\n * United States dollar.\n */\nexport const USD = {\n  code: 'USD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'USD'>;\n\n/**\n * United States dollar (next day).\n */\nexport const USN = {\n  code: 'USN',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'USN'>;\n\n/**\n * Uruguay Peso en Unidades Indexadas.\n */\nexport const UYI = {\n  code: 'UYI',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'UYI'>;\n\n/**\n * Uruguayan peso.\n */\nexport const UYU = {\n  code: 'UYU',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'UYU'>;\n\n/**\n * Unidad previsional.\n */\nexport const UYW = {\n  code: 'UYW',\n  base: 10,\n  exponent: 4,\n} as const satisfies DineroCurrency<number, 'UYW'>;\n\n/**\n * Uzbekistani soʻm.\n */\nexport const UZS = {\n  code: 'UZS',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'UZS'>;\n\n/**\n * Venezuelan digital bolívar.\n */\nexport const VED = {\n  code: 'VED',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'VED'>;\n\n/**\n * Venezuelan bolívar.\n */\nexport const VES = {\n  code: 'VES',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'VES'>;\n\n/**\n * Vietnamese đồng.\n */\nexport const VND = {\n  code: 'VND',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'VND'>;\n\n/**\n * Vanuatu vatu.\n */\nexport const VUV = {\n  code: 'VUV',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'VUV'>;\n\n/**\n * Samoan tālā.\n */\nexport const WST = {\n  code: 'WST',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'WST'>;\n\n/**\n * Arab Accounting Dinar.\n */\nexport const XAD = {\n  code: 'XAD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'XAD'>;\n\n/**\n * Central African CFA franc.\n */\nexport const XAF = {\n  code: 'XAF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'XAF'>;\n\n/**\n * East Caribbean dollar.\n */\nexport const XCD = {\n  code: 'XCD',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'XCD'>;\n\n/**\n * Caribbean guilder.\n */\nexport const XCG = {\n  code: 'XCG',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'XCG'>;\n\n/**\n * West African CFA franc.\n */\nexport const XOF = {\n  code: 'XOF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'XOF'>;\n\n/**\n * CFP franc.\n */\nexport const XPF = {\n  code: 'XPF',\n  base: 10,\n  exponent: 0,\n} as const satisfies DineroCurrency<number, 'XPF'>;\n\n/**\n * Yemeni rial.\n */\nexport const YER = {\n  code: 'YER',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'YER'>;\n\n/**\n * South African rand.\n */\nexport const ZAR = {\n  code: 'ZAR',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ZAR'>;\n\n/**\n * Zambian kwacha.\n */\nexport const ZMW = {\n  code: 'ZMW',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ZMW'>;\n\n/**\n * Zimbabwe Gold.\n */\nexport const ZWG = {\n  code: 'ZWG',\n  base: 10,\n  exponent: 2,\n} as const satisfies DineroCurrency<number, 'ZWG'>;\n"
  },
  {
    "path": "packages/dinero.js/src/currencies/types/DineroCurrency.ts",
    "content": "export type DineroCurrency<TAmount, TCurrency extends string = string> = {\n  /**\n   * The unique code of the currency.\n   */\n  readonly code: TCurrency;\n  /**\n   * The base, or radix of the currency.\n   */\n  readonly base: TAmount | readonly TAmount[];\n  /**\n   * The exponent of the currency.\n   */\n  readonly exponent: TAmount;\n};\n"
  },
  {
    "path": "packages/dinero.js/src/currencies/types/index.ts",
    "content": "export * from './DineroCurrency';\n"
  },
  {
    "path": "packages/dinero.js/src/dinero.ts",
    "content": "import { calculator } from './calculator/number';\nimport {\n  createDinero,\n  assert,\n  INVALID_AMOUNT_MESSAGE,\n  INVALID_SCALE_MESSAGE,\n} from './core';\n\n/**\n * Create a Dinero object.\n *\n * @param options.amount - The amount in minor currency units.\n * @param options.currency - The currency.\n * @param options.scale - The number of decimal places to represent.\n *\n * @returns The created Dinero object.\n *\n * @public\n */\nexport const dinero = createDinero({\n  calculator,\n  onCreate({ amount, scale }) {\n    assert(Number.isInteger(amount), INVALID_AMOUNT_MESSAGE);\n    assert(Number.isInteger(scale), INVALID_SCALE_MESSAGE);\n  },\n});\n"
  },
  {
    "path": "packages/dinero.js/src/index.ts",
    "content": "export * from './api';\nexport * from './currencies';\nexport * from './dinero';\nexport type {\n  CreateDineroOptions,\n  Dinero,\n  DineroBinaryOperation,\n  DineroCalculator,\n  DineroDivideOperation,\n  DineroFactory,\n  DineroFormatter,\n  DineroOptions,\n  DineroRate,\n  DineroRates,\n  DineroScaledAmount,\n  DineroSnapshot,\n  DineroTransformer,\n  DineroUnaryOperation,\n  TransformerOptions,\n} from './core';\nexport {\n  DineroComparisonOperator,\n  createDinero,\n  down,\n  halfAwayFromZero,\n  halfDown,\n  halfEven,\n  halfOdd,\n  halfTowardsZero,\n  halfUp,\n  up,\n} from './core';\n"
  },
  {
    "path": "packages/dinero.js/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \"src\"\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"**/__tests__/**/*\"]\n}\n"
  },
  {
    "path": "packages/dinero.js/tsdown.config.ts",
    "content": "import { execSync } from 'child_process';\nimport { readFileSync } from 'fs';\n\nimport { defineConfig } from 'tsdown';\n\nconst pkg = JSON.parse(readFileSync('./package.json', 'utf-8'));\n\nfunction getBanner() {\n  const lastCommitHash = execSync('git rev-parse --short HEAD')\n    .toString()\n    .trim();\n  const version = process.env.SHIPJS\n    ? pkg.version\n    : `${pkg.version} (UNRELEASED ${lastCommitHash})`;\n  const authors = `© ${pkg.author.name} and contributors`;\n\n  return `/*! ${pkg.name} ${version} | MIT License | ${authors} | ${pkg.homepage} */`;\n}\n\nconst banner = getBanner();\n\nexport default defineConfig([\n  // ESM build\n  {\n    entry: {\n      index: 'src/index.ts',\n      'currencies/index': 'src/currencies/index.ts',\n      'bigint/index': 'src/bigint/index.ts',\n      'bigint/currencies/index': 'src/bigint/currencies/index.ts',\n    },\n    format: 'esm',\n    outDir: 'dist/esm',\n    dts: true,\n    clean: true,\n    outExtensions: () => ({ js: '.js', dts: '.d.ts' }),\n    define: {\n      __DEV__: \"process.env.NODE_ENV !== 'production'\",\n      __TEST__: \"process.env.NODE_ENV === 'test'\",\n    },\n  },\n  // UMD main bundle (production)\n  {\n    entry: { 'index.production': 'src/index.ts' },\n    format: 'umd',\n    outDir: 'dist/umd',\n    globalName: 'dinerojs',\n    minify: true,\n    sourcemap: true,\n    clean: false,\n    outputOptions: {\n      banner,\n      entryFileNames: '[name].js',\n    },\n    define: {\n      'process.env.NODE_ENV': '\"production\"',\n      __DEV__: 'false',\n      __TEST__: 'false',\n    },\n  },\n  // UMD main bundle (development)\n  {\n    entry: { 'index.development': 'src/index.ts' },\n    format: 'umd',\n    outDir: 'dist/umd',\n    globalName: 'dinerojs',\n    minify: false,\n    sourcemap: true,\n    clean: false,\n    outputOptions: {\n      banner,\n      entryFileNames: '[name].js',\n    },\n    define: {\n      'process.env.NODE_ENV': '\"development\"',\n      __DEV__: 'true',\n      __TEST__: 'false',\n    },\n  },\n  // UMD bigint bundle (production)\n  {\n    entry: { 'bigint/index.production': 'src/bigint/index.ts' },\n    format: 'umd',\n    outDir: 'dist/umd',\n    globalName: 'dinerojs',\n    minify: true,\n    sourcemap: true,\n    clean: false,\n    outputOptions: {\n      banner,\n      entryFileNames: '[name].js',\n    },\n    define: {\n      'process.env.NODE_ENV': '\"production\"',\n      __DEV__: 'false',\n      __TEST__: 'false',\n    },\n  },\n  // UMD bigint bundle (development)\n  {\n    entry: { 'bigint/index.development': 'src/bigint/index.ts' },\n    format: 'umd',\n    outDir: 'dist/umd',\n    globalName: 'dinerojs',\n    minify: false,\n    sourcemap: true,\n    clean: false,\n    outputOptions: {\n      banner,\n      entryFileNames: '[name].js',\n    },\n    define: {\n      'process.env.NODE_ENV': '\"development\"',\n      __DEV__: 'true',\n      __TEST__: 'false',\n    },\n  },\n]);\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:js-lib\",\n    \"schedule:monthly\",\n    \":semanticCommitTypeAll(chore)\",\n    \":preserveSemverRanges\"\n  ],\n  \"timezone\": \"Europe/Paris\",\n  \"vulnerabilityAlerts\": {\n    \"enabled\": true,\n    \"schedule\": [\"at any time\"],\n    \"labels\": [\"security\"]\n  },\n  \"packageRules\": [\n    {\n      \"matchUpdateTypes\": [\"minor\", \"patch\"],\n      \"matchCurrentVersion\": \"!/^0/\",\n      \"automerge\": true,\n      \"automergeType\": \"branch\"\n    },\n    {\n      \"description\": \"Group Vite and Vitest packages together\",\n      \"matchPackageNames\": [\"vite\", \"vitest\", \"@vitest/**\"],\n      \"groupName\": \"vite\"\n    }\n  ]\n}\n"
  },
  {
    "path": "ship.config.cjs",
    "content": "module.exports = {\n  useOidcTokenProvider: true,\n  monorepo: {\n    mainVersionFile: 'package.json',\n    packagesToBump: ['packages/*', 'examples/*', 'docs'],\n    packagesToPublish: ['packages/*'],\n  },\n  publishCommand({ tag }) {\n    return `npm publish --access public --tag ${tag}`;\n  },\n  installCommand() {\n    return 'npm ci';\n  },\n  // Skip preparation if it contains only `chore` commits\n  shouldPrepare({ releaseType, commitNumbersPerType }) {\n    const { fix = 0 } = commitNumbersPerType;\n\n    return releaseType !== 'patch' || fix !== 0;\n  },\n  beforeCommitChanges({ exec }) {\n    // Regenerate the lockfile after version bumps\n    exec('npm install');\n  },\n};\n"
  },
  {
    "path": "skills-lock.json",
    "content": "{\n  \"version\": 1,\n  \"skills\": {\n    \"dinero-best-practices\": {\n      \"source\": \"dinerojs/skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"5782a3167685788e8dbd61cc594af9132e5adf29145d9f796193627093e9ce6c\"\n    },\n    \"dinero-currency-patterns\": {\n      \"source\": \"dinerojs/skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"50b60a4c1523f89d64717ca10c8156a7f0e2c28667f1a6d337c31eae3902f48d\"\n    },\n    \"dinero-formatting\": {\n      \"source\": \"dinerojs/skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"8f176bd8c9cc2a9b0d587fbbed1f3adf2fb9a66597c75a0bed9721dcaf763594\"\n    },\n    \"vercel-composition-patterns\": {\n      \"source\": \"vercel-labs/agent-skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"f98931159fa9c7fed043bcd18a891a46dcf89ababa38df13a4c5b7b30dc0ce07\"\n    },\n    \"vercel-react-best-practices\": {\n      \"source\": \"vercel-labs/agent-skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"4365be772fd3b41994a72ad478472c6d680678e7f3859860ab010e9c46e48ee0\"\n    },\n    \"web-design-guidelines\": {\n      \"source\": \"vercel-labs/agent-skills\",\n      \"sourceType\": \"github\",\n      \"computedHash\": \"a6a44d5498f7e8f68289902f3dedfc6f38ae0cee1e96527c80724cf27f727c2a\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/utils/castToBigintCurrency.ts",
    "content": "import type { DineroCurrency } from 'dinero.js';\n\nexport function castToBigintCurrency<TCurrency extends string>(\n  currency: DineroCurrency<number, TCurrency>\n): DineroCurrency<bigint, TCurrency> {\n  return {\n    ...currency,\n    base: Array.isArray(currency.base)\n      ? currency.base.map(BigInt)\n      : BigInt(currency.base as number),\n    exponent: BigInt(currency.exponent),\n  };\n}\n"
  },
  {
    "path": "test/utils/castToBigjsCurrency.ts",
    "content": "import Big from 'big.js';\n\nimport type { DineroCurrency } from 'dinero.js';\n\nexport function castToBigjsCurrency<TCurrency extends string>(\n  currency: DineroCurrency<number, TCurrency>\n): DineroCurrency<Big, TCurrency> {\n  return {\n    ...currency,\n    base: Array.isArray(currency.base)\n      ? currency.base.map((b) => new Big(b))\n      : new Big(currency.base as number),\n    exponent: new Big(currency.exponent),\n  };\n}\n"
  },
  {
    "path": "test/utils/createBigintDinero.ts",
    "content": "import { dinero } from 'dinero.js/bigint';\nimport type { DineroOptions } from 'dinero.js';\n\nexport function createBigintDinero<TCurrency extends string>(\n  options: DineroOptions<bigint, TCurrency>\n) {\n  return dinero(options);\n}\n"
  },
  {
    "path": "test/utils/createBigjsDinero.ts",
    "content": "import Big from 'big.js';\n\nimport { createDinero } from 'dinero.js';\nimport type { DineroComparisonOperator, DineroOptions } from 'dinero.js';\n\nconst dinero = createDinero({\n  calculator: {\n    add: (a, b) => a.plus(b),\n    compare: (a, b) => a.cmp(b) as unknown as DineroComparisonOperator,\n    decrement: (v) => v.minus(new Big(1)),\n    increment: (v) => v.plus(new Big(1)),\n    integerDivide: (a, b) => a.div(b).round(0, Big.roundDown),\n    modulo: (a, b) => a.mod(b),\n    multiply: (a, b) => a.times(b),\n    power: (a, b) => a.pow(Number(b)),\n    subtract: (a, b) => a.minus(b),\n    zero: () => new Big(0),\n  },\n});\n\nexport function createBigjsDinero<TCurrency extends string>(\n  options: DineroOptions<Big, TCurrency>\n) {\n  return dinero(options);\n}\n"
  },
  {
    "path": "test/utils/createNumberDinero.ts",
    "content": "import { dinero } from 'dinero.js';\nimport type { DineroOptions } from 'dinero.js';\n\nexport function createNumberDinero<TCurrency extends string>(\n  options: DineroOptions<number, TCurrency>\n) {\n  return dinero(options);\n}\n"
  },
  {
    "path": "test/utils/index.ts",
    "content": "export * from './castToBigintCurrency';\nexport * from './castToBigjsCurrency';\nexport * from './createBigintDinero';\nexport * from './createBigjsDinero';\nexport * from './createNumberDinero';\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"types\": [\"vitest/globals\"],\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"target\": \"ESNEXT\",\n    \"noEmit\": true,\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"dinero.js\": [\"packages/dinero.js/src/index\"],\n      \"dinero.js/currencies\": [\"packages/dinero.js/src/currencies/index\"],\n      \"dinero.js/bigint\": [\"packages/dinero.js/src/bigint/index\"],\n      \"test-utils\": [\"test/utils/index\"]\n    }\n  },\n  \"exclude\": [\n    \"**/dist\",\n    \"**/lib\",\n    \"**/temp\",\n    \"**/node_modules\",\n    \"docs\",\n    \"examples\"\n  ]\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build:clean\": {\n      \"cache\": false\n    },\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"src/**/*.ts\", \"tsdown.config.ts\", \"tsconfig.json\"],\n      \"outputs\": [\"dist/**\"]\n    },\n    \"test\": {\n      \"dependsOn\": [],\n      \"inputs\": [\"src/**/*.ts\"],\n      \"outputs\": []\n    },\n    \"lint\": {\n      \"inputs\": [\"**/*.ts\", \"**/*.js\"],\n      \"outputs\": []\n    }\n  },\n  \"globalDependencies\": [\n    \".oxlintrc.json\",\n    \".prettierrc\",\n    \".size-limit.json\",\n    \"vitest.config.ts\",\n    \"tsconfig.json\"\n  ]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\nimport path from 'path';\n\nexport default defineConfig({\n  esbuild: {\n    tsconfigRaw: '{}',\n  },\n  test: {\n    globals: true,\n    environment: 'node',\n    include: ['packages/*/src/**/__tests__/**/*.test.ts'],\n    exclude: ['node_modules', 'dist'],\n    coverage: {\n      provider: 'v8',\n      exclude: ['node_modules/', 'dist/', 'test/'],\n    },\n    clearMocks: true,\n    alias: {\n      'test-utils': path.resolve(__dirname, 'test/utils/'),\n      'dinero.js/currencies': path.resolve(\n        __dirname,\n        'packages/dinero.js/src/currencies/'\n      ),\n      'dinero.js/bigint': path.resolve(\n        __dirname,\n        'packages/dinero.js/src/bigint/'\n      ),\n      'dinero.js': path.resolve(__dirname, 'packages/dinero.js/src/'),\n    },\n  },\n  define: {\n    __DEV__: true,\n    __TEST__: true,\n  },\n});\n"
  }
]