@arki/money 0.0.0-stage → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +238 -2
- package/dist/bag.d.ts +48 -0
- package/dist/bag.d.ts.map +1 -0
- package/dist/bag.js +110 -0
- package/dist/bag.js.map +1 -0
- package/dist/currency.d.ts +64 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +148 -0
- package/dist/currency.js.map +1 -0
- package/dist/data/iso4217.d.ts +9 -0
- package/dist/data/iso4217.d.ts.map +1 -0
- package/dist/data/iso4217.js +11 -0
- package/dist/data/iso4217.js.map +1 -0
- package/dist/drizzle/config.d.ts +27 -0
- package/dist/drizzle/config.d.ts.map +1 -0
- package/dist/drizzle/config.js +16 -0
- package/dist/drizzle/config.js.map +1 -0
- package/dist/drizzle/mysql.d.ts +36 -0
- package/dist/drizzle/mysql.d.ts.map +1 -0
- package/dist/drizzle/mysql.js +45 -0
- package/dist/drizzle/mysql.js.map +1 -0
- package/dist/drizzle/pg.d.ts +38 -0
- package/dist/drizzle/pg.d.ts.map +1 -0
- package/dist/drizzle/pg.js +49 -0
- package/dist/drizzle/pg.js.map +1 -0
- package/dist/drizzle/sqlite.d.ts +38 -0
- package/dist/drizzle/sqlite.d.ts.map +1 -0
- package/dist/drizzle/sqlite.js +45 -0
- package/dist/drizzle/sqlite.js.map +1 -0
- package/dist/drizzle.d.ts +56 -0
- package/dist/drizzle.d.ts.map +1 -0
- package/dist/drizzle.js +77 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/exact.d.ts +20 -0
- package/dist/exact.d.ts.map +1 -0
- package/dist/exact.js +23 -0
- package/dist/exact.js.map +1 -0
- package/dist/exchange.d.ts +84 -0
- package/dist/exchange.d.ts.map +1 -0
- package/dist/exchange.js +215 -0
- package/dist/exchange.js.map +1 -0
- package/dist/format.d.ts +41 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +122 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/columns.d.ts +25 -0
- package/dist/internal/columns.d.ts.map +1 -0
- package/dist/internal/columns.js +47 -0
- package/dist/internal/columns.js.map +1 -0
- package/dist/internal/units.d.ts +66 -0
- package/dist/internal/units.d.ts.map +1 -0
- package/dist/internal/units.js +205 -0
- package/dist/internal/units.js.map +1 -0
- package/dist/money.d.ts +180 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +577 -0
- package/dist/money.js.map +1 -0
- package/dist/parse.d.ts +53 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +222 -0
- package/dist/parse.js.map +1 -0
- package/dist/provider.d.ts +22 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +56 -0
- package/dist/provider.js.map +1 -0
- package/dist/rational.d.ts +67 -0
- package/dist/rational.d.ts.map +1 -0
- package/dist/rational.js +169 -0
- package/dist/rational.js.map +1 -0
- package/dist/stripe.d.ts +37 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +75 -0
- package/dist/stripe.js.map +1 -0
- package/dist/zod.d.ts +54 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +96 -0
- package/dist/zod.js.map +1 -0
- package/package.json +137 -4
- package/src/bag.ts +128 -0
- package/src/currency.ts +174 -0
- package/src/data/iso4217.ts +13 -0
- package/src/drizzle/config.ts +39 -0
- package/src/drizzle/mysql.ts +61 -0
- package/src/drizzle/pg.ts +64 -0
- package/src/drizzle/sqlite.ts +61 -0
- package/src/errors.ts +61 -0
- package/src/exact.ts +29 -0
- package/src/exchange.ts +280 -0
- package/src/format.ts +148 -0
- package/src/index.ts +16 -0
- package/src/internal/columns.ts +74 -0
- package/src/internal/units.ts +223 -0
- package/src/money.ts +692 -0
- package/src/parse.ts +278 -0
- package/src/provider.ts +67 -0
- package/src/rational.ts +214 -0
- package/src/stripe.ts +84 -0
- package/src/zod.ts +123 -0
package/src/zod.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod 4 schemas that turn input into `Money`. Optional: needs `zod` installed.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const order = z.object({ total: moneySchema('EUR', { min: money(0, 'EUR') }) });
|
|
6
|
+
* order.parse({ total: { amount: '19.99', currency: 'EUR' } }).total; // Money<'EUR'>
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* The input type is the JSON shape `{ amount: string; currency: string }`, so
|
|
10
|
+
* RPC clients (tRPC, oRPC) infer what to send; the output type is `Money`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
import type { CurrencyLike } from './currency.js';
|
|
16
|
+
import type { Money, MoneyOptions } from './money.js';
|
|
17
|
+
import type { ParseOptions } from './parse.js';
|
|
18
|
+
import { Currency, resolveCurrency, toCurrency } from './currency.js';
|
|
19
|
+
import { MathError } from './errors.js';
|
|
20
|
+
import { Money as MoneyClass } from './money.js';
|
|
21
|
+
import { createMoneyParser } from './parse.js';
|
|
22
|
+
|
|
23
|
+
export type MoneySchemaOptions<C extends string> = {
|
|
24
|
+
/** Inclusive bounds. */
|
|
25
|
+
min?: Money<C>;
|
|
26
|
+
max?: Money<C>;
|
|
27
|
+
/** Extra digits are rounded with this mode instead of rejected. */
|
|
28
|
+
rounding?: MoneyOptions['rounding'];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type AnyMoneySchemaOptions = MoneySchemaOptions<string> & {
|
|
32
|
+
/** Custom currencies to resolve by code. */
|
|
33
|
+
currencies?: readonly Currency[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function message(error: unknown): string {
|
|
37
|
+
return error instanceof Error ? error.message : 'Invalid money';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function checkBounds<C extends string>(value: Money<C>, options: MoneySchemaOptions<C>, ctx: z.RefinementCtx): void {
|
|
41
|
+
if (options.min !== undefined && value.lt(options.min)) {
|
|
42
|
+
ctx.addIssue({ code: 'custom', message: `Must be at least ${options.min.toString()}` });
|
|
43
|
+
}
|
|
44
|
+
if (options.max !== undefined && value.gt(options.max)) {
|
|
45
|
+
ctx.addIssue({ code: 'custom', message: `Must be at most ${options.max.toString()}` });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const moneyJsonShape = z.object({ amount: z.string(), currency: z.string() });
|
|
50
|
+
|
|
51
|
+
function settle<C extends string>(
|
|
52
|
+
amount: string,
|
|
53
|
+
currency: Currency<C>,
|
|
54
|
+
options: MoneySchemaOptions<C>,
|
|
55
|
+
ctx: z.RefinementCtx,
|
|
56
|
+
): Money<C> {
|
|
57
|
+
try {
|
|
58
|
+
const value = MoneyClass.of(amount, currency, { rounding: options.rounding });
|
|
59
|
+
checkBounds(value, options, ctx);
|
|
60
|
+
return value;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (!(error instanceof MathError)) throw error;
|
|
63
|
+
ctx.addIssue({ code: 'custom', message: message(error), path: ['amount'] });
|
|
64
|
+
return z.NEVER;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `{ amount: '19.99', currency: 'EUR' }` → `Money<'EUR'>`. Another currency,
|
|
70
|
+
* a bad amount or a value out of bounds becomes a Zod issue.
|
|
71
|
+
*/
|
|
72
|
+
export function moneySchema<const C extends string>(currency: CurrencyLike<C>, options: MoneySchemaOptions<C> = {}) {
|
|
73
|
+
const expected = toCurrency(currency);
|
|
74
|
+
return moneyJsonShape.transform((input, ctx): Money<C> => {
|
|
75
|
+
if (input.currency !== expected.code) {
|
|
76
|
+
ctx.addIssue({ code: 'custom', message: `Expected ${expected.code}, got ${input.currency}`, path: ['currency'] });
|
|
77
|
+
return z.NEVER;
|
|
78
|
+
}
|
|
79
|
+
return settle(input.amount, expected, options, ctx);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `{ amount, currency }` in any ISO (or listed custom) currency → `Money`. */
|
|
84
|
+
export function anyMoneySchema(options: AnyMoneySchemaOptions = {}) {
|
|
85
|
+
return moneyJsonShape.transform((input, ctx): Money => {
|
|
86
|
+
let currency: Currency;
|
|
87
|
+
try {
|
|
88
|
+
currency = resolveCurrency(input.currency, options.currencies);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!(error instanceof MathError)) throw error;
|
|
91
|
+
ctx.addIssue({ code: 'custom', message: message(error), path: ['currency'] });
|
|
92
|
+
return z.NEVER;
|
|
93
|
+
}
|
|
94
|
+
return settle(input.amount, currency, options, ctx);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A form field: locale text such as `'1.234,56 €'` → `Money<C>`, with the
|
|
100
|
+
* strict rules of `@arki/money/parse`.
|
|
101
|
+
*/
|
|
102
|
+
export function moneyInputSchema<const C extends string>(
|
|
103
|
+
currency: CurrencyLike<C>,
|
|
104
|
+
options: ParseOptions & Pick<MoneySchemaOptions<C>, 'min' | 'max'> = {},
|
|
105
|
+
) {
|
|
106
|
+
const parser = createMoneyParser(currency, options);
|
|
107
|
+
return z.string().transform((text, ctx): Money<C> => {
|
|
108
|
+
try {
|
|
109
|
+
const value = parser.parse(text);
|
|
110
|
+
checkBounds(value, options, ctx);
|
|
111
|
+
return value;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!(error instanceof MathError)) throw error;
|
|
114
|
+
ctx.addIssue({ code: 'custom', message: message(error) });
|
|
115
|
+
return z.NEVER;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** An ISO 4217 code from the shipped table, such as `'EUR'`. */
|
|
121
|
+
export const currencyCodeSchema = z
|
|
122
|
+
.string()
|
|
123
|
+
.refine(code => Currency.isKnown(code), { message: 'Unknown ISO 4217 currency code' });
|