finprim 0.1.1 → 0.1.2
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 +73 -4
- package/dist/{card-D3WC2rzV.d.mts → card-D2-7wbam.d.mts} +8 -1
- package/dist/{card-D3WC2rzV.d.ts → card-D2-7wbam.d.ts} +8 -1
- package/dist/index.d.mts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +207 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +200 -30
- package/dist/index.mjs.map +1 -1
- package/dist/nest/index.d.mts +18 -0
- package/dist/nest/index.d.ts +18 -0
- package/dist/nest/index.js +448 -0
- package/dist/nest/index.js.map +1 -0
- package/dist/nest/index.mjs +438 -0
- package/dist/nest/index.mjs.map +1 -0
- package/dist/react/index.d.mts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +36 -20
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +36 -20
- package/dist/react/index.mjs.map +1 -1
- package/dist/zod/index.d.mts +3 -1
- package/dist/zod/index.d.ts +3 -1
- package/dist/zod/index.js +124 -25
- package/dist/zod/index.js.map +1 -1
- package/dist/zod/index.mjs +123 -26
- package/dist/zod/index.mjs.map +1 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -20,10 +20,15 @@ finprim is the open source version of what your team has already written three t
|
|
|
20
20
|
- ✅ UK sort code and account number validation
|
|
21
21
|
- ✅ BIC/SWIFT validation
|
|
22
22
|
- ✅ Card number validation (Luhn, network detection, formatting)
|
|
23
|
+
- ✅ EU VAT number format validation (member states)
|
|
24
|
+
- ✅ US ABA routing number validation
|
|
25
|
+
- ✅ Loan/EMI calculation and schedule
|
|
26
|
+
- ✅ Format-only helpers (IBAN, sort code, account number) for display
|
|
23
27
|
- ✅ Currency validation and formatting with locale support
|
|
24
28
|
- ✅ Branded types for compile-time correctness
|
|
25
29
|
- ✅ Zod schemas out of the box
|
|
26
30
|
- ✅ Optional React hooks for form inputs
|
|
31
|
+
- ✅ Optional NestJS validation pipes
|
|
27
32
|
- ✅ Zero dependencies at the core
|
|
28
33
|
- ✅ Tree-shakeable ESM and CJS builds
|
|
29
34
|
- ✅ Fully typed
|
|
@@ -56,6 +61,13 @@ import {
|
|
|
56
61
|
validateBIC,
|
|
57
62
|
validateCardNumber,
|
|
58
63
|
validateCurrencyCode,
|
|
64
|
+
validateEUVAT,
|
|
65
|
+
validateUSRoutingNumber,
|
|
66
|
+
formatIBAN,
|
|
67
|
+
formatSortCode,
|
|
68
|
+
formatUKAccountNumber,
|
|
69
|
+
calculateEMI,
|
|
70
|
+
getLoanSchedule,
|
|
59
71
|
} from 'finprim'
|
|
60
72
|
|
|
61
73
|
const iban = validateIBAN('GB29NWBK60161331926819')
|
|
@@ -69,6 +81,19 @@ const account = validateUKAccountNumber('31926819')
|
|
|
69
81
|
|
|
70
82
|
const card = validateCardNumber('4532015112830366')
|
|
71
83
|
// { valid: true, value: '...', formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }
|
|
84
|
+
|
|
85
|
+
const vat = validateEUVAT('DE123456789')
|
|
86
|
+
// { valid: true, value: 'DE123456789', formatted: 'DE 123456789', countryCode: 'DE' }
|
|
87
|
+
|
|
88
|
+
const routing = validateUSRoutingNumber('021000021')
|
|
89
|
+
// { valid: true, value: '021000021', formatted: '021000021' }
|
|
90
|
+
|
|
91
|
+
formatIBAN('GB29NWBK60161331926819') // 'GB29 NWBK 6016 1331 9268 19'
|
|
92
|
+
formatSortCode('601613') // '60-16-13'
|
|
93
|
+
formatUKAccountNumber('31926819') // '3192 6819'
|
|
94
|
+
|
|
95
|
+
const emi = calculateEMI(100_000, 10, 12) // monthly payment
|
|
96
|
+
const schedule = getLoanSchedule(100_000, 10, 12) // array of { month, payment, principal, interest, balance }
|
|
72
97
|
```
|
|
73
98
|
|
|
74
99
|
### Currency Formatting
|
|
@@ -101,7 +126,7 @@ if (iban.valid) {
|
|
|
101
126
|
### Zod Schemas
|
|
102
127
|
|
|
103
128
|
```ts
|
|
104
|
-
import { ibanSchema, sortCodeSchema, accountNumberSchema, currencySchema } from 'finprim/zod'
|
|
129
|
+
import { ibanSchema, sortCodeSchema, accountNumberSchema, currencySchema, vatSchema, routingNumberSchema } from 'finprim/zod'
|
|
105
130
|
|
|
106
131
|
const PaymentSchema = z.object({
|
|
107
132
|
iban: ibanSchema,
|
|
@@ -132,6 +157,20 @@ function PaymentForm() {
|
|
|
132
157
|
}
|
|
133
158
|
```
|
|
134
159
|
|
|
160
|
+
### NestJS Pipes
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { IbanValidationPipe, SortCodeValidationPipe, createValidationPipe } from 'finprim/nest'
|
|
164
|
+
import { validateIBAN } from 'finprim'
|
|
165
|
+
|
|
166
|
+
@Get('iban/:iban')
|
|
167
|
+
findByIban(@Param('iban', IbanValidationPipe) iban: string) {
|
|
168
|
+
return this.service.findByIban(iban)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const MyPipe = createValidationPipe(validateIBAN)
|
|
172
|
+
```
|
|
173
|
+
|
|
135
174
|
---
|
|
136
175
|
|
|
137
176
|
## API Reference
|
|
@@ -146,8 +185,25 @@ function PaymentForm() {
|
|
|
146
185
|
| `validateCurrencyCode(input)` | `string` | `ValidationResult<CurrencyCode>` |
|
|
147
186
|
| `validateBIC(input)` | `string` | `ValidationResult<BIC>` |
|
|
148
187
|
| `validateCardNumber(input)` | `string` | `CardValidationResult` (includes `network`, `last4` when valid) |
|
|
188
|
+
| `validateEUVAT(input)` | `string` | `VATValidationResult` (includes `countryCode` when valid) |
|
|
189
|
+
| `validateUSRoutingNumber(input)` | `string` | `ValidationResult<RoutingNumber>` |
|
|
149
190
|
|
|
150
|
-
### Formatting
|
|
191
|
+
### Formatting & display
|
|
192
|
+
|
|
193
|
+
| Function | Input | Returns |
|
|
194
|
+
|----------|-------|---------|
|
|
195
|
+
| `formatIBAN(input)` | `string` | `string` (space-separated, no validation) |
|
|
196
|
+
| `formatSortCode(input)` | `string` | `string` (XX-XX-XX) |
|
|
197
|
+
| `formatUKAccountNumber(input)` | `string` | `string` (XXXX XXXX) |
|
|
198
|
+
|
|
199
|
+
### Loan
|
|
200
|
+
|
|
201
|
+
| Function | Input | Returns |
|
|
202
|
+
|----------|-------|---------|
|
|
203
|
+
| `calculateEMI(principal, annualRatePercent, months)` | `number`, `number`, `number` | `number` |
|
|
204
|
+
| `getLoanSchedule(principal, annualRatePercent, months)` | `number`, `number`, `number` | `LoanScheduleEntry[]` |
|
|
205
|
+
|
|
206
|
+
### Formatting (currency)
|
|
151
207
|
|
|
152
208
|
| Function | Input | Returns |
|
|
153
209
|
|----------|-------|---------|
|
|
@@ -165,6 +221,7 @@ Validation results include a `formatted` string when valid (e.g. IBAN and card n
|
|
|
165
221
|
| `finprim` | Core validators and formatters | none |
|
|
166
222
|
| `finprim/zod` | Zod schemas | `zod` |
|
|
167
223
|
| `finprim/react` | React hooks | `react` |
|
|
224
|
+
| `finprim/nest` | NestJS validation pipes | `@nestjs/common` |
|
|
168
225
|
|
|
169
226
|
---
|
|
170
227
|
|
|
@@ -182,8 +239,11 @@ Validation results include a `formatted` string when valid (e.g. IBAN and card n
|
|
|
182
239
|
|
|
183
240
|
- [x] SWIFT / BIC validation
|
|
184
241
|
- [x] Luhn algorithm for card number validation
|
|
185
|
-
- [
|
|
186
|
-
- [
|
|
242
|
+
- [x] EU VAT number validation
|
|
243
|
+
- [x] NestJS pipe integration
|
|
244
|
+
- [x] US routing number validation
|
|
245
|
+
- [x] Loan/EMI calculation
|
|
246
|
+
- [x] Format-only helpers
|
|
187
247
|
- [ ] More locale coverage
|
|
188
248
|
|
|
189
249
|
---
|
|
@@ -202,6 +262,15 @@ npm run dev
|
|
|
202
262
|
|
|
203
263
|
---
|
|
204
264
|
|
|
265
|
+
## Security
|
|
266
|
+
|
|
267
|
+
- **Input length**: All string validators reject input longer than 256 characters to limit memory and CPU use.
|
|
268
|
+
- **Type checking**: Validators require non-empty strings; numeric helpers (e.g. loan/currency) require finite numbers and sane bounds.
|
|
269
|
+
- **No sensitive logging**: The library does not log or persist input; use it in a way that avoids logging full card or account numbers.
|
|
270
|
+
- **Format helpers**: `formatIBAN`, `formatSortCode`, and `formatUKAccountNumber` cap input length and accept only strings to avoid abuse.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
205
274
|
## License
|
|
206
275
|
|
|
207
276
|
MIT
|
|
@@ -8,6 +8,8 @@ type AccountNumber = Brand<string, 'AccountNumber'>;
|
|
|
8
8
|
type CurrencyCode = Brand<string, 'CurrencyCode'>;
|
|
9
9
|
type BIC = Brand<string, 'BIC'>;
|
|
10
10
|
type CardNumber = Brand<string, 'CardNumber'>;
|
|
11
|
+
type VATNumber = Brand<string, 'VATNumber'>;
|
|
12
|
+
type RoutingNumber = Brand<string, 'RoutingNumber'>;
|
|
11
13
|
type SupportedCurrency = 'GBP' | 'EUR' | 'USD' | 'JPY' | 'CHF' | 'CAD' | 'AUD' | 'NZD';
|
|
12
14
|
type ValidationSuccess<T> = {
|
|
13
15
|
valid: true;
|
|
@@ -20,6 +22,7 @@ type ValidationFailure = {
|
|
|
20
22
|
};
|
|
21
23
|
type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
|
|
22
24
|
declare function isValidationSuccess<T>(result: ValidationResult<T>): result is ValidationSuccess<T>;
|
|
25
|
+
declare function isValidationFailure<T>(result: ValidationResult<T>): result is ValidationFailure;
|
|
23
26
|
type IBANValidationSuccess = ValidationSuccess<IBAN> & {
|
|
24
27
|
countryCode: string;
|
|
25
28
|
};
|
|
@@ -33,6 +36,10 @@ type MoneyResult = {
|
|
|
33
36
|
valid: false;
|
|
34
37
|
error: string;
|
|
35
38
|
};
|
|
39
|
+
type VATValidationSuccess = ValidationSuccess<VATNumber> & {
|
|
40
|
+
countryCode: string;
|
|
41
|
+
};
|
|
42
|
+
type VATValidationResult = VATValidationSuccess | ValidationFailure;
|
|
36
43
|
|
|
37
44
|
type CardNetwork = 'Visa' | 'Mastercard' | 'Amex' | 'Discover' | 'Unknown';
|
|
38
45
|
type CardValidationResult = {
|
|
@@ -47,4 +54,4 @@ type CardValidationResult = {
|
|
|
47
54
|
};
|
|
48
55
|
declare function validateCardNumber(input: string): CardValidationResult;
|
|
49
56
|
|
|
50
|
-
export { type AccountNumber as A, type BIC as B, type CurrencyCode as C, type IBANValidationResult as I, type MoneyResult as M, type SortCode as S, type ValidationResult as V, type SupportedCurrency as a, type
|
|
57
|
+
export { type AccountNumber as A, type BIC as B, type CurrencyCode as C, type IBANValidationResult as I, type MoneyResult as M, type RoutingNumber as R, type SortCode as S, type ValidationResult as V, type SupportedCurrency as a, type VATValidationResult as b, type CardNetwork as c, type CardNumber as d, type CardValidationResult as e, type IBAN as f, type IBANValidationSuccess as g, type VATNumber as h, type VATValidationSuccess as i, type ValidationFailure as j, type ValidationSuccess as k, isValidationFailure as l, isValidationSuccess as m, validateCardNumber as v };
|
|
@@ -8,6 +8,8 @@ type AccountNumber = Brand<string, 'AccountNumber'>;
|
|
|
8
8
|
type CurrencyCode = Brand<string, 'CurrencyCode'>;
|
|
9
9
|
type BIC = Brand<string, 'BIC'>;
|
|
10
10
|
type CardNumber = Brand<string, 'CardNumber'>;
|
|
11
|
+
type VATNumber = Brand<string, 'VATNumber'>;
|
|
12
|
+
type RoutingNumber = Brand<string, 'RoutingNumber'>;
|
|
11
13
|
type SupportedCurrency = 'GBP' | 'EUR' | 'USD' | 'JPY' | 'CHF' | 'CAD' | 'AUD' | 'NZD';
|
|
12
14
|
type ValidationSuccess<T> = {
|
|
13
15
|
valid: true;
|
|
@@ -20,6 +22,7 @@ type ValidationFailure = {
|
|
|
20
22
|
};
|
|
21
23
|
type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
|
|
22
24
|
declare function isValidationSuccess<T>(result: ValidationResult<T>): result is ValidationSuccess<T>;
|
|
25
|
+
declare function isValidationFailure<T>(result: ValidationResult<T>): result is ValidationFailure;
|
|
23
26
|
type IBANValidationSuccess = ValidationSuccess<IBAN> & {
|
|
24
27
|
countryCode: string;
|
|
25
28
|
};
|
|
@@ -33,6 +36,10 @@ type MoneyResult = {
|
|
|
33
36
|
valid: false;
|
|
34
37
|
error: string;
|
|
35
38
|
};
|
|
39
|
+
type VATValidationSuccess = ValidationSuccess<VATNumber> & {
|
|
40
|
+
countryCode: string;
|
|
41
|
+
};
|
|
42
|
+
type VATValidationResult = VATValidationSuccess | ValidationFailure;
|
|
36
43
|
|
|
37
44
|
type CardNetwork = 'Visa' | 'Mastercard' | 'Amex' | 'Discover' | 'Unknown';
|
|
38
45
|
type CardValidationResult = {
|
|
@@ -47,4 +54,4 @@ type CardValidationResult = {
|
|
|
47
54
|
};
|
|
48
55
|
declare function validateCardNumber(input: string): CardValidationResult;
|
|
49
56
|
|
|
50
|
-
export { type AccountNumber as A, type BIC as B, type CurrencyCode as C, type IBANValidationResult as I, type MoneyResult as M, type SortCode as S, type ValidationResult as V, type SupportedCurrency as a, type
|
|
57
|
+
export { type AccountNumber as A, type BIC as B, type CurrencyCode as C, type IBANValidationResult as I, type MoneyResult as M, type RoutingNumber as R, type SortCode as S, type ValidationResult as V, type SupportedCurrency as a, type VATValidationResult as b, type CardNetwork as c, type CardNumber as d, type CardValidationResult as e, type IBAN as f, type IBANValidationSuccess as g, type VATNumber as h, type VATValidationSuccess as i, type ValidationFailure as j, type ValidationSuccess as k, isValidationFailure as l, isValidationSuccess as m, validateCardNumber as v };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { I as IBANValidationResult, V as ValidationResult, A as AccountNumber, S as SortCode, a as SupportedCurrency, M as MoneyResult, C as CurrencyCode, B as BIC } from './card-
|
|
2
|
-
export {
|
|
1
|
+
import { I as IBANValidationResult, V as ValidationResult, A as AccountNumber, S as SortCode, a as SupportedCurrency, M as MoneyResult, C as CurrencyCode, B as BIC, b as VATValidationResult, R as RoutingNumber } from './card-D2-7wbam.mjs';
|
|
2
|
+
export { c as CardNetwork, d as CardNumber, e as CardValidationResult, f as IBAN, g as IBANValidationSuccess, h as VATNumber, i as VATValidationSuccess, j as ValidationFailure, k as ValidationSuccess, l as isValidationFailure, m as isValidationSuccess, v as validateCardNumber } from './card-D2-7wbam.mjs';
|
|
3
3
|
|
|
4
|
+
declare function formatIBAN(input: string): string;
|
|
4
5
|
declare function validateIBAN(input: string): IBANValidationResult;
|
|
5
6
|
|
|
7
|
+
declare function formatSortCode(input: string): string;
|
|
8
|
+
declare function formatUKAccountNumber(input: string): string;
|
|
6
9
|
declare function validateUKSortCode(input: string): ValidationResult<SortCode>;
|
|
7
10
|
declare function validateUKAccountNumber(input: string): ValidationResult<AccountNumber>;
|
|
8
11
|
|
|
@@ -13,4 +16,18 @@ declare function parseMoney(input: string): MoneyResult;
|
|
|
13
16
|
|
|
14
17
|
declare function validateBIC(input: string): ValidationResult<BIC>;
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
declare function validateEUVAT(input: string): VATValidationResult;
|
|
20
|
+
|
|
21
|
+
declare function validateUSRoutingNumber(input: string): ValidationResult<RoutingNumber>;
|
|
22
|
+
|
|
23
|
+
type LoanScheduleEntry = {
|
|
24
|
+
month: number;
|
|
25
|
+
payment: number;
|
|
26
|
+
principal: number;
|
|
27
|
+
interest: number;
|
|
28
|
+
balance: number;
|
|
29
|
+
};
|
|
30
|
+
declare function calculateEMI(principal: number, annualRatePercent: number, months: number): number;
|
|
31
|
+
declare function getLoanSchedule(principal: number, annualRatePercent: number, months: number): LoanScheduleEntry[];
|
|
32
|
+
|
|
33
|
+
export { AccountNumber, BIC, CurrencyCode, IBANValidationResult, type LoanScheduleEntry, MoneyResult, RoutingNumber, SUPPORTED_CURRENCIES, SortCode, SupportedCurrency, VATValidationResult, ValidationResult, calculateEMI, formatCurrency, formatIBAN, formatSortCode, formatUKAccountNumber, getLoanSchedule, parseMoney, validateBIC, validateCurrencyCode, validateEUVAT, validateIBAN, validateUKAccountNumber, validateUKSortCode, validateUSRoutingNumber };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { I as IBANValidationResult, V as ValidationResult, A as AccountNumber, S as SortCode, a as SupportedCurrency, M as MoneyResult, C as CurrencyCode, B as BIC } from './card-
|
|
2
|
-
export {
|
|
1
|
+
import { I as IBANValidationResult, V as ValidationResult, A as AccountNumber, S as SortCode, a as SupportedCurrency, M as MoneyResult, C as CurrencyCode, B as BIC, b as VATValidationResult, R as RoutingNumber } from './card-D2-7wbam.js';
|
|
2
|
+
export { c as CardNetwork, d as CardNumber, e as CardValidationResult, f as IBAN, g as IBANValidationSuccess, h as VATNumber, i as VATValidationSuccess, j as ValidationFailure, k as ValidationSuccess, l as isValidationFailure, m as isValidationSuccess, v as validateCardNumber } from './card-D2-7wbam.js';
|
|
3
3
|
|
|
4
|
+
declare function formatIBAN(input: string): string;
|
|
4
5
|
declare function validateIBAN(input: string): IBANValidationResult;
|
|
5
6
|
|
|
7
|
+
declare function formatSortCode(input: string): string;
|
|
8
|
+
declare function formatUKAccountNumber(input: string): string;
|
|
6
9
|
declare function validateUKSortCode(input: string): ValidationResult<SortCode>;
|
|
7
10
|
declare function validateUKAccountNumber(input: string): ValidationResult<AccountNumber>;
|
|
8
11
|
|
|
@@ -13,4 +16,18 @@ declare function parseMoney(input: string): MoneyResult;
|
|
|
13
16
|
|
|
14
17
|
declare function validateBIC(input: string): ValidationResult<BIC>;
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
declare function validateEUVAT(input: string): VATValidationResult;
|
|
20
|
+
|
|
21
|
+
declare function validateUSRoutingNumber(input: string): ValidationResult<RoutingNumber>;
|
|
22
|
+
|
|
23
|
+
type LoanScheduleEntry = {
|
|
24
|
+
month: number;
|
|
25
|
+
payment: number;
|
|
26
|
+
principal: number;
|
|
27
|
+
interest: number;
|
|
28
|
+
balance: number;
|
|
29
|
+
};
|
|
30
|
+
declare function calculateEMI(principal: number, annualRatePercent: number, months: number): number;
|
|
31
|
+
declare function getLoanSchedule(principal: number, annualRatePercent: number, months: number): LoanScheduleEntry[];
|
|
32
|
+
|
|
33
|
+
export { AccountNumber, BIC, CurrencyCode, IBANValidationResult, type LoanScheduleEntry, MoneyResult, RoutingNumber, SUPPORTED_CURRENCIES, SortCode, SupportedCurrency, VATValidationResult, ValidationResult, calculateEMI, formatCurrency, formatIBAN, formatSortCode, formatUKAccountNumber, getLoanSchedule, parseMoney, validateBIC, validateCurrencyCode, validateEUVAT, validateIBAN, validateUKAccountNumber, validateUKSortCode, validateUSRoutingNumber };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/_guard.ts
|
|
4
|
+
var MAX_SAFE_INPUT_LENGTH = 256;
|
|
5
|
+
function guardStringInput(input, label = "Input") {
|
|
6
|
+
if (input == null || typeof input !== "string") {
|
|
7
|
+
return { ok: false, error: `${label} must be a non-empty string` };
|
|
8
|
+
}
|
|
9
|
+
if (input.length === 0) {
|
|
10
|
+
return { ok: false, error: `${label} must be a non-empty string` };
|
|
11
|
+
}
|
|
12
|
+
if (input.length > MAX_SAFE_INPUT_LENGTH) {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
error: `${label} must not exceed ${MAX_SAFE_INPUT_LENGTH} characters`
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return { ok: true, value: input };
|
|
19
|
+
}
|
|
20
|
+
function guardNumber(value, options) {
|
|
21
|
+
const label = options.label ?? "Value";
|
|
22
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
23
|
+
return { ok: false, error: `${label} must be a finite number` };
|
|
24
|
+
}
|
|
25
|
+
if (Number.isNaN(value)) {
|
|
26
|
+
return { ok: false, error: `${label} must not be NaN` };
|
|
27
|
+
}
|
|
28
|
+
if (options.integer && !Number.isInteger(value)) {
|
|
29
|
+
return { ok: false, error: `${label} must be an integer` };
|
|
30
|
+
}
|
|
31
|
+
if (options.min !== void 0 && value < options.min) {
|
|
32
|
+
return { ok: false, error: `${label} must be at least ${options.min}` };
|
|
33
|
+
}
|
|
34
|
+
if (options.max !== void 0 && value > options.max) {
|
|
35
|
+
return { ok: false, error: `${label} must be at most ${options.max}` };
|
|
36
|
+
}
|
|
37
|
+
return { ok: true, value };
|
|
38
|
+
}
|
|
39
|
+
|
|
3
40
|
// src/iban.ts
|
|
4
41
|
var IBAN_LENGTHS = {
|
|
5
42
|
AL: 28,
|
|
@@ -86,11 +123,15 @@ function ibanToDigits(iban) {
|
|
|
86
123
|
function formatIBANString(iban) {
|
|
87
124
|
return iban.replace(/(.{4})/g, "$1 ").trim();
|
|
88
125
|
}
|
|
126
|
+
function formatIBAN(input) {
|
|
127
|
+
if (typeof input !== "string") return "";
|
|
128
|
+
const cleaned = input.replace(/\s/g, "").toUpperCase().slice(0, 34);
|
|
129
|
+
return formatIBANString(cleaned);
|
|
130
|
+
}
|
|
89
131
|
function validateIBAN(input) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const cleaned = input.replace(/\s/g, "").toUpperCase();
|
|
132
|
+
const guarded = guardStringInput(input);
|
|
133
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
134
|
+
const cleaned = guarded.value.replace(/\s/g, "").toUpperCase();
|
|
94
135
|
if (cleaned.length < 4) {
|
|
95
136
|
return { valid: false, error: "IBAN is too short" };
|
|
96
137
|
}
|
|
@@ -124,11 +165,22 @@ function validateIBAN(input) {
|
|
|
124
165
|
}
|
|
125
166
|
|
|
126
167
|
// src/sortcode.ts
|
|
168
|
+
function formatSortCode(input) {
|
|
169
|
+
if (typeof input !== "string") return "";
|
|
170
|
+
const digits = input.replace(/[-\s]/g, "").slice(0, 6);
|
|
171
|
+
if (digits.length < 6) return digits;
|
|
172
|
+
return `${digits.slice(0, 2)}-${digits.slice(2, 4)}-${digits.slice(4, 6)}`;
|
|
173
|
+
}
|
|
174
|
+
function formatUKAccountNumber(input) {
|
|
175
|
+
if (typeof input !== "string") return "";
|
|
176
|
+
const digits = input.replace(/\s/g, "").slice(0, 8);
|
|
177
|
+
if (digits.length < 8) return digits;
|
|
178
|
+
return `${digits.slice(0, 4)} ${digits.slice(4, 8)}`;
|
|
179
|
+
}
|
|
127
180
|
function validateUKSortCode(input) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const cleaned = input.replace(/[-\s]/g, "");
|
|
181
|
+
const guarded = guardStringInput(input);
|
|
182
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
183
|
+
const cleaned = guarded.value.replace(/[-\s]/g, "");
|
|
132
184
|
if (!/^\d{6}$/.test(cleaned)) {
|
|
133
185
|
return {
|
|
134
186
|
valid: false,
|
|
@@ -143,10 +195,9 @@ function validateUKSortCode(input) {
|
|
|
143
195
|
};
|
|
144
196
|
}
|
|
145
197
|
function validateUKAccountNumber(input) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const cleaned = input.replace(/\s/g, "");
|
|
198
|
+
const guarded = guardStringInput(input);
|
|
199
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
200
|
+
const cleaned = guarded.value.replace(/\s/g, "");
|
|
150
201
|
if (!/^\d{8}$/.test(cleaned)) {
|
|
151
202
|
return {
|
|
152
203
|
valid: false,
|
|
@@ -190,14 +241,13 @@ var SYMBOL_MAP = {
|
|
|
190
241
|
"CHF": "CHF"
|
|
191
242
|
};
|
|
192
243
|
function validateCurrencyCode(input) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const upper = input.toUpperCase();
|
|
244
|
+
const guarded = guardStringInput(input);
|
|
245
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
246
|
+
const upper = guarded.value.toUpperCase();
|
|
197
247
|
if (!SUPPORTED_CURRENCIES.includes(upper)) {
|
|
198
248
|
return {
|
|
199
249
|
valid: false,
|
|
200
|
-
error: `Unsupported currency code: ${
|
|
250
|
+
error: `Unsupported currency code: ${upper}. Supported: ${SUPPORTED_CURRENCIES.join(", ")}`
|
|
201
251
|
};
|
|
202
252
|
}
|
|
203
253
|
return {
|
|
@@ -207,6 +257,9 @@ function validateCurrencyCode(input) {
|
|
|
207
257
|
};
|
|
208
258
|
}
|
|
209
259
|
function formatCurrency(amount, currency, locale) {
|
|
260
|
+
if (typeof amount !== "number" || !Number.isFinite(amount)) {
|
|
261
|
+
return "";
|
|
262
|
+
}
|
|
210
263
|
const resolvedLocale = locale ?? CURRENCY_LOCALES[currency] ?? "en-GB";
|
|
211
264
|
return new Intl.NumberFormat(resolvedLocale, {
|
|
212
265
|
style: "currency",
|
|
@@ -216,11 +269,10 @@ function formatCurrency(amount, currency, locale) {
|
|
|
216
269
|
}).format(amount);
|
|
217
270
|
}
|
|
218
271
|
function parseMoney(input) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
272
|
+
const guarded = guardStringInput(input);
|
|
273
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
222
274
|
let currency;
|
|
223
|
-
let cleaned =
|
|
275
|
+
let cleaned = guarded.value.trim();
|
|
224
276
|
for (const [symbol, code] of Object.entries(SYMBOL_MAP)) {
|
|
225
277
|
if (cleaned.startsWith(symbol) || cleaned.endsWith(symbol)) {
|
|
226
278
|
currency = code;
|
|
@@ -247,10 +299,9 @@ function parseMoney(input) {
|
|
|
247
299
|
// src/bic.ts
|
|
248
300
|
var BIC_REGEX = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/;
|
|
249
301
|
function validateBIC(input) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const cleaned = input.replace(/\s/g, "").toUpperCase();
|
|
302
|
+
const guarded = guardStringInput(input);
|
|
303
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
304
|
+
const cleaned = guarded.value.replace(/\s/g, "").toUpperCase();
|
|
254
305
|
if (cleaned.length !== 8 && cleaned.length !== 11) {
|
|
255
306
|
return {
|
|
256
307
|
valid: false,
|
|
@@ -289,10 +340,9 @@ function formatCardNumber(digits, network) {
|
|
|
289
340
|
return digits.replace(/(.{4})/g, "$1 ").trim();
|
|
290
341
|
}
|
|
291
342
|
function validateCardNumber(input) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const digits = input.replace(/[\s-]/g, "");
|
|
343
|
+
const guarded = guardStringInput(input);
|
|
344
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
345
|
+
const digits = guarded.value.replace(/[\s-]/g, "");
|
|
296
346
|
if (!/^\d+$/.test(digits)) {
|
|
297
347
|
return { valid: false, error: "Card number must contain only digits" };
|
|
298
348
|
}
|
|
@@ -326,20 +376,148 @@ function validateCardNumber(input) {
|
|
|
326
376
|
};
|
|
327
377
|
}
|
|
328
378
|
|
|
379
|
+
// src/vat.ts
|
|
380
|
+
var EU_VAT_PATTERNS = {
|
|
381
|
+
AT: /^ATU\d{8}$/,
|
|
382
|
+
BE: /^BE0?\d{9}$/,
|
|
383
|
+
BG: /^BG\d{9,10}$/,
|
|
384
|
+
CY: /^CY\d{8}[A-Z]$/,
|
|
385
|
+
CZ: /^CZ\d{8,10}$/,
|
|
386
|
+
DE: /^DE\d{9}$/,
|
|
387
|
+
DK: /^DK\d{8}$/,
|
|
388
|
+
EE: /^EE\d{9}$/,
|
|
389
|
+
EL: /^EL\d{9}$/,
|
|
390
|
+
ES: /^ES[A-Z0-9]\d{7}[A-Z0-9]$/,
|
|
391
|
+
FI: /^FI\d{8}$/,
|
|
392
|
+
FR: /^FR[A-HJ-NP-Z0-9]{2}\d{9}$/,
|
|
393
|
+
GR: /^GR\d{9}$/,
|
|
394
|
+
HR: /^HR\d{11}$/,
|
|
395
|
+
HU: /^HU\d{8}$/,
|
|
396
|
+
IE: /^IE\d[A-Z0-9]\d{5}[A-Z]$|^IE\d{7}[A-W][A-I0-9]?$/,
|
|
397
|
+
IT: /^IT\d{11}$/,
|
|
398
|
+
LT: /^LT\d{9}$|^LT\d{12}$/,
|
|
399
|
+
LU: /^LU\d{8}$/,
|
|
400
|
+
LV: /^LV\d{11}$/,
|
|
401
|
+
MT: /^MT\d{8}$/,
|
|
402
|
+
NL: /^NL\d{9}B\d{2}$/,
|
|
403
|
+
PL: /^PL\d{10}$/,
|
|
404
|
+
PT: /^PT\d{9}$/,
|
|
405
|
+
RO: /^RO\d{2,10}$/,
|
|
406
|
+
SE: /^SE\d{12}$/,
|
|
407
|
+
SI: /^SI\d{8}$/,
|
|
408
|
+
SK: /^SK\d{10}$/
|
|
409
|
+
};
|
|
410
|
+
function validateEUVAT(input) {
|
|
411
|
+
const guarded = guardStringInput(input);
|
|
412
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
413
|
+
const cleaned = guarded.value.replace(/\s/g, "").toUpperCase();
|
|
414
|
+
if (cleaned.length < 4) {
|
|
415
|
+
return { valid: false, error: "VAT number is too short" };
|
|
416
|
+
}
|
|
417
|
+
const countryCode = cleaned.slice(0, 2);
|
|
418
|
+
const pattern = EU_VAT_PATTERNS[countryCode];
|
|
419
|
+
if (!pattern) {
|
|
420
|
+
return { valid: false, error: `Unsupported EU VAT country code: ${countryCode}` };
|
|
421
|
+
}
|
|
422
|
+
if (!pattern.test(cleaned)) {
|
|
423
|
+
return { valid: false, error: `Invalid VAT format for ${countryCode}` };
|
|
424
|
+
}
|
|
425
|
+
const formatted = `${countryCode} ${cleaned.slice(2)}`;
|
|
426
|
+
return {
|
|
427
|
+
valid: true,
|
|
428
|
+
value: cleaned,
|
|
429
|
+
formatted,
|
|
430
|
+
countryCode
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/routing.ts
|
|
435
|
+
function routingChecksum(digits) {
|
|
436
|
+
if (digits.length !== 9) return false;
|
|
437
|
+
const sum = 3 * (Number(digits[0]) + Number(digits[3]) + Number(digits[6])) + 7 * (Number(digits[1]) + Number(digits[4]) + Number(digits[7])) + Number(digits[2]) + Number(digits[5]) + Number(digits[8]);
|
|
438
|
+
return sum % 10 === 0;
|
|
439
|
+
}
|
|
440
|
+
function validateUSRoutingNumber(input) {
|
|
441
|
+
const guarded = guardStringInput(input);
|
|
442
|
+
if (!guarded.ok) return { valid: false, error: guarded.error };
|
|
443
|
+
const cleaned = guarded.value.replace(/\s/g, "");
|
|
444
|
+
if (!/^\d{9}$/.test(cleaned)) {
|
|
445
|
+
return {
|
|
446
|
+
valid: false,
|
|
447
|
+
error: "US routing number must be exactly 9 digits"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (!routingChecksum(cleaned)) {
|
|
451
|
+
return { valid: false, error: "US routing number checksum is invalid" };
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
valid: true,
|
|
455
|
+
value: cleaned,
|
|
456
|
+
formatted: cleaned
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/loan.ts
|
|
461
|
+
var MAX_LOAN_MONTHS = 3600;
|
|
462
|
+
function calculateEMI(principal, annualRatePercent, months) {
|
|
463
|
+
const p = guardNumber(principal, { min: 0, label: "Principal" });
|
|
464
|
+
const m = guardNumber(months, { min: 1, max: MAX_LOAN_MONTHS, integer: true, label: "Months" });
|
|
465
|
+
const rate = guardNumber(annualRatePercent, { min: 0, label: "Annual rate" });
|
|
466
|
+
if (!p.ok || !m.ok || !rate.ok) return 0;
|
|
467
|
+
if (rate.value === 0) return p.value / m.value;
|
|
468
|
+
const r = rate.value / 100 / 12;
|
|
469
|
+
return p.value * (r * (1 + r) ** m.value) / ((1 + r) ** m.value - 1);
|
|
470
|
+
}
|
|
471
|
+
function getLoanSchedule(principal, annualRatePercent, months) {
|
|
472
|
+
const emi = calculateEMI(principal, annualRatePercent, months);
|
|
473
|
+
if (emi === 0) return [];
|
|
474
|
+
const p = guardNumber(principal, { min: 0, label: "Principal" });
|
|
475
|
+
const m = guardNumber(months, { min: 1, max: MAX_LOAN_MONTHS, integer: true, label: "Months" });
|
|
476
|
+
const rate = guardNumber(annualRatePercent, { min: 0, label: "Annual rate" });
|
|
477
|
+
if (!p.ok || !m.ok || !rate.ok) return [];
|
|
478
|
+
const r = rate.value / 100 / 12;
|
|
479
|
+
const schedule = [];
|
|
480
|
+
let balance = p.value;
|
|
481
|
+
for (let month = 1; month <= m.value; month++) {
|
|
482
|
+
const interest = balance * r;
|
|
483
|
+
const principalPayment = emi - interest;
|
|
484
|
+
balance = Math.max(0, balance - principalPayment);
|
|
485
|
+
schedule.push({
|
|
486
|
+
month,
|
|
487
|
+
payment: emi,
|
|
488
|
+
principal: principalPayment,
|
|
489
|
+
interest,
|
|
490
|
+
balance
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
return schedule;
|
|
494
|
+
}
|
|
495
|
+
|
|
329
496
|
// src/types.ts
|
|
330
497
|
function isValidationSuccess(result) {
|
|
331
498
|
return result.valid === true;
|
|
332
499
|
}
|
|
500
|
+
function isValidationFailure(result) {
|
|
501
|
+
return result.valid === false;
|
|
502
|
+
}
|
|
333
503
|
|
|
334
504
|
exports.SUPPORTED_CURRENCIES = SUPPORTED_CURRENCIES;
|
|
505
|
+
exports.calculateEMI = calculateEMI;
|
|
335
506
|
exports.formatCurrency = formatCurrency;
|
|
507
|
+
exports.formatIBAN = formatIBAN;
|
|
508
|
+
exports.formatSortCode = formatSortCode;
|
|
509
|
+
exports.formatUKAccountNumber = formatUKAccountNumber;
|
|
510
|
+
exports.getLoanSchedule = getLoanSchedule;
|
|
511
|
+
exports.isValidationFailure = isValidationFailure;
|
|
336
512
|
exports.isValidationSuccess = isValidationSuccess;
|
|
337
513
|
exports.parseMoney = parseMoney;
|
|
338
514
|
exports.validateBIC = validateBIC;
|
|
339
515
|
exports.validateCardNumber = validateCardNumber;
|
|
340
516
|
exports.validateCurrencyCode = validateCurrencyCode;
|
|
517
|
+
exports.validateEUVAT = validateEUVAT;
|
|
341
518
|
exports.validateIBAN = validateIBAN;
|
|
342
519
|
exports.validateUKAccountNumber = validateUKAccountNumber;
|
|
343
520
|
exports.validateUKSortCode = validateUKSortCode;
|
|
521
|
+
exports.validateUSRoutingNumber = validateUSRoutingNumber;
|
|
344
522
|
//# sourceMappingURL=index.js.map
|
|
345
523
|
//# sourceMappingURL=index.js.map
|