@dizzlkheinz/ynab-mcpb 0.12.1
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/.chunkhound.json +11 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
- package/.dxtignore +57 -0
- package/.env.example +44 -0
- package/.gemini/settings.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
- package/.github/pull_request_template.md +41 -0
- package/.github/workflows/ci-tests.yml +41 -0
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/full-integration.yml +22 -0
- package/.github/workflows/pr-description-check.yml +88 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/release.yml +89 -0
- package/.mcpbignore +58 -0
- package/.prettierignore +10 -0
- package/.prettierrc.json +10 -0
- package/ADOS-2-Module-1-Complete-Manual.md +757 -0
- package/AGENTS.md +36 -0
- package/CHANGELOG.md +187 -0
- package/CLAUDE.md +414 -0
- package/CODEREVIEW_RESPONSE.md +128 -0
- package/LICENSE +17 -0
- package/NUL +1 -0
- package/README.md +222 -0
- package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
- package/TESTING_NOTES.md +217 -0
- package/WARP.md +245 -0
- package/accountactivity-merged.csv +149 -0
- package/bin/ynab-mcp-server.cjs +4 -0
- package/bin/ynab-mcp-server.js +8 -0
- package/bundle-analysis.html +13110 -0
- package/dist/bundle/index.cjs +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/server/YNABMCPServer.d.ts +264 -0
- package/dist/server/YNABMCPServer.js +845 -0
- package/dist/server/budgetResolver.d.ts +15 -0
- package/dist/server/budgetResolver.js +99 -0
- package/dist/server/cacheManager.d.ts +74 -0
- package/dist/server/cacheManager.js +306 -0
- package/dist/server/config.d.ts +3 -0
- package/dist/server/config.js +19 -0
- package/dist/server/deltaCache.d.ts +61 -0
- package/dist/server/deltaCache.js +206 -0
- package/dist/server/deltaCache.merge.d.ts +9 -0
- package/dist/server/deltaCache.merge.js +111 -0
- package/dist/server/diagnostics.d.ts +90 -0
- package/dist/server/diagnostics.js +163 -0
- package/dist/server/errorHandler.d.ts +69 -0
- package/dist/server/errorHandler.js +524 -0
- package/dist/server/prompts.d.ts +31 -0
- package/dist/server/prompts.js +205 -0
- package/dist/server/rateLimiter.d.ts +27 -0
- package/dist/server/rateLimiter.js +82 -0
- package/dist/server/requestLogger.d.ts +62 -0
- package/dist/server/requestLogger.js +190 -0
- package/dist/server/resources.d.ts +39 -0
- package/dist/server/resources.js +85 -0
- package/dist/server/responseFormatter.d.ts +14 -0
- package/dist/server/responseFormatter.js +42 -0
- package/dist/server/securityMiddleware.d.ts +87 -0
- package/dist/server/securityMiddleware.js +117 -0
- package/dist/server/serverKnowledgeStore.d.ts +11 -0
- package/dist/server/serverKnowledgeStore.js +42 -0
- package/dist/server/toolRegistry.d.ts +85 -0
- package/dist/server/toolRegistry.js +272 -0
- package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
- package/dist/tools/__tests__/deltaTestUtils.js +26 -0
- package/dist/tools/accountTools.d.ts +37 -0
- package/dist/tools/accountTools.js +175 -0
- package/dist/tools/budgetTools.d.ts +10 -0
- package/dist/tools/budgetTools.js +68 -0
- package/dist/tools/categoryTools.d.ts +27 -0
- package/dist/tools/categoryTools.js +232 -0
- package/dist/tools/compareTransactions/formatter.d.ts +71 -0
- package/dist/tools/compareTransactions/formatter.js +97 -0
- package/dist/tools/compareTransactions/index.d.ts +30 -0
- package/dist/tools/compareTransactions/index.js +160 -0
- package/dist/tools/compareTransactions/matcher.d.ts +12 -0
- package/dist/tools/compareTransactions/matcher.js +140 -0
- package/dist/tools/compareTransactions/parser.d.ts +14 -0
- package/dist/tools/compareTransactions/parser.js +430 -0
- package/dist/tools/compareTransactions/types.d.ts +27 -0
- package/dist/tools/compareTransactions/types.js +1 -0
- package/dist/tools/compareTransactions.d.ts +1 -0
- package/dist/tools/compareTransactions.js +1 -0
- package/dist/tools/deltaFetcher.d.ts +22 -0
- package/dist/tools/deltaFetcher.js +137 -0
- package/dist/tools/deltaSupport.d.ts +20 -0
- package/dist/tools/deltaSupport.js +176 -0
- package/dist/tools/exportTransactions.d.ts +17 -0
- package/dist/tools/exportTransactions.js +191 -0
- package/dist/tools/monthTools.d.ts +16 -0
- package/dist/tools/monthTools.js +107 -0
- package/dist/tools/payeeTools.d.ts +17 -0
- package/dist/tools/payeeTools.js +82 -0
- package/dist/tools/reconcileAdapter.d.ts +25 -0
- package/dist/tools/reconcileAdapter.js +167 -0
- package/dist/tools/reconciliation/analyzer.d.ts +3 -0
- package/dist/tools/reconciliation/analyzer.js +567 -0
- package/dist/tools/reconciliation/executor.d.ts +94 -0
- package/dist/tools/reconciliation/executor.js +611 -0
- package/dist/tools/reconciliation/index.d.ts +54 -0
- package/dist/tools/reconciliation/index.js +249 -0
- package/dist/tools/reconciliation/matcher.d.ts +3 -0
- package/dist/tools/reconciliation/matcher.js +160 -0
- package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
- package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
- package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
- package/dist/tools/reconciliation/recommendationEngine.js +273 -0
- package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
- package/dist/tools/reconciliation/reportFormatter.js +214 -0
- package/dist/tools/reconciliation/types.d.ts +172 -0
- package/dist/tools/reconciliation/types.js +7 -0
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
- package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
- package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
- package/dist/tools/schemas/outputs/index.d.ts +14 -0
- package/dist/tools/schemas/outputs/index.js +14 -0
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
- package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
- package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
- package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
- package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
- package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
- package/dist/tools/schemas/shared/commonOutputs.js +27 -0
- package/dist/tools/toolCategories.d.ts +32 -0
- package/dist/tools/toolCategories.js +32 -0
- package/dist/tools/transactionTools.d.ts +315 -0
- package/dist/tools/transactionTools.js +1722 -0
- package/dist/tools/utilityTools.d.ts +10 -0
- package/dist/tools/utilityTools.js +56 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.js +16 -0
- package/dist/types/toolAnnotations.d.ts +7 -0
- package/dist/types/toolAnnotations.js +1 -0
- package/dist/utils/amountUtils.d.ts +3 -0
- package/dist/utils/amountUtils.js +10 -0
- package/dist/utils/dateUtils.d.ts +9 -0
- package/dist/utils/dateUtils.js +43 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +51 -0
- package/docs/README.md +72 -0
- package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
- package/docs/assets/schemas/reconciliation-v2.json +338 -0
- package/docs/getting-started/CONFIGURATION.md +175 -0
- package/docs/getting-started/INSTALLATION.md +333 -0
- package/docs/getting-started/QUICKSTART.md +282 -0
- package/docs/guides/ARCHITECTURE.md +650 -0
- package/docs/guides/DEPLOYMENT.md +189 -0
- package/docs/guides/INTEGRATION_TESTING.md +730 -0
- package/docs/guides/TESTING.md +591 -0
- package/docs/reconciliation-flow.md +83 -0
- package/docs/reference/API.md +1450 -0
- package/docs/reference/EXAMPLES.md +946 -0
- package/docs/reference/TOOLS.md +348 -0
- package/docs/reference/TROUBLESHOOTING.md +481 -0
- package/esbuild.config.mjs +68 -0
- package/eslint.config.js +49 -0
- package/fix-types.sh +17 -0
- package/meta.json +12550 -0
- package/package.json +105 -0
- package/package.json.tmp +105 -0
- package/scripts/analyze-bundle.mjs +41 -0
- package/scripts/create-pr-description.js +203 -0
- package/scripts/generate-mcpb.ps1 +96 -0
- package/scripts/run-domain-integration-tests.js +33 -0
- package/scripts/run-generate-mcpb.js +29 -0
- package/scripts/run-throttled-integration-tests.js +116 -0
- package/scripts/test-delta-params.mjs +140 -0
- package/scripts/test-recommendations.ts +53 -0
- package/scripts/tmpTransaction.ts +48 -0
- package/scripts/validate-env.js +122 -0
- package/scripts/verify-build.js +105 -0
- package/scripts/watch-and-restart.ps1 +50 -0
- package/src/__tests__/comprehensive.integration.test.ts +1196 -0
- package/src/__tests__/delta.performance.test.ts +80 -0
- package/src/__tests__/performance.test.ts +725 -0
- package/src/__tests__/setup.ts +449 -0
- package/src/__tests__/testRunner.ts +444 -0
- package/src/__tests__/testUtils.ts +563 -0
- package/src/__tests__/workflows.e2e.test.ts +1675 -0
- package/src/index.ts +124 -0
- package/src/server/.gitkeep +1 -0
- package/src/server/YNABMCPServer.ts +1188 -0
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
- package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
- package/src/server/__tests__/budgetResolver.test.ts +425 -0
- package/src/server/__tests__/cacheManager.test.ts +880 -0
- package/src/server/__tests__/config.test.ts +166 -0
- package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
- package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
- package/src/server/__tests__/deltaCache.test.ts +774 -0
- package/src/server/__tests__/diagnostics.test.ts +823 -0
- package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
- package/src/server/__tests__/errorHandler.test.ts +416 -0
- package/src/server/__tests__/prompts.test.ts +354 -0
- package/src/server/__tests__/rateLimiter.test.ts +314 -0
- package/src/server/__tests__/requestLogger.test.ts +408 -0
- package/src/server/__tests__/resources.test.ts +299 -0
- package/src/server/__tests__/security.integration.test.ts +426 -0
- package/src/server/__tests__/securityMiddleware.test.ts +449 -0
- package/src/server/__tests__/server-startup.integration.test.ts +477 -0
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
- package/src/server/__tests__/toolRegistry.test.ts +855 -0
- package/src/server/budgetResolver.ts +235 -0
- package/src/server/cacheManager.ts +503 -0
- package/src/server/config.ts +41 -0
- package/src/server/deltaCache.merge.ts +149 -0
- package/src/server/deltaCache.ts +341 -0
- package/src/server/diagnostics.ts +338 -0
- package/src/server/errorHandler.ts +756 -0
- package/src/server/prompts.ts +291 -0
- package/src/server/rateLimiter.ts +156 -0
- package/src/server/requestLogger.ts +344 -0
- package/src/server/resources.ts +168 -0
- package/src/server/responseFormatter.ts +51 -0
- package/src/server/securityMiddleware.ts +236 -0
- package/src/server/serverKnowledgeStore.ts +91 -0
- package/src/server/toolRegistry.ts +489 -0
- package/src/tools/.gitkeep +1 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
- package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
- package/src/tools/__tests__/accountTools.test.ts +653 -0
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
- package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
- package/src/tools/__tests__/budgetTools.test.ts +423 -0
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
- package/src/tools/__tests__/categoryTools.test.ts +622 -0
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
- package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
- package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
- package/src/tools/__tests__/compareTransactions.test.ts +342 -0
- package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
- package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
- package/src/tools/__tests__/deltaSupport.test.ts +188 -0
- package/src/tools/__tests__/deltaTestUtils.ts +46 -0
- package/src/tools/__tests__/exportTransactions.test.ts +213 -0
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
- package/src/tools/__tests__/monthTools.test.ts +523 -0
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
- package/src/tools/__tests__/payeeTools.test.ts +445 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
- package/src/tools/__tests__/transactionTools.test.ts +3521 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
- package/src/tools/__tests__/utilityTools.test.ts +205 -0
- package/src/tools/accountTools.ts +283 -0
- package/src/tools/budgetTools.ts +112 -0
- package/src/tools/categoryTools.ts +366 -0
- package/src/tools/compareTransactions/formatter.ts +163 -0
- package/src/tools/compareTransactions/index.ts +228 -0
- package/src/tools/compareTransactions/matcher.ts +240 -0
- package/src/tools/compareTransactions/parser.ts +557 -0
- package/src/tools/compareTransactions/types.ts +60 -0
- package/src/tools/compareTransactions.ts +3 -0
- package/src/tools/deltaFetcher.ts +278 -0
- package/src/tools/deltaSupport.ts +293 -0
- package/src/tools/exportTransactions.ts +273 -0
- package/src/tools/monthTools.ts +164 -0
- package/src/tools/payeeTools.ts +140 -0
- package/src/tools/reconcileAdapter.ts +312 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
- package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
- package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
- package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
- package/src/tools/reconciliation/analyzer.ts +824 -0
- package/src/tools/reconciliation/executor.ts +880 -0
- package/src/tools/reconciliation/index.ts +400 -0
- package/src/tools/reconciliation/matcher.ts +269 -0
- package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
- package/src/tools/reconciliation/recommendationEngine.ts +506 -0
- package/src/tools/reconciliation/reportFormatter.ts +363 -0
- package/src/tools/reconciliation/types.ts +314 -0
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
- package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
- package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
- package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
- package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
- package/src/tools/schemas/outputs/index.ts +270 -0
- package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
- package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
- package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
- package/src/tools/schemas/shared/commonOutputs.ts +140 -0
- package/src/tools/toolCategories.ts +140 -0
- package/src/tools/transactionTools.ts +2509 -0
- package/src/tools/utilityTools.ts +90 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/__tests__/index.test.ts +52 -0
- package/src/types/index.ts +67 -0
- package/src/types/integration-tests.d.ts +35 -0
- package/src/types/toolAnnotations.ts +44 -0
- package/src/utils/__tests__/dateUtils.test.ts +170 -0
- package/src/utils/__tests__/money.test.ts +189 -0
- package/src/utils/amountUtils.ts +32 -0
- package/src/utils/dateUtils.ts +108 -0
- package/src/utils/money.ts +123 -0
- package/test-csv-sample.csv +28 -0
- package/test-exports/sample_bank_statement.csv +7 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
- package/test-reconcile-autodetect.js +40 -0
- package/test-reconcile-tool.js +152 -0
- package/test-reconcile-with-csv.cjs +89 -0
- package/test-statement.csv +8 -0
- package/test_debug.js +47 -0
- package/test_simple.mjs +16 -0
- package/tsconfig.json +31 -0
- package/tsconfig.prod.json +18 -0
- package/vitest-reporters/split-json-reporter.ts +211 -0
- package/vitest.config.ts +96 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
export declare class BudgetResolver {
|
|
3
|
+
private static readonly UUID_REGEX;
|
|
4
|
+
private static readonly ALLOWED_KEYWORDS;
|
|
5
|
+
static resolveBudgetId(providedId?: string, defaultId?: string): string | CallToolResult;
|
|
6
|
+
static validateBudgetId(budgetId: string): string | CallToolResult;
|
|
7
|
+
static createMissingBudgetError(): CallToolResult;
|
|
8
|
+
static createInvalidBudgetError(details: string): CallToolResult;
|
|
9
|
+
static resolveBudgetIdOrThrow(providedId?: string, defaultId?: string): string;
|
|
10
|
+
static validateBudgetIdOrThrow(budgetId: string): string;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveBudgetId(providedId?: string, defaultId?: string): string | CallToolResult;
|
|
13
|
+
export declare function validateBudgetId(budgetId: string): string | CallToolResult;
|
|
14
|
+
export declare function createMissingBudgetError(): CallToolResult;
|
|
15
|
+
export declare function createInvalidBudgetError(details: string): CallToolResult;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ErrorHandler } from './errorHandler.js';
|
|
2
|
+
export class BudgetResolver {
|
|
3
|
+
static resolveBudgetId(providedId, defaultId) {
|
|
4
|
+
if (providedId !== undefined && providedId !== null) {
|
|
5
|
+
const trimmed = providedId.trim();
|
|
6
|
+
if (trimmed === 'default') {
|
|
7
|
+
if (defaultId) {
|
|
8
|
+
return this.validateBudgetId(defaultId);
|
|
9
|
+
}
|
|
10
|
+
return this.createMissingBudgetError();
|
|
11
|
+
}
|
|
12
|
+
if (trimmed === 'last-used') {
|
|
13
|
+
return ErrorHandler.createValidationError('Unsupported keyword', 'The "last-used" keyword is not supported yet. Please use a specific budget ID or set a default budget.', [
|
|
14
|
+
'Use a specific budget ID (UUID format)',
|
|
15
|
+
'Set a default budget using the set_default_budget tool',
|
|
16
|
+
'Use the "default" keyword after setting a default budget',
|
|
17
|
+
'Run the list_budgets tool to see available budget IDs',
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
return this.validateBudgetId(providedId);
|
|
21
|
+
}
|
|
22
|
+
if (defaultId) {
|
|
23
|
+
return this.validateBudgetId(defaultId);
|
|
24
|
+
}
|
|
25
|
+
return this.createMissingBudgetError();
|
|
26
|
+
}
|
|
27
|
+
static validateBudgetId(budgetId) {
|
|
28
|
+
if (!budgetId || typeof budgetId !== 'string') {
|
|
29
|
+
return this.createInvalidBudgetError('Budget ID must be provided as a non-empty string');
|
|
30
|
+
}
|
|
31
|
+
const trimmed = budgetId.trim();
|
|
32
|
+
if (!trimmed) {
|
|
33
|
+
return this.createInvalidBudgetError('Budget ID cannot be empty or whitespace only');
|
|
34
|
+
}
|
|
35
|
+
if (process.env['NODE_ENV'] === 'test') {
|
|
36
|
+
const testIdentifierPattern = /^(test|budget|account|category|transaction|payee|mock)-[a-z0-9_-]+$/i;
|
|
37
|
+
if (testIdentifierPattern.test(trimmed)) {
|
|
38
|
+
return trimmed;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!this.UUID_REGEX.test(trimmed)) {
|
|
42
|
+
return this.createInvalidBudgetError(`Invalid budget ID format: '${trimmed}'. Must be a valid UUID format (versions 1-5)`);
|
|
43
|
+
}
|
|
44
|
+
return trimmed;
|
|
45
|
+
}
|
|
46
|
+
static createMissingBudgetError() {
|
|
47
|
+
const detailMessage = `A budget ID is required for this operation. You can either:
|
|
48
|
+
1. Provide a specific budget_id parameter
|
|
49
|
+
2. Set a default budget using the set_default_budget tool first`;
|
|
50
|
+
return ErrorHandler.createValidationError('No budget ID provided and no default budget set', detailMessage, [
|
|
51
|
+
'Set a default budget first using the set_default_budget tool',
|
|
52
|
+
'Provide a budget_id parameter when invoking the tool',
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
static createInvalidBudgetError(details) {
|
|
56
|
+
const detailMessage = `${details}
|
|
57
|
+
|
|
58
|
+
Valid formats:
|
|
59
|
+
- UUID format (versions 1-5, e.g., "123e4567-e89b-12d3-a456-426614174000")
|
|
60
|
+
- Special keywords: ${this.ALLOWED_KEYWORDS.map((k) => `"${k}"`).join(', ')}
|
|
61
|
+
|
|
62
|
+
You can use the list_budgets tool to see available budget IDs.`;
|
|
63
|
+
return ErrorHandler.createValidationError('Invalid budget ID format', detailMessage, [
|
|
64
|
+
'Use a valid UUID format (UUID v1-v5, e.g., 123e4567-e89b-12d3-a456-426614174000; standard UUID v4 format works as well)',
|
|
65
|
+
'Run the list_budgets tool to view available budget IDs',
|
|
66
|
+
'Use the special keyword "default" for convenience',
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
static resolveBudgetIdOrThrow(providedId, defaultId) {
|
|
70
|
+
const result = this.resolveBudgetId(providedId, defaultId);
|
|
71
|
+
if (typeof result === 'string') {
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
const errorText = result.content?.[0]?.type === 'text' ? result.content[0].text : 'Budget resolution failed';
|
|
75
|
+
throw new Error(errorText);
|
|
76
|
+
}
|
|
77
|
+
static validateBudgetIdOrThrow(budgetId) {
|
|
78
|
+
const result = this.validateBudgetId(budgetId);
|
|
79
|
+
if (typeof result === 'string') {
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
const errorText = result.content?.[0]?.type === 'text' ? result.content[0].text : 'Budget validation failed';
|
|
83
|
+
throw new Error(errorText);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
BudgetResolver.UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
87
|
+
BudgetResolver.ALLOWED_KEYWORDS = ['default'];
|
|
88
|
+
export function resolveBudgetId(providedId, defaultId) {
|
|
89
|
+
return BudgetResolver.resolveBudgetId(providedId, defaultId);
|
|
90
|
+
}
|
|
91
|
+
export function validateBudgetId(budgetId) {
|
|
92
|
+
return BudgetResolver.validateBudgetId(budgetId);
|
|
93
|
+
}
|
|
94
|
+
export function createMissingBudgetError() {
|
|
95
|
+
return BudgetResolver.createMissingBudgetError();
|
|
96
|
+
}
|
|
97
|
+
export function createInvalidBudgetError(details) {
|
|
98
|
+
return BudgetResolver.createInvalidBudgetError(details);
|
|
99
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
interface CacheEntry<T> {
|
|
2
|
+
data: T;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
ttl: number;
|
|
5
|
+
staleWhileRevalidate?: number;
|
|
6
|
+
}
|
|
7
|
+
interface CacheSetOptions {
|
|
8
|
+
ttl?: number;
|
|
9
|
+
staleWhileRevalidate?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class CacheManager {
|
|
12
|
+
private cache;
|
|
13
|
+
private readonly defaultTTL;
|
|
14
|
+
private hits;
|
|
15
|
+
private misses;
|
|
16
|
+
private evictions;
|
|
17
|
+
private lastCleanup;
|
|
18
|
+
private maxEntries;
|
|
19
|
+
private defaultStaleWindow;
|
|
20
|
+
private pendingFetches;
|
|
21
|
+
private pendingRefresh;
|
|
22
|
+
constructor();
|
|
23
|
+
get<T>(key: string): T | null;
|
|
24
|
+
has(key: string): boolean;
|
|
25
|
+
set<T>(key: string, data: T, ttlOrOptions?: number | CacheSetOptions): void;
|
|
26
|
+
delete(key: string): boolean;
|
|
27
|
+
deleteMany(keys: Iterable<string>): void;
|
|
28
|
+
deleteByPrefix(prefix: string): number;
|
|
29
|
+
deleteByBudgetId(budgetId: string): number;
|
|
30
|
+
getKeys(): string[];
|
|
31
|
+
clear(): void;
|
|
32
|
+
getStats(): {
|
|
33
|
+
size: number;
|
|
34
|
+
keys: string[];
|
|
35
|
+
hits: number;
|
|
36
|
+
misses: number;
|
|
37
|
+
evictions: number;
|
|
38
|
+
lastCleanup: number | null;
|
|
39
|
+
maxEntries: number;
|
|
40
|
+
hitRate: number;
|
|
41
|
+
};
|
|
42
|
+
getEntriesForSizeEstimation(): [string, CacheEntry<unknown>][];
|
|
43
|
+
getCacheMetadata(): {
|
|
44
|
+
key: string;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
ttl: number;
|
|
47
|
+
staleWhileRevalidate?: number;
|
|
48
|
+
dataType: string;
|
|
49
|
+
isExpired: boolean;
|
|
50
|
+
}[];
|
|
51
|
+
cleanup(): number;
|
|
52
|
+
cleanupDetailed(): {
|
|
53
|
+
cleaned: number;
|
|
54
|
+
evictions: number;
|
|
55
|
+
};
|
|
56
|
+
wrap<T>(key: string, options: CacheSetOptions & {
|
|
57
|
+
loader: () => Promise<T>;
|
|
58
|
+
}): Promise<T>;
|
|
59
|
+
private evictIfNeeded;
|
|
60
|
+
private parseEnvInt;
|
|
61
|
+
static generateKey(prefix: string, ...params: (string | number | boolean | undefined)[]): string;
|
|
62
|
+
}
|
|
63
|
+
export declare const CACHE_TTLS: {
|
|
64
|
+
readonly BUDGETS: number;
|
|
65
|
+
readonly ACCOUNTS: number;
|
|
66
|
+
readonly CATEGORIES: number;
|
|
67
|
+
readonly PAYEES: number;
|
|
68
|
+
readonly TRANSACTIONS: number;
|
|
69
|
+
readonly SCHEDULED_TRANSACTIONS: number;
|
|
70
|
+
readonly USER_INFO: number;
|
|
71
|
+
readonly MONTHS: number;
|
|
72
|
+
};
|
|
73
|
+
export declare const cacheManager: CacheManager;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
export class CacheManager {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.cache = new Map();
|
|
4
|
+
this.hits = 0;
|
|
5
|
+
this.misses = 0;
|
|
6
|
+
this.evictions = 0;
|
|
7
|
+
this.lastCleanup = null;
|
|
8
|
+
this.pendingFetches = new Map();
|
|
9
|
+
this.pendingRefresh = new Set();
|
|
10
|
+
this.maxEntries = this.parseEnvInt('YNAB_MCP_CACHE_MAX_ENTRIES', 1000);
|
|
11
|
+
this.defaultStaleWindow = this.parseEnvInt('YNAB_MCP_CACHE_STALE_MS', 2 * 60 * 1000);
|
|
12
|
+
this.defaultTTL = this.parseEnvInt('YNAB_MCP_CACHE_DEFAULT_TTL_MS', 300000);
|
|
13
|
+
}
|
|
14
|
+
get(key) {
|
|
15
|
+
const entry = this.cache.get(key);
|
|
16
|
+
if (!entry) {
|
|
17
|
+
this.misses++;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const age = now - entry.timestamp;
|
|
22
|
+
if (age > entry.ttl) {
|
|
23
|
+
const staleWindow = entry.staleWhileRevalidate || 0;
|
|
24
|
+
if (staleWindow > 0 && age <= entry.ttl + staleWindow) {
|
|
25
|
+
this.hits++;
|
|
26
|
+
this.cache.delete(key);
|
|
27
|
+
this.cache.set(key, entry);
|
|
28
|
+
this.pendingRefresh.add(key);
|
|
29
|
+
return entry.data;
|
|
30
|
+
}
|
|
31
|
+
this.cache.delete(key);
|
|
32
|
+
this.pendingFetches.delete(key);
|
|
33
|
+
this.pendingRefresh.delete(key);
|
|
34
|
+
this.misses++;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
this.hits++;
|
|
38
|
+
this.cache.delete(key);
|
|
39
|
+
this.cache.set(key, entry);
|
|
40
|
+
return entry.data;
|
|
41
|
+
}
|
|
42
|
+
has(key) {
|
|
43
|
+
const entry = this.cache.get(key);
|
|
44
|
+
if (!entry) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const age = now - entry.timestamp;
|
|
49
|
+
if (age > entry.ttl) {
|
|
50
|
+
const staleWindow = entry.staleWhileRevalidate || 0;
|
|
51
|
+
if (staleWindow > 0 && age <= entry.ttl + staleWindow) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
this.cache.delete(key);
|
|
55
|
+
this.pendingFetches.delete(key);
|
|
56
|
+
this.pendingRefresh.delete(key);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
set(key, data, ttlOrOptions) {
|
|
62
|
+
if (this.maxEntries <= 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const isUpdate = this.cache.has(key);
|
|
66
|
+
if (!isUpdate) {
|
|
67
|
+
this.evictIfNeeded();
|
|
68
|
+
}
|
|
69
|
+
let ttl;
|
|
70
|
+
let staleWhileRevalidate;
|
|
71
|
+
if (typeof ttlOrOptions === 'number') {
|
|
72
|
+
ttl = Number.isFinite(ttlOrOptions) ? ttlOrOptions : this.defaultTTL;
|
|
73
|
+
staleWhileRevalidate = undefined;
|
|
74
|
+
}
|
|
75
|
+
else if (ttlOrOptions === undefined) {
|
|
76
|
+
ttl = this.defaultTTL;
|
|
77
|
+
staleWhileRevalidate = undefined;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const providedTtl = ttlOrOptions?.ttl;
|
|
81
|
+
ttl = providedTtl !== undefined ? providedTtl : this.defaultTTL;
|
|
82
|
+
if (ttlOrOptions && 'staleWhileRevalidate' in ttlOrOptions) {
|
|
83
|
+
staleWhileRevalidate = ttlOrOptions.staleWhileRevalidate;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
staleWhileRevalidate = ttlOrOptions?.staleWhileRevalidate;
|
|
87
|
+
}
|
|
88
|
+
if (staleWhileRevalidate === undefined && this.defaultStaleWindow > 0) {
|
|
89
|
+
staleWhileRevalidate = this.defaultStaleWindow;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const entry = {
|
|
93
|
+
data,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
ttl,
|
|
96
|
+
};
|
|
97
|
+
if (staleWhileRevalidate !== undefined) {
|
|
98
|
+
entry.staleWhileRevalidate = staleWhileRevalidate;
|
|
99
|
+
}
|
|
100
|
+
if (isUpdate) {
|
|
101
|
+
this.cache.delete(key);
|
|
102
|
+
}
|
|
103
|
+
this.cache.set(key, entry);
|
|
104
|
+
this.pendingFetches.delete(key);
|
|
105
|
+
this.pendingRefresh.delete(key);
|
|
106
|
+
}
|
|
107
|
+
delete(key) {
|
|
108
|
+
const deleted = this.cache.delete(key);
|
|
109
|
+
if (deleted) {
|
|
110
|
+
this.pendingFetches.delete(key);
|
|
111
|
+
this.pendingRefresh.delete(key);
|
|
112
|
+
}
|
|
113
|
+
return deleted;
|
|
114
|
+
}
|
|
115
|
+
deleteMany(keys) {
|
|
116
|
+
for (const key of keys) {
|
|
117
|
+
this.cache.delete(key);
|
|
118
|
+
this.pendingFetches.delete(key);
|
|
119
|
+
this.pendingRefresh.delete(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
deleteByPrefix(prefix) {
|
|
123
|
+
if (!prefix) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const normalizedPrefix = prefix.endsWith(':') ? prefix.slice(0, -1) : prefix;
|
|
127
|
+
const prefixWithColon = `${normalizedPrefix}:`;
|
|
128
|
+
let removed = 0;
|
|
129
|
+
for (const key of this.cache.keys()) {
|
|
130
|
+
if (key === normalizedPrefix || key.startsWith(prefixWithColon)) {
|
|
131
|
+
this.cache.delete(key);
|
|
132
|
+
this.pendingFetches.delete(key);
|
|
133
|
+
this.pendingRefresh.delete(key);
|
|
134
|
+
removed++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return removed;
|
|
138
|
+
}
|
|
139
|
+
deleteByBudgetId(budgetId) {
|
|
140
|
+
if (!budgetId) {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
let removed = 0;
|
|
144
|
+
for (const key of this.cache.keys()) {
|
|
145
|
+
const segments = key.split(':');
|
|
146
|
+
if (segments.some((segment) => segment === budgetId)) {
|
|
147
|
+
this.cache.delete(key);
|
|
148
|
+
this.pendingFetches.delete(key);
|
|
149
|
+
this.pendingRefresh.delete(key);
|
|
150
|
+
removed++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return removed;
|
|
154
|
+
}
|
|
155
|
+
getKeys() {
|
|
156
|
+
return Array.from(this.cache.keys());
|
|
157
|
+
}
|
|
158
|
+
clear() {
|
|
159
|
+
this.cache.clear();
|
|
160
|
+
this.hits = 0;
|
|
161
|
+
this.misses = 0;
|
|
162
|
+
this.evictions = 0;
|
|
163
|
+
this.lastCleanup = null;
|
|
164
|
+
this.pendingFetches.clear();
|
|
165
|
+
this.pendingRefresh.clear();
|
|
166
|
+
}
|
|
167
|
+
getStats() {
|
|
168
|
+
const totalRequests = this.hits + this.misses;
|
|
169
|
+
return {
|
|
170
|
+
size: this.cache.size,
|
|
171
|
+
keys: Array.from(this.cache.keys()),
|
|
172
|
+
hits: this.hits,
|
|
173
|
+
misses: this.misses,
|
|
174
|
+
evictions: this.evictions,
|
|
175
|
+
lastCleanup: this.lastCleanup,
|
|
176
|
+
maxEntries: this.maxEntries,
|
|
177
|
+
hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
getEntriesForSizeEstimation() {
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
return Array.from(this.cache.entries()).filter(([, entry]) => now - entry.timestamp <= entry.ttl);
|
|
183
|
+
}
|
|
184
|
+
getCacheMetadata() {
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
return Array.from(this.cache.entries()).map(([key, entry]) => {
|
|
187
|
+
const metadata = {
|
|
188
|
+
key,
|
|
189
|
+
timestamp: entry.timestamp,
|
|
190
|
+
ttl: entry.ttl,
|
|
191
|
+
dataType: typeof entry.data,
|
|
192
|
+
isExpired: now - entry.timestamp > entry.ttl,
|
|
193
|
+
};
|
|
194
|
+
if (entry.staleWhileRevalidate !== undefined) {
|
|
195
|
+
metadata.staleWhileRevalidate = entry.staleWhileRevalidate;
|
|
196
|
+
}
|
|
197
|
+
return metadata;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
cleanup() {
|
|
201
|
+
const result = this.cleanupDetailed();
|
|
202
|
+
return result.cleaned;
|
|
203
|
+
}
|
|
204
|
+
cleanupDetailed() {
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
let cleaned = 0;
|
|
207
|
+
const initialEvictions = this.evictions;
|
|
208
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
209
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
210
|
+
this.cache.delete(key);
|
|
211
|
+
this.pendingFetches.delete(key);
|
|
212
|
+
this.pendingRefresh.delete(key);
|
|
213
|
+
cleaned++;
|
|
214
|
+
this.evictions++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.lastCleanup = now;
|
|
218
|
+
return { cleaned, evictions: this.evictions - initialEvictions };
|
|
219
|
+
}
|
|
220
|
+
async wrap(key, options) {
|
|
221
|
+
const existingEntry = this.cache.get(key);
|
|
222
|
+
const cached = this.get(key);
|
|
223
|
+
if (cached !== null) {
|
|
224
|
+
if (this.pendingRefresh.has(key) && !this.pendingFetches.has(key)) {
|
|
225
|
+
const refreshPromise = options.loader().then((result) => {
|
|
226
|
+
const refreshOptions = {};
|
|
227
|
+
const ttl = options.ttl ?? existingEntry?.ttl;
|
|
228
|
+
if (ttl !== undefined) {
|
|
229
|
+
refreshOptions.ttl = ttl;
|
|
230
|
+
}
|
|
231
|
+
const staleWhileRevalidate = options.staleWhileRevalidate ?? existingEntry?.staleWhileRevalidate;
|
|
232
|
+
if (staleWhileRevalidate !== undefined) {
|
|
233
|
+
refreshOptions.staleWhileRevalidate = staleWhileRevalidate;
|
|
234
|
+
}
|
|
235
|
+
this.set(key, result, refreshOptions);
|
|
236
|
+
this.pendingFetches.delete(key);
|
|
237
|
+
this.pendingRefresh.delete(key);
|
|
238
|
+
return result;
|
|
239
|
+
}, (error) => {
|
|
240
|
+
this.pendingFetches.delete(key);
|
|
241
|
+
this.pendingRefresh.delete(key);
|
|
242
|
+
throw error;
|
|
243
|
+
});
|
|
244
|
+
this.pendingFetches.set(key, refreshPromise);
|
|
245
|
+
}
|
|
246
|
+
return cached;
|
|
247
|
+
}
|
|
248
|
+
const existingFetch = this.pendingFetches.get(key);
|
|
249
|
+
if (existingFetch) {
|
|
250
|
+
return existingFetch;
|
|
251
|
+
}
|
|
252
|
+
const fetchPromise = options.loader().then((result) => {
|
|
253
|
+
this.set(key, result, options);
|
|
254
|
+
this.pendingFetches.delete(key);
|
|
255
|
+
this.pendingRefresh.delete(key);
|
|
256
|
+
return result;
|
|
257
|
+
}, (error) => {
|
|
258
|
+
this.pendingFetches.delete(key);
|
|
259
|
+
this.pendingRefresh.delete(key);
|
|
260
|
+
throw error;
|
|
261
|
+
});
|
|
262
|
+
this.pendingFetches.set(key, fetchPromise);
|
|
263
|
+
return fetchPromise;
|
|
264
|
+
}
|
|
265
|
+
evictIfNeeded() {
|
|
266
|
+
if (this.maxEntries <= 0)
|
|
267
|
+
return;
|
|
268
|
+
while (this.cache.size >= this.maxEntries) {
|
|
269
|
+
const firstKey = this.cache.keys().next().value;
|
|
270
|
+
if (firstKey) {
|
|
271
|
+
this.cache.delete(firstKey);
|
|
272
|
+
this.pendingFetches.delete(firstKey);
|
|
273
|
+
this.pendingRefresh.delete(firstKey);
|
|
274
|
+
this.evictions++;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
parseEnvInt(key, defaultValue) {
|
|
282
|
+
const value = process.env[key];
|
|
283
|
+
if (!value)
|
|
284
|
+
return defaultValue;
|
|
285
|
+
const parsed = parseInt(value, 10);
|
|
286
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
287
|
+
}
|
|
288
|
+
static generateKey(prefix, ...params) {
|
|
289
|
+
const cleanParams = params
|
|
290
|
+
.filter((p) => p !== undefined)
|
|
291
|
+
.map((p) => String(p))
|
|
292
|
+
.join(':');
|
|
293
|
+
return `${prefix}:${cleanParams}`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
export const CACHE_TTLS = {
|
|
297
|
+
BUDGETS: 10 * 60 * 1000,
|
|
298
|
+
ACCOUNTS: 5 * 60 * 1000,
|
|
299
|
+
CATEGORIES: 5 * 60 * 1000,
|
|
300
|
+
PAYEES: 10 * 60 * 1000,
|
|
301
|
+
TRANSACTIONS: 2 * 60 * 1000,
|
|
302
|
+
SCHEDULED_TRANSACTIONS: 5 * 60 * 1000,
|
|
303
|
+
USER_INFO: 30 * 60 * 1000,
|
|
304
|
+
MONTHS: 5 * 60 * 1000,
|
|
305
|
+
};
|
|
306
|
+
export const cacheManager = new CacheManager();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConfigurationError } from '../types/index.js';
|
|
2
|
+
export function validateEnvironment() {
|
|
3
|
+
const accessToken = process.env['YNAB_ACCESS_TOKEN'];
|
|
4
|
+
const defaultBudgetId = process.env['YNAB_DEFAULT_BUDGET_ID'];
|
|
5
|
+
if (accessToken === undefined) {
|
|
6
|
+
throw new ConfigurationError('YNAB_ACCESS_TOKEN environment variable is required but not set');
|
|
7
|
+
}
|
|
8
|
+
if (typeof accessToken !== 'string' || accessToken.trim().length === 0) {
|
|
9
|
+
throw new ConfigurationError('YNAB_ACCESS_TOKEN must be a non-empty string');
|
|
10
|
+
}
|
|
11
|
+
const trimmedDefaultBudgetId = defaultBudgetId?.trim();
|
|
12
|
+
const config = {
|
|
13
|
+
accessToken: accessToken.trim(),
|
|
14
|
+
};
|
|
15
|
+
if (trimmedDefaultBudgetId && trimmedDefaultBudgetId.length > 0) {
|
|
16
|
+
config.defaultBudgetId = trimmedDefaultBudgetId;
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CacheManager } from './cacheManager.js';
|
|
2
|
+
import { ServerKnowledgeStore } from './serverKnowledgeStore.js';
|
|
3
|
+
export interface Logger {
|
|
4
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
5
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
6
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
7
|
+
}
|
|
8
|
+
export interface MergeOptions {
|
|
9
|
+
preserveDeleted?: boolean;
|
|
10
|
+
equalityFn?: (a: unknown, b: unknown) => boolean;
|
|
11
|
+
}
|
|
12
|
+
export type MergeFn<T> = (snapshot: T[], delta: T[], options?: MergeOptions) => T[];
|
|
13
|
+
export interface DeltaFetchResult<T> {
|
|
14
|
+
data: T[];
|
|
15
|
+
wasCached: boolean;
|
|
16
|
+
usedDelta: boolean;
|
|
17
|
+
serverKnowledge: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DeltaCacheEntry<T> {
|
|
20
|
+
snapshot: T[];
|
|
21
|
+
serverKnowledge: number;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
ttl: number;
|
|
24
|
+
staleWhileRevalidate?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface DeltaCacheStats {
|
|
27
|
+
deltaHits: number;
|
|
28
|
+
deltaMisses: number;
|
|
29
|
+
mergeOperations: number;
|
|
30
|
+
knowledgeGapEvents: number;
|
|
31
|
+
}
|
|
32
|
+
type DeltaFetcher<T> = (lastKnowledge?: number) => Promise<{
|
|
33
|
+
data: T[];
|
|
34
|
+
serverKnowledge: number;
|
|
35
|
+
}>;
|
|
36
|
+
export declare class DeltaCache {
|
|
37
|
+
private readonly cacheManager;
|
|
38
|
+
private readonly knowledgeStore;
|
|
39
|
+
private readonly logger;
|
|
40
|
+
private deltaHits;
|
|
41
|
+
private deltaMisses;
|
|
42
|
+
private mergeOperations;
|
|
43
|
+
private knowledgeGapEvents;
|
|
44
|
+
constructor(cacheManager: CacheManager, knowledgeStore: ServerKnowledgeStore, logger?: Logger);
|
|
45
|
+
fetchWithDelta<T extends {
|
|
46
|
+
deleted?: boolean;
|
|
47
|
+
}>(cacheKey: string, budgetId: string, fetcher: DeltaFetcher<T>, merger: MergeFn<T>, options: {
|
|
48
|
+
ttl: number;
|
|
49
|
+
forceFullRefresh?: boolean;
|
|
50
|
+
mergeOptions?: MergeOptions;
|
|
51
|
+
staleWhileRevalidate?: number;
|
|
52
|
+
}): Promise<DeltaFetchResult<T>>;
|
|
53
|
+
getStats(): DeltaCacheStats;
|
|
54
|
+
invalidate(budgetId: string, resourceType?: string): void;
|
|
55
|
+
forceFullRefresh(budgetId?: string, resourceType?: string): void;
|
|
56
|
+
private fetchWithoutDelta;
|
|
57
|
+
private filterDeleted;
|
|
58
|
+
private assertFiniteTtl;
|
|
59
|
+
private isDeltaEnabled;
|
|
60
|
+
}
|
|
61
|
+
export {};
|