cata-centavo 0.1.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 +159 -0
- package/dist/bin/cata-centavo.js +226 -0
- package/dist/bin/cata-centavo.js.map +1 -0
- package/dist/cli/dispatch.js +67 -0
- package/dist/cli/dispatch.js.map +1 -0
- package/dist/cli/doctor.js +178 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.js +125 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/progress.js +85 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/cli/sync.js +26 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/config.js +119 -0
- package/dist/config.js.map +1 -0
- package/dist/core/account.js +12 -0
- package/dist/core/account.js.map +1 -0
- package/dist/core/accounts.js +91 -0
- package/dist/core/accounts.js.map +1 -0
- package/dist/core/aggregate.js +83 -0
- package/dist/core/aggregate.js.map +1 -0
- package/dist/core/balance.js +68 -0
- package/dist/core/balance.js.map +1 -0
- package/dist/core/bill-rows.js +132 -0
- package/dist/core/bill-rows.js.map +1 -0
- package/dist/core/bill.js +103 -0
- package/dist/core/bill.js.map +1 -0
- package/dist/core/category-source.js +26 -0
- package/dist/core/category-source.js.map +1 -0
- package/dist/core/category.js +72 -0
- package/dist/core/category.js.map +1 -0
- package/dist/core/consent.js +19 -0
- package/dist/core/consent.js.map +1 -0
- package/dist/core/contracts.js +9 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/counterparty.js +69 -0
- package/dist/core/counterparty.js.map +1 -0
- package/dist/core/date.js +25 -0
- package/dist/core/date.js.map +1 -0
- package/dist/core/description.js +58 -0
- package/dist/core/description.js.map +1 -0
- package/dist/core/diagnose.js +32 -0
- package/dist/core/diagnose.js.map +1 -0
- package/dist/core/mcc.js +124 -0
- package/dist/core/mcc.js.map +1 -0
- package/dist/core/refresh.js +142 -0
- package/dist/core/refresh.js.map +1 -0
- package/dist/core/self-transfer.js +22 -0
- package/dist/core/self-transfer.js.map +1 -0
- package/dist/core/taxonomy-tree.js +148 -0
- package/dist/core/taxonomy-tree.js.map +1 -0
- package/dist/core/taxonomy.js +70 -0
- package/dist/core/taxonomy.js.map +1 -0
- package/dist/core/transaction.js +2 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/transactions.js +46 -0
- package/dist/core/transactions.js.map +1 -0
- package/dist/logging.js +93 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp/cursor.js +64 -0
- package/dist/mcp/cursor.js.map +1 -0
- package/dist/mcp/format.js +38 -0
- package/dist/mcp/format.js.map +1 -0
- package/dist/mcp/server.js +50 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/source.js +2 -0
- package/dist/mcp/source.js.map +1 -0
- package/dist/mcp/tools/accounts.js +144 -0
- package/dist/mcp/tools/accounts.js.map +1 -0
- package/dist/mcp/tools/balance.js +96 -0
- package/dist/mcp/tools/balance.js.map +1 -0
- package/dist/mcp/tools/bill-summary-format.js +161 -0
- package/dist/mcp/tools/bill-summary-format.js.map +1 -0
- package/dist/mcp/tools/bill-summary.js +105 -0
- package/dist/mcp/tools/bill-summary.js.map +1 -0
- package/dist/mcp/tools/bills.js +144 -0
- package/dist/mcp/tools/bills.js.map +1 -0
- package/dist/mcp/tools/closing-days.js +90 -0
- package/dist/mcp/tools/closing-days.js.map +1 -0
- package/dist/mcp/tools/list-transactions.js +122 -0
- package/dist/mcp/tools/list-transactions.js.map +1 -0
- package/dist/mcp/tools/result.js +12 -0
- package/dist/mcp/tools/result.js.map +1 -0
- package/dist/mcp/tools/set-category.js +74 -0
- package/dist/mcp/tools/set-category.js.map +1 -0
- package/dist/mcp/tools/sources.js +73 -0
- package/dist/mcp/tools/sources.js.map +1 -0
- package/dist/mcp/tools/transaction-details.js +146 -0
- package/dist/mcp/tools/transaction-details.js.map +1 -0
- package/dist/mcp/tools/transaction-input.js +51 -0
- package/dist/mcp/tools/transaction-input.js.map +1 -0
- package/dist/mcp/tools/transactions.js +186 -0
- package/dist/mcp/tools/transactions.js.map +1 -0
- package/dist/pluggy/client.js +123 -0
- package/dist/pluggy/client.js.map +1 -0
- package/dist/pluggy/errors.js +152 -0
- package/dist/pluggy/errors.js.map +1 -0
- package/dist/pluggy/mapper.js +155 -0
- package/dist/pluggy/mapper.js.map +1 -0
- package/dist/pluggy/money.js +53 -0
- package/dist/pluggy/money.js.map +1 -0
- package/dist/pluggy/transaction-mapper.js +124 -0
- package/dist/pluggy/transaction-mapper.js.map +1 -0
- package/dist/pluggy/transport.js +170 -0
- package/dist/pluggy/transport.js.map +1 -0
- package/dist/pluggy/wire.js +207 -0
- package/dist/pluggy/wire.js.map +1 -0
- package/dist/storage/categories.js +101 -0
- package/dist/storage/categories.js.map +1 -0
- package/dist/storage/category-sql.js +37 -0
- package/dist/storage/category-sql.js.map +1 -0
- package/dist/storage/closing-days.js +27 -0
- package/dist/storage/closing-days.js.map +1 -0
- package/dist/storage/db.js +162 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/diagnostics.js +57 -0
- package/dist/storage/diagnostics.js.map +1 -0
- package/dist/storage/harvest.js +87 -0
- package/dist/storage/harvest.js.map +1 -0
- package/dist/storage/migrations.js +125 -0
- package/dist/storage/migrations.js.map +1 -0
- package/dist/storage/transaction-row.js +74 -0
- package/dist/storage/transaction-row.js.map +1 -0
- package/dist/storage/transactions.js +213 -0
- package/dist/storage/transactions.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { failureFor, parse, readJson, ResponseShapeError } from "./errors.js";
|
|
2
|
+
import { toAccount, toBill, toConnection, toConsent, toTransaction } from "./mapper.js";
|
|
3
|
+
import { createTransport } from "./transport.js";
|
|
4
|
+
import { ACCOUNT, ACCOUNT_PAGE, BILL_PAGE, CONSENT_PAGE, ITEM, TRANSACTION_PAGE } from "./wire.js";
|
|
5
|
+
const MAX_TRANSACTION_HOPS = 500;
|
|
6
|
+
function createTransactionWalker(get) {
|
|
7
|
+
return async (account) => {
|
|
8
|
+
const transactions = [];
|
|
9
|
+
const seenIds = new Set();
|
|
10
|
+
let query = `?accountId=${encodeURIComponent(account.id)}`;
|
|
11
|
+
for (let hop = 0; hop < MAX_TRANSACTION_HOPS; hop += 1) {
|
|
12
|
+
const page = await get(`/v2/transactions${query}`, TRANSACTION_PAGE, `transactions ${account.id}`);
|
|
13
|
+
for (const row of page.results) {
|
|
14
|
+
if (seenIds.has(row.id)) {
|
|
15
|
+
throw new ResponseShapeError(`Transaction ${row.id} was already seen while walking ${account.id}`);
|
|
16
|
+
}
|
|
17
|
+
seenIds.add(row.id);
|
|
18
|
+
transactions.push(toTransaction(row, account));
|
|
19
|
+
}
|
|
20
|
+
if (page.next === null) {
|
|
21
|
+
return transactions;
|
|
22
|
+
}
|
|
23
|
+
if (page.next === query) {
|
|
24
|
+
throw new ResponseShapeError(`The cursor stopped advancing while walking ${account.id}`);
|
|
25
|
+
}
|
|
26
|
+
query = page.next;
|
|
27
|
+
}
|
|
28
|
+
throw new ResponseShapeError(`Walking ${account.id} exceeded ${MAX_TRANSACTION_HOPS} pages without reaching the end`);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function newestBillFirst(left, right) {
|
|
32
|
+
if (left.closingDate === null) {
|
|
33
|
+
if (right.closingDate !== null) {
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (right.closingDate === null) {
|
|
38
|
+
return -1;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const closingDateOrder = right.closingDate.localeCompare(left.closingDate);
|
|
42
|
+
if (closingDateOrder !== 0) {
|
|
43
|
+
return closingDateOrder;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return right.dueDate.localeCompare(left.dueDate);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The Pluggy client, written rather than taken from `pluggy-sdk`.
|
|
50
|
+
*
|
|
51
|
+
* The SDK resolves the key lazily and checks the JWT already, so the ADR's
|
|
52
|
+
* original complaint does not apply to it. What it has no way to offer is a
|
|
53
|
+
* margin, a single-flight, a 401 retry or a rate limiter, because the key cache
|
|
54
|
+
* is private instance state and the send function is not ours. ADR §15 Phase 0
|
|
55
|
+
* records the whole comparison. All four live in `transport.ts`; what is left
|
|
56
|
+
* here is the vocabulary of connections.
|
|
57
|
+
*
|
|
58
|
+
* Construction performs no I/O (§16.2).
|
|
59
|
+
*/
|
|
60
|
+
// eslint-disable-next-line max-lines-per-function -- a factory that assembles and returns one object, not logic to cut apart
|
|
61
|
+
export function createPluggyClient(options) {
|
|
62
|
+
const transport = createTransport(options);
|
|
63
|
+
async function get(path, schema, describe) {
|
|
64
|
+
const response = await transport.authorized("GET", path);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw await failureFor(response, describe);
|
|
67
|
+
}
|
|
68
|
+
return parse(schema, await readJson(response), describe);
|
|
69
|
+
}
|
|
70
|
+
const walkTransactions = createTransactionWalker(get);
|
|
71
|
+
return {
|
|
72
|
+
verifyCredentials: async () => {
|
|
73
|
+
await transport.key();
|
|
74
|
+
},
|
|
75
|
+
getConnection: async (id) => {
|
|
76
|
+
const item = await get(`/items/${encodeURIComponent(id)}`, ITEM, `connection ${id}`);
|
|
77
|
+
return toConnection(item);
|
|
78
|
+
},
|
|
79
|
+
getAccounts: async (connectionId) => {
|
|
80
|
+
const encodedConnectionId = encodeURIComponent(connectionId);
|
|
81
|
+
const [item, firstPage] = await Promise.all([
|
|
82
|
+
get(`/items/${encodedConnectionId}`, ITEM, `connection ${connectionId}`),
|
|
83
|
+
get(`/accounts?itemId=${encodedConnectionId}&pageSize=500&page=1`, ACCOUNT_PAGE, `accounts ${connectionId}`),
|
|
84
|
+
]);
|
|
85
|
+
const connection = toConnection(item);
|
|
86
|
+
const pages = await Promise.all(Array.from({ length: firstPage.totalPages - 1 }, (_, index) => get(`/accounts?itemId=${encodedConnectionId}&pageSize=500&page=${index + 2}`, ACCOUNT_PAGE, `accounts ${connectionId}`)));
|
|
87
|
+
return [firstPage, ...pages].flatMap((page) => page.results.map((account) => toAccount(account, connection)));
|
|
88
|
+
},
|
|
89
|
+
getAccount: async (accountId) => {
|
|
90
|
+
const account = await get(`/accounts/${encodeURIComponent(accountId)}`, ACCOUNT, `account ${accountId}`);
|
|
91
|
+
const item = await get(`/items/${encodeURIComponent(account.itemId)}`, ITEM, `connection ${account.itemId}`);
|
|
92
|
+
return toAccount(account, toConnection(item));
|
|
93
|
+
},
|
|
94
|
+
getTransactions: (account) => walkTransactions(account),
|
|
95
|
+
getBills: async (account) => {
|
|
96
|
+
const encodedAccountId = encodeURIComponent(account.id);
|
|
97
|
+
const firstPage = await get(`/bills?accountId=${encodedAccountId}&pageSize=500&page=1`, BILL_PAGE, `bills ${account.id}`);
|
|
98
|
+
const pages = await Promise.all(Array.from({ length: firstPage.totalPages - 1 }, (_, index) => get(`/bills?accountId=${encodedAccountId}&pageSize=500&page=${index + 2}`, BILL_PAGE, `bills ${account.id}`)));
|
|
99
|
+
const seenIds = new Set();
|
|
100
|
+
const bills = [];
|
|
101
|
+
for (const page of [firstPage, ...pages]) {
|
|
102
|
+
for (const row of page.results) {
|
|
103
|
+
if (seenIds.has(row.id)) {
|
|
104
|
+
throw new ResponseShapeError(`Bill ${row.id} was already seen while walking ${account.id}`);
|
|
105
|
+
}
|
|
106
|
+
seenIds.add(row.id);
|
|
107
|
+
bills.push(toBill(row, account));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return bills.sort(newestBillFirst);
|
|
111
|
+
},
|
|
112
|
+
getConsent: async (connectionId) => {
|
|
113
|
+
const encodedConnectionId = encodeURIComponent(connectionId);
|
|
114
|
+
const page = await get(`/consents?itemId=${encodedConnectionId}`, CONSENT_PAGE, `consent ${connectionId}`);
|
|
115
|
+
const [first] = page.results;
|
|
116
|
+
if (first === undefined) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return toConsent(first);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/pluggy/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { API_ERROR } from "./wire.js";
|
|
2
|
+
/**
|
|
3
|
+
* Typed failures from the Pluggy boundary, and the translation from a response
|
|
4
|
+
* into one of them.
|
|
5
|
+
*
|
|
6
|
+
* They exist because ADR §16.4 requires deciding *per failure* between a
|
|
7
|
+
* protocol error and readable `isError` tool content, and that decision needs
|
|
8
|
+
* something to switch on. Anything the model should recover from — a revoked
|
|
9
|
+
* consent, an unknown connection — has to arrive as a distinguishable type
|
|
10
|
+
* rather than as a status code buried in a string.
|
|
11
|
+
*
|
|
12
|
+
* The translation lives here rather than beside either caller because both the
|
|
13
|
+
* transport and the client need it: a refused `POST /auth` and a refused
|
|
14
|
+
* `GET /items/{id}` are the same failure wearing different sentences.
|
|
15
|
+
*
|
|
16
|
+
* No constructor here performs I/O and nothing calls `process.exit` (§16.2).
|
|
17
|
+
*/
|
|
18
|
+
export class PluggyError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
kind;
|
|
21
|
+
constructor(message, kind, status) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = new.target.name;
|
|
24
|
+
this.kind = kind;
|
|
25
|
+
this.status = status;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Credentials refused, or a key that could not be renewed. */
|
|
29
|
+
export class AuthError extends PluggyError {
|
|
30
|
+
constructor(message, status) {
|
|
31
|
+
super(message, "auth", status);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The resource does not exist, or belongs to another Pluggy account. */
|
|
35
|
+
export class NotFoundError extends PluggyError {
|
|
36
|
+
constructor(message, status) {
|
|
37
|
+
super(message, "unknown-connection", status);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Rate limited. Recoverable by waiting; see the limiter in `transport.ts`. */
|
|
41
|
+
export class RateLimitError extends PluggyError {
|
|
42
|
+
constructor(message, status) {
|
|
43
|
+
super(message, "rate-limited", status);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Any other non-2xx response. */
|
|
47
|
+
export class HttpError extends PluggyError {
|
|
48
|
+
constructor(message, status) {
|
|
49
|
+
super(message, "unavailable", status);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A 2xx body that does not match what we expected. Separate from `HttpError`
|
|
54
|
+
* because it means Pluggy changed, not that the request was wrong — the case
|
|
55
|
+
* Phase 0.5 step 5 warns about, where trusting a shape we never checked deletes
|
|
56
|
+
* the evidence before a human sees it.
|
|
57
|
+
*/
|
|
58
|
+
export class ResponseShapeError extends PluggyError {
|
|
59
|
+
constructor(message, status) {
|
|
60
|
+
super(message, "bad-response", status);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Turns a thrown boundary error into the core's portable failure contract. */
|
|
64
|
+
export function toFailure(error) {
|
|
65
|
+
if (error instanceof PluggyError) {
|
|
66
|
+
return { kind: error.kind, message: error.message };
|
|
67
|
+
}
|
|
68
|
+
return { kind: "unavailable", message: String(error) };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Which failure this is, decided once. §16.4 requires choosing per case between
|
|
72
|
+
* a protocol error and readable tool content, and that choice needs these to be
|
|
73
|
+
* distinguishable rather than a status code inside a message.
|
|
74
|
+
*/
|
|
75
|
+
export function classify(status, describe, detail = null) {
|
|
76
|
+
if (status === 401 || status === 403) {
|
|
77
|
+
return new AuthError(`Pluggy refused the request while ${describe} — check PLUGGY_CLIENT_ID and PLUGGY_CLIENT_SECRET`, status);
|
|
78
|
+
}
|
|
79
|
+
if (status === 404) {
|
|
80
|
+
return new NotFoundError("not found — wrong id, or an id belonging to another Pluggy account", status);
|
|
81
|
+
}
|
|
82
|
+
if (status === 429) {
|
|
83
|
+
return new RateLimitError(`Pluggy rate limited the request while ${describe}`, status);
|
|
84
|
+
}
|
|
85
|
+
return new HttpError(`Pluggy returned ${status} while ${describe}${because(detail)}`, status);
|
|
86
|
+
}
|
|
87
|
+
function because(detail) {
|
|
88
|
+
if (detail === null) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
return ` — ${detail}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The failure with Pluggy's own explanation attached, when it gave one. A 404
|
|
95
|
+
* already has a better sentence than Pluggy's, so that one keeps ours.
|
|
96
|
+
*/
|
|
97
|
+
export async function failureFor(response, describe) {
|
|
98
|
+
let detail;
|
|
99
|
+
if (response.status === 404) {
|
|
100
|
+
detail = null;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
detail = await readErrorDetail(response);
|
|
104
|
+
}
|
|
105
|
+
return classify(response.status, describe, detail);
|
|
106
|
+
}
|
|
107
|
+
export async function readErrorDetail(response) {
|
|
108
|
+
const parsed = API_ERROR.safeParse(await readJsonOrNull(response));
|
|
109
|
+
if (parsed.success) {
|
|
110
|
+
return detailOf(parsed.data);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function detailOf(error) {
|
|
115
|
+
const parts = [error.codeDescription, error.message, retryHint(error.data?.canRetryAfterDate)];
|
|
116
|
+
const detail = parts.filter((part) => part !== null && part !== undefined).join(": ");
|
|
117
|
+
if (detail === "") {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return detail;
|
|
121
|
+
}
|
|
122
|
+
function retryHint(at) {
|
|
123
|
+
if (at === null || at === undefined) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return `retry after ${at}`;
|
|
127
|
+
}
|
|
128
|
+
async function readJsonOrNull(response) {
|
|
129
|
+
try {
|
|
130
|
+
return await response.json();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export async function readJson(response) {
|
|
137
|
+
try {
|
|
138
|
+
return await response.json();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new ResponseShapeError("Pluggy returned a body that is not JSON", response.status);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function parse(schema, body, describe) {
|
|
145
|
+
const result = schema.safeParse(body);
|
|
146
|
+
if (!result.success) {
|
|
147
|
+
const where = result.error.issues.map((issue) => issue.path.join(".") || "(root)").join(", ");
|
|
148
|
+
throw new ResponseShapeError(`${describe} did not match the shape we expect, at: ${where}`);
|
|
149
|
+
}
|
|
150
|
+
return result.data;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/pluggy/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ACCOUNT_TYPES } from "../core/account.js";
|
|
2
|
+
import { ResponseShapeError } from "./errors.js";
|
|
3
|
+
import { toCents } from "./money.js";
|
|
4
|
+
export { toCents } from "./money.js";
|
|
5
|
+
export { toTransaction } from "./transaction-mapper.js";
|
|
6
|
+
/**
|
|
7
|
+
* Where Pluggy's vocabulary stops and ours begins (ADR §14.0). An *item* is a
|
|
8
|
+
* connection, and no domain type past this file mentions the other word.
|
|
9
|
+
*
|
|
10
|
+
* This is also the only place that knows dates arrive as strings, since we
|
|
11
|
+
* parse the raw body rather than going through the SDK's date reviver.
|
|
12
|
+
*/
|
|
13
|
+
export function toConnection(item) {
|
|
14
|
+
let lastUpdatedAt;
|
|
15
|
+
if (item.lastUpdatedAt === null) {
|
|
16
|
+
lastUpdatedAt = null;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
lastUpdatedAt = new Date(item.lastUpdatedAt);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
id: item.id,
|
|
23
|
+
institution: item.connector.name,
|
|
24
|
+
status: item.status,
|
|
25
|
+
executionStatus: item.executionStatus ?? null,
|
|
26
|
+
lastUpdatedAt,
|
|
27
|
+
parameter: item.parameter?.label ?? null,
|
|
28
|
+
warnings: toWarnings(item.statusDetail),
|
|
29
|
+
failedLogins: item.consecutiveFailedLoginAttempts ?? null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The consent's own `itemId` never reaches this function; see `wire.ts`. */
|
|
33
|
+
export function toConsent(consent) {
|
|
34
|
+
return {
|
|
35
|
+
expiresAt: toNullableDate(consent.expiresAt),
|
|
36
|
+
revokedAt: toNullableDate(consent.revokedAt),
|
|
37
|
+
products: consent.products ?? [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function toNullableDate(value) {
|
|
41
|
+
if (value === null || value === undefined) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return new Date(value);
|
|
45
|
+
}
|
|
46
|
+
/** Maps Pluggy's account vocabulary and raw monetary values onto our domain. */
|
|
47
|
+
export function toAccount(account, connection) {
|
|
48
|
+
let credit;
|
|
49
|
+
if (account.type === ACCOUNT_TYPES.credit) {
|
|
50
|
+
credit = toCreditDetails(account.creditData);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
credit = null;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
id: account.id,
|
|
57
|
+
connectionId: connection.id,
|
|
58
|
+
institution: connection.institution,
|
|
59
|
+
name: account.marketingName ?? account.name,
|
|
60
|
+
type: toAccountType(account.type),
|
|
61
|
+
subtype: account.subtype ?? null,
|
|
62
|
+
amountCents: toCents(account.balance),
|
|
63
|
+
currency: account.currencyCode,
|
|
64
|
+
lastUpdatedAt: connection.lastUpdatedAt,
|
|
65
|
+
credit,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Maps Pluggy's bill body onto our statement vocabulary. */
|
|
69
|
+
export function toBill(bill, account) {
|
|
70
|
+
return {
|
|
71
|
+
id: bill.id,
|
|
72
|
+
closingDate: utcDayOrNull(bill.billClosingDate),
|
|
73
|
+
dueDate: utcDayOf(bill.dueDate),
|
|
74
|
+
totalCents: toCents(bill.totalAmount),
|
|
75
|
+
currency: bill.totalAmountCurrencyCode ?? account.currency,
|
|
76
|
+
minimumPaymentCents: toNullableCents(bill.minimumPaymentAmount),
|
|
77
|
+
financeChargesCents: sumAmounts(bill.financeCharges),
|
|
78
|
+
paymentsCents: sumAmounts(bill.payments),
|
|
79
|
+
paymentCount: bill.payments.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function utcDayOrNull(value) {
|
|
83
|
+
if (value === null || value === undefined) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return utcDayOf(value);
|
|
87
|
+
}
|
|
88
|
+
function utcDayOf(value) {
|
|
89
|
+
const date = new Date(value);
|
|
90
|
+
if (Number.isNaN(date.getTime())) {
|
|
91
|
+
throw new ResponseShapeError(`Pluggy returned an invalid bill date: ${value}`);
|
|
92
|
+
}
|
|
93
|
+
const year = date.getUTCFullYear();
|
|
94
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
95
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
96
|
+
return `${year}-${month}-${day}`;
|
|
97
|
+
}
|
|
98
|
+
function sumAmounts(rows) {
|
|
99
|
+
return rows.reduce((sum, row) => sum + toCents(row.amount), 0);
|
|
100
|
+
}
|
|
101
|
+
function toAccountType(type) {
|
|
102
|
+
if (type === ACCOUNT_TYPES.bank || type === ACCOUNT_TYPES.credit) {
|
|
103
|
+
return type;
|
|
104
|
+
}
|
|
105
|
+
throw new ResponseShapeError(`Pluggy returned an unrecognised account type: ${type}`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Selects the limit the cardholder actually has. Pluggy's available amount can
|
|
109
|
+
* be based on a customized limit rather than the bank's headline limit.
|
|
110
|
+
*/
|
|
111
|
+
function toLimitCents(creditData) {
|
|
112
|
+
const total = creditData.disaggregatedCreditLimits?.find((line) => line.creditLineLimitType === "LIMITE_CREDITO_TOTAL");
|
|
113
|
+
return toNullableCents(total?.customizedLimitAmount ?? creditData.creditLimit);
|
|
114
|
+
}
|
|
115
|
+
function toCreditDetails(creditData) {
|
|
116
|
+
if (creditData === null || creditData === undefined) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
limitCents: toLimitCents(creditData),
|
|
121
|
+
availableLimitCents: toNullableCents(creditData.availableCreditLimit),
|
|
122
|
+
balanceCloseDate: toDate(creditData.balanceCloseDate),
|
|
123
|
+
balanceDueDate: toDate(creditData.balanceDueDate),
|
|
124
|
+
brand: creditData.brand ?? null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function toNullableCents(value) {
|
|
128
|
+
if (value === null || value === undefined) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return toCents(value);
|
|
132
|
+
}
|
|
133
|
+
function toDate(value) {
|
|
134
|
+
if (value === null || value === undefined || /^\d{4}-\d{2}$/.test(value)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const parsed = new Date(value);
|
|
138
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* `statusDetail` is keyed by product, and a `PARTIAL_SUCCESS` puts the reason one
|
|
145
|
+
* product went unrefreshed in its `warnings`. Flattened with the product name in
|
|
146
|
+
* front, because "credit card bills hit their monthly quota" is the useful
|
|
147
|
+
* sentence and "the monthly quota was reached" on its own is not.
|
|
148
|
+
*/
|
|
149
|
+
function toWarnings(statusDetail) {
|
|
150
|
+
if (statusDetail === null || statusDetail === undefined) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
return Object.entries(statusDetail).flatMap(([product, detail]) => (detail?.warnings ?? []).map((warning) => `${product}: ${warning.message}`));
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.js","sourceRoot":"","sources":["../../src/pluggy/mapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function parseDecimalDigits(value) {
|
|
2
|
+
const representation = Math.abs(value).toString();
|
|
3
|
+
const match = /^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/.exec(representation);
|
|
4
|
+
if (match === null) {
|
|
5
|
+
throw new Error(`Cannot parse numeric value: ${representation}`);
|
|
6
|
+
}
|
|
7
|
+
const integerDigits = match[1] ?? "0";
|
|
8
|
+
const fractionDigits = match[2] ?? "";
|
|
9
|
+
const exponent = Number(match[3] ?? "0");
|
|
10
|
+
return {
|
|
11
|
+
coefficient: BigInt(`${integerDigits}${fractionDigits}`),
|
|
12
|
+
decimalPlaces: fractionDigits.length - exponent,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function roundDecimalCents({ coefficient, decimalPlaces }) {
|
|
16
|
+
if (decimalPlaces <= 2) {
|
|
17
|
+
return coefficient * 10n ** BigInt(2 - decimalPlaces);
|
|
18
|
+
}
|
|
19
|
+
const divisor = 10n ** BigInt(decimalPlaces - 2);
|
|
20
|
+
const whole = coefficient / divisor;
|
|
21
|
+
const remainder = coefficient % divisor;
|
|
22
|
+
let roundingUp;
|
|
23
|
+
if (remainder * 2n >= divisor) {
|
|
24
|
+
roundingUp = 1n;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
roundingUp = 0n;
|
|
28
|
+
}
|
|
29
|
+
return whole + roundingUp;
|
|
30
|
+
}
|
|
31
|
+
/** Money as integer cents, rounded half away from zero without binary floats. */
|
|
32
|
+
export function toCents(value) {
|
|
33
|
+
if (!Number.isFinite(value)) {
|
|
34
|
+
throw new RangeError("Money value must be finite");
|
|
35
|
+
}
|
|
36
|
+
let sign;
|
|
37
|
+
if (value < 0) {
|
|
38
|
+
sign = -1;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
sign = 1;
|
|
42
|
+
}
|
|
43
|
+
const roundedCents = roundDecimalCents(parseDecimalDigits(value));
|
|
44
|
+
const result = sign * Number(roundedCents);
|
|
45
|
+
if (!Number.isSafeInteger(result)) {
|
|
46
|
+
throw new RangeError("Money value exceeds the safe integer cent range");
|
|
47
|
+
}
|
|
48
|
+
if (result === 0) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=money.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"money.js","sourceRoot":"","sources":["../../src/pluggy/money.ts"],"names":[],"mappings":"AAKA,SAAS,kBAAkB,CAAC,KAAa;IACvC,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClD,MAAM,KAAK,GAAG,qCAAqC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAEzE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,+BAA+B,cAAc,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IACtC,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAEzC,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,GAAG,aAAa,GAAG,cAAc,EAAE,CAAC;QACxD,aAAa,EAAE,cAAc,CAAC,MAAM,GAAG,QAAQ;KAChD,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,WAAW,EAAE,aAAa,EAAiB;IACtE,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,WAAW,GAAG,GAAG,IAAI,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,WAAW,GAAG,OAAO,CAAC;IACpC,MAAM,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IAExC,IAAI,UAAkB,CAAC;IACvB,IAAI,SAAS,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC;QAC9B,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,GAAG,UAAU,CAAC;AAC5B,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,UAAU,CAAC,4BAA4B,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,IAAI,GAAG,CAAC,CAAC,CAAC;IACZ,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;IAED,MAAM,YAAY,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,UAAU,CAAC,iDAAiD,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { localDayOf } from "../core/date.js";
|
|
2
|
+
import { normalizeDescription } from "../core/description.js";
|
|
3
|
+
import { ResponseShapeError } from "./errors.js";
|
|
4
|
+
import { toCents } from "./money.js";
|
|
5
|
+
function toDigits(value) {
|
|
6
|
+
if (value === null || value === undefined) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const digits = value.replace(/\D/gu, "");
|
|
10
|
+
if (digits === "") {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return digits;
|
|
14
|
+
}
|
|
15
|
+
function signFor(type) {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case "BANK":
|
|
18
|
+
return 1;
|
|
19
|
+
case "CREDIT":
|
|
20
|
+
return -1;
|
|
21
|
+
default:
|
|
22
|
+
throw new ResponseShapeError(`${type} accounts have no transaction feed on /v2/transactions`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function localDayOrThrow(value) {
|
|
26
|
+
try {
|
|
27
|
+
return localDayOf(value);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof RangeError) {
|
|
31
|
+
throw new ResponseShapeError(`Pluggy returned an invalid transaction date: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function originalDetails(wire, sign) {
|
|
37
|
+
const foreign = wire.amountInAccountCurrency !== null && wire.amountInAccountCurrency !== undefined;
|
|
38
|
+
if (!foreign) {
|
|
39
|
+
return { originalAmountCents: null, originalCurrency: null };
|
|
40
|
+
}
|
|
41
|
+
return { originalAmountCents: sign * toCents(wire.amount), originalCurrency: wire.currencyCode ?? null };
|
|
42
|
+
}
|
|
43
|
+
function paymentDetails(wire) {
|
|
44
|
+
if (wire.paymentData === null || wire.paymentData === undefined) {
|
|
45
|
+
return { document: null, counterpartyName: null, paymentMethod: null };
|
|
46
|
+
}
|
|
47
|
+
const receiver = receiverDetails(wire.paymentData.receiver);
|
|
48
|
+
return { ...receiver, paymentMethod: wire.paymentData.paymentMethod ?? null };
|
|
49
|
+
}
|
|
50
|
+
function receiverDetails(receiver) {
|
|
51
|
+
if (receiver === null || receiver === undefined) {
|
|
52
|
+
return { document: null, counterpartyName: null };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
document: toDigits(receiver.documentNumber?.value),
|
|
56
|
+
counterpartyName: receiver.name ?? null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function cardDetails(wire) {
|
|
60
|
+
if (wire.creditCardMetadata === null || wire.creditCardMetadata === undefined) {
|
|
61
|
+
return {
|
|
62
|
+
mcc: null,
|
|
63
|
+
billId: null,
|
|
64
|
+
billForecastDate: null,
|
|
65
|
+
instalmentNumber: null,
|
|
66
|
+
instalmentTotal: null,
|
|
67
|
+
purchaseDate: null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return cardMetadataDetails(wire.creditCardMetadata);
|
|
71
|
+
}
|
|
72
|
+
function cardMetadataDetails(metadata) {
|
|
73
|
+
return {
|
|
74
|
+
mcc: mccOf(metadata.payeeMCC),
|
|
75
|
+
billId: stringOrNull(metadata.billId),
|
|
76
|
+
billForecastDate: stringOrNull(metadata.billForecastDate),
|
|
77
|
+
instalmentNumber: numberOrNull(metadata.installmentNumber),
|
|
78
|
+
instalmentTotal: numberOrNull(metadata.totalInstallments),
|
|
79
|
+
purchaseDate: stringOrNull(metadata.purchaseDate),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function mccOf(value) {
|
|
83
|
+
if (value === undefined) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return value.toString();
|
|
87
|
+
}
|
|
88
|
+
function stringOrNull(value) {
|
|
89
|
+
if (value === undefined) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function numberOrNull(value) {
|
|
95
|
+
if (value === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
/** One Pluggy transaction, in our vocabulary, with the sign normalized. */
|
|
101
|
+
export function toTransaction(wire, account) {
|
|
102
|
+
const sign = signFor(account.type);
|
|
103
|
+
const original = originalDetails(wire, sign);
|
|
104
|
+
const payment = paymentDetails(wire);
|
|
105
|
+
const card = cardDetails(wire);
|
|
106
|
+
return {
|
|
107
|
+
...original,
|
|
108
|
+
...payment,
|
|
109
|
+
...card,
|
|
110
|
+
id: wire.id,
|
|
111
|
+
accountId: account.id,
|
|
112
|
+
connectionId: account.connectionId,
|
|
113
|
+
accountType: account.type,
|
|
114
|
+
accountSubtype: account.subtype,
|
|
115
|
+
occurredAt: wire.date,
|
|
116
|
+
localDate: localDayOrThrow(wire.date),
|
|
117
|
+
amountCents: sign * toCents(wire.amountInAccountCurrency ?? wire.amount),
|
|
118
|
+
currency: account.currency,
|
|
119
|
+
description: wire.description,
|
|
120
|
+
descriptionNorm: normalizeDescription(wire.description),
|
|
121
|
+
categoryId: wire.categoryId ?? null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=transaction-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-mapper.js","sourceRoot":"","sources":["../../src/pluggy/transaction-mapper.ts"],"names":[],"mappings":"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"}
|