finprim 0.1.2 → 0.1.3
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 +137 -118
- package/package.json +18 -2
package/README.md
CHANGED
|
@@ -1,55 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">finprim</h1>
|
|
3
|
+
<p align="center">Financial primitives for modern TypeScript applications.</p>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/finprim"><img src="https://img.shields.io/npm/v/finprim.svg" alt="npm version"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/finprim"><img src="https://img.shields.io/npm/dm/finprim.svg" alt="npm downloads"></a>
|
|
9
|
+
<a href="https://github.com/tintolee/finprim/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/finprim.svg" alt="license"></a>
|
|
10
|
+
<a href="https://github.com/tintolee/finprim"><img src="https://img.shields.io/github/stars/tintolee/finprim?style=social" alt="GitHub stars"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="zero dependencies">
|
|
12
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-blue" alt="TypeScript strict">
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<b>IBAN · BIC/SWIFT · Card · Sort Code · VAT · Routing Number · Loan/EMI · Currency</b><br/>
|
|
17
|
+
One library. Zero dependencies. Fully typed.
|
|
18
|
+
</p>
|
|
2
19
|
|
|
3
|
-
|
|
20
|
+
---
|
|
4
21
|
|
|
5
|
-
|
|
22
|
+
> Every fintech team builds this internally. Sort code validation here, an IBAN check there, a custom currency formatter somewhere else. It's fragmented, inconsistent, and expensive to maintain. **finprim is the open source version of what your team has already written three times.**
|
|
6
23
|
|
|
7
24
|
---
|
|
8
25
|
|
|
9
|
-
##
|
|
26
|
+
## Quick Start
|
|
10
27
|
|
|
11
|
-
|
|
28
|
+
```bash
|
|
29
|
+
npm install finprim
|
|
30
|
+
```
|
|
12
31
|
|
|
13
|
-
|
|
32
|
+
```ts
|
|
33
|
+
import { validateIBAN, validateCardNumber, formatCurrency } from 'finprim'
|
|
34
|
+
|
|
35
|
+
const iban = validateIBAN('GB29NWBK60161331926819')
|
|
36
|
+
// { valid: true, value: 'GB29NWBK60161331926819', formatted: 'GB29 NWBK 6016 1331 9268 19', countryCode: 'GB' }
|
|
37
|
+
|
|
38
|
+
const card = validateCardNumber('4532015112830366')
|
|
39
|
+
// { valid: true, formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }
|
|
40
|
+
|
|
41
|
+
formatCurrency(1000.5, 'GBP', 'en-GB')
|
|
42
|
+
// '£1,000.50'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**That's it.** No config. No setup. Just import and use.
|
|
14
46
|
|
|
15
47
|
---
|
|
16
48
|
|
|
17
|
-
##
|
|
49
|
+
## Why finprim?
|
|
18
50
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
- ✅ Format-only helpers (IBAN, sort code, account number) for display
|
|
27
|
-
- ✅ Currency validation and formatting with locale support
|
|
28
|
-
- ✅ Branded types for compile-time correctness
|
|
29
|
-
- ✅ Zod schemas out of the box
|
|
30
|
-
- ✅ Optional React hooks for form inputs
|
|
31
|
-
- ✅ Optional NestJS validation pipes
|
|
32
|
-
- ✅ Zero dependencies at the core
|
|
33
|
-
- ✅ Tree-shakeable ESM and CJS builds
|
|
34
|
-
- ✅ Fully typed
|
|
51
|
+
| Problem | finprim |
|
|
52
|
+
|---|---|
|
|
53
|
+
| 5 different npm packages for financial validation | **One unified library** |
|
|
54
|
+
| Custom glue code between validators | **Consistent API across all validators** |
|
|
55
|
+
| Runtime type confusion | **Branded TypeScript types** for compile-time safety |
|
|
56
|
+
| No framework integration | **Built-in Zod schemas, React hooks, NestJS pipes** |
|
|
57
|
+
| Heavy dependency trees | **Zero runtime dependencies** |
|
|
35
58
|
|
|
36
59
|
---
|
|
37
60
|
|
|
38
|
-
##
|
|
61
|
+
## Features
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
- **Validators** - IBAN (80+ countries), BIC/SWIFT, UK sort code & account number, card number (Luhn + network detection), EU VAT, US ABA routing number
|
|
64
|
+
- **Loan math** - EMI calculation and full amortization schedules
|
|
65
|
+
- **Formatting** - Display-ready IBAN, sort code, account number, and multi-locale currency formatting
|
|
66
|
+
- **Branded types** - Compile-time correctness that prevents invalid data from flowing through your system
|
|
67
|
+
- **Framework integrations** - Zod schemas, React hooks, NestJS pipes (all optional)
|
|
68
|
+
- **Production-ready** - Input length guards, type checking, no sensitive data logging
|
|
69
|
+
- **Lightweight** - Zero dependencies, tree-shakeable ESM + CJS
|
|
70
|
+
|
|
71
|
+
---
|
|
43
72
|
|
|
44
|
-
|
|
73
|
+
## Integrations
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
75
|
+
finprim works standalone or plugs into your existing stack:
|
|
76
|
+
|
|
77
|
+
| Import path | What it contains | Extra dependency |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `finprim` | Core validators, formatters, loan math | none |
|
|
80
|
+
| `finprim/zod` | Zod schemas for validation pipelines | `zod` |
|
|
81
|
+
| `finprim/react` | React hooks for form inputs | `react` |
|
|
82
|
+
| `finprim/nest` | NestJS validation pipes | `@nestjs/common` |
|
|
49
83
|
|
|
50
84
|
---
|
|
51
85
|
|
|
52
|
-
## Usage
|
|
86
|
+
## Usage Examples
|
|
53
87
|
|
|
54
88
|
### Validation
|
|
55
89
|
|
|
@@ -63,70 +97,75 @@ import {
|
|
|
63
97
|
validateCurrencyCode,
|
|
64
98
|
validateEUVAT,
|
|
65
99
|
validateUSRoutingNumber,
|
|
66
|
-
formatIBAN,
|
|
67
|
-
formatSortCode,
|
|
68
|
-
formatUKAccountNumber,
|
|
69
|
-
calculateEMI,
|
|
70
|
-
getLoanSchedule,
|
|
71
100
|
} from 'finprim'
|
|
72
101
|
|
|
73
|
-
|
|
102
|
+
validateIBAN('GB29NWBK60161331926819')
|
|
74
103
|
// { valid: true, value: 'GB29NWBK60161331926819', formatted: 'GB29 NWBK 6016 1331 9268 19', countryCode: 'GB' }
|
|
75
104
|
|
|
76
|
-
|
|
105
|
+
validateUKSortCode('60-16-13')
|
|
77
106
|
// { valid: true, value: '601613', formatted: '60-16-13' }
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
validateUKAccountNumber('31926819')
|
|
80
109
|
// { valid: true, value: '31926819', formatted: '3192 6819' }
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
// { valid: true,
|
|
111
|
+
validateCardNumber('4532015112830366')
|
|
112
|
+
// { valid: true, formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }
|
|
84
113
|
|
|
85
|
-
|
|
114
|
+
validateEUVAT('DE123456789')
|
|
86
115
|
// { valid: true, value: 'DE123456789', formatted: 'DE 123456789', countryCode: 'DE' }
|
|
87
116
|
|
|
88
|
-
|
|
117
|
+
validateUSRoutingNumber('021000021')
|
|
89
118
|
// { valid: true, value: '021000021', formatted: '021000021' }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Formatting & Display
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { formatIBAN, formatSortCode, formatUKAccountNumber, formatCurrency, parseMoney } from 'finprim'
|
|
90
125
|
|
|
91
126
|
formatIBAN('GB29NWBK60161331926819') // 'GB29 NWBK 6016 1331 9268 19'
|
|
92
|
-
formatSortCode('601613')
|
|
93
|
-
formatUKAccountNumber('31926819')
|
|
127
|
+
formatSortCode('601613') // '60-16-13'
|
|
128
|
+
formatUKAccountNumber('31926819') // '3192 6819'
|
|
129
|
+
|
|
130
|
+
formatCurrency(1000.5, 'GBP', 'en-GB') // '£1,000.50'
|
|
131
|
+
formatCurrency(1000.5, 'EUR', 'de-DE') // '1.000,50 €'
|
|
132
|
+
formatCurrency(1000.5, 'USD', 'en-US') // '$1,000.50'
|
|
94
133
|
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
parseMoney('£1,000.50')
|
|
135
|
+
// { valid: true, amount: 1000.50, currency: 'GBP', formatted: '£1,000.50' }
|
|
97
136
|
```
|
|
98
137
|
|
|
99
|
-
###
|
|
138
|
+
### Loan / EMI Calculation
|
|
100
139
|
|
|
101
140
|
```ts
|
|
102
|
-
import {
|
|
141
|
+
import { calculateEMI, getLoanSchedule } from 'finprim'
|
|
103
142
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
formatCurrency(1000.5, 'USD', 'en-US') // '$1,000.50'
|
|
143
|
+
calculateEMI(100_000, 10, 12)
|
|
144
|
+
// Monthly payment amount
|
|
107
145
|
|
|
108
|
-
|
|
146
|
+
getLoanSchedule(100_000, 10, 12)
|
|
147
|
+
// [{ month, payment, principal, interest, balance }, ...]
|
|
109
148
|
```
|
|
110
149
|
|
|
111
150
|
### Branded Types
|
|
112
151
|
|
|
113
152
|
```ts
|
|
114
|
-
import type { IBAN, SortCode, AccountNumber
|
|
153
|
+
import type { IBAN, SortCode, AccountNumber } from 'finprim'
|
|
115
154
|
|
|
116
155
|
// Invalid data cannot be passed where valid data is expected
|
|
117
|
-
function processPayment(iban: IBAN, amount: number) { ... }
|
|
156
|
+
function processPayment(iban: IBAN, amount: number) { /* ... */ }
|
|
118
157
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
processPayment(iban.value, 100) // iban.value is typed as IBAN
|
|
158
|
+
const result = validateIBAN(input)
|
|
159
|
+
if (result.valid) {
|
|
160
|
+
processPayment(result.value, 100) // result.value is typed as IBAN
|
|
123
161
|
}
|
|
124
162
|
```
|
|
125
163
|
|
|
126
164
|
### Zod Schemas
|
|
127
165
|
|
|
128
166
|
```ts
|
|
129
|
-
import {
|
|
167
|
+
import { z } from 'zod'
|
|
168
|
+
import { ibanSchema, sortCodeSchema, accountNumberSchema, currencySchema } from 'finprim/zod'
|
|
130
169
|
|
|
131
170
|
const PaymentSchema = z.object({
|
|
132
171
|
iban: ibanSchema,
|
|
@@ -139,20 +178,20 @@ const PaymentSchema = z.object({
|
|
|
139
178
|
|
|
140
179
|
### React Hooks
|
|
141
180
|
|
|
142
|
-
```
|
|
181
|
+
```tsx
|
|
143
182
|
import { useIBANInput, useCardNumberInput, useCurrencyInput } from 'finprim/react'
|
|
144
183
|
|
|
145
184
|
function PaymentForm() {
|
|
146
185
|
const iban = useIBANInput()
|
|
147
186
|
const card = useCardNumberInput()
|
|
148
|
-
const
|
|
187
|
+
const currency = useCurrencyInput('GBP', 'en-GB')
|
|
149
188
|
|
|
150
189
|
return (
|
|
151
|
-
|
|
190
|
+
<form>
|
|
152
191
|
<input value={iban.value} onChange={iban.onChange} aria-invalid={iban.valid === false} />
|
|
153
192
|
<input value={card.formatted} onChange={card.onChange} aria-invalid={card.valid === false} />
|
|
154
|
-
<input value={formatted} onChange={onChange} />
|
|
155
|
-
|
|
193
|
+
<input value={currency.formatted} onChange={currency.onChange} />
|
|
194
|
+
</form>
|
|
156
195
|
)
|
|
157
196
|
}
|
|
158
197
|
```
|
|
@@ -168,6 +207,7 @@ findByIban(@Param('iban', IbanValidationPipe) iban: string) {
|
|
|
168
207
|
return this.service.findByIban(iban)
|
|
169
208
|
}
|
|
170
209
|
|
|
210
|
+
// Create a custom pipe from any validator
|
|
171
211
|
const MyPipe = createValidationPipe(validateIBAN)
|
|
172
212
|
```
|
|
173
213
|
|
|
@@ -179,98 +219,77 @@ const MyPipe = createValidationPipe(validateIBAN)
|
|
|
179
219
|
|
|
180
220
|
| Function | Input | Returns |
|
|
181
221
|
|----------|-------|---------|
|
|
182
|
-
| `validateIBAN(input)` | `string` | `IBANValidationResult` (includes `countryCode`
|
|
222
|
+
| `validateIBAN(input)` | `string` | `IBANValidationResult` (includes `countryCode`) |
|
|
183
223
|
| `validateUKSortCode(input)` | `string` | `ValidationResult<SortCode>` |
|
|
184
224
|
| `validateUKAccountNumber(input)` | `string` | `ValidationResult<AccountNumber>` |
|
|
185
225
|
| `validateCurrencyCode(input)` | `string` | `ValidationResult<CurrencyCode>` |
|
|
186
226
|
| `validateBIC(input)` | `string` | `ValidationResult<BIC>` |
|
|
187
|
-
| `validateCardNumber(input)` | `string` | `CardValidationResult` (includes `network`, `last4`
|
|
188
|
-
| `validateEUVAT(input)` | `string` | `VATValidationResult` (includes `countryCode`
|
|
227
|
+
| `validateCardNumber(input)` | `string` | `CardValidationResult` (includes `network`, `last4`) |
|
|
228
|
+
| `validateEUVAT(input)` | `string` | `VATValidationResult` (includes `countryCode`) |
|
|
189
229
|
| `validateUSRoutingNumber(input)` | `string` | `ValidationResult<RoutingNumber>` |
|
|
190
230
|
|
|
191
|
-
### Formatting
|
|
231
|
+
### Formatting
|
|
192
232
|
|
|
193
233
|
| Function | Input | Returns |
|
|
194
234
|
|----------|-------|---------|
|
|
195
|
-
| `formatIBAN(input)` | `string` | `string` (space-separated
|
|
235
|
+
| `formatIBAN(input)` | `string` | `string` (space-separated) |
|
|
196
236
|
| `formatSortCode(input)` | `string` | `string` (XX-XX-XX) |
|
|
197
237
|
| `formatUKAccountNumber(input)` | `string` | `string` (XXXX XXXX) |
|
|
238
|
+
| `formatCurrency(amount, currency, locale?)` | `number`, `SupportedCurrency`, `string?` | `string` |
|
|
239
|
+
| `parseMoney(input)` | `string` | `MoneyResult` |
|
|
198
240
|
|
|
199
241
|
### Loan
|
|
200
242
|
|
|
201
243
|
| Function | Input | Returns |
|
|
202
244
|
|----------|-------|---------|
|
|
203
|
-
| `calculateEMI(principal,
|
|
204
|
-
| `getLoanSchedule(principal,
|
|
205
|
-
|
|
206
|
-
### Formatting (currency)
|
|
207
|
-
|
|
208
|
-
| Function | Input | Returns |
|
|
209
|
-
|----------|-------|---------|
|
|
210
|
-
| `formatCurrency(amount, currency, locale?)` | `number`, `SupportedCurrency`, `string?` | `string` |
|
|
211
|
-
| `parseMoney(input)` | `string` | `MoneyResult` |
|
|
212
|
-
|
|
213
|
-
Validation results include a `formatted` string when valid (e.g. IBAN and card numbers are space-separated).
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Packages
|
|
218
|
-
|
|
219
|
-
| Import path | What it contains | Extra dependency |
|
|
220
|
-
|---|---|---|
|
|
221
|
-
| `finprim` | Core validators and formatters | none |
|
|
222
|
-
| `finprim/zod` | Zod schemas | `zod` |
|
|
223
|
-
| `finprim/react` | React hooks | `react` |
|
|
224
|
-
| `finprim/nest` | NestJS validation pipes | `@nestjs/common` |
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## Tech Stack
|
|
229
|
-
|
|
230
|
-
- TypeScript
|
|
231
|
-
- tsup (build)
|
|
232
|
-
- Vitest (testing)
|
|
233
|
-
- React (optional hooks subpath)
|
|
234
|
-
- Zod (optional schema subpath)
|
|
245
|
+
| `calculateEMI(principal, rate, months)` | `number`, `number`, `number` | `number` |
|
|
246
|
+
| `getLoanSchedule(principal, rate, months)` | `number`, `number`, `number` | `LoanScheduleEntry[]` |
|
|
235
247
|
|
|
236
248
|
---
|
|
237
249
|
|
|
238
250
|
## Roadmap
|
|
239
251
|
|
|
240
|
-
- [x]
|
|
241
|
-
- [x]
|
|
252
|
+
- [x] IBAN validation (80+ countries)
|
|
253
|
+
- [x] BIC/SWIFT validation
|
|
254
|
+
- [x] Card number validation (Luhn + network detection)
|
|
242
255
|
- [x] EU VAT number validation
|
|
243
|
-
- [x] NestJS pipe integration
|
|
244
256
|
- [x] US routing number validation
|
|
257
|
+
- [x] UK sort code and account number validation
|
|
245
258
|
- [x] Loan/EMI calculation
|
|
246
259
|
- [x] Format-only helpers
|
|
260
|
+
- [x] Currency formatting with locale support
|
|
261
|
+
- [x] Branded TypeScript types
|
|
262
|
+
- [x] Zod schema integration
|
|
263
|
+
- [x] React hooks
|
|
264
|
+
- [x] NestJS pipes
|
|
247
265
|
- [ ] More locale coverage
|
|
266
|
+
- [ ] SEPA credit transfer XML generation
|
|
267
|
+
- [ ] ACH file format support
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Security
|
|
272
|
+
|
|
273
|
+
- **Input length** - All string validators reject input longer than 256 characters
|
|
274
|
+
- **Type checking** - Validators require non-empty strings; numeric helpers require finite numbers and sane bounds
|
|
275
|
+
- **No sensitive logging** - The library does not log or persist input
|
|
276
|
+
- **Format helpers** - Cap input length and accept only strings
|
|
248
277
|
|
|
249
278
|
---
|
|
250
279
|
|
|
251
280
|
## Contributing
|
|
252
281
|
|
|
253
|
-
Contributions are welcome
|
|
282
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
254
283
|
|
|
255
284
|
```bash
|
|
256
|
-
git clone https://github.com/
|
|
285
|
+
git clone https://github.com/tintolee/finprim.git
|
|
257
286
|
cd finprim
|
|
258
287
|
npm install
|
|
259
288
|
npm test
|
|
260
|
-
npm run dev
|
|
261
289
|
```
|
|
262
290
|
|
|
263
291
|
---
|
|
264
292
|
|
|
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
|
-
|
|
274
293
|
## License
|
|
275
294
|
|
|
276
295
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "finprim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Financial primitives for modern TypeScript applications",
|
|
5
5
|
"author": "Oluwatosin Adelaja , Silke Nodwell,Mohammead Hassani Sadiq, Nick Goddard ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,12 +13,28 @@
|
|
|
13
13
|
"typescript",
|
|
14
14
|
"iban",
|
|
15
15
|
"sort-code",
|
|
16
|
+
"bic",
|
|
17
|
+
"swift",
|
|
16
18
|
"currency",
|
|
17
19
|
"validation",
|
|
18
20
|
"formatting",
|
|
19
21
|
"financial",
|
|
20
22
|
"primitives",
|
|
21
|
-
"zod"
|
|
23
|
+
"zod",
|
|
24
|
+
"react",
|
|
25
|
+
"nestjs",
|
|
26
|
+
"payment",
|
|
27
|
+
"luhn",
|
|
28
|
+
"card-validation",
|
|
29
|
+
"credit-card",
|
|
30
|
+
"vat",
|
|
31
|
+
"routing-number",
|
|
32
|
+
"emi",
|
|
33
|
+
"loan",
|
|
34
|
+
"amortization",
|
|
35
|
+
"branded-types",
|
|
36
|
+
"bank",
|
|
37
|
+
"account-number"
|
|
22
38
|
],
|
|
23
39
|
"main": "./dist/index.js",
|
|
24
40
|
"module": "./dist/index.mjs",
|