@dizzlkheinz/ynab-mcpb 0.18.4 → 0.19.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/CLAUDE.md +87 -8
- package/bin/ynab-mcp-server.cjs +2 -2
- package/bin/ynab-mcp-server.js +3 -3
- package/biome.json +39 -0
- package/dist/bundle/index.cjs +67 -67
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -27
- package/dist/server/YNABMCPServer.d.ts +3 -4
- package/dist/server/YNABMCPServer.js +111 -116
- package/dist/server/budgetResolver.d.ts +6 -5
- package/dist/server/budgetResolver.js +46 -36
- package/dist/server/cacheKeys.js +6 -6
- package/dist/server/cacheManager.js +14 -11
- package/dist/server/completions.d.ts +2 -2
- package/dist/server/completions.js +20 -15
- package/dist/server/config.d.ts +10 -5
- package/dist/server/config.js +24 -7
- package/dist/server/deltaCache.d.ts +2 -2
- package/dist/server/deltaCache.js +22 -16
- package/dist/server/deltaCache.merge.d.ts +2 -2
- package/dist/server/diagnostics.d.ts +4 -4
- package/dist/server/diagnostics.js +38 -32
- package/dist/server/errorHandler.d.ts +5 -12
- package/dist/server/errorHandler.js +219 -217
- package/dist/server/prompts.d.ts +2 -2
- package/dist/server/prompts.js +45 -45
- package/dist/server/rateLimiter.js +4 -4
- package/dist/server/requestLogger.d.ts +1 -1
- package/dist/server/requestLogger.js +40 -35
- package/dist/server/resources.d.ts +3 -3
- package/dist/server/resources.js +55 -52
- package/dist/server/responseFormatter.js +6 -6
- package/dist/server/securityMiddleware.d.ts +2 -2
- package/dist/server/securityMiddleware.js +22 -20
- package/dist/server/serverKnowledgeStore.js +1 -1
- package/dist/server/toolRegistry.d.ts +3 -3
- package/dist/server/toolRegistry.js +47 -40
- package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
- package/dist/tools/__tests__/deltaTestUtils.js +2 -2
- package/dist/tools/accountTools.d.ts +9 -8
- package/dist/tools/accountTools.js +47 -47
- package/dist/tools/adapters.d.ts +13 -8
- package/dist/tools/adapters.js +21 -11
- package/dist/tools/budgetTools.d.ts +8 -7
- package/dist/tools/budgetTools.js +22 -22
- package/dist/tools/categoryTools.d.ts +9 -8
- package/dist/tools/categoryTools.js +68 -59
- package/dist/tools/compareTransactions/formatter.d.ts +3 -3
- package/dist/tools/compareTransactions/formatter.js +9 -9
- package/dist/tools/compareTransactions/index.d.ts +6 -6
- package/dist/tools/compareTransactions/index.js +58 -43
- package/dist/tools/compareTransactions/matcher.d.ts +1 -1
- package/dist/tools/compareTransactions/matcher.js +28 -15
- package/dist/tools/compareTransactions/parser.d.ts +2 -2
- package/dist/tools/compareTransactions/parser.js +144 -138
- package/dist/tools/compareTransactions/types.d.ts +4 -4
- package/dist/tools/compareTransactions.d.ts +1 -1
- package/dist/tools/compareTransactions.js +1 -1
- package/dist/tools/deltaFetcher.d.ts +2 -2
- package/dist/tools/deltaFetcher.js +16 -15
- package/dist/tools/deltaSupport.d.ts +4 -4
- package/dist/tools/deltaSupport.js +35 -41
- package/dist/tools/exportTransactions.d.ts +5 -4
- package/dist/tools/exportTransactions.js +61 -59
- package/dist/tools/monthTools.d.ts +7 -6
- package/dist/tools/monthTools.js +31 -29
- package/dist/tools/payeeTools.d.ts +7 -6
- package/dist/tools/payeeTools.js +28 -28
- package/dist/tools/reconcileAdapter.d.ts +2 -2
- package/dist/tools/reconcileAdapter.js +19 -12
- package/dist/tools/reconciliation/analyzer.d.ts +4 -4
- package/dist/tools/reconciliation/analyzer.js +73 -59
- package/dist/tools/reconciliation/csvParser.d.ts +3 -3
- package/dist/tools/reconciliation/csvParser.js +128 -104
- package/dist/tools/reconciliation/executor.d.ts +4 -4
- package/dist/tools/reconciliation/executor.js +148 -109
- package/dist/tools/reconciliation/index.d.ts +10 -10
- package/dist/tools/reconciliation/index.js +96 -83
- package/dist/tools/reconciliation/matcher.d.ts +3 -3
- package/dist/tools/reconciliation/matcher.js +17 -16
- package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
- package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
- package/dist/tools/reconciliation/recommendationEngine.js +40 -40
- package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
- package/dist/tools/reconciliation/reportFormatter.js +59 -58
- package/dist/tools/reconciliation/signDetector.d.ts +1 -1
- package/dist/tools/reconciliation/types.d.ts +16 -16
- package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
- package/dist/tools/schemas/common.d.ts +1 -1
- package/dist/tools/schemas/common.js +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
- package/dist/tools/schemas/outputs/index.d.ts +14 -14
- package/dist/tools/schemas/outputs/index.js +14 -14
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
- package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
- package/dist/tools/schemas/shared/commonOutputs.js +15 -9
- package/dist/tools/transactionReadTools.d.ts +11 -0
- package/dist/tools/transactionReadTools.js +202 -0
- package/dist/tools/transactionSchemas.d.ts +7 -7
- package/dist/tools/transactionSchemas.js +77 -57
- package/dist/tools/transactionTools.d.ts +6 -24
- package/dist/tools/transactionTools.js +7 -1499
- package/dist/tools/transactionUtils.d.ts +6 -6
- package/dist/tools/transactionUtils.js +78 -63
- package/dist/tools/transactionWriteTools.d.ts +20 -0
- package/dist/tools/transactionWriteTools.js +1342 -0
- package/dist/tools/utilityTools.d.ts +5 -4
- package/dist/tools/utilityTools.js +11 -11
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +6 -6
- package/dist/types/reconciliation.d.ts +1 -1
- package/dist/types/toolRegistration.d.ts +14 -12
- package/dist/utils/amountUtils.js +1 -1
- package/dist/utils/dateUtils.js +4 -4
- package/dist/utils/errors.d.ts +3 -3
- package/dist/utils/errors.js +4 -4
- package/dist/utils/money.d.ts +2 -2
- package/dist/utils/money.js +8 -8
- package/dist/utils/validationError.d.ts +1 -1
- package/dist/utils/validationError.js +1 -1
- package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
- package/docs/assets/schemas/reconciliation-v2.json +360 -336
- package/esbuild.config.mjs +53 -50
- package/meta.json +12548 -12548
- package/package.json +98 -111
- package/scripts/analyze-bundle.mjs +33 -30
- package/scripts/create-pr-description.js +169 -120
- package/scripts/run-all-tests.js +178 -169
- package/scripts/run-domain-integration-tests.js +28 -18
- package/scripts/run-generate-mcpb.js +19 -17
- package/scripts/run-throttled-integration-tests.js +92 -83
- package/scripts/test-delta-params.mjs +149 -120
- package/scripts/test-recommendations.ts +36 -32
- package/scripts/tmpTransaction.ts +80 -43
- package/scripts/validate-env.js +98 -91
- package/scripts/verify-build.js +78 -76
- package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
- package/src/__tests__/performance.test.ts +723 -671
- package/src/__tests__/setup.ts +442 -395
- package/src/__tests__/smoke.e2e.test.ts +41 -39
- package/src/__tests__/testRunner.ts +314 -295
- package/src/__tests__/testUtils.ts +456 -364
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
- package/src/index.ts +68 -59
- package/src/server/CLAUDE.md +480 -0
- package/src/server/YNABMCPServer.ts +821 -794
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
- package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
- package/src/server/__tests__/budgetResolver.test.ts +466 -423
- package/src/server/__tests__/cacheManager.test.ts +891 -874
- package/src/server/__tests__/completions.integration.test.ts +115 -106
- package/src/server/__tests__/completions.test.ts +334 -313
- package/src/server/__tests__/config.test.ts +98 -86
- package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
- package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
- package/src/server/__tests__/deltaCache.test.ts +946 -759
- package/src/server/__tests__/diagnostics.test.ts +825 -792
- package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
- package/src/server/__tests__/errorHandler.test.ts +402 -397
- package/src/server/__tests__/prompts.test.ts +424 -347
- package/src/server/__tests__/rateLimiter.test.ts +313 -309
- package/src/server/__tests__/requestLogger.test.ts +443 -403
- package/src/server/__tests__/resources.template.test.ts +196 -185
- package/src/server/__tests__/resources.test.ts +294 -288
- package/src/server/__tests__/security.integration.test.ts +487 -421
- package/src/server/__tests__/securityMiddleware.test.ts +519 -444
- package/src/server/__tests__/server-startup.integration.test.ts +509 -490
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
- package/src/server/__tests__/toolRegistration.test.ts +239 -210
- package/src/server/__tests__/toolRegistry.test.ts +907 -845
- package/src/server/budgetResolver.ts +221 -181
- package/src/server/cacheKeys.ts +6 -6
- package/src/server/cacheManager.ts +498 -484
- package/src/server/completions.ts +267 -243
- package/src/server/config.ts +35 -14
- package/src/server/deltaCache.merge.ts +146 -128
- package/src/server/deltaCache.ts +352 -309
- package/src/server/diagnostics.ts +257 -242
- package/src/server/errorHandler.ts +747 -744
- package/src/server/prompts.ts +181 -176
- package/src/server/rateLimiter.ts +131 -129
- package/src/server/requestLogger.ts +350 -322
- package/src/server/resources.ts +442 -374
- package/src/server/responseFormatter.ts +41 -37
- package/src/server/securityMiddleware.ts +223 -205
- package/src/server/serverKnowledgeStore.ts +67 -67
- package/src/server/toolRegistry.ts +508 -474
- package/src/tools/CLAUDE.md +604 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
- package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
- package/src/tools/__tests__/accountTools.test.ts +685 -638
- package/src/tools/__tests__/adapters.test.ts +142 -108
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
- package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
- package/src/tools/__tests__/budgetTools.test.ts +442 -413
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
- package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
- package/src/tools/__tests__/categoryTools.test.ts +656 -625
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
- package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
- package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
- package/src/tools/__tests__/compareTransactions.test.ts +352 -332
- package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
- package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
- package/src/tools/__tests__/deltaSupport.test.ts +211 -184
- package/src/tools/__tests__/deltaTestUtils.ts +37 -33
- package/src/tools/__tests__/exportTransactions.test.ts +205 -200
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
- package/src/tools/__tests__/monthTools.test.ts +561 -512
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
- package/src/tools/__tests__/payeeTools.test.ts +486 -434
- package/src/tools/__tests__/transactionSchemas.test.ts +1202 -1186
- package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
- package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
- package/src/tools/__tests__/transactionUtils.test.ts +1004 -977
- package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
- package/src/tools/__tests__/utilityTools.test.ts +68 -58
- package/src/tools/accountTools.ts +293 -271
- package/src/tools/adapters.ts +120 -63
- package/src/tools/budgetTools.ts +121 -116
- package/src/tools/categoryTools.ts +379 -339
- package/src/tools/compareTransactions/formatter.ts +131 -119
- package/src/tools/compareTransactions/index.ts +249 -214
- package/src/tools/compareTransactions/matcher.ts +259 -209
- package/src/tools/compareTransactions/parser.ts +517 -487
- package/src/tools/compareTransactions/types.ts +38 -38
- package/src/tools/compareTransactions.ts +1 -1
- package/src/tools/deltaFetcher.ts +281 -260
- package/src/tools/deltaSupport.ts +264 -259
- package/src/tools/exportTransactions.ts +230 -218
- package/src/tools/monthTools.ts +180 -165
- package/src/tools/payeeTools.ts +152 -140
- package/src/tools/reconcileAdapter.ts +297 -252
- package/src/tools/reconciliation/CLAUDE.md +506 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +133 -124
- package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -230
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -400
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
- package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
- package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
- package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -989
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -533
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -74
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -62
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +56 -55
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
- package/src/tools/reconciliation/analyzer.ts +564 -504
- package/src/tools/reconciliation/csvParser.ts +656 -609
- package/src/tools/reconciliation/executor.ts +1290 -1128
- package/src/tools/reconciliation/index.ts +580 -528
- package/src/tools/reconciliation/matcher.ts +256 -240
- package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
- package/src/tools/reconciliation/recommendationEngine.ts +357 -345
- package/src/tools/reconciliation/reportFormatter.ts +343 -307
- package/src/tools/reconciliation/signDetector.ts +89 -83
- package/src/tools/reconciliation/types.ts +164 -159
- package/src/tools/reconciliation/ynabAdapter.ts +17 -15
- package/src/tools/schemas/CLAUDE.md +546 -0
- package/src/tools/schemas/common.ts +1 -1
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
- package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
- package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
- package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
- package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
- package/src/tools/schemas/outputs/index.ts +163 -163
- package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
- package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
- package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
- package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
- package/src/tools/schemas/shared/commonOutputs.ts +27 -19
- package/src/tools/toolCategories.ts +114 -114
- package/src/tools/transactionReadTools.ts +327 -0
- package/src/tools/transactionSchemas.ts +322 -291
- package/src/tools/transactionTools.ts +84 -2246
- package/src/tools/transactionUtils.ts +507 -422
- package/src/tools/transactionWriteTools.ts +2110 -0
- package/src/tools/utilityTools.ts +46 -41
- package/src/types/CLAUDE.md +477 -0
- package/src/types/__tests__/index.test.ts +51 -51
- package/src/types/index.ts +43 -39
- package/src/types/integration-tests.d.ts +26 -26
- package/src/types/reconciliation.ts +29 -29
- package/src/types/toolAnnotations.ts +30 -30
- package/src/types/toolRegistration.ts +43 -32
- package/src/utils/CLAUDE.md +508 -0
- package/src/utils/__tests__/dateUtils.test.ts +174 -168
- package/src/utils/__tests__/money.test.ts +193 -187
- package/src/utils/amountUtils.ts +5 -5
- package/src/utils/baseError.ts +5 -5
- package/src/utils/dateUtils.ts +29 -26
- package/src/utils/errors.ts +14 -14
- package/src/utils/money.ts +66 -52
- package/src/utils/validationError.ts +1 -1
- package/tsconfig.json +29 -29
- package/tsconfig.prod.json +16 -16
- package/vitest-reporters/split-json-reporter.ts +247 -204
- package/vitest.config.ts +99 -95
- package/.prettierignore +0 -10
- package/.prettierrc.json +0 -10
- package/eslint.config.js +0 -49
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parse
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parse } from "csv-parse/sync";
|
|
3
|
+
import { parse as parseDateFns } from "date-fns";
|
|
4
|
+
import { toMilli } from "../../utils/money.js";
|
|
5
5
|
export function parseDate(dateStr, format) {
|
|
6
6
|
const cleanDate = dateStr.trim();
|
|
7
7
|
const formatMap = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
"MM/DD/YYYY": "MM/dd/yyyy",
|
|
9
|
+
"M/D/YYYY": "M/d/yyyy",
|
|
10
|
+
"DD/MM/YYYY": "dd/MM/yyyy",
|
|
11
|
+
"D/M/YYYY": "d/M/yyyy",
|
|
12
|
+
"YYYY-MM-DD": "yyyy-MM-dd",
|
|
13
|
+
"MM-DD-YYYY": "MM-dd-yyyy",
|
|
14
|
+
"MMM dd, yyyy": "MMM dd, yyyy",
|
|
15
|
+
"MMM d, yyyy": "MMM d, yyyy",
|
|
16
16
|
};
|
|
17
17
|
const dateFnsFormat = formatMap[format];
|
|
18
18
|
if (dateFnsFormat) {
|
|
19
19
|
try {
|
|
20
20
|
const parsed = parseDateFns(cleanDate, dateFnsFormat, new Date());
|
|
21
|
-
if (!isNaN(parsed.getTime())) {
|
|
21
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
22
22
|
return parsed;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -26,22 +26,23 @@ export function parseDate(dateStr, format) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
const parsed = new Date(cleanDate);
|
|
29
|
-
if (isNaN(parsed.getTime())) {
|
|
29
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
30
30
|
throw new Error(`Unable to parse date: ${dateStr} with format: ${format}`);
|
|
31
31
|
}
|
|
32
32
|
return parsed;
|
|
33
33
|
}
|
|
34
34
|
export function amountToMilliunits(amountStr) {
|
|
35
|
-
const cleaned = amountStr.replace(/[$,\s]/g,
|
|
36
|
-
let s = cleaned
|
|
37
|
-
|
|
35
|
+
const cleaned = amountStr.replace(/[$,\s]/g, "").trim();
|
|
36
|
+
let s = cleaned;
|
|
37
|
+
let neg = false;
|
|
38
|
+
if (s.startsWith("(") && s.endsWith(")")) {
|
|
38
39
|
neg = true;
|
|
39
40
|
s = s.slice(1, -1);
|
|
40
41
|
}
|
|
41
|
-
if (s.startsWith(
|
|
42
|
+
if (s.startsWith("+"))
|
|
42
43
|
s = s.slice(1);
|
|
43
44
|
const n = Number(s);
|
|
44
|
-
if (isNaN(n) || !isFinite(n)) {
|
|
45
|
+
if (Number.isNaN(n) || !Number.isFinite(n)) {
|
|
45
46
|
throw new Error(`Invalid amount value: "${amountStr}" (cleaned: "${s}")`);
|
|
46
47
|
}
|
|
47
48
|
return toMilli(neg ? -n : n);
|
|
@@ -59,31 +60,29 @@ function isDateLike(str) {
|
|
|
59
60
|
}
|
|
60
61
|
export function detectDateFormat(dateStr) {
|
|
61
62
|
if (!dateStr)
|
|
62
|
-
return
|
|
63
|
+
return "MM/DD/YYYY";
|
|
63
64
|
const cleaned = dateStr.trim();
|
|
64
|
-
if (cleaned.includes(
|
|
65
|
-
return
|
|
65
|
+
if (cleaned.includes("/")) {
|
|
66
|
+
return "MM/DD/YYYY";
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
if (cleaned.includes("-")) {
|
|
68
69
|
if (/^\d{4}-/.test(cleaned)) {
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
return 'MM-DD-YYYY';
|
|
70
|
+
return "YYYY-MM-DD";
|
|
73
71
|
}
|
|
72
|
+
return "MM-DD-YYYY";
|
|
74
73
|
}
|
|
75
|
-
|
|
76
|
-
return
|
|
74
|
+
if (/^[A-Za-z]{3}\s+\d{1,2},\s+\d{4}$/.test(cleaned)) {
|
|
75
|
+
return "MMM dd, yyyy";
|
|
77
76
|
}
|
|
78
|
-
return
|
|
77
|
+
return "MM/DD/YYYY";
|
|
79
78
|
}
|
|
80
79
|
function detectDelimiter(lines) {
|
|
81
|
-
const candidates = [
|
|
80
|
+
const candidates = [",", ";", "\t", "|"];
|
|
82
81
|
const sampleLines = lines.slice(0, 3).filter((line) => line.trim());
|
|
83
82
|
if (sampleLines.length === 0) {
|
|
84
|
-
return
|
|
83
|
+
return ",";
|
|
85
84
|
}
|
|
86
|
-
let bestDelimiter =
|
|
85
|
+
let bestDelimiter = ",";
|
|
87
86
|
let bestScore = -1;
|
|
88
87
|
for (const delimiter of candidates) {
|
|
89
88
|
let score = 0;
|
|
@@ -100,7 +99,9 @@ function detectDelimiter(lines) {
|
|
|
100
99
|
relax_column_count: true,
|
|
101
100
|
});
|
|
102
101
|
if (rows && rows.length > 0 && rows[0]) {
|
|
103
|
-
const columns = Array.isArray(rows[0])
|
|
102
|
+
const columns = Array.isArray(rows[0])
|
|
103
|
+
? rows[0]
|
|
104
|
+
: Object.values(rows[0]);
|
|
104
105
|
columnCounts.push(columns.length);
|
|
105
106
|
}
|
|
106
107
|
else {
|
|
@@ -121,9 +122,9 @@ function detectDelimiter(lines) {
|
|
|
121
122
|
const isConsistent = columnCounts.every((count) => count === firstCount);
|
|
122
123
|
if (isConsistent && firstCount > 1) {
|
|
123
124
|
score = Math.min(firstCount, 10);
|
|
124
|
-
if (delimiter ===
|
|
125
|
+
if (delimiter === ",")
|
|
125
126
|
score += 0.5;
|
|
126
|
-
if (delimiter ===
|
|
127
|
+
if (delimiter === ";")
|
|
127
128
|
score += 0.3;
|
|
128
129
|
if (!parseFailed)
|
|
129
130
|
score += 0.2;
|
|
@@ -165,39 +166,46 @@ function analyzeHeaders(headers) {
|
|
|
165
166
|
creditColumn = cleanHeader;
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
dateColumn,
|
|
171
|
+
amountColumn,
|
|
172
|
+
descriptionColumn,
|
|
173
|
+
debitColumn,
|
|
174
|
+
creditColumn,
|
|
175
|
+
};
|
|
169
176
|
}
|
|
170
177
|
export function autoDetectCSVFormat(csvContent) {
|
|
171
|
-
const linesRaw = csvContent.trim().split(
|
|
178
|
+
const linesRaw = csvContent.trim().split("\n").slice(0, 3);
|
|
172
179
|
if (linesRaw.length === 0) {
|
|
173
|
-
throw new Error(
|
|
180
|
+
throw new Error("CSV file is empty");
|
|
174
181
|
}
|
|
175
182
|
const firstLineRaw = linesRaw[0];
|
|
176
183
|
if (!firstLineRaw || !firstLineRaw.trim()) {
|
|
177
|
-
throw new Error(
|
|
184
|
+
throw new Error("CSV file contains empty first line");
|
|
178
185
|
}
|
|
179
186
|
const delimiter = detectDelimiter(linesRaw);
|
|
180
187
|
const firstLine = firstLineRaw.split(delimiter);
|
|
181
|
-
const hasHeader = !isDateLike(firstLine[0] ||
|
|
188
|
+
const hasHeader = !isDateLike(firstLine[0] || "");
|
|
182
189
|
let hasDebitCredit = false;
|
|
183
190
|
if (linesRaw.length > 1) {
|
|
184
191
|
const dataLines = hasHeader ? linesRaw.slice(1) : linesRaw;
|
|
185
192
|
hasDebitCredit = dataLines.some((line) => {
|
|
186
193
|
const cols = line.split(delimiter);
|
|
187
194
|
return (cols.length >= 4 &&
|
|
188
|
-
((cols[2]?.trim() && !cols[3]?.trim()) ||
|
|
195
|
+
((cols[2]?.trim() && !cols[3]?.trim()) ||
|
|
196
|
+
(!cols[2]?.trim() && cols[3]?.trim())));
|
|
189
197
|
});
|
|
190
198
|
}
|
|
191
199
|
if (hasHeader) {
|
|
192
|
-
const { dateColumn, amountColumn, descriptionColumn, debitColumn, creditColumn } = analyzeHeaders(firstLine);
|
|
193
|
-
const safe = (v) => (v
|
|
200
|
+
const { dateColumn, amountColumn, descriptionColumn, debitColumn, creditColumn, } = analyzeHeaders(firstLine);
|
|
201
|
+
const safe = (v) => (v?.trim() ? v : undefined);
|
|
194
202
|
if (hasDebitCredit && debitColumn && creditColumn) {
|
|
195
203
|
const dateCol = safe(dateColumn ?? undefined) ?? safe(firstLine[0]);
|
|
196
204
|
if (!dateCol)
|
|
197
|
-
throw new Error(
|
|
205
|
+
throw new Error("Unable to detect date column name from header");
|
|
198
206
|
const descCol = safe(descriptionColumn ?? undefined) ?? safe(firstLine[1]);
|
|
199
207
|
if (!descCol)
|
|
200
|
-
throw new Error(
|
|
208
|
+
throw new Error("Unable to detect description column name from header");
|
|
201
209
|
return {
|
|
202
210
|
date_column: dateCol,
|
|
203
211
|
description_column: descCol,
|
|
@@ -208,72 +216,69 @@ export function autoDetectCSVFormat(csvContent) {
|
|
|
208
216
|
delimiter: delimiter,
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
};
|
|
230
|
-
}
|
|
219
|
+
const dateCol = safe(dateColumn ?? undefined) ?? safe(firstLine[0]);
|
|
220
|
+
if (!dateCol)
|
|
221
|
+
throw new Error("Unable to detect date column name from header");
|
|
222
|
+
const amountCol = safe(amountColumn ?? undefined) ?? safe(firstLine[1]);
|
|
223
|
+
if (!amountCol)
|
|
224
|
+
throw new Error("Unable to detect amount column name from header");
|
|
225
|
+
const descCol = safe(descriptionColumn ?? undefined) ??
|
|
226
|
+
safe(firstLine.length >= 3 ? firstLine[2] : firstLine[1]);
|
|
227
|
+
if (!descCol)
|
|
228
|
+
throw new Error("Unable to detect description column name from header");
|
|
229
|
+
return {
|
|
230
|
+
date_column: dateCol,
|
|
231
|
+
amount_column: amountCol,
|
|
232
|
+
description_column: descCol,
|
|
233
|
+
date_format: detectDateFormat(linesRaw[1]?.split(delimiter)[0]),
|
|
234
|
+
has_header: hasHeader,
|
|
235
|
+
delimiter: delimiter,
|
|
236
|
+
};
|
|
231
237
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
return {
|
|
246
|
-
date_column: 0,
|
|
247
|
-
amount_column: 1,
|
|
248
|
-
description_column: firstLine.length >= 3 ? 2 : 1,
|
|
249
|
-
date_format: detectDateFormat(firstLine[0]),
|
|
250
|
-
has_header: hasHeader,
|
|
251
|
-
delimiter: delimiter,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
238
|
+
if (hasDebitCredit && firstLine.length >= 4) {
|
|
239
|
+
return {
|
|
240
|
+
date_column: 0,
|
|
241
|
+
description_column: 1,
|
|
242
|
+
debit_column: 2,
|
|
243
|
+
credit_column: 3,
|
|
244
|
+
date_format: detectDateFormat(firstLine[0]),
|
|
245
|
+
has_header: hasHeader,
|
|
246
|
+
delimiter: delimiter,
|
|
247
|
+
};
|
|
254
248
|
}
|
|
249
|
+
return {
|
|
250
|
+
date_column: 0,
|
|
251
|
+
amount_column: 1,
|
|
252
|
+
description_column: firstLine.length >= 3 ? 2 : 1,
|
|
253
|
+
date_format: detectDateFormat(firstLine[0]),
|
|
254
|
+
has_header: hasHeader,
|
|
255
|
+
delimiter: delimiter,
|
|
256
|
+
};
|
|
255
257
|
}
|
|
256
258
|
function preprocessCSV(csvContent, format) {
|
|
257
|
-
if (format.date_format?.includes(
|
|
258
|
-
|
|
259
|
+
if (format.date_format?.includes("MMM") &&
|
|
260
|
+
format.date_format?.includes(",")) {
|
|
261
|
+
const lines = csvContent.split("\n");
|
|
259
262
|
const fixedLines = lines.map((line, index) => {
|
|
260
263
|
if (format.has_header && index === 0)
|
|
261
264
|
return line;
|
|
262
265
|
if (!line.trim())
|
|
263
266
|
return line;
|
|
264
267
|
const parts = line.split(format.delimiter);
|
|
265
|
-
const expectedColumns = format.has_header
|
|
268
|
+
const expectedColumns = format.has_header
|
|
269
|
+
? lines[0]?.split(format.delimiter).length || 3
|
|
270
|
+
: 3;
|
|
266
271
|
if (parts.length > expectedColumns) {
|
|
267
|
-
const potentialDate = parts.slice(0, 2).join(
|
|
272
|
+
const potentialDate = parts.slice(0, 2).join(",");
|
|
268
273
|
if (/^[A-Za-z]{3}\s+\d{1,2},\s+\d{4}/.test(potentialDate)) {
|
|
269
|
-
const dateField = parts.slice(0, 2).join(
|
|
274
|
+
const dateField = parts.slice(0, 2).join(",");
|
|
270
275
|
const remainingFields = parts.slice(2);
|
|
271
276
|
return `"${dateField}"${format.delimiter}${remainingFields.join(format.delimiter)}`;
|
|
272
277
|
}
|
|
273
278
|
}
|
|
274
279
|
return line;
|
|
275
280
|
});
|
|
276
|
-
return fixedLines.join(
|
|
281
|
+
return fixedLines.join("\n");
|
|
277
282
|
}
|
|
278
283
|
return csvContent;
|
|
279
284
|
}
|
|
@@ -298,73 +303,75 @@ export function parseBankCSV(csvContent, format, options = {}) {
|
|
|
298
303
|
let description;
|
|
299
304
|
if (format.has_header) {
|
|
300
305
|
const recordObj = record;
|
|
301
|
-
rawDate = recordObj[format.date_column] ||
|
|
306
|
+
rawDate = recordObj[format.date_column] || "";
|
|
302
307
|
if (format.amount_column) {
|
|
303
|
-
rawAmount = recordObj[format.amount_column] ||
|
|
308
|
+
rawAmount = recordObj[format.amount_column] || "";
|
|
304
309
|
}
|
|
305
|
-
else if (format.debit_column !== undefined &&
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
310
|
+
else if (format.debit_column !== undefined &&
|
|
311
|
+
format.credit_column !== undefined) {
|
|
312
|
+
const debitVal = recordObj[format.debit_column] || "";
|
|
313
|
+
const creditVal = recordObj[format.credit_column] || "";
|
|
314
|
+
const debitNum = Number.parseFloat(debitVal.replace(/[^\d.-]/g, ""));
|
|
315
|
+
const creditNum = Number.parseFloat(creditVal.replace(/[^\d.-]/g, ""));
|
|
316
|
+
if (!Number.isNaN(debitNum) && debitNum !== 0) {
|
|
311
317
|
rawAmount = `-${debitVal}`;
|
|
312
318
|
}
|
|
313
|
-
else if (!isNaN(creditNum) && creditNum !== 0) {
|
|
319
|
+
else if (!Number.isNaN(creditNum) && creditNum !== 0) {
|
|
314
320
|
rawAmount = creditVal;
|
|
315
321
|
}
|
|
316
322
|
else {
|
|
317
|
-
rawAmount =
|
|
323
|
+
rawAmount = "0";
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
326
|
else {
|
|
321
|
-
throw new Error(
|
|
327
|
+
throw new Error("No amount column configuration found");
|
|
322
328
|
}
|
|
323
|
-
description = recordObj[format.description_column] ||
|
|
329
|
+
description = recordObj[format.description_column] || "";
|
|
324
330
|
}
|
|
325
331
|
else {
|
|
326
332
|
const recordArray = record;
|
|
327
|
-
const dateIndex = typeof format.date_column ===
|
|
333
|
+
const dateIndex = typeof format.date_column === "number"
|
|
328
334
|
? format.date_column
|
|
329
|
-
: parseInt(format.date_column, 10);
|
|
330
|
-
const descIndex = typeof format.description_column ===
|
|
335
|
+
: Number.parseInt(format.date_column, 10);
|
|
336
|
+
const descIndex = typeof format.description_column === "number"
|
|
331
337
|
? format.description_column
|
|
332
|
-
: parseInt(format.description_column, 10);
|
|
333
|
-
const safeDateIndex = isNaN(dateIndex) ? 0 : dateIndex;
|
|
334
|
-
const safeDescIndex = isNaN(descIndex) ? 2 : descIndex;
|
|
335
|
-
rawDate = recordArray[safeDateIndex] ||
|
|
338
|
+
: Number.parseInt(format.description_column, 10);
|
|
339
|
+
const safeDateIndex = Number.isNaN(dateIndex) ? 0 : dateIndex;
|
|
340
|
+
const safeDescIndex = Number.isNaN(descIndex) ? 2 : descIndex;
|
|
341
|
+
rawDate = recordArray[safeDateIndex] || "";
|
|
336
342
|
if (format.amount_column !== undefined) {
|
|
337
|
-
const amountIndex = typeof format.amount_column ===
|
|
343
|
+
const amountIndex = typeof format.amount_column === "number"
|
|
338
344
|
? format.amount_column
|
|
339
|
-
: parseInt(format.amount_column, 10);
|
|
340
|
-
const safeAmountIndex = isNaN(amountIndex) ? 1 : amountIndex;
|
|
341
|
-
rawAmount = recordArray[safeAmountIndex] ||
|
|
345
|
+
: Number.parseInt(format.amount_column, 10);
|
|
346
|
+
const safeAmountIndex = Number.isNaN(amountIndex) ? 1 : amountIndex;
|
|
347
|
+
rawAmount = recordArray[safeAmountIndex] || "";
|
|
342
348
|
}
|
|
343
|
-
else if (format.debit_column !== undefined &&
|
|
344
|
-
|
|
349
|
+
else if (format.debit_column !== undefined &&
|
|
350
|
+
format.credit_column !== undefined) {
|
|
351
|
+
const debitIndex = typeof format.debit_column === "number"
|
|
345
352
|
? format.debit_column
|
|
346
|
-
: parseInt(format.debit_column, 10);
|
|
347
|
-
const creditIndex = typeof format.credit_column ===
|
|
353
|
+
: Number.parseInt(format.debit_column, 10);
|
|
354
|
+
const creditIndex = typeof format.credit_column === "number"
|
|
348
355
|
? format.credit_column
|
|
349
|
-
: parseInt(format.credit_column, 10);
|
|
350
|
-
const debitVal = recordArray[debitIndex] ||
|
|
351
|
-
const creditVal = recordArray[creditIndex] ||
|
|
352
|
-
const debitNum = parseFloat(debitVal.replace(/[^\d.-]/g,
|
|
353
|
-
const creditNum = parseFloat(creditVal.replace(/[^\d.-]/g,
|
|
354
|
-
if (!isNaN(debitNum) && debitNum !== 0) {
|
|
356
|
+
: Number.parseInt(format.credit_column, 10);
|
|
357
|
+
const debitVal = recordArray[debitIndex] || "";
|
|
358
|
+
const creditVal = recordArray[creditIndex] || "";
|
|
359
|
+
const debitNum = Number.parseFloat(debitVal.replace(/[^\d.-]/g, ""));
|
|
360
|
+
const creditNum = Number.parseFloat(creditVal.replace(/[^\d.-]/g, ""));
|
|
361
|
+
if (!Number.isNaN(debitNum) && debitNum !== 0) {
|
|
355
362
|
rawAmount = `-${debitVal}`;
|
|
356
363
|
}
|
|
357
|
-
else if (!isNaN(creditNum) && creditNum !== 0) {
|
|
364
|
+
else if (!Number.isNaN(creditNum) && creditNum !== 0) {
|
|
358
365
|
rawAmount = creditVal;
|
|
359
366
|
}
|
|
360
367
|
else {
|
|
361
|
-
rawAmount =
|
|
368
|
+
rawAmount = "0";
|
|
362
369
|
}
|
|
363
370
|
}
|
|
364
371
|
else {
|
|
365
|
-
throw new Error(
|
|
372
|
+
throw new Error("No amount column configuration found");
|
|
366
373
|
}
|
|
367
|
-
description = recordArray[safeDescIndex] ||
|
|
374
|
+
description = recordArray[safeDescIndex] || "";
|
|
368
375
|
}
|
|
369
376
|
if (!rawDate || !rawAmount) {
|
|
370
377
|
if (options.debug) {
|
|
@@ -379,7 +386,7 @@ export function parseBankCSV(csvContent, format, options = {}) {
|
|
|
379
386
|
}
|
|
380
387
|
catch (error) {
|
|
381
388
|
if (options.debug) {
|
|
382
|
-
console.warn(`Skipping row ${rowNumber}: ${error instanceof Error ? error.message :
|
|
389
|
+
console.warn(`Skipping row ${rowNumber}: ${error instanceof Error ? error.message : "Invalid amount"}`);
|
|
383
390
|
}
|
|
384
391
|
continue;
|
|
385
392
|
}
|
|
@@ -396,31 +403,30 @@ export function parseBankCSV(csvContent, format, options = {}) {
|
|
|
396
403
|
if (options.debug) {
|
|
397
404
|
console.warn(`Error parsing row ${rowNumber}:`, error);
|
|
398
405
|
}
|
|
399
|
-
continue;
|
|
400
406
|
}
|
|
401
407
|
}
|
|
402
408
|
return transactions;
|
|
403
409
|
}
|
|
404
410
|
export function readCSVFile(filePath) {
|
|
405
411
|
try {
|
|
406
|
-
return readFileSync(filePath,
|
|
412
|
+
return readFileSync(filePath, "utf-8");
|
|
407
413
|
}
|
|
408
414
|
catch (error) {
|
|
409
|
-
throw new Error(`Unable to read CSV file: ${error instanceof Error ? error.message :
|
|
415
|
+
throw new Error(`Unable to read CSV file: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
410
416
|
}
|
|
411
417
|
}
|
|
412
418
|
export function extractDateRangeFromCSV(csvContent, format) {
|
|
413
419
|
const transactions = parseBankCSV(csvContent, format);
|
|
414
420
|
if (transactions.length === 0) {
|
|
415
|
-
throw new Error(
|
|
421
|
+
throw new Error("No transactions found in CSV");
|
|
416
422
|
}
|
|
417
423
|
const dates = transactions.map((txn) => txn.date.getTime());
|
|
418
424
|
const minDateObj = new Date(Math.min(...dates));
|
|
419
425
|
const maxDateObj = new Date(Math.max(...dates));
|
|
420
426
|
const toYYYYMMDD = (date) => {
|
|
421
427
|
const year = date.getFullYear();
|
|
422
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
423
|
-
const day = String(date.getDate()).padStart(2,
|
|
428
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
429
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
424
430
|
return `${year}-${month}-${day}`;
|
|
425
431
|
};
|
|
426
432
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type * as ynab from
|
|
2
|
-
import { z } from
|
|
3
|
-
import type { CompareTransactionsSchema } from
|
|
1
|
+
import type * as ynab from "ynab";
|
|
2
|
+
import type { z } from "zod/v4";
|
|
3
|
+
import type { CompareTransactionsSchema } from "./index.js";
|
|
4
4
|
export interface BankTransaction {
|
|
5
5
|
date: Date;
|
|
6
6
|
amount: number;
|
|
@@ -24,4 +24,4 @@ export interface TransactionMatch {
|
|
|
24
24
|
match_score: number;
|
|
25
25
|
match_reasons: string[];
|
|
26
26
|
}
|
|
27
|
-
export type CSVFormat = z.infer<typeof CompareTransactionsSchema>[
|
|
27
|
+
export type CSVFormat = z.infer<typeof CompareTransactionsSchema>["csv_format"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./compareTransactions/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./compareTransactions/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as ynab from
|
|
2
|
-
import { DeltaCache,
|
|
1
|
+
import type * as ynab from "ynab";
|
|
2
|
+
import type { DeltaCache, DeltaFetchResult } from "../server/deltaCache.js";
|
|
3
3
|
export interface DeltaFetchOptions {
|
|
4
4
|
forceFullRefresh?: boolean;
|
|
5
5
|
ttl?: number;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CACHE_TTLS, CacheManager } from "../server/cacheManager.js";
|
|
2
|
+
import { mergeCategories, mergeFlatEntities, mergeMonths, mergeTransactions, } from "../server/deltaCache.merge.js";
|
|
3
3
|
export class DeltaFetcher {
|
|
4
4
|
constructor(ynabAPI, deltaCache) {
|
|
5
5
|
this.ynabAPI = ynabAPI;
|
|
6
6
|
this.deltaCache = deltaCache;
|
|
7
7
|
}
|
|
8
8
|
async fetchAccounts(budgetId, options) {
|
|
9
|
-
const cacheKey = CacheManager.generateKey(
|
|
9
|
+
const cacheKey = CacheManager.generateKey("accounts", "list", budgetId);
|
|
10
10
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
11
11
|
const response = lastKnowledge !== undefined
|
|
12
12
|
? await this.ynabAPI.accounts.getAccounts(budgetId, lastKnowledge)
|
|
@@ -18,7 +18,7 @@ export class DeltaFetcher {
|
|
|
18
18
|
}, mergeFlatEntities, this.buildDeltaOptions(CACHE_TTLS.ACCOUNTS, options));
|
|
19
19
|
}
|
|
20
20
|
async fetchCategories(budgetId, options) {
|
|
21
|
-
const cacheKey = CacheManager.generateKey(
|
|
21
|
+
const cacheKey = CacheManager.generateKey("categories", "list", budgetId);
|
|
22
22
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
23
23
|
const response = lastKnowledge !== undefined
|
|
24
24
|
? await this.ynabAPI.categories.getCategories(budgetId, lastKnowledge)
|
|
@@ -30,9 +30,9 @@ export class DeltaFetcher {
|
|
|
30
30
|
}, mergeCategories, this.buildDeltaOptions(CACHE_TTLS.CATEGORIES, options));
|
|
31
31
|
}
|
|
32
32
|
async fetchTransactions(budgetId, sinceDate, type, options) {
|
|
33
|
-
const normalizedSince = sinceDate ??
|
|
34
|
-
const normalizedType = type ??
|
|
35
|
-
const cacheKey = CacheManager.generateKey(
|
|
33
|
+
const normalizedSince = sinceDate ?? "all";
|
|
34
|
+
const normalizedType = type ?? "all";
|
|
35
|
+
const cacheKey = CacheManager.generateKey("transactions", "list", budgetId, normalizedSince, normalizedType);
|
|
36
36
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
37
37
|
const response = await this.ynabAPI.transactions.getTransactions(budgetId, sinceDate, type, lastKnowledge);
|
|
38
38
|
return {
|
|
@@ -42,8 +42,8 @@ export class DeltaFetcher {
|
|
|
42
42
|
}, mergeTransactions, this.buildDeltaOptions(CACHE_TTLS.TRANSACTIONS, options));
|
|
43
43
|
}
|
|
44
44
|
async fetchTransactionsByAccount(budgetId, accountId, sinceDate, options) {
|
|
45
|
-
const normalizedSince = sinceDate ??
|
|
46
|
-
const cacheKey = CacheManager.generateKey(
|
|
45
|
+
const normalizedSince = sinceDate ?? "all";
|
|
46
|
+
const cacheKey = CacheManager.generateKey("transactions", "account", budgetId, accountId, normalizedSince);
|
|
47
47
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
48
48
|
const response = await this.ynabAPI.transactions.getTransactionsByAccount(budgetId, accountId, sinceDate, undefined, lastKnowledge);
|
|
49
49
|
return {
|
|
@@ -73,7 +73,7 @@ export class DeltaFetcher {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
async fetchScheduledTransactions(budgetId, options) {
|
|
76
|
-
const cacheKey = CacheManager.generateKey(
|
|
76
|
+
const cacheKey = CacheManager.generateKey("scheduled_transactions", "list", budgetId);
|
|
77
77
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
78
78
|
const response = lastKnowledge !== undefined
|
|
79
79
|
? await this.ynabAPI.scheduledTransactions.getScheduledTransactions(budgetId, lastKnowledge)
|
|
@@ -85,7 +85,7 @@ export class DeltaFetcher {
|
|
|
85
85
|
}, mergeFlatEntities, this.buildDeltaOptions(CACHE_TTLS.SCHEDULED_TRANSACTIONS, options));
|
|
86
86
|
}
|
|
87
87
|
async fetchPayees(budgetId, options) {
|
|
88
|
-
const cacheKey = CacheManager.generateKey(
|
|
88
|
+
const cacheKey = CacheManager.generateKey("payees", "list", budgetId);
|
|
89
89
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
90
90
|
const response = lastKnowledge !== undefined
|
|
91
91
|
? await this.ynabAPI.payees.getPayees(budgetId, lastKnowledge)
|
|
@@ -97,7 +97,7 @@ export class DeltaFetcher {
|
|
|
97
97
|
}, mergeFlatEntities, this.buildDeltaOptions(CACHE_TTLS.PAYEES, options));
|
|
98
98
|
}
|
|
99
99
|
async fetchMonths(budgetId, options) {
|
|
100
|
-
const cacheKey = CacheManager.generateKey(
|
|
100
|
+
const cacheKey = CacheManager.generateKey("months", "list", budgetId);
|
|
101
101
|
return await this.deltaCache.fetchWithDelta(cacheKey, budgetId, async (lastKnowledge) => {
|
|
102
102
|
const response = lastKnowledge !== undefined
|
|
103
103
|
? await this.ynabAPI.months.getBudgetMonths(budgetId, lastKnowledge)
|
|
@@ -109,10 +109,11 @@ export class DeltaFetcher {
|
|
|
109
109
|
}, mergeMonths, this.buildDeltaOptions(CACHE_TTLS.MONTHS, options));
|
|
110
110
|
}
|
|
111
111
|
async fetchBudgets(options) {
|
|
112
|
-
const cacheKey = CacheManager.generateKey(
|
|
113
|
-
const result = await this.deltaCache.fetchWithDelta(cacheKey,
|
|
112
|
+
const cacheKey = CacheManager.generateKey("budgets", "list");
|
|
113
|
+
const result = await this.deltaCache.fetchWithDelta(cacheKey, "global", async () => {
|
|
114
114
|
const response = await this.ynabAPI.budgets.getBudgets();
|
|
115
|
-
const serverKnowledge = response.data.server_knowledge ??
|
|
115
|
+
const serverKnowledge = response.data.server_knowledge ??
|
|
116
|
+
0;
|
|
116
117
|
return {
|
|
117
118
|
data: response.data.budgets,
|
|
118
119
|
serverKnowledge,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as ynab from
|
|
2
|
-
import { DeltaCache } from
|
|
3
|
-
import { ServerKnowledgeStore } from
|
|
4
|
-
import { DeltaFetcher } from
|
|
1
|
+
import type * as ynab from "ynab";
|
|
2
|
+
import { DeltaCache } from "../server/deltaCache.js";
|
|
3
|
+
import { ServerKnowledgeStore } from "../server/serverKnowledgeStore.js";
|
|
4
|
+
import { DeltaFetcher } from "./deltaFetcher.js";
|
|
5
5
|
export interface SharedDeltaSupportOptions {
|
|
6
6
|
deltaFetcher?: DeltaFetcher;
|
|
7
7
|
deltaCache?: DeltaCache;
|