@ekwo-ai/camt053 0.4.0
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 +240 -0
- package/dist/decimal.d.ts +20 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +46 -0
- package/dist/decimal.js.map +1 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +454 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +210 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/xml.d.ts +40 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +274 -0
- package/dist/xml.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ekwo AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# `@ekwo-ai/camt053`
|
|
2
|
+
|
|
3
|
+
Reads an **ISO 20022 bank-to-customer statement** — `camt.053`, the account
|
|
4
|
+
statement banks deliver as XML — into plain objects, in TypeScript, with no
|
|
5
|
+
dependencies.
|
|
6
|
+
|
|
7
|
+
Give it the file and it gives you the statements in it, their lines, and the
|
|
8
|
+
list of what does not add up.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { readCamt053 } from '@ekwo-ai/camt053';
|
|
12
|
+
|
|
13
|
+
const { version, statements, violations } = readCamt053(bytesOrString);
|
|
14
|
+
|
|
15
|
+
for (const statement of statements) {
|
|
16
|
+
statement.account.identifier; // { kind: 'iban', value } or { kind: 'other', value, scheme, issuer }
|
|
17
|
+
statement.openingBalance; // { type: 'OPBD', amount: '1000.00', currency: 'EUR', date: '2026-03-01' }
|
|
18
|
+
statement.closingBalance;
|
|
19
|
+
statement.balanced; // true, false, or null when it could not be checked
|
|
20
|
+
for (const line of statement.lines) {
|
|
21
|
+
line.amount; // '-450.50' — signed, a decimal string, never a float
|
|
22
|
+
line.bookingDate; // '2026-03-05'
|
|
23
|
+
line.counterparty; // { name, account, agentBic, ultimateName }
|
|
24
|
+
line.remittance; // { unstructured: ['Invoice 2026-0107'], structured: [{ reference, type, issuer }] }
|
|
25
|
+
line.bankReference; // the bank's own reference for the movement
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It knows no database, no ledger and no country. The shape it returns is
|
|
31
|
+
declared in [`src/types.ts`](./src/types.ts) and imported from nowhere.
|
|
32
|
+
|
|
33
|
+
Every other brick beside this one *writes* a file an administration takes. This
|
|
34
|
+
one *reads* a file a bank sent, and that changes what has to be proved: not
|
|
35
|
+
that the output is valid, but that the input was understood — and that a file
|
|
36
|
+
written to hurt the reader does not.
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
- Reads **versions 02 to 14** of the message, and **says which one it read**
|
|
41
|
+
(`version`, `namespace`). The version is the last part of the namespace of
|
|
42
|
+
the document element, and nothing is assumed. The same invented statement is
|
|
43
|
+
written in each of the thirteen versions, held against the schema ISO
|
|
44
|
+
publishes for that version, and has to read to the same objects out of all
|
|
45
|
+
thirteen. A later version is still read and reported as
|
|
46
|
+
`version_not_verified`; version 01 is refused, because under the same name it
|
|
47
|
+
is another message.
|
|
48
|
+
- Returns **every statement** of the file — a message may carry several
|
|
49
|
+
accounts — and says on each violation which statement it is about.
|
|
50
|
+
- Identifies an account by **what the file gives**: an IBAN *or* `Othr/Id` with
|
|
51
|
+
its scheme and issuer. ISO 20022 offers the choice because half the world has
|
|
52
|
+
no IBAN; a reader that returned only IBANs would read half the statements.
|
|
53
|
+
- Signs every amount from the account holder's side — money in is positive, an
|
|
54
|
+
overdrawn balance is negative — and returns it as a **decimal string with the
|
|
55
|
+
digits the file wrote**. Sums are made on integers of hundred-thousandths
|
|
56
|
+
(the schema allows five decimals); no figure passes through a `number`.
|
|
57
|
+
- Checks that **opening balance + booked entries = closing balance**, to the
|
|
58
|
+
last decimal. When it is not, the statement comes back as the bank wrote it,
|
|
59
|
+
`balanced` is `false`, and `balance_mismatch` says the difference. **Nothing
|
|
60
|
+
is corrected**: a reader that adjusts a balance hides the one fact the check
|
|
61
|
+
exists to show — a truncated export, a missing page, a line lost on the way.
|
|
62
|
+
- Counts only **booked** entries (`BOOK`). A pending or informational entry is
|
|
63
|
+
returned, marked `booked: false`, and left out of the arithmetic, which is
|
|
64
|
+
what the bank did when it computed the closing balance.
|
|
65
|
+
- Keeps the **structured communication and the free one apart**, and keeps both
|
|
66
|
+
when a payment carries both. A creditor reference is something a payer copied
|
|
67
|
+
from an invoice and a machine can match; free text is something a person
|
|
68
|
+
typed. Folding one into the other throws away the difference reconciliation
|
|
69
|
+
lives on.
|
|
70
|
+
- **Splits a batch into its transactions** — one line per `TxDtls` — when, and
|
|
71
|
+
only when, every transaction carries an amount in the entry's currency and
|
|
72
|
+
they add up to the entry exactly. Each line then keeps its own references,
|
|
73
|
+
counterparty and communication, plus `entry`, `detail`, `detailCount` and
|
|
74
|
+
`entryAmount` to put the batch back together. Otherwise the entry stays one
|
|
75
|
+
line and `batch_not_split` says why. Either way the lines of a statement add
|
|
76
|
+
up to what moved on the account, so the balance check does not depend on the
|
|
77
|
+
choice. (Reconciliation wants the transaction; the balance wants the entry.
|
|
78
|
+
The split is made only where both get what they want.)
|
|
79
|
+
- Names the **counterparty** as the debtor of money coming in and the creditor
|
|
80
|
+
of money going out — and the other way round when `RvslInd` says the entry
|
|
81
|
+
undoes an earlier one, where the parties keep the roles they had in the
|
|
82
|
+
original: a transfer that bounced comes back *from* the party it was sent to.
|
|
83
|
+
- Takes the **day the bank wrote**. `2026-03-31T23:30:00+02:00` is 31 March; it
|
|
84
|
+
is not moved to UTC, which would book it on another day for half the planet.
|
|
85
|
+
- Returns the bank's own reference (`AcctSvcrRef`, of the transaction, else of
|
|
86
|
+
the entry), the entry reference, the end-to-end identifier **as written** —
|
|
87
|
+
including the `NOTPROVIDED` placeholder, which is the bank's word and not an
|
|
88
|
+
absence this reader should decide — the mandate, the bank transaction code
|
|
89
|
+
(ISO domain/family/sub-family and the proprietary one), the return reason,
|
|
90
|
+
and what was ordered in another currency with the rate, unconverted.
|
|
91
|
+
|
|
92
|
+
## A statement is hostile input
|
|
93
|
+
|
|
94
|
+
It arrives by e-mail, from a download, from a third party's API. The XML reader
|
|
95
|
+
is written in this package, some three hundred lines, and is strict on purpose:
|
|
96
|
+
|
|
97
|
+
- **Any `<!DOCTYPE` is refused**, unread. No statement needs one; the
|
|
98
|
+
billion-laughs family and the external-entity family (XXE) both need one.
|
|
99
|
+
There is no entity table to expand and nothing that opens a file or a socket.
|
|
100
|
+
- The five predefined entities and numeric character references are decoded;
|
|
101
|
+
**any other entity is an error**, not an empty string. A character reference
|
|
102
|
+
that is not an XML character is an error.
|
|
103
|
+
- **Size** is checked before a character is read (`maxBytes`, 32 MiB by
|
|
104
|
+
default), **depth** and **element count** while reading (`maxDepth` 64,
|
|
105
|
+
`maxElements` 2,000,000). The reader does not recurse: a hundred thousand
|
|
106
|
+
nested elements are refused by the depth limit, and would not have touched
|
|
107
|
+
the stack.
|
|
108
|
+
- **UTF-8 only, decoded fatally.** A byte that is not UTF-8 is an error and not
|
|
109
|
+
a replacement character in somebody's name. A file that declares another
|
|
110
|
+
encoding is refused: decode it and pass the string.
|
|
111
|
+
- Namespaces are resolved, so `<Document xmlns="…">` and
|
|
112
|
+
`<ns2:Document xmlns:ns2="…">` are the same statement, and an element of
|
|
113
|
+
another namespace is never read as the statement's.
|
|
114
|
+
- Not well-formed is not read: mismatched tags, a second root, a bare `&`, a
|
|
115
|
+
repeated attribute, a control character.
|
|
116
|
+
|
|
117
|
+
It is not a general XML parser: element and attribute names are ASCII, which
|
|
118
|
+
every ISO 20022 name is, and nothing is validated against a schema at runtime.
|
|
119
|
+
|
|
120
|
+
## What is thrown, and what comes back as a violation
|
|
121
|
+
|
|
122
|
+
**Thrown** (`StatementFileError`, with a `code`), because there is no statement
|
|
123
|
+
to report on:
|
|
124
|
+
|
|
125
|
+
| code | when |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `too_large`, `too_deep`, `too_many_elements` | a limit was passed |
|
|
128
|
+
| `unsupported_encoding` | not UTF-8 |
|
|
129
|
+
| `doctype_forbidden`, `undefined_entity` | see above |
|
|
130
|
+
| `malformed_xml` | not well-formed |
|
|
131
|
+
| `not_a_statement` | well-formed, and not a camt.053 — a camt.052, an HTML error page |
|
|
132
|
+
| `unsupported_version` | camt.053.001.01 |
|
|
133
|
+
| `incomplete_statement` | no `Stmt`, or a statement without an identifier or an account |
|
|
134
|
+
|
|
135
|
+
**Returned** in `violations`, with the statement and the line it is on, because
|
|
136
|
+
one bad line should not cost the other three hundred:
|
|
137
|
+
|
|
138
|
+
| code | when |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `balance_mismatch` | opening + booked entries ≠ closing; the message says the difference |
|
|
141
|
+
| `opening_balance_missing`, `closing_balance_missing` | no `OPBD` (or `PRCD`), no `CLBD`; the balance is not checked |
|
|
142
|
+
| `balance_currency_mismatch` | a balance in another currency than the account |
|
|
143
|
+
| `foreign_currency_entry` | an entry in another currency than the account — reported, **not converted**; the balance is not checked |
|
|
144
|
+
| `invalid_amount`, `invalid_direction`, `invalid_date` | the line comes back with `null` there |
|
|
145
|
+
| `booking_date_missing` | a booked entry without `BookgDt` (the schema allows it) |
|
|
146
|
+
| `invalid_iban` | written as an IBAN and failing ISO 13616's own check digits — returned as written |
|
|
147
|
+
| `duplicate_bank_reference`, `duplicate_entry_reference` | two entries of one statement under one reference |
|
|
148
|
+
| `batch_not_split`, `batch_count_mismatch` | see above |
|
|
149
|
+
| `version_not_verified` | a version this package has no schema for |
|
|
150
|
+
|
|
151
|
+
`invalid_amount`, `invalid_direction` and `invalid_date` are each something the
|
|
152
|
+
schema refuses: the tests put each one in a file and watch validation fail.
|
|
153
|
+
`balance_mismatch` is the opposite, and the reason this package exists — the
|
|
154
|
+
tests show the schema **accepting** a statement that does not add up.
|
|
155
|
+
|
|
156
|
+
## What it does not do
|
|
157
|
+
|
|
158
|
+
- **It converts nothing.** An entry in another currency is reported; what was
|
|
159
|
+
ordered in dollars is returned in dollars beside what moved in the account's
|
|
160
|
+
currency. Rates belong to whoever keeps the books.
|
|
161
|
+
- **It matches nothing.** It does not know an invoice from a salary. It hands
|
|
162
|
+
over what reconciliation needs, separated.
|
|
163
|
+
- **It does not decide what a duplicate is.** Two lines with the same date,
|
|
164
|
+
amount and counterparty are two lines; whether a file was already imported is
|
|
165
|
+
a question for whoever holds the previous ones.
|
|
166
|
+
- **It does not read every element.** Charges, interest, tax, securities and
|
|
167
|
+
card details, availability, and the postal address of a party are not
|
|
168
|
+
returned. They are skipped, not misread.
|
|
169
|
+
- **It does not read camt.052 or camt.054.** The intraday report and the
|
|
170
|
+
debit/credit notification share most of their types with the statement, and
|
|
171
|
+
the reading would be close to the same — but a report has no closing booked
|
|
172
|
+
balance to check and a notification has no balance at all, so they are other
|
|
173
|
+
documents with other guarantees, and they are refused by name until someone
|
|
174
|
+
needs them.
|
|
175
|
+
- **It reads no paginated statement as one.** `StmtPgntn` is returned (`page`);
|
|
176
|
+
a statement delivered over several files opens and closes each page on
|
|
177
|
+
interim balances (`ITBD`), which this package returns in `balances` and does
|
|
178
|
+
not take for an opening or a closing. Such a page reports
|
|
179
|
+
`opening_balance_missing` rather than guessing.
|
|
180
|
+
|
|
181
|
+
## Sources
|
|
182
|
+
|
|
183
|
+
- ISO 20022, *Bank-to-Customer Cash Management*, message
|
|
184
|
+
`BankToCustomerStatement` (`camt.053.001.02` to `.14`): the schemas under
|
|
185
|
+
[`test/xsd/`](./test/xsd/README.md), from the ISO 20022 message catalogue and
|
|
186
|
+
archive, <https://www.iso20022.org/iso-20022-message-definitions>.
|
|
187
|
+
- ISO 13616 — the IBAN and its check digits (ISO 7064, MOD 97-10).
|
|
188
|
+
- ISO 11649 — the structured creditor reference (`RF…`), which this package
|
|
189
|
+
returns and does not validate.
|
|
190
|
+
- Extensible Markup Language (XML) 1.0, Fifth Edition, and Namespaces in XML
|
|
191
|
+
1.0 — <https://www.w3.org/TR/xml/>, <https://www.w3.org/TR/xml-names/>.
|
|
192
|
+
|
|
193
|
+
## What is verified, and what is not
|
|
194
|
+
|
|
195
|
+
**Verified**: every file this package is tested on is first held against the
|
|
196
|
+
schema ISO publishes for its version (`test/schema.test.ts`, thirteen schemas,
|
|
197
|
+
unmodified under `test/xsd/`, with no network and no system tool) — so the
|
|
198
|
+
reader is tested on camt.053, not on its author's idea of camt.053. One
|
|
199
|
+
statement is written in all thirteen versions and reads to the same objects.
|
|
200
|
+
The violations that are schema violations are shown to be refused by the
|
|
201
|
+
schema; the one that matters most, the balance, is shown **not** to be. The
|
|
202
|
+
hostile files are each refused by name, and the two that ask for time or
|
|
203
|
+
memory are refused within a bound the test measures.
|
|
204
|
+
|
|
205
|
+
Writing the fixtures against the schemas refuted the first draft once, the same
|
|
206
|
+
hour: `BIC` became `BICFI` in version 03, not in 04 as the draft had it. And
|
|
207
|
+
fetching the schemas found two versions the draft did not know: 14, published
|
|
208
|
+
in March 2026, and 01, which is served under the same name and is another
|
|
209
|
+
message.
|
|
210
|
+
|
|
211
|
+
**Not verified** — and this is the larger half:
|
|
212
|
+
|
|
213
|
+
- **No file from a bank.** Every statement here is invented, because a real one
|
|
214
|
+
is somebody's account. The schema says what a bank *may* send; it does not
|
|
215
|
+
say what any bank *does* send. Banks differ on exactly the things this
|
|
216
|
+
package cares about: whether a batch is detailed, where the reference is
|
|
217
|
+
(`AcctSvcrRef` on the entry, on the transaction, on both, on neither),
|
|
218
|
+
whether the counterparty of a card payment is a party or a line of free text,
|
|
219
|
+
what `PRCD` means on a Monday. *A reading that passes on invented files is an
|
|
220
|
+
indication, not a proof*, and the first real statement from each bank is a
|
|
221
|
+
test nobody has run.
|
|
222
|
+
- **No usage guideline.** EPC, CGI-MP, and the implementation guides of
|
|
223
|
+
national banking federations restrict the message — which elements are
|
|
224
|
+
filled, with which codes. None is tested.
|
|
225
|
+
- **The counterparty of a reversal.** That the parties keep their original
|
|
226
|
+
roles when `RvslInd` is true is this package's reading of the message
|
|
227
|
+
definition and of common practice; the schema cannot say, and banks may
|
|
228
|
+
differ.
|
|
229
|
+
- **Versions 02 and 08 are the ones in use.** The eleven others are verified
|
|
230
|
+
against their schemas and, as far as the authors know, against nothing else:
|
|
231
|
+
nobody here has seen a camt.053.001.11 in the wild.
|
|
232
|
+
- **The external code sets** (bank transaction codes, return reasons, balance
|
|
233
|
+
types) are returned as written and not checked against ISO's published
|
|
234
|
+
lists.
|
|
235
|
+
- **Performance** is bounded, not measured: a statement at the default size
|
|
236
|
+
limit is read in memory, as a whole.
|
|
237
|
+
|
|
238
|
+
## Licence
|
|
239
|
+
|
|
240
|
+
MIT. See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as the file wrote them, added without a float.
|
|
3
|
+
*
|
|
4
|
+
* ISO 20022 amounts are `xs:decimal` with at most five fraction digits and
|
|
5
|
+
* eighteen digits in all, and unsigned: the sign is a separate element. A
|
|
6
|
+
* statement's arithmetic is a sum of those, and a sum of binary floats is not
|
|
7
|
+
* the sum the bank computed. Everything here is a `bigint` of hundred-
|
|
8
|
+
* thousandths, and what leaves is a string.
|
|
9
|
+
*/
|
|
10
|
+
/** An unsigned ISO 20022 amount in hundred-thousandths, or null if it is not one. */
|
|
11
|
+
export declare function parseUnsigned(text: string): bigint | null;
|
|
12
|
+
/**
|
|
13
|
+
* The amount as a decimal string: signed, no exponent, no leading zeros, and
|
|
14
|
+
* the fraction digits the file wrote — never fewer than it wrote, so that
|
|
15
|
+
* `100.00` does not come back as `100`, and never more.
|
|
16
|
+
*/
|
|
17
|
+
export declare function signedText(text: string, negative: boolean): string;
|
|
18
|
+
/** Hundred-thousandths back to a decimal string, with at least two decimals. */
|
|
19
|
+
export declare function formatScaled(value: bigint): string;
|
|
20
|
+
//# sourceMappingURL=decimal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decimal.d.ts","sourceRoot":"","sources":["../src/decimal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUzD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAMlE;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD"}
|
package/dist/decimal.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as the file wrote them, added without a float.
|
|
3
|
+
*
|
|
4
|
+
* ISO 20022 amounts are `xs:decimal` with at most five fraction digits and
|
|
5
|
+
* eighteen digits in all, and unsigned: the sign is a separate element. A
|
|
6
|
+
* statement's arithmetic is a sum of those, and a sum of binary floats is not
|
|
7
|
+
* the sum the bank computed. Everything here is a `bigint` of hundred-
|
|
8
|
+
* thousandths, and what leaves is a string.
|
|
9
|
+
*/
|
|
10
|
+
const SCALE = 5;
|
|
11
|
+
const UNSIGNED = /^\+?([0-9]*)(?:\.([0-9]*))?$/;
|
|
12
|
+
/** An unsigned ISO 20022 amount in hundred-thousandths, or null if it is not one. */
|
|
13
|
+
export function parseUnsigned(text) {
|
|
14
|
+
const match = UNSIGNED.exec(text.trim());
|
|
15
|
+
if (!match)
|
|
16
|
+
return null;
|
|
17
|
+
const whole = match[1] ?? '';
|
|
18
|
+
const fraction = match[2] ?? '';
|
|
19
|
+
if (whole === '' && fraction === '')
|
|
20
|
+
return null;
|
|
21
|
+
if (fraction.length > SCALE || whole.replace(/^0+/, '').length + fraction.length > 18) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return BigInt((whole === '' ? '0' : whole) + fraction.padEnd(SCALE, '0'));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The amount as a decimal string: signed, no exponent, no leading zeros, and
|
|
28
|
+
* the fraction digits the file wrote — never fewer than it wrote, so that
|
|
29
|
+
* `100.00` does not come back as `100`, and never more.
|
|
30
|
+
*/
|
|
31
|
+
export function signedText(text, negative) {
|
|
32
|
+
const match = UNSIGNED.exec(text.trim());
|
|
33
|
+
const whole = (match?.[1] ?? '').replace(/^0+(?=[0-9])/, '') || '0';
|
|
34
|
+
const fraction = match?.[2] ?? '';
|
|
35
|
+
const unsigned = fraction === '' ? whole : `${whole}.${fraction}`;
|
|
36
|
+
return negative && /[1-9]/.test(unsigned) ? `-${unsigned}` : unsigned;
|
|
37
|
+
}
|
|
38
|
+
/** Hundred-thousandths back to a decimal string, with at least two decimals. */
|
|
39
|
+
export function formatScaled(value) {
|
|
40
|
+
const negative = value < 0n;
|
|
41
|
+
const digits = (negative ? -value : value).toString().padStart(SCALE + 1, '0');
|
|
42
|
+
const whole = digits.slice(0, -SCALE);
|
|
43
|
+
const fraction = digits.slice(-SCALE).replace(/0{1,3}$/, '');
|
|
44
|
+
return `${negative ? '-' : ''}${whole}.${fraction}`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=decimal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decimal.js","sourceRoot":"","sources":["../src/decimal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,KAAK,GAAG,CAAC,CAAC;AAChB,MAAM,QAAQ,GAAG,8BAA8B,CAAC;AAEhD,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,KAAK,KAAK,EAAE,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,QAAQ,CAAC,MAAM,GAAG,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAAiB;IACxD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IACpE,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC;IAClE,OAAO,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AACxE,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,QAAQ,GAAG,KAAK,GAAG,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7D,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC;AACtD,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What makes a file unreadable, as opposed to a statement that reads and does
|
|
3
|
+
* not add up. The second comes back in `violations`; the first is thrown,
|
|
4
|
+
* because there is no statement to hang a violation on.
|
|
5
|
+
*/
|
|
6
|
+
export type StatementFileErrorCode =
|
|
7
|
+
/** Larger than `maxBytes`. Refused before a single character is read. */
|
|
8
|
+
'too_large'
|
|
9
|
+
/** Bytes that are not UTF-8, or a declaration that names another encoding. */
|
|
10
|
+
| 'unsupported_encoding'
|
|
11
|
+
/** A `<!DOCTYPE`. No statement needs one, and every entity attack does. */
|
|
12
|
+
| 'doctype_forbidden'
|
|
13
|
+
/** An entity other than the five XML predefines and character references. */
|
|
14
|
+
| 'undefined_entity'
|
|
15
|
+
/** Elements nested deeper than `maxDepth`. */
|
|
16
|
+
| 'too_deep'
|
|
17
|
+
/** More elements than `maxElements`. */
|
|
18
|
+
| 'too_many_elements'
|
|
19
|
+
/** Not well-formed XML, in the subset this reader takes. */
|
|
20
|
+
| 'malformed_xml'
|
|
21
|
+
/** Well-formed, and not a camt.053: another root, another namespace. */
|
|
22
|
+
| 'not_a_statement'
|
|
23
|
+
/** camt.053.001.01, the 2007 message: same name, another shape. */
|
|
24
|
+
| 'unsupported_version'
|
|
25
|
+
/** A camt.053 without the parts that make it one: no statement, no account. */
|
|
26
|
+
| 'incomplete_statement';
|
|
27
|
+
export declare class StatementFileError extends Error {
|
|
28
|
+
readonly code: StatementFileErrorCode;
|
|
29
|
+
constructor(code: StatementFileErrorCode, message: string);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,sBAAsB;AAChC,yEAAyE;AACvE,WAAW;AACb,8EAA8E;GAC5E,sBAAsB;AACxB,2EAA2E;GACzE,mBAAmB;AACrB,6EAA6E;GAC3E,kBAAkB;AACpB,8CAA8C;GAC5C,UAAU;AACZ,wCAAwC;GACtC,mBAAmB;AACrB,4DAA4D;GAC1D,eAAe;AACjB,wEAAwE;GACtE,iBAAiB;AACnB,mEAAmE;GACjE,qBAAqB;AACvB,+EAA+E;GAC7E,sBAAsB,CAAC;AAE3B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAE1B,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAK1D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA2BA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAyB;IAEtC,YAAY,IAA4B,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReadOptions, StatementFile } from './types.js';
|
|
2
|
+
export { StatementFileError, type StatementFileErrorCode } from './errors.js';
|
|
3
|
+
export type * from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The versions of camt.053 this package's tests hold against the schema ISO
|
|
6
|
+
* publishes for each: a fixture per version validates, and is read. Another
|
|
7
|
+
* version of the same message is still read — the elements this package takes
|
|
8
|
+
* have kept their names since 2009 — and reported as `version_not_verified`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const VERIFIED_VERSIONS: readonly string[];
|
|
11
|
+
/** ISO 13616: move four characters to the end, letters to numbers, mod 97 is 1. */
|
|
12
|
+
export declare function isValidIban(iban: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Read a camt.053 file — a string, or the bytes as they came — into its
|
|
15
|
+
* statements, their lines, and what does not add up.
|
|
16
|
+
*
|
|
17
|
+
* Throws a `StatementFileError` for what is not a readable camt.053 at all.
|
|
18
|
+
* Everything else comes back, with the trouble named in `violations` and
|
|
19
|
+
* nothing silently repaired: a closing balance that does not follow from the
|
|
20
|
+
* opening one is returned as the bank wrote it, and reported.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readCamt053(input: string | Uint8Array, options?: ReadOptions): StatementFile;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,WAAW,EAIX,aAAa,EAId,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC9E,mBAAmB,YAAY,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAc9C,CAAC;AAgEF,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUjD;AAgYD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,GAAE,WAAgB,GAAG,aAAa,CAoChG"}
|