ofx-data-extractor 1.4.7 → 1.5.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/README.md CHANGED
@@ -1,98 +1,120 @@
1
1
  # [ofx-data-extractor](https://www.npmjs.com/package/ofx-data-extractor)
2
2
 
3
- [![npm version](https://badge.fury.io/js/ofx-data-extractor.svg)](https://badge.fury.io/js/ofx-data-extractor) [![MIT License][license-image]][license-url]
3
+ [![npm version](https://badge.fury.io/js/ofx-data-extractor.svg)](https://badge.fury.io/js/ofx-data-extractor)
4
+ [![MIT License][license-image]][license-url]
4
5
  [![codecov](https://codecov.io/gh/Fabiopf02/ofx-data-extractor/branch/main/graph/badge.svg?token=L4A7E4H8IN)](https://codecov.io/gh/Fabiopf02/ofx-data-extractor)
5
6
  [![Release Package](https://github.com/Fabiopf02/ofx-data-extractor/actions/workflows/release.yml/badge.svg)](https://github.com/Fabiopf02/ofx-data-extractor/actions/workflows/release.yml)
6
7
 
7
- ## Ofx Data Extractor and Formatter
8
- This is a `Node.js` and `Browser` module written in `TypeScript` that provides a utility for extracting data from an `OFX` file. The module can also format some of the data and retrieve specific parts of the file if requested.
8
+ `ofx-data-extractor` is a TypeScript library for parsing, normalizing and validating OFX files in Node.js and browser environments.
9
9
 
10
- ### Installation
11
- You can install this module using the Node Package Manager (`NPM`) with the following command:
10
+ ## Table of Contents
11
+
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Node.js](#nodejs)
15
+ - [Browser (`fromBlob` is async)](#browser-fromblob-is-async)
16
+ - [Main Public API (`Ofx`)](#main-public-api-ofx)
17
+ - [Configuration (Extract)](#configuration-extract)
18
+ - [Developer Documentation](#developer-documentation)
19
+ - [Examples](#examples)
20
+ - [License](#license)
21
+
22
+ ## Installation
12
23
 
13
- **npm**
14
24
  ```bash
15
25
  npm install ofx-data-extractor
16
26
  ```
17
- **yarn**
27
+
18
28
  ```bash
19
29
  yarn add ofx-data-extractor
20
30
  ```
21
31
 
22
- ### Methods
23
- The `Ofx` class provides the following methods:
24
-
25
- - `getType(): Types`: Used to get type of transactions from ofx (BANK or CREDIT_CARD)
26
- - `fromBuffer(data: Buffer)`: Used to read files on the node. Returns the methods
27
- - `fromBlob(data: Blob)`: Used to read files in the browser. Returns the methods below.
28
- - `config(options: OfxConfig)`: Used for formatting the generated json.`
29
- - `getHeaders(): OFXMetaData`: Returns the metadata section of the OFX file as an object.
30
- - `getBankTransferList(): Pick<BankTransferList, 'STRTTRN'>`: Returns a list of bank transfer transactions as an object.
31
- - `getCreditCardTransferList(): Pick<BankTransferList, 'STRTTRN'>`: Returns a list of credit card transactions as an object.
32
- - `getTransactionsSummary()`: Object: Returns a summary of transactions for a bank statement as an object.
33
- - `getContent(): OfxStructure`: Returns the OFX file content as an object.
34
- - `toJson(): OfxResponse`: Returns the entire OFX file content as a JSON object.
35
-
36
- ### Usage
37
- The module provides a class called Ofx that can be used to extract and format data from an `OFX` file. Here's an example of how to use it:
32
+ ## Quick Start
38
33
 
39
- ```typescript
34
+ ```ts
40
35
  import { Ofx } from 'ofx-data-extractor'
41
36
 
42
37
  const data = 'OFXHEADER:100\nDATA:OFXSGML\nVERSION:102\n...'
43
- const ofx = new Ofx(data) // works in node.js and browser
38
+ const ofx = new Ofx(data)
44
39
 
45
- const headers = ofx.getHeaders()
46
- console.log(headers)
40
+ const raw = ofx.toJson()
41
+ const summary = ofx.getTransactionsSummary()
42
+ const normalized = ofx.toNormalized()
43
+ const validation = ofx.validate()
44
+ ```
47
45
 
48
- const transactionsSummary = ofx.getTransactionsSummary()
49
- console.log(transactionsSummary)
46
+ ## Node.js
50
47
 
51
- const bankTransferList = ofx.getBankTransferList()
52
- console.log(bankTransferList)
48
+ ```ts
49
+ import fs from 'fs'
50
+ import { Ofx } from 'ofx-data-extractor'
53
51
 
54
- const ofxResponse = ofx.toJson()
55
- console.log(ofxResponse)
52
+ const file = fs.readFileSync('/path/to/file.ofx')
53
+ const ofx = Ofx.fromBuffer(file)
54
+ console.log(ofx.getTransactionsSummary())
56
55
  ```
57
- ### Constructor
58
- The `Ofx` class constructor takes in two arguments:
59
56
 
60
- `data`: The `OFX` file content as a string or a Buffer.
61
- `config`: Optional configuration options for formatting the OFX data.
57
+ ## Browser (`fromBlob` is async)
62
58
 
63
- ### Read data - static methods (`Node.js`)
64
- ```typescript
59
+ ```ts
65
60
  import { Ofx } from 'ofx-data-extractor'
66
- import fs from 'fs'
67
61
 
68
- const file = await fs.readFile('/path/to/file')
69
- const ofx = await Ofx.fromBuffer(file)
62
+ async function handleFile(event: Event) {
63
+ const input = event.target as HTMLInputElement
64
+ const file = input.files?.[0]
65
+ if (!file) return
70
66
 
71
- const ofxResponse = ofx.toJson()
72
- console.log(ofxResponse)
67
+ const ofx = await Ofx.fromBlob(file)
68
+ console.log(ofx.toJson())
69
+ }
73
70
  ```
74
71
 
75
- ### Read data - static methods (`Browser`)
76
- ```typescript
77
- import { Ofx } from 'ofx-data-extractor'
72
+ ## Main Public API (`Ofx`)
78
73
 
79
- function handleFile(event) {
80
- const ofx = Ofx.fromBlob(event.target.files[0])
81
- const ofxResponse = ofx.toJson()
82
- console.log(ofxResponse)
83
- }
74
+ - `new Ofx(data: string, config?: ExtractorConfig)`
75
+ - `Ofx.fromBuffer(buffer: Buffer)`
76
+ - `Ofx.fromBlob(blob: Blob)`
77
+ - `config(options)`
78
+ - `getType()`
79
+ - `getHeaders()`
80
+ - `getBankTransferList()`
81
+ - `getCreditCardTransferList()`
82
+ - `getTransactionsSummary()`
83
+ - `getContent()`
84
+ - `toJson()`
85
+ - `toNormalized(options?)`
86
+ - `validate()`
87
+ - `getWarnings()`
84
88
 
85
- // tsx/jsx/html
86
- <input type="file" onChange={handleFile} />
87
- ```
89
+ ## Configuration (Extract)
90
+
91
+ - `nativeTypes?: boolean`
92
+ - `fitId?: 'normal' | 'separated'`
93
+ - `formatDate?: string`
94
+ - `parserMode?: 'strict' | 'lenient'`
95
+
96
+ Parser mode:
97
+
98
+ - `strict` (default): parsing errors throw.
99
+ - `lenient`: parsing errors fallback and are collected via `getWarnings()`.
100
+
101
+ ## Developer Documentation
102
+
103
+ - [API Reference](docs/api.md)
104
+ - [Normalization Guide](docs/normalization.md)
105
+ - [Validation Guide](docs/validation.md)
106
+ - [Recipes](docs/recipes.md)
107
+ - [Migration Guide](docs/migration.md)
108
+
109
+ ## Examples
88
110
 
111
+ - [Node basic](examples/node-basic.ts)
112
+ - [Browser basic](examples/browser-basic.ts)
113
+ - [Normalize + validate](examples/normalize-and-validate.ts)
89
114
 
90
- ### Configuration
91
- The `Ofx` (`constructor` and `config` method) class can be configured with the following options:
115
+ ## License
92
116
 
93
- - `formatDate`: A function that takes in a date string and returns a formatted date string.
94
- - `fitId`: A string that determines how the financial institution transaction ID is handled. Possible values are "separated" (the default) or "included".
95
- - `nativeTypes`: A boolean value that determines whether numeric fields should be represented as numbers or strings in the resulting JSON object.
117
+ MIT
96
118
 
97
119
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat
98
120
  [license-url]: LICENSE
@@ -35,9 +35,13 @@ export type ConfigDate = {
35
35
  formatDate?: string;
36
36
  };
37
37
  export type ConfigFitId = 'normal' | 'separated';
38
+ export type ParserMode = 'strict' | 'lenient';
39
+ export type AmountMode = 'string' | 'number' | 'cents';
40
+ export type DateMode = 'raw' | 'formatted' | 'iso' | 'date' | 'timestamp';
38
41
  export type ExtractorConfig = ConfigDate & {
39
42
  fitId?: ConfigFitId;
40
43
  nativeTypes?: boolean;
44
+ parserMode?: ParserMode;
41
45
  };
42
46
  export type TransactionsSummary = {
43
47
  credit: number;
@@ -51,3 +55,47 @@ export declare enum Types {
51
55
  'BANK' = "BANK",
52
56
  'CREDIT_CARD' = "CREDIT_CARD"
53
57
  }
58
+ export type OfxSeverity = 'warning' | 'error';
59
+ export type OfxDiagnostic = {
60
+ code: string;
61
+ message: string;
62
+ severity: OfxSeverity;
63
+ path?: string;
64
+ context?: string;
65
+ };
66
+ export type ValidationStats = {
67
+ totalTransactions: number;
68
+ bankTransactions: number;
69
+ creditCardTransactions: number;
70
+ duplicatedFitIds: number;
71
+ };
72
+ export type ValidationReport = {
73
+ isValid: boolean;
74
+ warnings: OfxDiagnostic[];
75
+ errors: OfxDiagnostic[];
76
+ stats: ValidationStats;
77
+ };
78
+ export type NormalizeOptions = ConfigDate & {
79
+ amountMode?: AmountMode;
80
+ dateMode?: DateMode;
81
+ };
82
+ export type NormalizedDirection = 'credit' | 'debit';
83
+ export type NormalizedTransaction = {
84
+ source: 'bank' | 'credit_card';
85
+ direction: NormalizedDirection;
86
+ amount: number | string | null;
87
+ amountAbs: number | string | null;
88
+ postedAt: string | number | Date | null;
89
+ description: string;
90
+ descriptionNormalized: string;
91
+ fitId: string;
92
+ currency: string | null;
93
+ account: Record<string, any> | null;
94
+ institution: Record<string, any> | null;
95
+ raw: Record<string, any>;
96
+ warnings: OfxDiagnostic[];
97
+ };
98
+ export type NormalizedOfxData = {
99
+ transactions: NormalizedTransaction[];
100
+ warnings: OfxDiagnostic[];
101
+ };
@@ -26,7 +26,7 @@ export type BankAccount = {
26
26
  export type BankTransactionList = {
27
27
  DTSTART: string;
28
28
  DTEND: string;
29
- STMTTRN: StatementTransaction[];
30
- STRTTRN: StatementTransaction[];
29
+ STMTTRN?: StatementTransaction[];
30
+ STRTTRN?: StatementTransaction[];
31
31
  [key: string]: any;
32
32
  };
@@ -0,0 +1,4 @@
1
+ import { NormalizedOfxData, NormalizeOptions, ValidationReport } from '../@types/common';
2
+ import { OfxResponse } from '../@types/ofx/index';
3
+ export declare function normalizeOfxData(data: OfxResponse, options?: NormalizeOptions): NormalizedOfxData;
4
+ export declare function validateOfxData(data: OfxResponse): ValidationReport;
@@ -1 +1,14 @@
1
1
  export declare function formatDate(date: string, format: string): string;
2
+ export declare function formatDateFromUtcDate(date: Date, format: string): string;
3
+ type ParsedDateParts = {
4
+ year: number;
5
+ month: number;
6
+ day: number;
7
+ hour: number;
8
+ minutes: number;
9
+ seconds: number;
10
+ };
11
+ export declare function parseDateParts(date: string): ParsedDateParts | null;
12
+ export declare function isValidParsedDate(parts: ParsedDateParts): boolean;
13
+ export declare function parseDateToUtc(date: string): Date | null;
14
+ export {};
@@ -1,3 +1,4 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  export declare function bufferToString(data: Buffer): string;
3
4
  export declare function blobToString(blob: Blob): Promise<string>;
@@ -1,9 +1,8 @@
1
- // Ofx-data-extractor v1.4.7 Copyright (c) 2025 Fábio Pereira França
2
- const E=/<\/[\w.]+>/g,N=/<[\w.]+>/g,u="<BANKMSGSRSV1>",d="</BANKMSGSRSV1>",g="<CREDITCARDMSGSRSV1>",S="</CREDITCARDMSGSRSV1>",R="<STMTTRN>",T="</STMTTRN>",x="_#_",M=new RegExp(x,"g"),b=[u,R,g],L=[d,S,T];function _(e){const t=e.slice(0,4),n=e.slice(4,6),r=e.slice(6,8),s=e.slice(8,10),a=e.slice(10,12),i=e.slice(12,14);return{yyyy:t,yy:t.slice(2),y:t,MM:n,M:n,dd:r,d:r,hh:s,h:s,mm:a,m:a,ss:i,s:i}}function k(e,t){const n=_(e);let r=t;for(const[s,a]of Object.entries(n))r=r.replace(s,a);return r}var l;(function(e){e.BANK="BANK",e.CREDIT_CARD="CREDIT_CARD"})(l||(l={}));const G=["debit","fee","srvchg","atm","pos","check","payment","directdebit","cash","repeatpmt"],F=e=>e.includes(u)?l.BANK:l.CREDIT_CARD;function K(e){if(String(e.TRNAMT).startsWith("-"))return!0;const t=String(e.TRNTYPE).toLocaleLowerCase();return t==="1"||G.includes(t)}function f(e){const t=e.replace(/(\\)/g,"\\\\").replace(E,n=>A(n,!0)).replace(N,n=>w(n,!0)).replace(/(},})/g,"}}").replace(/(}")/g,'},"').replace(/(]")/g,'],"').replace(/(},])/g,"}]").replace(/(,})/g,"}").replace(/,\s*}/g,"}").replace(/(,",)/,'",').replace(M,'\\"').slice(0,-1);return t.endsWith(",")?t.slice(0,-1):t}const J=e=>JSON.stringify({date:e.slice(0,12),transactionCode:e.slice(12,19),protocol:e.slice(19)}),P=e=>e.startsWith("DT"),D=e=>e.trim();function w(e,t=!1){return b.includes(e)&&!t?e:e.replace(/[<]/g,`
3
- "`).replace(/[>]/g,'":{')}function A(e,t=!1){return L.includes(e)&&!t?`
4
- ${e}`:`},
5
- `}function X({fitId:e,fitValue:t}){return e==="separated"?J(t):`"${t}",`}function $(e){return e.search(",")>e.search(".")?e.replace(/[.]/g,"").replace(/[,]/g,"."):e.replace(/[,]/g,"")}function V({dateString:e,formatDate:t="y-M-d"}){return k(e,t)}function W(e){let t=e.value.replace(/[{]/g,"").replace(/(},)/g,"").replace(/["]/g,x);const n=e.field.replace(/['"]/g,"");return n.endsWith("AMT")&&(t=$(t)),P(n)&&(t=V({dateString:t,formatDate:e.formatDate})),n==="FITID"?X({fitId:e.fitId,fitValue:t}):e.nativeTypes&&B(n,t)?`${Number(t)},`:`"${t}",`}function z(e,t){let n=e;return e.match(/{(\w|\W)+/)&&(n=n.replace(/({(\w|\W)+)$/,r=>W({field:n.slice(0,n.indexOf(":")),value:r,...t}))),n}function B(e,t){return e.endsWith("ID")||e.endsWith("NUM")?!1:!isNaN(Number(t))}function y(e){const t=e.indexOf(R),n=e.lastIndexOf(T)+T.length,r=e.substring(t,n),s=new RegExp(R,"g"),a=new RegExp(T,"g"),i=`"STRTTRN":[${r.replace(s,"{").replace(a,"},")}]`;return{oldListText:e,newListText:e.replace(r,i)}}function m(e){if(!(e.indexOf(u)>0))return{newBankStatementTransactions:null,oldBankStatementTransactions:null};const n=y(e.substring(e.indexOf(u),e.indexOf(d)+d.length));return{newBankStatementTransactions:n.newListText,oldBankStatementTransactions:n.oldListText}}function h(e){if(!(e.indexOf(g)>0))return{newCreditCardStatementTransactions:null,oldCreditCardStatementTransactions:null};const n=e.substring(e.indexOf(g),e.indexOf(S)+S.length),r=y(n);return{newCreditCardStatementTransactions:r.newListText,oldCreditCardStatementTransactions:r.oldListText}}function j(e){const t=[];for(const n of e)t.push(...n.replace("<?xml"," ").replace("<?OFX"," ").replace("?>"," ").split(" ").filter(Boolean).map(D).map(r=>r.replace("=",":").replace(/["]/g,"")));return t}function H(e,t){const n={};e.join("").search("<?")>-1&&(e=j(e));for(const r of e){const[s,a]=r.split(":"),i=s.replace(`
6
- `,"").toUpperCase();n[i]=t&&B(s,a)?Number(a):String(a).replace(/\?>/,"")}return n}function U(e){return e.reduce((t,n)=>{const r=Math.abs(+n.TRNAMT);return K(n)?(t.amountOfDebits++,t.debit+=r,t):(t.amountOfCredits++,t.credit+=r,t)},{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0})}class v{config;internConfig={};constructor(t){this.config=t,this.internConfig=t}getConfig(){return this.internConfig}sanitizeRow(t){return z(t,this.internConfig)}getPartialJsonData(t){const[n,r]=t.split("<OFX>"),s="<OFX>"+r,{sanitizeRow:a}=this;return s.replace(/<(?=[^\/])/g,`
7
- <`).replace(E,i=>A(i)).replace(N,i=>w(i)).split(`
8
- `).map(D).filter(Boolean).map(a,this).join("")}}function I(e){return e.toString()}async function p(e){return await new Promise((n,r)=>{if(typeof window<"u"&&window.FileReader){const s=new window.FileReader;s.onload=a=>n(a.target.result),s.onerror=a=>r(a.target.error),s.readAsText(e)}else r(new Error("FileReader is not available in this environment."))})}class c{data;dataRead="";constructor(t){this.data=t,t&&(this.dataRead=t)}getData(){return this.dataRead}fromString(t){return new c(t)}fromBuffer(t){return new c(I(t))}async fromBlob(t){return new c(await p(t))}static fromString(t){return new c(t)}static fromBuffer(t){return new c(I(t))}static async fromBlob(t){return new c(await p(t))}}class Y{configInstance={}}class O extends Y{setConfig(t){this.configInstance=t}getBankTransferList(t){const{newBankStatementTransactions:n}=m(this.configInstance.getPartialJsonData(t));return n?JSON.parse(`{${f(n)}}`)?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS?.BANKTRANLIST?.STRTTRN:[]}getCreditCardTransferList(t){const{newCreditCardStatementTransactions:n}=h(this.configInstance.getPartialJsonData(t));return n?JSON.parse(`{${f(n)}}`)?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS?.BANKTRANLIST?.STRTTRN:[]}getTransactionsSummary(t){const n=this.getContent(t),{DTEND:r,DTSTART:s,...a}=n.OFX.BANKMSGSRSV1.STMTTRNRS.STMTRS.BANKTRANLIST,i=U(a.STMTTRN||a.STRTTRN);return{dateStart:s,dateEnd:r,...i}}getContent(t){const n=this.configInstance.getPartialJsonData(t),{newBankStatementTransactions:r,oldBankStatementTransactions:s}=m(n),{newCreditCardStatementTransactions:a,oldCreditCardStatementTransactions:i}=h(n);let o=n;return r&&(o=o.replace(s,r)),a&&(o=o.replace(i,a)),JSON.parse(`{${f(o)}}`)}}class Q{customExtractor;customExtractorInstance;dataReaderInstance=new c("");constructor(t){this.customExtractor=t,this.customExtractorInstance=t||new O,this.config({})}data(t){return this.dataReaderInstance=t,this}config(t){return this.customExtractorInstance.setConfig(new v(t)),this}getType(){return F(this.dataReaderInstance.getData())}getHeaders(){const[t]=this.dataReaderInstance.getData().split("<OFX>");return H(t.split(`
9
- `).filter(n=>!!n.trim().length),!!this.customExtractorInstance.configInstance.getConfig().nativeTypes)}getBankTransferList(){return this.customExtractorInstance.getBankTransferList(this.dataReaderInstance.getData())}getCreditCardTransferList(){return this.customExtractorInstance.getCreditCardTransferList(this.dataReaderInstance.getData())}getTransactionsSummary(){return this.customExtractorInstance.getTransactionsSummary(this.dataReaderInstance.getData())}getContent(){return this.customExtractorInstance.getContent(this.dataReaderInstance.getData())}toJson(){const t=this.getHeaders(),n=this.customExtractorInstance.getContent(this.dataReaderInstance.getData());return{...t,...n}}}class C{extractor;constructor(t,n){this.extractor=new Q(new O),this.extractor.data(new c(t)),this.extractor.config(n||{})}getType(){return this.extractor.getType()}static fromBuffer(t){return new C(c.fromBuffer(t).getData())}static async fromBlob(t){const n=await c.fromBlob(t);return new C(n.getData())}config(t){return this.extractor.config(t),this}getHeaders(){return this.extractor.getHeaders()}getBankTransferList(){return this.extractor.getBankTransferList()}getCreditCardTransferList(){return this.extractor.getCreditCardTransferList()}getTransactionsSummary(){return this.extractor.getTransactionsSummary()}getContent(){return this.extractor.getContent()}toJson(){return this.extractor.toJson()}}export{Q as Extractor,C as Ofx,O as OfxExtractor,c as Reader,p as blobToString,I as bufferToString,f as fixJsonProblems,k as formatDate};
1
+ const L=/<\/[\w.]+>/g,B=/<[\w.]+>/g,g="<BANKMSGSRSV1>",m="</BANKMSGSRSV1>",S="<CREDITCARDMSGSRSV1>",C="</CREDITCARDMSGSRSV1>",D="<STMTTRN>",h="</STMTTRN>";const j=new RegExp("_#_","g"),Q=[g,D,S],Z=[m,C,h];function q(n){const t=n.slice(0,4),e=n.slice(4,6),r=n.slice(6,8),a=n.slice(8,10),s=n.slice(10,12),i=n.slice(12,14);return{yyyy:t,yy:t.slice(2),y:t,MM:e,M:e,dd:r,d:r,hh:a,h:a,mm:s,m:s,ss:i,s:i}}function _(n,t){const e=["yyyy","yy","y","MM","M","dd","d","hh","h","mm","m","ss","s"];let r=n;for(const a of e)r=r.replace(a,t[a]);return r}function tt(n,t){const e=q(n);return _(t,e)}function nt(n,t){const e=String(n.getUTCFullYear()),r=String(n.getUTCMonth()+1).padStart(2,"0"),a=String(n.getUTCDate()).padStart(2,"0"),s=String(n.getUTCHours()).padStart(2,"0"),i=String(n.getUTCMinutes()).padStart(2,"0"),c=String(n.getUTCSeconds()).padStart(2,"0");return _(t,{yyyy:e,yy:e.slice(2),y:e,MM:r,M:r,dd:a,d:a,hh:s,h:s,mm:i,m:i,ss:c,s:c})}function et(n){return n%400===0?!0:n%100===0?!1:n%4===0}function rt(n,t){return n===2?et(t)?29:28:[4,6,9,11].includes(n)?30:31}function at(n){if(!/^\d{4}-\d{2}-\d{2}$/.test(n))return null;const[t,e,r]=n.split("-").map(Number);return{year:t,month:e,day:r,hour:0,minutes:0,seconds:0}}function st(n){if(!/^\d{4}-\d{2}-\d{2}T/.test(n))return null;const t=n.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):?(\d{2})(?::?(\d{2}))?(?:\.\d+)?(?:([+-]\d{2}):?(\d{2})|Z)?$/);if(!t)return null;const e=Number(t[1]),r=Number(t[2]),a=Number(t[3]),s=Number(t[4]),i=Number(t[5]),c=Number(t[6]||"0");if(!v({year:e,month:r,day:a,hour:s,minutes:i,seconds:c}))return null;const l=Number(t[7]||"0"),f=Number(t[8]||"0"),T=l<0?l*60-f:l*60+f,u=new Date(Date.UTC(e,r-1,a,s,i,c)-T*6e4);return Number.isNaN(u.getTime())?null:{year:u.getUTCFullYear(),month:u.getUTCMonth()+1,day:u.getUTCDate(),hour:u.getUTCHours(),minutes:u.getUTCMinutes(),seconds:u.getUTCSeconds()}}function it(n){const e=n.split("[")[0].split(".")[0];return/^\d{8}(\d{6})?$/.test(e)?{year:Number(e.slice(0,4)),month:Number(e.slice(4,6)),day:Number(e.slice(6,8)),hour:Number(e.slice(8,10)||"00"),minutes:Number(e.slice(10,12)||"00"),seconds:Number(e.slice(12,14)||"00")}:null}function ot(n){const t=n.match(/^(\d{1,4})[/-](\d{1,2})[/-](\d{1,4})$/);if(!t)return null;const e=Number(t[1]),r=Number(t[2]),a=Number(t[3]),s=String(t[1]).length===4,i=String(t[3]).length===4;return!s&&!i?null:{year:s?e:a,month:r,day:s?a:e,hour:0,minutes:0,seconds:0}}function ct(n){return at(n)||st(n)||ot(n)||it(n)}function v(n){return!(!Number.isFinite(n.year)||!Number.isFinite(n.month)||!Number.isFinite(n.day)||!Number.isFinite(n.hour)||!Number.isFinite(n.minutes)||!Number.isFinite(n.seconds)||n.month<1||n.month>12||n.day<1||n.day>rt(n.month,n.year)||n.hour<0||n.hour>23||n.minutes<0||n.minutes>59||n.seconds<0||n.seconds>59)}function N(n){const t=ct(n);return!t||!v(t)?null:new Date(Date.UTC(t.year,t.month-1,t.day,t.hour,t.minutes,t.seconds))}var p;(function(n){n.BANK="BANK",n.CREDIT_CARD="CREDIT_CARD"})(p||(p={}));const ut=["debit","fee","srvchg","atm","pos","check","payment","directdebit","cash","repeatpmt"],lt=n=>n.includes(g)?p.BANK:p.CREDIT_CARD;function k(n){if(String(n.TRNAMT).startsWith("-"))return!0;const t=String(n.TRNTYPE).toLocaleLowerCase();return t==="1"||ut.includes(t)}function I(n){const t=n.replace(/(\\)/g,"\\\\").replace(L,e=>$(e,!0)).replace(B,e=>P(e,!0)).replace(/(},})/g,"}}").replace(/(}")/g,'},"').replace(/(]")/g,'],"').replace(/(},])/g,"}]").replace(/(,})/g,"}").replace(/,\s*}/g,"}").replace(/(,",)/,'",').replace(j,'\\"').slice(0,-1);return t.endsWith(",")?t.slice(0,-1):t}const dt=n=>JSON.stringify({date:n.slice(0,12),transactionCode:n.slice(12,19),protocol:n.slice(19)}),Tt=n=>n.startsWith("DT"),U=n=>n.trim();function P(n,t=!1){return Q.includes(n)&&!t?n:n.replace(/[<]/g,`
2
+ "`).replace(/[>]/g,'":{')}function $(n,t=!1){return Z.includes(n)&&!t?`
3
+ ${n}`:`},
4
+ `}function ft({fitId:n,fitValue:t}){return n==="separated"?dt(t):`"${t}",`}function gt(n){return n.search(",")>n.search(".")?n.replace(/[.]/g,"").replace(/[,]/g,"."):n.replace(/[,]/g,"")}function E({dateString:n,formatDate:t="y-M-d"}){return!/^\d{8}(\d{6})?(\.\d+)?(\[[^\]]+\])?$/.test(n)||!N(n)?n:tt(n,t)}function ht(n){let t=n.value.replace(/[{]/g,"").replace(/(},)/g,"").replace(/["]/g,"_#_");const e=n.field.replace(/['"]/g,"");return e.endsWith("AMT")&&(t=gt(t)),Tt(e)&&(t=E({dateString:t,formatDate:n.formatDate})),e==="FITID"?ft({fitId:n.fitId,fitValue:t}):n.nativeTypes&&G(e,t)?`${Number(t)},`:`"${t}",`}function mt(n,t){let e=n;return n.match(/{(\w|\W)+/)&&(e=e.replace(/({(\w|\W)+)$/,r=>ht({field:e.slice(0,e.indexOf(":")),value:r,...t}))),e}function G(n,t){return n.endsWith("ID")||n.endsWith("NUM")?!1:!isNaN(Number(t))}function K(n){const t=n.indexOf(D),e=n.lastIndexOf(h)+h.length;if(t<0||e<0||t>=e)return{oldListText:n,newListText:n};const r=n.substring(t,e),a=new RegExp(D,"g"),s=new RegExp(h,"g"),i=`"STRTTRN":[${r.replace(a,"{").replace(s,"},")}]`;return{oldListText:n,newListText:n.replace(r,i)}}function O(n){if(!(n.indexOf(g)>0))return{newBankStatementTransactions:null,oldBankStatementTransactions:null};const e=K(n.substring(n.indexOf(g),n.indexOf(m)+m.length));return{newBankStatementTransactions:e.newListText,oldBankStatementTransactions:e.oldListText}}function w(n){if(!(n.indexOf(S)>0))return{newCreditCardStatementTransactions:null,oldCreditCardStatementTransactions:null};const e=n.substring(n.indexOf(S),n.indexOf(C)+C.length),r=K(e);return{newCreditCardStatementTransactions:r.newListText,oldCreditCardStatementTransactions:r.oldListText}}function St(n){const t=[];for(const e of n)t.push(...e.replace("<?xml"," ").replace("<?OFX"," ").replace("?>"," ").split(" ").filter(Boolean).map(U).map(r=>r.replace("=",":").replace(/["]/g,"")));return t}function Ct(n,t){const e={};n.join("").search("<?")>-1&&(n=St(n));for(const r of n){const[a,s]=r.split(":"),i=a.replace(`
5
+ `,"").toUpperCase();e[i]=t&&G(a,s)?Number(s):String(s).replace(/\?>/,"")}return e}function Nt(n){return n.reduce((t,e)=>{const r=Math.abs(+e.TRNAMT);return k(e)?(t.amountOfDebits++,t.debit+=r,t):(t.amountOfCredits++,t.credit+=r,t)},{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0})}class pt{config;internConfig={};constructor(t){this.config=t,this.internConfig=t}getConfig(){return this.internConfig}sanitizeRow(t){return mt(t,this.internConfig)}getPartialJsonData(t){const[e,r]=t.split("<OFX>"),a="<OFX>"+r,{sanitizeRow:s}=this;return a.replace(/<(?=[^\/])/g,`
6
+ <`).replace(L,i=>$(i)).replace(B,i=>P(i)).split(`
7
+ `).map(U).filter(Boolean).map(s,this).join("")}}function x(n){return n.toString()}async function F(n){return await new Promise((e,r)=>{if(typeof window<"u"&&window.FileReader){const a=new window.FileReader;a.onload=s=>e(s.target.result),a.onerror=s=>r(s.target.error),a.readAsText(n)}else r(new Error("FileReader is not available in this environment."))})}class d{data;dataRead="";constructor(t){this.data=t,t&&(this.dataRead=t)}getData(){return this.dataRead}fromString(t){return new d(t)}fromBuffer(t){return new d(x(t))}async fromBlob(t){return new d(await F(t))}static fromString(t){return new d(t)}static fromBuffer(t){return new d(x(t))}static async fromBlob(t){return new d(await F(t))}}class Rt{configInstance={}}class X extends Rt{setConfig(t){this.configInstance=t}getBankTransferList(t){const{newBankStatementTransactions:e}=O(this.configInstance.getPartialJsonData(t));return e?JSON.parse(`{${I(e)}}`)?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS?.BANKTRANLIST?.STRTTRN:[]}getCreditCardTransferList(t){const{newCreditCardStatementTransactions:e}=w(this.configInstance.getPartialJsonData(t));return e?JSON.parse(`{${I(e)}}`)?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS?.BANKTRANLIST?.STRTTRN:[]}getTransactionsSummary(t){let e=[],r=[];const a=this.configInstance?.getConfig?.().parserMode||"strict";let s=null,i=null;try{e=this.getBankTransferList(t)||[]}catch(R){s=R,e=[]}try{r=this.getCreditCardTransferList(t)||[]}catch(R){i=R,r=[]}const c=t.includes("<STMTTRN>");if(a==="strict"&&s&&i&&c)throw s;const o=e.length?e:r,l=e.length?g:S,f=e.length?m:C,T=t.indexOf(l),u=t.indexOf(f),b=T>=0&&u>T?t.slice(T,u+f.length):"",A=b.match(/<DTSTART>([^\n<]+)/),M=b.match(/<DTEND>([^\n<]+)/),V=Nt(o||[]),H=A?.[1]?E({dateString:A[1],formatDate:this.configInstance.getConfig().formatDate}):"",Y=M?.[1]?E({dateString:M[1],formatDate:this.configInstance.getConfig().formatDate}):"";return{dateStart:H,dateEnd:Y,...V}}getContent(t){const e=this.configInstance.getPartialJsonData(t),{newBankStatementTransactions:r,oldBankStatementTransactions:a}=O(e),{newCreditCardStatementTransactions:s,oldCreditCardStatementTransactions:i}=w(e);let c=e;return r&&(c=c.replace(a,r)),s&&(c=c.replace(i,s)),JSON.parse(`{${I(c)}}`)}}function z(n){if(n==null)return"";if(typeof n=="string"||typeof n=="number")return String(n);if(typeof n=="object"){const t=n;if(t.date!==void 0&&t.transactionCode!==void 0&&t.protocol!==void 0)return`${t.date}${t.transactionCode}${t.protocol}`;try{return JSON.stringify(n)}catch{return String(n)}}return String(n)}function W(n){if(n==null)return null;const t=Number(String(n).replace(/,/g,"."));return Number.isNaN(t)?null:t}function It(n){return n.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/\s+/g," ").trim().toLowerCase()}function Dt(n,t){const e=t.dateMode||"formatted";if(!n)return{value:null,warning:{code:"MISSING_DATE",message:"Transaction does not include DTPOSTED.",severity:"warning"}};if(e==="raw"||e==="formatted"){const a=N(n);return a?e==="formatted"&&t.formatDate?{value:nt(a,t.formatDate)}:{value:n}:{value:n,warning:{code:"INVALID_DATE",message:`Unable to parse date '${n}'.`,severity:"warning"}}}const r=N(n);return r?e==="date"?{value:r}:e==="timestamp"?{value:r.getTime()}:{value:r.toISOString()}:{value:null,warning:{code:"INVALID_DATE",message:`Unable to parse date '${n}'.`,severity:"warning"}}}function Et(n,t){const e=W(n);return e===null?{amount:null,amountAbs:null,warning:{code:"INVALID_AMOUNT",message:`Unable to parse amount '${String(n)}'.`,severity:"warning"}}:t==="string"?{amount:String(n),amountAbs:String(Math.abs(e))}:t==="cents"?{amount:Math.round(e*100),amountAbs:Math.round(Math.abs(e)*100)}:{amount:e,amountAbs:Math.abs(e)}}function J(n){const t=[],e=n?.OFX?.SIGNONMSGSRSV1?.SONRS?.FI||null,r=n?.OFX?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS;(r?.BANKTRANLIST?.STRTTRN||r?.BANKTRANLIST?.STMTTRN||[]).forEach((c,o)=>{t.push({source:"bank",transaction:c,currency:r?.CURDEF||null,account:r?.BANKACCTFROM||null,institution:e,path:`OFX.BANKMSGSRSV1.STMTTRNRS.STMTRS.BANKTRANLIST.${o}`})});const s=n?.OFX?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS;return(s?.BANKTRANLIST?.STRTTRN||s?.BANKTRANLIST?.STMTTRN||[]).forEach((c,o)=>{t.push({source:"credit_card",transaction:c,currency:s?.CURDEF||null,account:s?.CCACCTFROM||null,institution:e,path:`OFX.CREDITCARDMSGSRSV1.CCSTMTTRNRS.CCSTMTRS.BANKTRANLIST.${o}`})}),t}function yt(n,t={}){const e=t.amountMode||"number",r=[],a=[];return J(n).forEach(i=>{const c=[],o=String(i.transaction.MEMO||i.transaction.NAME||i.transaction.TRNTYPE||"").trim(),{amount:l,amountAbs:f,warning:T}=Et(i.transaction.TRNAMT,e);T&&(T.path=i.path,c.push(T),r.push(T));const u=Dt(i.transaction.DTPOSTED,t);u.warning&&(u.warning.path=i.path,c.push(u.warning),r.push(u.warning)),a.push({source:i.source,direction:k(i.transaction)?"debit":"credit",amount:l,amountAbs:f,postedAt:u.value,description:o,descriptionNormalized:It(o),fitId:z(i.transaction.FITID),currency:i.currency,account:i.account,institution:i.institution,raw:i.transaction,warnings:c})}),{transactions:a,warnings:r}}function bt(n){const t=[],e=[],r=J(n);n?.OFX||e.push({code:"MISSING_OFX_BLOCK",message:"Missing OFX root block.",severity:"error",path:"OFX"}),r.length||t.push({code:"NO_TRANSACTIONS_FOUND",message:"No transactions were found in bank or credit card statements.",severity:"warning",path:"OFX"});const a=new Map;r.forEach(o=>{const l=z(o.transaction.FITID);l?a.set(l,(a.get(l)||0)+1):t.push({code:"MISSING_FITID",message:"Transaction does not include FITID.",severity:"warning",path:o.path}),W(o.transaction.TRNAMT)===null&&e.push({code:"INVALID_AMOUNT",message:`Invalid amount '${String(o.transaction.TRNAMT)}'.`,severity:"error",path:o.path}),N(String(o.transaction.DTPOSTED||""))||t.push({code:"INVALID_DATE",message:`Invalid DTPOSTED '${String(o.transaction.DTPOSTED||"")}'.`,severity:"warning",path:o.path})});const s=Array.from(a.values()).filter(o=>o>1).length;s>0&&t.push({code:"DUPLICATED_FITID",message:`Found ${s} duplicated FITID values.`,severity:"warning",path:"OFX"});const i=r.filter(o=>o.source==="bank").length,c=r.filter(o=>o.source==="credit_card").length;return{isValid:e.length===0,warnings:t,errors:e,stats:{totalTransactions:r.length,bankTransactions:i,creditCardTransactions:c,duplicatedFitIds:s}}}class At{customExtractor;customExtractorInstance;dataReaderInstance=new d("");diagnostics=[];constructor(t){this.customExtractor=t,this.customExtractorInstance=t||new X,this.config({})}data(t){return this.dataReaderInstance=t,this}config(t){return this.customExtractorInstance.setConfig(new pt(t)),this}getType(){return lt(this.dataReaderInstance.getData())}beginOperation(){this.diagnostics=[]}getParserMode(){return this.customExtractorInstance.configInstance.getConfig().parserMode}pushWarning(t){this.diagnostics.push(t)}safelyExecute(t,e,r){try{return r()}catch(a){if(this.getParserMode()!=="lenient")throw a;return this.pushWarning({code:"PARSE_ERROR",message:`Failed to parse OFX while running '${t}'.`,severity:"warning",context:a instanceof Error?a.message:String(a),path:t}),e}}getHeadersInternal(){return this.safelyExecute("getHeaders",{},()=>{const[t]=this.dataReaderInstance.getData().split("<OFX>");return Ct(String(t).split(`
8
+ `).filter(e=>!!e.trim().length),!!this.customExtractorInstance.configInstance.getConfig().nativeTypes)})}getContentInternal(){return this.safelyExecute("getContent",{OFX:{}},()=>this.customExtractorInstance.getContent(this.dataReaderInstance.getData()))}getResponseInternal(){const t=this.getHeadersInternal(),e=this.getContentInternal();return{...t,...e}}getHeaders(){return this.beginOperation(),this.getHeadersInternal()}getBankTransferList(){return this.beginOperation(),this.safelyExecute("getBankTransferList",[],()=>this.customExtractorInstance.getBankTransferList(this.dataReaderInstance.getData()))}getCreditCardTransferList(){return this.beginOperation(),this.safelyExecute("getCreditCardTransferList",[],()=>this.customExtractorInstance.getCreditCardTransferList(this.dataReaderInstance.getData()))}getTransactionsSummary(){return this.beginOperation(),this.safelyExecute("getTransactionsSummary",{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0,dateStart:"",dateEnd:""},()=>this.customExtractorInstance.getTransactionsSummary(this.dataReaderInstance.getData()))}getContent(){return this.beginOperation(),this.getContentInternal()}toJson(){return this.beginOperation(),this.getResponseInternal()}toNormalized(t={}){this.beginOperation();const e=this.getResponseInternal();return yt(e,{...t,formatDate:t.formatDate||this.customExtractorInstance.configInstance.getConfig().formatDate})}validate(){this.beginOperation();const t=this.getResponseInternal();return bt(t)}getWarnings(){return[...this.diagnostics]}}class y{extractor;constructor(t,e){this.extractor=new At(new X),this.extractor.data(new d(t)),this.extractor.config(e||{})}getType(){return this.extractor.getType()}static fromBuffer(t){return new y(d.fromBuffer(t).getData())}static async fromBlob(t){const e=await d.fromBlob(t);return new y(e.getData())}config(t){return this.extractor.config(t),this}getHeaders(){return this.extractor.getHeaders()}getBankTransferList(){return this.extractor.getBankTransferList()}getCreditCardTransferList(){return this.extractor.getCreditCardTransferList()}getTransactionsSummary(){return this.extractor.getTransactionsSummary()}getContent(){return this.extractor.getContent()}toJson(){return this.extractor.toJson()}toNormalized(t){return this.extractor.toNormalized(t)}validate(){return this.extractor.validate()}getWarnings(){return this.extractor.getWarnings()}}export{At as Extractor,y as Ofx,X as OfxExtractor,d as Reader,F as blobToString,x as bufferToString,I as fixJsonProblems,tt as formatDate};
@@ -1,4 +1,4 @@
1
- import type { ExtractorConfig, MetaData, TransactionsSummary } from '../@types/common';
1
+ import type { ExtractorConfig, MetaData, NormalizeOptions, NormalizedOfxData, OfxDiagnostic, TransactionsSummary, ValidationReport } from '../@types/common';
2
2
  import { IExtractor } from '../interfaces/extractor.interface';
3
3
  import { CustomExtractor } from '../interfaces/custom-extractor.interface';
4
4
  import { Reader } from './reader';
@@ -8,14 +8,25 @@ export declare class Extractor<T = any> implements IExtractor<T> {
8
8
  private readonly customExtractor?;
9
9
  private customExtractorInstance;
10
10
  private dataReaderInstance;
11
+ private diagnostics;
11
12
  constructor(customExtractor?: CustomExtractor | undefined);
12
13
  data(readData: Reader): this;
13
14
  config(config: ExtractorConfig): this;
14
15
  getType(): import("../@types/common").Types;
16
+ private beginOperation;
17
+ private getParserMode;
18
+ private pushWarning;
19
+ private safelyExecute;
20
+ private getHeadersInternal;
21
+ private getContentInternal;
22
+ private getResponseInternal;
15
23
  getHeaders(): MetaData;
16
24
  getBankTransferList(): StatementTransaction[];
17
25
  getCreditCardTransferList(): StatementTransaction[];
18
26
  getTransactionsSummary(): TransactionsSummary;
19
27
  getContent(): OfxStructure;
20
28
  toJson(): OfxResponse;
29
+ toNormalized(options?: NormalizeOptions): NormalizedOfxData;
30
+ validate(): ValidationReport;
31
+ getWarnings(): OfxDiagnostic[];
21
32
  }
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  export declare class Reader {
3
4
  private readonly data?;
4
5
  private dataRead;
package/dist/index.d.ts CHANGED
@@ -9,4 +9,4 @@ export type { BankTransactionList } from './@types/ofx/banking';
9
9
  export { Reader } from './implementations/reader';
10
10
  export { OfxExtractor } from './implementations/ofx-extractor';
11
11
  export { Extractor } from './implementations/extractor';
12
- export type { MetaData, ExtractorConfig, DateResponse } from './@types/common';
12
+ export type { MetaData, ExtractorConfig, DateResponse, ValidationReport, ValidationStats, OfxDiagnostic, NormalizeOptions, NormalizedTransaction, NormalizedOfxData, ParserMode, AmountMode, DateMode, } from './@types/common';
@@ -1,5 +1,5 @@
1
1
  import { OfxResponse, OfxStructure } from '../@types/ofx/index';
2
- import { ExtractorConfig, MetaData, TransactionsSummary, Types } from '../@types/common';
2
+ import { ExtractorConfig, MetaData, NormalizeOptions, NormalizedOfxData, TransactionsSummary, Types, ValidationReport, OfxDiagnostic } from '../@types/common';
3
3
  import type { StatementTransaction } from '../@types/ofx/common';
4
4
  export interface IExtractor<T> {
5
5
  getType(): Types;
@@ -10,4 +10,7 @@ export interface IExtractor<T> {
10
10
  getTransactionsSummary(): TransactionsSummary;
11
11
  getContent(): OfxStructure;
12
12
  toJson(): OfxResponse;
13
+ toNormalized(options?: NormalizeOptions): NormalizedOfxData;
14
+ validate(): ValidationReport;
15
+ getWarnings(): OfxDiagnostic[];
13
16
  }
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" />
2
- import { ExtractorConfig, MetaData } from '../@types/common';
2
+ /// <reference types="node" />
3
+ import { ExtractorConfig, MetaData, NormalizeOptions, NormalizedOfxData, OfxDiagnostic, ValidationReport } from '../@types/common';
3
4
  import { OfxResponse, OfxStructure } from '../@types/ofx/index';
4
5
  import { StatementTransaction } from '../@types/ofx/common';
5
6
  export declare class Ofx {
@@ -15,4 +16,7 @@ export declare class Ofx {
15
16
  getTransactionsSummary(): import("../@types/common").TransactionsSummary;
16
17
  getContent(): OfxStructure;
17
18
  toJson(): OfxResponse;
19
+ toNormalized(options?: NormalizeOptions): NormalizedOfxData;
20
+ validate(): ValidationReport;
21
+ getWarnings(): OfxDiagnostic[];
18
22
  }
@@ -1,9 +1,8 @@
1
- // Ofx-data-extractor v1.4.7 Copyright (c) 2025 Fábio Pereira França
2
- (function(c,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(c=typeof globalThis<"u"?globalThis:c||self,u(c.ofx={}))})(this,function(c){"use strict";const u=/<\/[\w.]+>/g,N=/<[\w.]+>/g,f="<BANKMSGSRSV1>",R="</BANKMSGSRSV1>",m="<CREDITCARDMSGSRSV1>",C="</CREDITCARDMSGSRSV1>",h="<STMTTRN>",d="</STMTTRN>",x="_#_",_=new RegExp(x,"g"),k=[f,h,m],G=[R,C,d];function F(e){const t=e.slice(0,4),n=e.slice(4,6),r=e.slice(6,8),s=e.slice(8,10),a=e.slice(10,12),i=e.slice(12,14);return{yyyy:t,yy:t.slice(2),y:t,MM:n,M:n,dd:r,d:r,hh:s,h:s,mm:a,m:a,ss:i,s:i}}function D(e,t){const n=F(e);let r=t;for(const[s,a]of Object.entries(n))r=r.replace(s,a);return r}var l;(function(e){e.BANK="BANK",e.CREDIT_CARD="CREDIT_CARD"})(l||(l={}));const J=["debit","fee","srvchg","atm","pos","check","payment","directdebit","cash","repeatpmt"],K=e=>e.includes(f)?l.BANK:l.CREDIT_CARD;function P(e){if(String(e.TRNAMT).startsWith("-"))return!0;const t=String(e.TRNTYPE).toLocaleLowerCase();return t==="1"||J.includes(t)}function g(e){const t=e.replace(/(\\)/g,"\\\\").replace(u,n=>y(n,!0)).replace(N,n=>A(n,!0)).replace(/(},})/g,"}}").replace(/(}")/g,'},"').replace(/(]")/g,'],"').replace(/(},])/g,"}]").replace(/(,})/g,"}").replace(/,\s*}/g,"}").replace(/(,",)/,'",').replace(_,'\\"').slice(0,-1);return t.endsWith(",")?t.slice(0,-1):t}const X=e=>JSON.stringify({date:e.slice(0,12),transactionCode:e.slice(12,19),protocol:e.slice(19)}),$=e=>e.startsWith("DT"),w=e=>e.trim();function A(e,t=!1){return k.includes(e)&&!t?e:e.replace(/[<]/g,`
3
- "`).replace(/[>]/g,'":{')}function y(e,t=!1){return G.includes(e)&&!t?`
4
- ${e}`:`},
5
- `}function V({fitId:e,fitValue:t}){return e==="separated"?X(t):`"${t}",`}function W(e){return e.search(",")>e.search(".")?e.replace(/[.]/g,"").replace(/[,]/g,"."):e.replace(/[,]/g,"")}function j({dateString:e,formatDate:t="y-M-d"}){return D(e,t)}function z(e){let t=e.value.replace(/[{]/g,"").replace(/(},)/g,"").replace(/["]/g,x);const n=e.field.replace(/['"]/g,"");return n.endsWith("AMT")&&(t=W(t)),$(n)&&(t=j({dateString:t,formatDate:e.formatDate})),n==="FITID"?V({fitId:e.fitId,fitValue:t}):e.nativeTypes&&B(n,t)?`${Number(t)},`:`"${t}",`}function H(e,t){let n=e;return e.match(/{(\w|\W)+/)&&(n=n.replace(/({(\w|\W)+)$/,r=>z({field:n.slice(0,n.indexOf(":")),value:r,...t}))),n}function B(e,t){return e.endsWith("ID")||e.endsWith("NUM")?!1:!isNaN(Number(t))}function O(e){const t=e.indexOf(h),n=e.lastIndexOf(d)+d.length,r=e.substring(t,n),s=new RegExp(h,"g"),a=new RegExp(d,"g"),i=`"STRTTRN":[${r.replace(s,"{").replace(a,"},")}]`;return{oldListText:e,newListText:e.replace(r,i)}}function b(e){if(!(e.indexOf(f)>0))return{newBankStatementTransactions:null,oldBankStatementTransactions:null};const n=O(e.substring(e.indexOf(f),e.indexOf(R)+R.length));return{newBankStatementTransactions:n.newListText,oldBankStatementTransactions:n.oldListText}}function M(e){if(!(e.indexOf(m)>0))return{newCreditCardStatementTransactions:null,oldCreditCardStatementTransactions:null};const n=e.substring(e.indexOf(m),e.indexOf(C)+C.length),r=O(n);return{newCreditCardStatementTransactions:r.newListText,oldCreditCardStatementTransactions:r.oldListText}}function U(e){const t=[];for(const n of e)t.push(...n.replace("<?xml"," ").replace("<?OFX"," ").replace("?>"," ").split(" ").filter(Boolean).map(w).map(r=>r.replace("=",":").replace(/["]/g,"")));return t}function v(e,t){const n={};e.join("").search("<?")>-1&&(e=U(e));for(const r of e){const[s,a]=r.split(":"),i=s.replace(`
6
- `,"").toUpperCase();n[i]=t&&B(s,a)?Number(a):String(a).replace(/\?>/,"")}return n}function Y(e){return e.reduce((t,n)=>{const r=Math.abs(+n.TRNAMT);return P(n)?(t.amountOfDebits++,t.debit+=r,t):(t.amountOfCredits++,t.credit+=r,t)},{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0})}class Q{config;internConfig={};constructor(t){this.config=t,this.internConfig=t}getConfig(){return this.internConfig}sanitizeRow(t){return H(t,this.internConfig)}getPartialJsonData(t){const[n,r]=t.split("<OFX>"),s="<OFX>"+r,{sanitizeRow:a}=this;return s.replace(/<(?=[^\/])/g,`
7
- <`).replace(u,i=>y(i)).replace(N,i=>A(i)).split(`
8
- `).map(w).filter(Boolean).map(a,this).join("")}}function E(e){return e.toString()}async function I(e){return await new Promise((n,r)=>{if(typeof window<"u"&&window.FileReader){const s=new window.FileReader;s.onload=a=>n(a.target.result),s.onerror=a=>r(a.target.error),s.readAsText(e)}else r(new Error("FileReader is not available in this environment."))})}class o{data;dataRead="";constructor(t){this.data=t,t&&(this.dataRead=t)}getData(){return this.dataRead}fromString(t){return new o(t)}fromBuffer(t){return new o(E(t))}async fromBlob(t){return new o(await I(t))}static fromString(t){return new o(t)}static fromBuffer(t){return new o(E(t))}static async fromBlob(t){return new o(await I(t))}}class q{configInstance={}}class p extends q{setConfig(t){this.configInstance=t}getBankTransferList(t){const{newBankStatementTransactions:n}=b(this.configInstance.getPartialJsonData(t));return n?JSON.parse(`{${g(n)}}`)?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS?.BANKTRANLIST?.STRTTRN:[]}getCreditCardTransferList(t){const{newCreditCardStatementTransactions:n}=M(this.configInstance.getPartialJsonData(t));return n?JSON.parse(`{${g(n)}}`)?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS?.BANKTRANLIST?.STRTTRN:[]}getTransactionsSummary(t){const n=this.getContent(t),{DTEND:r,DTSTART:s,...a}=n.OFX.BANKMSGSRSV1.STMTTRNRS.STMTRS.BANKTRANLIST,i=Y(a.STMTTRN||a.STRTTRN);return{dateStart:s,dateEnd:r,...i}}getContent(t){const n=this.configInstance.getPartialJsonData(t),{newBankStatementTransactions:r,oldBankStatementTransactions:s}=b(n),{newCreditCardStatementTransactions:a,oldCreditCardStatementTransactions:i}=M(n);let T=n;return r&&(T=T.replace(s,r)),a&&(T=T.replace(i,a)),JSON.parse(`{${g(T)}}`)}}class L{customExtractor;customExtractorInstance;dataReaderInstance=new o("");constructor(t){this.customExtractor=t,this.customExtractorInstance=t||new p,this.config({})}data(t){return this.dataReaderInstance=t,this}config(t){return this.customExtractorInstance.setConfig(new Q(t)),this}getType(){return K(this.dataReaderInstance.getData())}getHeaders(){const[t]=this.dataReaderInstance.getData().split("<OFX>");return v(t.split(`
9
- `).filter(n=>!!n.trim().length),!!this.customExtractorInstance.configInstance.getConfig().nativeTypes)}getBankTransferList(){return this.customExtractorInstance.getBankTransferList(this.dataReaderInstance.getData())}getCreditCardTransferList(){return this.customExtractorInstance.getCreditCardTransferList(this.dataReaderInstance.getData())}getTransactionsSummary(){return this.customExtractorInstance.getTransactionsSummary(this.dataReaderInstance.getData())}getContent(){return this.customExtractorInstance.getContent(this.dataReaderInstance.getData())}toJson(){const t=this.getHeaders(),n=this.customExtractorInstance.getContent(this.dataReaderInstance.getData());return{...t,...n}}}class S{extractor;constructor(t,n){this.extractor=new L(new p),this.extractor.data(new o(t)),this.extractor.config(n||{})}getType(){return this.extractor.getType()}static fromBuffer(t){return new S(o.fromBuffer(t).getData())}static async fromBlob(t){const n=await o.fromBlob(t);return new S(n.getData())}config(t){return this.extractor.config(t),this}getHeaders(){return this.extractor.getHeaders()}getBankTransferList(){return this.extractor.getBankTransferList()}getCreditCardTransferList(){return this.extractor.getCreditCardTransferList()}getTransactionsSummary(){return this.extractor.getTransactionsSummary()}getContent(){return this.extractor.getContent()}toJson(){return this.extractor.toJson()}}c.Extractor=L,c.Ofx=S,c.OfxExtractor=p,c.Reader=o,c.blobToString=I,c.bufferToString=E,c.fixJsonProblems=g,c.formatDate=D});
1
+ (function(l,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(l=typeof globalThis<"u"?globalThis:l||self,h(l.ofx={}))})(this,(function(l){"use strict";const h=/<\/[\w.]+>/g,x=/<[\w.]+>/g,m="<BANKMSGSRSV1>",S="</BANKMSGSRSV1>",C="<CREDITCARDMSGSRSV1>",N="</CREDITCARDMSGSRSV1>",y="<STMTTRN>",p="</STMTTRN>",j=new RegExp("_#_","g"),Q=[m,y,C],Z=[S,N,p];function q(n){const t=n.slice(0,4),e=n.slice(4,6),r=n.slice(6,8),a=n.slice(8,10),s=n.slice(10,12),i=n.slice(12,14);return{yyyy:t,yy:t.slice(2),y:t,MM:e,M:e,dd:r,d:r,hh:a,h:a,mm:s,m:s,ss:i,s:i}}function F(n,t){const e=["yyyy","yy","y","MM","M","dd","d","hh","h","mm","m","ss","s"];let r=n;for(const a of e)r=r.replace(a,t[a]);return r}function L(n,t){const e=q(n);return F(t,e)}function tt(n,t){const e=String(n.getUTCFullYear()),r=String(n.getUTCMonth()+1).padStart(2,"0"),a=String(n.getUTCDate()).padStart(2,"0"),s=String(n.getUTCHours()).padStart(2,"0"),i=String(n.getUTCMinutes()).padStart(2,"0"),c=String(n.getUTCSeconds()).padStart(2,"0");return F(t,{yyyy:e,yy:e.slice(2),y:e,MM:r,M:r,dd:a,d:a,hh:s,h:s,mm:i,m:i,ss:c,s:c})}function nt(n){return n%400===0?!0:n%100===0?!1:n%4===0}function et(n,t){return n===2?nt(t)?29:28:[4,6,9,11].includes(n)?30:31}function rt(n){if(!/^\d{4}-\d{2}-\d{2}$/.test(n))return null;const[t,e,r]=n.split("-").map(Number);return{year:t,month:e,day:r,hour:0,minutes:0,seconds:0}}function at(n){if(!/^\d{4}-\d{2}-\d{2}T/.test(n))return null;const t=n.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):?(\d{2})(?::?(\d{2}))?(?:\.\d+)?(?:([+-]\d{2}):?(\d{2})|Z)?$/);if(!t)return null;const e=Number(t[1]),r=Number(t[2]),a=Number(t[3]),s=Number(t[4]),i=Number(t[5]),c=Number(t[6]||"0");if(!B({year:e,month:r,day:a,hour:s,minutes:i,seconds:c}))return null;const f=Number(t[7]||"0"),g=Number(t[8]||"0"),T=f<0?f*60-g:f*60+g,u=new Date(Date.UTC(e,r-1,a,s,i,c)-T*6e4);return Number.isNaN(u.getTime())?null:{year:u.getUTCFullYear(),month:u.getUTCMonth()+1,day:u.getUTCDate(),hour:u.getUTCHours(),minutes:u.getUTCMinutes(),seconds:u.getUTCSeconds()}}function st(n){const e=n.split("[")[0].split(".")[0];return/^\d{8}(\d{6})?$/.test(e)?{year:Number(e.slice(0,4)),month:Number(e.slice(4,6)),day:Number(e.slice(6,8)),hour:Number(e.slice(8,10)||"00"),minutes:Number(e.slice(10,12)||"00"),seconds:Number(e.slice(12,14)||"00")}:null}function it(n){const t=n.match(/^(\d{1,4})[/-](\d{1,2})[/-](\d{1,4})$/);if(!t)return null;const e=Number(t[1]),r=Number(t[2]),a=Number(t[3]),s=String(t[1]).length===4,i=String(t[3]).length===4;return!s&&!i?null:{year:s?e:a,month:r,day:s?a:e,hour:0,minutes:0,seconds:0}}function ot(n){return rt(n)||at(n)||it(n)||st(n)}function B(n){return!(!Number.isFinite(n.year)||!Number.isFinite(n.month)||!Number.isFinite(n.day)||!Number.isFinite(n.hour)||!Number.isFinite(n.minutes)||!Number.isFinite(n.seconds)||n.month<1||n.month>12||n.day<1||n.day>et(n.month,n.year)||n.hour<0||n.hour>23||n.minutes<0||n.minutes>59||n.seconds<0||n.seconds>59)}function R(n){const t=ot(n);return!t||!B(t)?null:new Date(Date.UTC(t.year,t.month-1,t.day,t.hour,t.minutes,t.seconds))}var I;(function(n){n.BANK="BANK",n.CREDIT_CARD="CREDIT_CARD"})(I||(I={}));const ct=["debit","fee","srvchg","atm","pos","check","payment","directdebit","cash","repeatpmt"],ut=n=>n.includes(m)?I.BANK:I.CREDIT_CARD;function _(n){if(String(n.TRNAMT).startsWith("-"))return!0;const t=String(n.TRNTYPE).toLocaleLowerCase();return t==="1"||ct.includes(t)}function D(n){const t=n.replace(/(\\)/g,"\\\\").replace(h,e=>U(e,!0)).replace(x,e=>k(e,!0)).replace(/(},})/g,"}}").replace(/(}")/g,'},"').replace(/(]")/g,'],"').replace(/(},])/g,"}]").replace(/(,})/g,"}").replace(/,\s*}/g,"}").replace(/(,",)/,'",').replace(j,'\\"').slice(0,-1);return t.endsWith(",")?t.slice(0,-1):t}const lt=n=>JSON.stringify({date:n.slice(0,12),transactionCode:n.slice(12,19),protocol:n.slice(19)}),dt=n=>n.startsWith("DT"),v=n=>n.trim();function k(n,t=!1){return Q.includes(n)&&!t?n:n.replace(/[<]/g,`
2
+ "`).replace(/[>]/g,'":{')}function U(n,t=!1){return Z.includes(n)&&!t?`
3
+ ${n}`:`},
4
+ `}function ft({fitId:n,fitValue:t}){return n==="separated"?lt(t):`"${t}",`}function Tt(n){return n.search(",")>n.search(".")?n.replace(/[.]/g,"").replace(/[,]/g,"."):n.replace(/[,]/g,"")}function b({dateString:n,formatDate:t="y-M-d"}){return!/^\d{8}(\d{6})?(\.\d+)?(\[[^\]]+\])?$/.test(n)||!R(n)?n:L(n,t)}function gt(n){let t=n.value.replace(/[{]/g,"").replace(/(},)/g,"").replace(/["]/g,"_#_");const e=n.field.replace(/['"]/g,"");return e.endsWith("AMT")&&(t=Tt(t)),dt(e)&&(t=b({dateString:t,formatDate:n.formatDate})),e==="FITID"?ft({fitId:n.fitId,fitValue:t}):n.nativeTypes&&P(e,t)?`${Number(t)},`:`"${t}",`}function ht(n,t){let e=n;return n.match(/{(\w|\W)+/)&&(e=e.replace(/({(\w|\W)+)$/,r=>gt({field:e.slice(0,e.indexOf(":")),value:r,...t}))),e}function P(n,t){return n.endsWith("ID")||n.endsWith("NUM")?!1:!isNaN(Number(t))}function $(n){const t=n.indexOf(y),e=n.lastIndexOf(p)+p.length;if(t<0||e<0||t>=e)return{oldListText:n,newListText:n};const r=n.substring(t,e),a=new RegExp(y,"g"),s=new RegExp(p,"g"),i=`"STRTTRN":[${r.replace(a,"{").replace(s,"},")}]`;return{oldListText:n,newListText:n.replace(r,i)}}function G(n){if(!(n.indexOf(m)>0))return{newBankStatementTransactions:null,oldBankStatementTransactions:null};const e=$(n.substring(n.indexOf(m),n.indexOf(S)+S.length));return{newBankStatementTransactions:e.newListText,oldBankStatementTransactions:e.oldListText}}function K(n){if(!(n.indexOf(C)>0))return{newCreditCardStatementTransactions:null,oldCreditCardStatementTransactions:null};const e=n.substring(n.indexOf(C),n.indexOf(N)+N.length),r=$(e);return{newCreditCardStatementTransactions:r.newListText,oldCreditCardStatementTransactions:r.oldListText}}function mt(n){const t=[];for(const e of n)t.push(...e.replace("<?xml"," ").replace("<?OFX"," ").replace("?>"," ").split(" ").filter(Boolean).map(v).map(r=>r.replace("=",":").replace(/["]/g,"")));return t}function St(n,t){const e={};n.join("").search("<?")>-1&&(n=mt(n));for(const r of n){const[a,s]=r.split(":"),i=a.replace(`
5
+ `,"").toUpperCase();e[i]=t&&P(a,s)?Number(s):String(s).replace(/\?>/,"")}return e}function Ct(n){return n.reduce((t,e)=>{const r=Math.abs(+e.TRNAMT);return _(e)?(t.amountOfDebits++,t.debit+=r,t):(t.amountOfCredits++,t.credit+=r,t)},{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0})}class Nt{config;internConfig={};constructor(t){this.config=t,this.internConfig=t}getConfig(){return this.internConfig}sanitizeRow(t){return ht(t,this.internConfig)}getPartialJsonData(t){const[e,r]=t.split("<OFX>"),a="<OFX>"+r,{sanitizeRow:s}=this;return a.replace(/<(?=[^\/])/g,`
6
+ <`).replace(h,i=>U(i)).replace(x,i=>k(i)).split(`
7
+ `).map(v).filter(Boolean).map(s,this).join("")}}function A(n){return n.toString()}async function O(n){return await new Promise((e,r)=>{if(typeof window<"u"&&window.FileReader){const a=new window.FileReader;a.onload=s=>e(s.target.result),a.onerror=s=>r(s.target.error),a.readAsText(n)}else r(new Error("FileReader is not available in this environment."))})}class d{data;dataRead="";constructor(t){this.data=t,t&&(this.dataRead=t)}getData(){return this.dataRead}fromString(t){return new d(t)}fromBuffer(t){return new d(A(t))}async fromBlob(t){return new d(await O(t))}static fromString(t){return new d(t)}static fromBuffer(t){return new d(A(t))}static async fromBlob(t){return new d(await O(t))}}class pt{configInstance={}}class M extends pt{setConfig(t){this.configInstance=t}getBankTransferList(t){const{newBankStatementTransactions:e}=G(this.configInstance.getPartialJsonData(t));return e?JSON.parse(`{${D(e)}}`)?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS?.BANKTRANLIST?.STRTTRN:[]}getCreditCardTransferList(t){const{newCreditCardStatementTransactions:e}=K(this.configInstance.getPartialJsonData(t));return e?JSON.parse(`{${D(e)}}`)?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS?.BANKTRANLIST?.STRTTRN:[]}getTransactionsSummary(t){let e=[],r=[];const a=this.configInstance?.getConfig?.().parserMode||"strict";let s=null,i=null;try{e=this.getBankTransferList(t)||[]}catch(w){s=w,e=[]}try{r=this.getCreditCardTransferList(t)||[]}catch(w){i=w,r=[]}const c=t.includes("<STMTTRN>");if(a==="strict"&&s&&i&&c)throw s;const o=e.length?e:r,f=e.length?m:C,g=e.length?S:N,T=t.indexOf(f),u=t.indexOf(g),V=T>=0&&u>T?t.slice(T,u+g.length):"",H=V.match(/<DTSTART>([^\n<]+)/),Y=V.match(/<DTEND>([^\n<]+)/),bt=Ct(o||[]),At=H?.[1]?b({dateString:H[1],formatDate:this.configInstance.getConfig().formatDate}):"",Ot=Y?.[1]?b({dateString:Y[1],formatDate:this.configInstance.getConfig().formatDate}):"";return{dateStart:At,dateEnd:Ot,...bt}}getContent(t){const e=this.configInstance.getPartialJsonData(t),{newBankStatementTransactions:r,oldBankStatementTransactions:a}=G(e),{newCreditCardStatementTransactions:s,oldCreditCardStatementTransactions:i}=K(e);let c=e;return r&&(c=c.replace(a,r)),s&&(c=c.replace(i,s)),JSON.parse(`{${D(c)}}`)}}function X(n){if(n==null)return"";if(typeof n=="string"||typeof n=="number")return String(n);if(typeof n=="object"){const t=n;if(t.date!==void 0&&t.transactionCode!==void 0&&t.protocol!==void 0)return`${t.date}${t.transactionCode}${t.protocol}`;try{return JSON.stringify(n)}catch{return String(n)}}return String(n)}function z(n){if(n==null)return null;const t=Number(String(n).replace(/,/g,"."));return Number.isNaN(t)?null:t}function Rt(n){return n.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/\s+/g," ").trim().toLowerCase()}function It(n,t){const e=t.dateMode||"formatted";if(!n)return{value:null,warning:{code:"MISSING_DATE",message:"Transaction does not include DTPOSTED.",severity:"warning"}};if(e==="raw"||e==="formatted"){const a=R(n);return a?e==="formatted"&&t.formatDate?{value:tt(a,t.formatDate)}:{value:n}:{value:n,warning:{code:"INVALID_DATE",message:`Unable to parse date '${n}'.`,severity:"warning"}}}const r=R(n);return r?e==="date"?{value:r}:e==="timestamp"?{value:r.getTime()}:{value:r.toISOString()}:{value:null,warning:{code:"INVALID_DATE",message:`Unable to parse date '${n}'.`,severity:"warning"}}}function Dt(n,t){const e=z(n);return e===null?{amount:null,amountAbs:null,warning:{code:"INVALID_AMOUNT",message:`Unable to parse amount '${String(n)}'.`,severity:"warning"}}:t==="string"?{amount:String(n),amountAbs:String(Math.abs(e))}:t==="cents"?{amount:Math.round(e*100),amountAbs:Math.round(Math.abs(e)*100)}:{amount:e,amountAbs:Math.abs(e)}}function W(n){const t=[],e=n?.OFX?.SIGNONMSGSRSV1?.SONRS?.FI||null,r=n?.OFX?.BANKMSGSRSV1?.STMTTRNRS?.STMTRS;(r?.BANKTRANLIST?.STRTTRN||r?.BANKTRANLIST?.STMTTRN||[]).forEach((c,o)=>{t.push({source:"bank",transaction:c,currency:r?.CURDEF||null,account:r?.BANKACCTFROM||null,institution:e,path:`OFX.BANKMSGSRSV1.STMTTRNRS.STMTRS.BANKTRANLIST.${o}`})});const s=n?.OFX?.CREDITCARDMSGSRSV1?.CCSTMTTRNRS?.CCSTMTRS;return(s?.BANKTRANLIST?.STRTTRN||s?.BANKTRANLIST?.STMTTRN||[]).forEach((c,o)=>{t.push({source:"credit_card",transaction:c,currency:s?.CURDEF||null,account:s?.CCACCTFROM||null,institution:e,path:`OFX.CREDITCARDMSGSRSV1.CCSTMTTRNRS.CCSTMTRS.BANKTRANLIST.${o}`})}),t}function Et(n,t={}){const e=t.amountMode||"number",r=[],a=[];return W(n).forEach(i=>{const c=[],o=String(i.transaction.MEMO||i.transaction.NAME||i.transaction.TRNTYPE||"").trim(),{amount:f,amountAbs:g,warning:T}=Dt(i.transaction.TRNAMT,e);T&&(T.path=i.path,c.push(T),r.push(T));const u=It(i.transaction.DTPOSTED,t);u.warning&&(u.warning.path=i.path,c.push(u.warning),r.push(u.warning)),a.push({source:i.source,direction:_(i.transaction)?"debit":"credit",amount:f,amountAbs:g,postedAt:u.value,description:o,descriptionNormalized:Rt(o),fitId:X(i.transaction.FITID),currency:i.currency,account:i.account,institution:i.institution,raw:i.transaction,warnings:c})}),{transactions:a,warnings:r}}function yt(n){const t=[],e=[],r=W(n);n?.OFX||e.push({code:"MISSING_OFX_BLOCK",message:"Missing OFX root block.",severity:"error",path:"OFX"}),r.length||t.push({code:"NO_TRANSACTIONS_FOUND",message:"No transactions were found in bank or credit card statements.",severity:"warning",path:"OFX"});const a=new Map;r.forEach(o=>{const f=X(o.transaction.FITID);f?a.set(f,(a.get(f)||0)+1):t.push({code:"MISSING_FITID",message:"Transaction does not include FITID.",severity:"warning",path:o.path}),z(o.transaction.TRNAMT)===null&&e.push({code:"INVALID_AMOUNT",message:`Invalid amount '${String(o.transaction.TRNAMT)}'.`,severity:"error",path:o.path}),R(String(o.transaction.DTPOSTED||""))||t.push({code:"INVALID_DATE",message:`Invalid DTPOSTED '${String(o.transaction.DTPOSTED||"")}'.`,severity:"warning",path:o.path})});const s=Array.from(a.values()).filter(o=>o>1).length;s>0&&t.push({code:"DUPLICATED_FITID",message:`Found ${s} duplicated FITID values.`,severity:"warning",path:"OFX"});const i=r.filter(o=>o.source==="bank").length,c=r.filter(o=>o.source==="credit_card").length;return{isValid:e.length===0,warnings:t,errors:e,stats:{totalTransactions:r.length,bankTransactions:i,creditCardTransactions:c,duplicatedFitIds:s}}}class J{customExtractor;customExtractorInstance;dataReaderInstance=new d("");diagnostics=[];constructor(t){this.customExtractor=t,this.customExtractorInstance=t||new M,this.config({})}data(t){return this.dataReaderInstance=t,this}config(t){return this.customExtractorInstance.setConfig(new Nt(t)),this}getType(){return ut(this.dataReaderInstance.getData())}beginOperation(){this.diagnostics=[]}getParserMode(){return this.customExtractorInstance.configInstance.getConfig().parserMode}pushWarning(t){this.diagnostics.push(t)}safelyExecute(t,e,r){try{return r()}catch(a){if(this.getParserMode()!=="lenient")throw a;return this.pushWarning({code:"PARSE_ERROR",message:`Failed to parse OFX while running '${t}'.`,severity:"warning",context:a instanceof Error?a.message:String(a),path:t}),e}}getHeadersInternal(){return this.safelyExecute("getHeaders",{},()=>{const[t]=this.dataReaderInstance.getData().split("<OFX>");return St(String(t).split(`
8
+ `).filter(e=>!!e.trim().length),!!this.customExtractorInstance.configInstance.getConfig().nativeTypes)})}getContentInternal(){return this.safelyExecute("getContent",{OFX:{}},()=>this.customExtractorInstance.getContent(this.dataReaderInstance.getData()))}getResponseInternal(){const t=this.getHeadersInternal(),e=this.getContentInternal();return{...t,...e}}getHeaders(){return this.beginOperation(),this.getHeadersInternal()}getBankTransferList(){return this.beginOperation(),this.safelyExecute("getBankTransferList",[],()=>this.customExtractorInstance.getBankTransferList(this.dataReaderInstance.getData()))}getCreditCardTransferList(){return this.beginOperation(),this.safelyExecute("getCreditCardTransferList",[],()=>this.customExtractorInstance.getCreditCardTransferList(this.dataReaderInstance.getData()))}getTransactionsSummary(){return this.beginOperation(),this.safelyExecute("getTransactionsSummary",{credit:0,debit:0,amountOfCredits:0,amountOfDebits:0,dateStart:"",dateEnd:""},()=>this.customExtractorInstance.getTransactionsSummary(this.dataReaderInstance.getData()))}getContent(){return this.beginOperation(),this.getContentInternal()}toJson(){return this.beginOperation(),this.getResponseInternal()}toNormalized(t={}){this.beginOperation();const e=this.getResponseInternal();return Et(e,{...t,formatDate:t.formatDate||this.customExtractorInstance.configInstance.getConfig().formatDate})}validate(){this.beginOperation();const t=this.getResponseInternal();return yt(t)}getWarnings(){return[...this.diagnostics]}}class E{extractor;constructor(t,e){this.extractor=new J(new M),this.extractor.data(new d(t)),this.extractor.config(e||{})}getType(){return this.extractor.getType()}static fromBuffer(t){return new E(d.fromBuffer(t).getData())}static async fromBlob(t){const e=await d.fromBlob(t);return new E(e.getData())}config(t){return this.extractor.config(t),this}getHeaders(){return this.extractor.getHeaders()}getBankTransferList(){return this.extractor.getBankTransferList()}getCreditCardTransferList(){return this.extractor.getCreditCardTransferList()}getTransactionsSummary(){return this.extractor.getTransactionsSummary()}getContent(){return this.extractor.getContent()}toJson(){return this.extractor.toJson()}toNormalized(t){return this.extractor.toNormalized(t)}validate(){return this.extractor.validate()}getWarnings(){return this.extractor.getWarnings()}}l.Extractor=J,l.Ofx=E,l.OfxExtractor=M,l.Reader=d,l.blobToString=O,l.bufferToString=A,l.fixJsonProblems=D,l.formatDate=L}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofx-data-extractor",
3
- "version": "1.4.7",
3
+ "version": "1.5.0",
4
4
  "keywords": [
5
5
  "ofx",
6
6
  "financial",
@@ -28,16 +28,25 @@
28
28
  "author": "Fábio Pereira França",
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "https://github.com/Fabiopf02/ofx-ts.git"
31
+ "url": "https://github.com/Fabiopf02/ofx-data-extractor.git"
32
32
  },
33
33
  "bugs": {
34
- "url": "https://github.com/Fabiopf02/ofx-ts/issues"
34
+ "url": "https://github.com/Fabiopf02/ofx-data-extractor/issues"
35
35
  },
36
36
  "license": "MIT",
37
+ "sideEffects": false,
37
38
  "description": "A module written in TypeScript that provides a utility to extract data from an OFX file in Node.js and Browser",
38
39
  "main": "dist/umd/bundle.min.js",
39
40
  "module": "dist/esm/index.min.js",
40
41
  "typings": "dist/index.d.ts",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.ts",
45
+ "import": "./dist/esm/index.min.js",
46
+ "require": "./dist/umd/bundle.min.js",
47
+ "default": "./dist/esm/index.min.js"
48
+ }
49
+ },
41
50
  "files": [
42
51
  "dist/**/*"
43
52
  ],
@@ -45,25 +54,26 @@
45
54
  "test": "jest tests/*.spec.ts --config jest.config.js",
46
55
  "test:coverage": "jest tests/*.spec.ts --coverage --config jest.config.js",
47
56
  "build": "rollup --config",
57
+ "build:analyze": "npm run build && node -e \"const fs=require('fs');const z=require('zlib');const files=['dist/esm/index.min.js','dist/umd/bundle.min.js'];for(const f of files){const b=fs.readFileSync(f);const gz=z.gzipSync(b);console.log(f, 'raw='+b.length+'B', 'gzip='+gz.length+'B');}\"",
48
58
  "start": "node dist/index.js",
49
59
  "format": "prettier --write \"src/**/*.ts\"",
50
60
  "prepublish": "npm run build",
51
61
  "lint": "tslint -p tsconfig.json"
52
62
  },
53
63
  "devDependencies": {
54
- "@rollup/plugin-commonjs": "^25.0.3",
55
- "@rollup/plugin-node-resolve": "^15.1.0",
64
+ "@rollup/plugin-commonjs": "^25.0.8",
65
+ "@rollup/plugin-node-resolve": "^15.3.1",
56
66
  "@rollup/plugin-typescript": "^10.0.1",
57
- "@types/jest": "^29.2.3",
58
- "@types/node": "^18.11.10",
59
- "esbuild": "^0.25.0",
60
- "jest": "^29.3.1",
61
- "prettier": "^2.8.0",
62
- "rollup": "^3.5.1",
67
+ "@types/jest": "^29.5.14",
68
+ "@types/node": "^18.19.130",
69
+ "esbuild": "^0.25.12",
70
+ "jest": "^29.7.0",
71
+ "prettier": "^2.8.8",
72
+ "rollup": "^3.30.0",
63
73
  "rollup-plugin-esbuild": "^5.0.0",
64
- "ts-jest": "^29.0.3",
74
+ "ts-jest": "^29.4.6",
65
75
  "tslint": "^6.1.3",
66
76
  "tslint-config-prettier": "^1.18.0",
67
- "typescript": "^4.9.3"
77
+ "typescript": "^4.9.5"
68
78
  }
69
79
  }