finprim 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +137 -118
  2. package/package.json +18 -2
package/README.md CHANGED
@@ -1,55 +1,89 @@
1
- # finprim
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 &middot; BIC/SWIFT &middot; Card &middot; Sort Code &middot; VAT &middot; Routing Number &middot; Loan/EMI &middot; Currency</b><br/>
17
+ One library. Zero dependencies. Fully typed.
18
+ </p>
2
19
 
3
- **Financial primitives for modern TypeScript applications.**
20
+ ---
4
21
 
5
- A unified, production-grade TypeScript library for validating and formatting financial data. No more stitching together five different packages. No more custom glue code. Just clean, typed, fintech-first utilities that work anywhere TypeScript runs.
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
- ## Why finprim?
26
+ ## Quick Start
10
27
 
11
- 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.
28
+ ```bash
29
+ npm install finprim
30
+ ```
12
31
 
13
- finprim is the open source version of what your team has already written three times.
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
- ## Features
49
+ ## Why finprim?
18
50
 
19
- - IBAN validation and formatting (80+ countries, with country code)
20
- - ✅ UK sort code and account number validation
21
- - BIC/SWIFT validation
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
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
- ## Installation
61
+ ## Features
39
62
 
40
- ```bash
41
- npm install finprim
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
- For Zod integration:
73
+ ## Integrations
45
74
 
46
- ```bash
47
- npm install finprim zod
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
- const iban = validateIBAN('GB29NWBK60161331926819')
102
+ validateIBAN('GB29NWBK60161331926819')
74
103
  // { valid: true, value: 'GB29NWBK60161331926819', formatted: 'GB29 NWBK 6016 1331 9268 19', countryCode: 'GB' }
75
104
 
76
- const sortCode = validateUKSortCode('60-16-13')
105
+ validateUKSortCode('60-16-13')
77
106
  // { valid: true, value: '601613', formatted: '60-16-13' }
78
107
 
79
- const account = validateUKAccountNumber('31926819')
108
+ validateUKAccountNumber('31926819')
80
109
  // { valid: true, value: '31926819', formatted: '3192 6819' }
81
110
 
82
- const card = validateCardNumber('4532015112830366')
83
- // { valid: true, value: '...', formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }
111
+ validateCardNumber('4532015112830366')
112
+ // { valid: true, formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }
84
113
 
85
- const vat = validateEUVAT('DE123456789')
114
+ validateEUVAT('DE123456789')
86
115
  // { valid: true, value: 'DE123456789', formatted: 'DE 123456789', countryCode: 'DE' }
87
116
 
88
- const routing = validateUSRoutingNumber('021000021')
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') // '60-16-13'
93
- formatUKAccountNumber('31926819') // '3192 6819'
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
- const emi = calculateEMI(100_000, 10, 12) // monthly payment
96
- const schedule = getLoanSchedule(100_000, 10, 12) // array of { month, payment, principal, interest, balance }
134
+ parseMoney('£1,000.50')
135
+ // { valid: true, amount: 1000.50, currency: 'GBP', formatted: '£1,000.50' }
97
136
  ```
98
137
 
99
- ### Currency Formatting
138
+ ### Loan / EMI Calculation
100
139
 
101
140
  ```ts
102
- import { formatCurrency, parseMoney } from 'finprim'
141
+ import { calculateEMI, getLoanSchedule } from 'finprim'
103
142
 
104
- formatCurrency(1000.5, 'GBP', 'en-GB') // '£1,000.50'
105
- formatCurrency(1000.5, 'EUR', 'de-DE') // '1.000,50 €'
106
- formatCurrency(1000.5, 'USD', 'en-US') // '$1,000.50'
143
+ calculateEMI(100_000, 10, 12)
144
+ // Monthly payment amount
107
145
 
108
- parseMoney('£1,000.50') // { valid: true, amount: 1000.50, currency: 'GBP', formatted: '£1,000.50' }
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, CurrencyCode } from 'finprim'
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
- // This forces validation before use
120
- const iban = validateIBAN(input)
121
- if (iban.valid) {
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 { ibanSchema, sortCodeSchema, accountNumberSchema, currencySchema, vatSchema, routingNumberSchema } from 'finprim/zod'
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
- ```ts
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 { rawValue, formatted, onChange } = useCurrencyInput('GBP', 'en-GB')
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` when valid) |
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` when valid) |
188
- | `validateEUVAT(input)` | `string` | `VATValidationResult` (includes `countryCode` when valid) |
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 & display
231
+ ### Formatting
192
232
 
193
233
  | Function | Input | Returns |
194
234
  |----------|-------|---------|
195
- | `formatIBAN(input)` | `string` | `string` (space-separated, no validation) |
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, annualRatePercent, months)` | `number`, `number`, `number` | `number` |
204
- | `getLoanSchedule(principal, annualRatePercent, months)` | `number`, `number`, `number` | `LoanScheduleEntry[]` |
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] SWIFT / BIC validation
241
- - [x] Luhn algorithm for card number validation
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. Please open an issue before submitting a pull request so we can discuss the change.
282
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
254
283
 
255
284
  ```bash
256
- git clone https://github.com/YOUR_USERNAME/finprim
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.2",
3
+ "version": "0.1.4",
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",