@storepass/money 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -111,6 +111,40 @@ const json = m.toJSON();
111
111
 
112
112
  `amount` is always in minor units.
113
113
 
114
+ ### Zod schemas and codecs
115
+
116
+ Zod support is available from a separate entry point, so importing the core
117
+ money primitives does not require Zod:
118
+
119
+ ```ts
120
+ import { Currency, defineCurrency, Money } from "@storepass/money";
121
+ import {
122
+ createMoneyCodec,
123
+ currencyDefinitionSchema,
124
+ moneyCodec,
125
+ moneyInstanceSchema,
126
+ moneySchema,
127
+ } from "@storepass/money/zod";
128
+ import { decode, encode } from "zod";
129
+
130
+ const serialized = moneySchema.parse({ amount: 1234, currency: "USD" });
131
+ const money = decode(moneyCodec, serialized);
132
+
133
+ moneyInstanceSchema.parse(money); // Money
134
+ encode(moneyCodec, money); // { amount: 1234, currency: "USD" }
135
+
136
+ currencyDefinitionSchema.parse(Currency.USD);
137
+
138
+ const XAU = defineCurrency("XAU", 4);
139
+ const goldCodec = createMoneyCodec({ XAU });
140
+ decode(goldCodec, { amount: 12345, currency: "XAU" });
141
+ ```
142
+
143
+ `moneySchema` (also exported as `serializedMoneySchema`) accepts custom
144
+ currency codes and validates that `amount` is a safe integer in minor units.
145
+ `builtInSerializedMoneySchema` and `moneyCodec` restrict values to the
146
+ currencies included in `Currency`.
147
+
114
148
  ### Display formatting
115
149
 
116
150
  `Money#format` wraps `Intl.NumberFormat`, honoring the currency's
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { Currency, type CurrencyCode, type CurrencyDefinition, type CurrencyType, type DefineCurrencyOptions, defineCurrency, } from "./currency";
2
- export { type FromNumberOptions, Money, type RoundingMode } from "./money";
2
+ export { type FromNumberOptions, Money, type RoundingMode, type SerializedMoney, } from "./money";
package/dist/money.d.ts CHANGED
@@ -4,6 +4,11 @@ export interface FromNumberOptions {
4
4
  rounding?: RoundingMode;
5
5
  strict?: boolean;
6
6
  }
7
+ /** JSON-safe representation of a Money value. Amount is in minor units. */
8
+ export interface SerializedMoney {
9
+ amount: number;
10
+ currency: string;
11
+ }
7
12
  type MoneyFormatOptions = Omit<Intl.NumberFormatOptions, "style" | "currency" | "minimumFractionDigits" | "maximumFractionDigits"> & {
8
13
  minimumFractionDigits?: number;
9
14
  maximumFractionDigits?: number;
@@ -64,10 +69,7 @@ export declare class Money {
64
69
  decrementByPercent(percent: number, options?: {
65
70
  rounding?: RoundingMode;
66
71
  }): Money;
67
- toJSON(): {
68
- amount: number;
69
- currency: string;
70
- };
72
+ toJSON(): SerializedMoney;
71
73
  /**
72
74
  * Format this Money for display using `Intl.NumberFormat`.
73
75
  *
package/dist/zod.cjs ADDED
@@ -0,0 +1,119 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __toCommonJS = (from) => {
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
+ if (entry)
11
+ return entry;
12
+ entry = __defProp({}, "__esModule", { value: true });
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ __moduleCache.set(from, entry);
22
+ return entry;
23
+ };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
29
+ var __export = (target, all) => {
30
+ for (var name in all)
31
+ __defProp(target, name, {
32
+ get: all[name],
33
+ enumerable: true,
34
+ configurable: true,
35
+ set: __exportSetter.bind(all, name)
36
+ });
37
+ };
38
+
39
+ // src/zod.ts
40
+ var exports_zod = {};
41
+ __export(exports_zod, {
42
+ serializedMoneySchema: () => serializedMoneySchema,
43
+ moneySchema: () => moneySchema,
44
+ moneyInstanceSchema: () => moneyInstanceSchema,
45
+ moneyCodec: () => moneyCodec,
46
+ currencyDefinitionSchema: () => currencyDefinitionSchema,
47
+ currencyCodeSchema: () => currencyCodeSchema,
48
+ createMoneyCodec: () => createMoneyCodec,
49
+ builtInSerializedMoneySchema: () => builtInSerializedMoneySchema
50
+ });
51
+ module.exports = __toCommonJS(exports_zod);
52
+ var import_money = require("@storepass/money");
53
+ var import_zod = require("zod");
54
+ var builtInCurrencyCodes = Object.keys(import_money.Currency);
55
+ var currencyCodeSchema = import_zod.z.enum(builtInCurrencyCodes);
56
+ var currencyDefinitionSchema = import_zod.z.object({
57
+ code: import_zod.z.string().min(1),
58
+ decimalPlaces: import_zod.z.number().int().nonnegative(),
59
+ numericCode: import_zod.z.number().int().min(0).max(999).optional(),
60
+ iso4217: import_zod.z.boolean().optional(),
61
+ displayCode: import_zod.z.string().regex(/^[A-Z]{3}$/).optional()
62
+ }).superRefine((currency, context) => {
63
+ try {
64
+ import_money.defineCurrency(currency.code, currency.decimalPlaces, {
65
+ numericCode: currency.numericCode,
66
+ iso4217: currency.iso4217,
67
+ displayCode: currency.displayCode
68
+ });
69
+ } catch (error) {
70
+ context.addIssue({
71
+ code: "custom",
72
+ message: error instanceof Error ? error.message : "Invalid currency definition"
73
+ });
74
+ }
75
+ });
76
+ var serializedMoneySchema = import_zod.z.object({
77
+ amount: import_zod.z.number().int(),
78
+ currency: import_zod.z.string().min(1)
79
+ });
80
+ var moneySchema = serializedMoneySchema;
81
+ var builtInSerializedMoneySchema = serializedMoneySchema.extend({
82
+ currency: currencyCodeSchema
83
+ });
84
+ var moneyInstanceSchema = import_zod.z.custom((value) => value instanceof import_money.Money, { error: "Expected Money instance" });
85
+ function assertCurrencyRegistry(currencies) {
86
+ for (const [code, currency] of Object.entries(currencies)) {
87
+ if (currency.code !== code) {
88
+ throw new Error(`Currency registry key "${code}" does not match definition code "${currency.code}"`);
89
+ }
90
+ currencyDefinitionSchema.parse(currency);
91
+ }
92
+ }
93
+ function createMoneyCodec(currencies) {
94
+ assertCurrencyRegistry(currencies);
95
+ const registeredCurrencyCodes = new Set(Object.keys(currencies));
96
+ const registeredCurrencyCodeSchema = import_zod.z.custom((value) => typeof value === "string" && registeredCurrencyCodes.has(value), { error: "Unknown currency code" });
97
+ const registeredSerializedMoneySchema = serializedMoneySchema.extend({
98
+ currency: registeredCurrencyCodeSchema
99
+ });
100
+ return import_zod.z.codec(registeredSerializedMoneySchema, moneyInstanceSchema, {
101
+ decode: ({ amount, currency }) => {
102
+ const registeredCurrency = currencies[currency];
103
+ if (registeredCurrency === undefined) {
104
+ throw new Error(`Unknown currency code: ${currency}`);
105
+ }
106
+ return import_money.Money.fromMinor(registeredCurrency, amount);
107
+ },
108
+ encode: (money) => {
109
+ const serialized = money.toJSON();
110
+ const currency = serialized.currency;
111
+ const registeredCurrency = currencies[currency];
112
+ if (registeredCurrency === undefined || registeredCurrency.decimalPlaces !== money.currency.decimalPlaces) {
113
+ throw new Error(`Money currency ${serialized.currency} is not registered with matching precision`);
114
+ }
115
+ return { amount: serialized.amount, currency };
116
+ }
117
+ });
118
+ }
119
+ var moneyCodec = createMoneyCodec(import_money.Currency);
package/dist/zod.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ import { type CurrencyDefinition, Money } from "@storepass/money";
2
+ import { z } from "zod";
3
+ /** Schema for the currency codes included with @storepass/money. */
4
+ export declare const currencyCodeSchema: z.ZodEnum<{
5
+ USD: "USD";
6
+ USDH: "USDH";
7
+ EUR: "EUR";
8
+ GBP: "GBP";
9
+ JPY: "JPY";
10
+ CAD: "CAD";
11
+ AUD: "AUD";
12
+ NZD: "NZD";
13
+ CHF: "CHF";
14
+ SEK: "SEK";
15
+ NOK: "NOK";
16
+ DKK: "DKK";
17
+ PLN: "PLN";
18
+ CZK: "CZK";
19
+ HUF: "HUF";
20
+ BRL: "BRL";
21
+ MXN: "MXN";
22
+ ARS: "ARS";
23
+ CLP: "CLP";
24
+ INR: "INR";
25
+ SGD: "SGD";
26
+ HKD: "HKD";
27
+ KRW: "KRW";
28
+ CNY: "CNY";
29
+ ZAR: "ZAR";
30
+ AED: "AED";
31
+ SAR: "SAR";
32
+ BTC: "BTC";
33
+ }>;
34
+ /** Schema for built-in and custom currency definitions. */
35
+ export declare const currencyDefinitionSchema: z.ZodObject<{
36
+ code: z.ZodString;
37
+ decimalPlaces: z.ZodNumber;
38
+ numericCode: z.ZodOptional<z.ZodNumber>;
39
+ iso4217: z.ZodOptional<z.ZodBoolean>;
40
+ displayCode: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>;
42
+ /**
43
+ * Schema for Money's JSON-safe wire representation.
44
+ *
45
+ * `amount` is a safe integer in minor units. Custom currency codes are allowed.
46
+ */
47
+ export declare const serializedMoneySchema: z.ZodObject<{
48
+ amount: z.ZodNumber;
49
+ currency: z.ZodString;
50
+ }, z.core.$strip>;
51
+ /** Short alias for serializedMoneySchema. */
52
+ export declare const moneySchema: z.ZodObject<{
53
+ amount: z.ZodNumber;
54
+ currency: z.ZodString;
55
+ }, z.core.$strip>;
56
+ /** Serialized Money restricted to the package's built-in currencies. */
57
+ export declare const builtInSerializedMoneySchema: z.ZodObject<{
58
+ amount: z.ZodNumber;
59
+ currency: z.ZodEnum<{
60
+ USD: "USD";
61
+ USDH: "USDH";
62
+ EUR: "EUR";
63
+ GBP: "GBP";
64
+ JPY: "JPY";
65
+ CAD: "CAD";
66
+ AUD: "AUD";
67
+ NZD: "NZD";
68
+ CHF: "CHF";
69
+ SEK: "SEK";
70
+ NOK: "NOK";
71
+ DKK: "DKK";
72
+ PLN: "PLN";
73
+ CZK: "CZK";
74
+ HUF: "HUF";
75
+ BRL: "BRL";
76
+ MXN: "MXN";
77
+ ARS: "ARS";
78
+ CLP: "CLP";
79
+ INR: "INR";
80
+ SGD: "SGD";
81
+ HKD: "HKD";
82
+ KRW: "KRW";
83
+ CNY: "CNY";
84
+ ZAR: "ZAR";
85
+ AED: "AED";
86
+ SAR: "SAR";
87
+ BTC: "BTC";
88
+ }>;
89
+ }, z.core.$strip>;
90
+ /** Schema for an already-hydrated Money instance. */
91
+ export declare const moneyInstanceSchema: z.ZodCustom<Money, Money>;
92
+ export type BuiltInSerializedMoney = z.infer<typeof builtInSerializedMoneySchema>;
93
+ export type CurrencyRegistry = Readonly<Record<string, CurrencyDefinition>>;
94
+ /**
95
+ * Creates a bidirectional Zod codec for a currency registry.
96
+ *
97
+ * Decoding turns serialized minor units into Money. Encoding calls toJSON and
98
+ * rejects Money values whose currency is not represented by the registry with
99
+ * the same precision.
100
+ */
101
+ export declare function createMoneyCodec<const TCurrencyRegistry extends CurrencyRegistry>(currencies: TCurrencyRegistry): z.ZodCodec<z.ZodObject<{
102
+ amount: z.ZodNumber;
103
+ currency: z.ZodCustom<Extract<keyof TCurrencyRegistry, string>, Extract<keyof TCurrencyRegistry, string>>;
104
+ }, z.core.$strip>, z.ZodCustom<Money, Money>>;
105
+ /** Codec for the currencies included with @storepass/money. */
106
+ export declare const moneyCodec: z.ZodCodec<z.ZodObject<{
107
+ amount: z.ZodNumber;
108
+ currency: z.ZodCustom<"USD" | "USDH" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD" | "NZD" | "CHF" | "SEK" | "NOK" | "DKK" | "PLN" | "CZK" | "HUF" | "BRL" | "MXN" | "ARS" | "CLP" | "INR" | "SGD" | "HKD" | "KRW" | "CNY" | "ZAR" | "AED" | "SAR" | "BTC", "USD" | "USDH" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD" | "NZD" | "CHF" | "SEK" | "NOK" | "DKK" | "PLN" | "CZK" | "HUF" | "BRL" | "MXN" | "ARS" | "CLP" | "INR" | "SGD" | "HKD" | "KRW" | "CNY" | "ZAR" | "AED" | "SAR" | "BTC">;
109
+ }, z.core.$strip>, z.ZodCustom<Money, Money>>;
package/dist/zod.mjs ADDED
@@ -0,0 +1,83 @@
1
+ // src/zod.ts
2
+ import {
3
+ Currency,
4
+ defineCurrency,
5
+ Money
6
+ } from "@storepass/money";
7
+ import { z } from "zod";
8
+ var builtInCurrencyCodes = Object.keys(Currency);
9
+ var currencyCodeSchema = z.enum(builtInCurrencyCodes);
10
+ var currencyDefinitionSchema = z.object({
11
+ code: z.string().min(1),
12
+ decimalPlaces: z.number().int().nonnegative(),
13
+ numericCode: z.number().int().min(0).max(999).optional(),
14
+ iso4217: z.boolean().optional(),
15
+ displayCode: z.string().regex(/^[A-Z]{3}$/).optional()
16
+ }).superRefine((currency, context) => {
17
+ try {
18
+ defineCurrency(currency.code, currency.decimalPlaces, {
19
+ numericCode: currency.numericCode,
20
+ iso4217: currency.iso4217,
21
+ displayCode: currency.displayCode
22
+ });
23
+ } catch (error) {
24
+ context.addIssue({
25
+ code: "custom",
26
+ message: error instanceof Error ? error.message : "Invalid currency definition"
27
+ });
28
+ }
29
+ });
30
+ var serializedMoneySchema = z.object({
31
+ amount: z.number().int(),
32
+ currency: z.string().min(1)
33
+ });
34
+ var moneySchema = serializedMoneySchema;
35
+ var builtInSerializedMoneySchema = serializedMoneySchema.extend({
36
+ currency: currencyCodeSchema
37
+ });
38
+ var moneyInstanceSchema = z.custom((value) => value instanceof Money, { error: "Expected Money instance" });
39
+ function assertCurrencyRegistry(currencies) {
40
+ for (const [code, currency] of Object.entries(currencies)) {
41
+ if (currency.code !== code) {
42
+ throw new Error(`Currency registry key "${code}" does not match definition code "${currency.code}"`);
43
+ }
44
+ currencyDefinitionSchema.parse(currency);
45
+ }
46
+ }
47
+ function createMoneyCodec(currencies) {
48
+ assertCurrencyRegistry(currencies);
49
+ const registeredCurrencyCodes = new Set(Object.keys(currencies));
50
+ const registeredCurrencyCodeSchema = z.custom((value) => typeof value === "string" && registeredCurrencyCodes.has(value), { error: "Unknown currency code" });
51
+ const registeredSerializedMoneySchema = serializedMoneySchema.extend({
52
+ currency: registeredCurrencyCodeSchema
53
+ });
54
+ return z.codec(registeredSerializedMoneySchema, moneyInstanceSchema, {
55
+ decode: ({ amount, currency }) => {
56
+ const registeredCurrency = currencies[currency];
57
+ if (registeredCurrency === undefined) {
58
+ throw new Error(`Unknown currency code: ${currency}`);
59
+ }
60
+ return Money.fromMinor(registeredCurrency, amount);
61
+ },
62
+ encode: (money) => {
63
+ const serialized = money.toJSON();
64
+ const currency = serialized.currency;
65
+ const registeredCurrency = currencies[currency];
66
+ if (registeredCurrency === undefined || registeredCurrency.decimalPlaces !== money.currency.decimalPlaces) {
67
+ throw new Error(`Money currency ${serialized.currency} is not registered with matching precision`);
68
+ }
69
+ return { amount: serialized.amount, currency };
70
+ }
71
+ });
72
+ }
73
+ var moneyCodec = createMoneyCodec(Currency);
74
+ export {
75
+ serializedMoneySchema,
76
+ moneySchema,
77
+ moneyInstanceSchema,
78
+ moneyCodec,
79
+ currencyDefinitionSchema,
80
+ currencyCodeSchema,
81
+ createMoneyCodec,
82
+ builtInSerializedMoneySchema
83
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storepass/money",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Money primitive library",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -15,6 +15,16 @@
15
15
  "types": "./dist/index.d.ts",
16
16
  "default": "./dist/index.cjs"
17
17
  }
18
+ },
19
+ "./zod": {
20
+ "import": {
21
+ "types": "./dist/zod.d.ts",
22
+ "default": "./dist/zod.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/zod.d.ts",
26
+ "default": "./dist/zod.cjs"
27
+ }
18
28
  }
19
29
  },
20
30
  "files": [
@@ -22,8 +32,8 @@
22
32
  ],
23
33
  "scripts": {
24
34
  "build": "bun run build:esm && bun run build:cjs && bun run build:types",
25
- "build:esm": "bun build ./src/index.ts --outfile ./dist/index.mjs --format esm --target node",
26
- "build:cjs": "bun build ./src/index.ts --outfile ./dist/index.cjs --format cjs --target node",
35
+ "build:esm": "bun build ./src/index.ts --outfile ./dist/index.mjs --format esm --target node && bun build ./src/zod.ts --outfile ./dist/zod.mjs --format esm --target node --external '@storepass/money' --external zod",
36
+ "build:cjs": "bun build ./src/index.ts --outfile ./dist/index.cjs --format cjs --target node && bun build ./src/zod.ts --outfile ./dist/zod.cjs --format cjs --target node --external '@storepass/money' --external zod",
27
37
  "build:types": "tsc -p tsconfig.build.json",
28
38
  "test": "bun test",
29
39
  "typecheck": "tsc --noEmit",
@@ -54,9 +64,18 @@
54
64
  "registry": "https://registry.npmjs.org",
55
65
  "access": "public"
56
66
  },
67
+ "peerDependencies": {
68
+ "zod": "^4.1.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "zod": {
72
+ "optional": true
73
+ }
74
+ },
57
75
  "devDependencies": {
58
76
  "@biomejs/biome": "2.3.11",
59
77
  "@types/bun": "latest",
60
- "typescript": "^5.9.3"
78
+ "typescript": "^5.9.3",
79
+ "zod": "^4.4.3"
61
80
  }
62
81
  }