github-to-mcp-monorepo 1.0.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/.env.example +8 -0
- package/.github/CODEOWNERS +6 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +4 -0
- package/ARCHITECTURE.md +1429 -0
- package/CHANGELOG.md +167 -0
- package/CONTRIBUTING.md +327 -0
- package/LICENSE +201 -0
- package/README.md +1028 -0
- package/SECURITY.md +248 -0
- package/VISUAL_GUIDE.md +437 -0
- package/apps/vscode/IMPLEMENTATION.md +480 -0
- package/apps/vscode/README.md +248 -0
- package/apps/vscode/package.json +381 -0
- package/apps/vscode/resources/icon.png +0 -0
- package/apps/vscode/resources/icon.svg +5 -0
- package/apps/vscode/src/commands/browseRegistry.ts +211 -0
- package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
- package/apps/vscode/src/commands/convert.ts +82 -0
- package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
- package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
- package/apps/vscode/src/commands/index.ts +121 -0
- package/apps/vscode/src/commands/validate.ts +197 -0
- package/apps/vscode/src/extension.ts +464 -0
- package/apps/vscode/src/global.d.ts +36 -0
- package/apps/vscode/src/test/extension.test.ts +73 -0
- package/apps/vscode/src/utils/file-generator.ts +529 -0
- package/apps/vscode/src/utils/github-api.ts +335 -0
- package/apps/vscode/src/utils/index.ts +29 -0
- package/apps/vscode/src/utils/mcp-config.ts +334 -0
- package/apps/vscode/src/utils/storage.ts +87 -0
- package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
- package/apps/vscode/src/views/OutputChannelView.ts +195 -0
- package/apps/vscode/src/views/StatusBarItem.ts +251 -0
- package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
- package/apps/vscode/src/views/historyProvider.ts +75 -0
- package/apps/vscode/src/views/index.ts +12 -0
- package/apps/vscode/src/views/resultsPanel.ts +330 -0
- package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
- package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
- package/apps/vscode/src/webviews/index.ts +9 -0
- package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
- package/apps/vscode/tsconfig.json +20 -0
- package/apps/web/PLAYGROUND_GUIDE.md +499 -0
- package/apps/web/README.md +505 -0
- package/apps/web/app/api/convert/route.ts +100 -0
- package/apps/web/app/api/convert/stream/route.ts +198 -0
- package/apps/web/app/api/deploy/route.ts +157 -0
- package/apps/web/app/api/edge/route.ts +308 -0
- package/apps/web/app/api/export-docker/route.ts +284 -0
- package/apps/web/app/api/generate-openapi/route.ts +119 -0
- package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
- package/apps/web/app/api/playground/connect/route.ts +143 -0
- package/apps/web/app/api/playground/disconnect/route.ts +78 -0
- package/apps/web/app/api/playground/execute/route.ts +135 -0
- package/apps/web/app/api/playground/sessions/route.ts +103 -0
- package/apps/web/app/api/playground/tools/route.ts +117 -0
- package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
- package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
- package/apps/web/app/api/playground/v2/health/route.ts +80 -0
- package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
- package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
- package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
- package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
- package/apps/web/app/api/stream/route.ts +232 -0
- package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
- package/apps/web/app/batch/page.tsx +37 -0
- package/apps/web/app/convert/page.tsx +269 -0
- package/apps/web/app/dashboard/page.tsx +380 -0
- package/apps/web/app/globals.css +622 -0
- package/apps/web/app/layout.tsx +120 -0
- package/apps/web/app/manifest.ts +31 -0
- package/apps/web/app/opengraph-image.tsx +112 -0
- package/apps/web/app/page.old.tsx +924 -0
- package/apps/web/app/page.tsx +77 -0
- package/apps/web/app/playground/page.tsx +306 -0
- package/apps/web/app/playground/v2/error.tsx +163 -0
- package/apps/web/app/playground/v2/layout.tsx +58 -0
- package/apps/web/app/playground/v2/loading.tsx +152 -0
- package/apps/web/app/playground/v2/page.tsx +644 -0
- package/apps/web/app/playground/v2/providers.tsx +214 -0
- package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
- package/apps/web/app/playground/v2/use-url-state.ts +296 -0
- package/apps/web/app/providers.tsx +22 -0
- package/apps/web/app/sitemap.ts +32 -0
- package/apps/web/app/twitter-image.tsx +112 -0
- package/apps/web/components/BranchSelector.tsx +401 -0
- package/apps/web/components/ClaudeConfigExport.tsx +226 -0
- package/apps/web/components/Features.tsx +84 -0
- package/apps/web/components/Footer.tsx +119 -0
- package/apps/web/components/GenerationProgress.tsx +248 -0
- package/apps/web/components/GithubUrlInput.tsx +483 -0
- package/apps/web/components/Header.tsx +175 -0
- package/apps/web/components/Hero.tsx +117 -0
- package/apps/web/components/HowItWorks.tsx +119 -0
- package/apps/web/components/InstallBanner.tsx +158 -0
- package/apps/web/components/Logo.tsx +116 -0
- package/apps/web/components/ParticleBackground.tsx +105 -0
- package/apps/web/components/Playground.tsx +472 -0
- package/apps/web/components/PlaygroundToolTester.tsx +410 -0
- package/apps/web/components/ProductCards.tsx +179 -0
- package/apps/web/components/SplitView.tsx +194 -0
- package/apps/web/components/ToolFilter.tsx +260 -0
- package/apps/web/components/ToolList.tsx +325 -0
- package/apps/web/components/batch/BatchConvert.tsx +785 -0
- package/apps/web/components/batch/index.ts +7 -0
- package/apps/web/components/convert/ConfigTabs.tsx +230 -0
- package/apps/web/components/convert/ConversionResult.tsx +482 -0
- package/apps/web/components/convert/InlinePlayground.tsx +259 -0
- package/apps/web/components/convert/LoadingSteps.tsx +311 -0
- package/apps/web/components/convert/OneClickInstall.tsx +224 -0
- package/apps/web/components/convert/ToolCard.tsx +189 -0
- package/apps/web/components/convert/TryInPlayground.tsx +242 -0
- package/apps/web/components/convert/index.ts +12 -0
- package/apps/web/components/deploy/DeployButton.tsx +369 -0
- package/apps/web/components/deploy/index.ts +7 -0
- package/apps/web/components/docker/DockerExport.tsx +690 -0
- package/apps/web/components/docker/index.ts +7 -0
- package/apps/web/components/install/OneClickInstall.tsx +676 -0
- package/apps/web/components/install/index.ts +7 -0
- package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
- package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
- package/apps/web/components/playground/EmptyStates.tsx +305 -0
- package/apps/web/components/playground/ExecutionLog.tsx +260 -0
- package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
- package/apps/web/components/playground/JsonViewer.tsx +388 -0
- package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
- package/apps/web/components/playground/PromptsPanel.tsx +385 -0
- package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
- package/apps/web/components/playground/SchemaForm.tsx +477 -0
- package/apps/web/components/playground/ServerStatus.tsx +151 -0
- package/apps/web/components/playground/ShareButton.tsx +239 -0
- package/apps/web/components/playground/ToolsPanel.tsx +309 -0
- package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
- package/apps/web/components/playground/index.ts +74 -0
- package/apps/web/components/playground/types.ts +202 -0
- package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
- package/apps/web/components/streaming/index.ts +7 -0
- package/apps/web/components/ui/badge.tsx +42 -0
- package/apps/web/components/ui/button.tsx +88 -0
- package/apps/web/components/ui/card.tsx +75 -0
- package/apps/web/components/ui/code-block.tsx +122 -0
- package/apps/web/components/ui/index.ts +12 -0
- package/apps/web/components/ui/input.tsx +55 -0
- package/apps/web/components/ui/tabs.tsx +61 -0
- package/apps/web/hooks/index.ts +85 -0
- package/apps/web/hooks/types.ts +1173 -0
- package/apps/web/hooks/use-conversion.ts +133 -0
- package/apps/web/hooks/use-execution-history.ts +376 -0
- package/apps/web/hooks/use-generation-progress.ts +147 -0
- package/apps/web/hooks/use-local-storage.ts +88 -0
- package/apps/web/hooks/use-mcp-client.ts +623 -0
- package/apps/web/hooks/use-mcp-connection.ts +500 -0
- package/apps/web/hooks/use-mcp-execution.ts +282 -0
- package/apps/web/hooks/use-mcp-prompts.ts +441 -0
- package/apps/web/hooks/use-mcp-resources.ts +430 -0
- package/apps/web/hooks/use-mcp-tools.ts +540 -0
- package/apps/web/hooks/use-playground-store.ts +299 -0
- package/apps/web/hooks/use-playground.ts +184 -0
- package/apps/web/hooks/use-streaming-conversion.ts +227 -0
- package/apps/web/hooks/useBatchConversion.ts +271 -0
- package/apps/web/hooks/useDockerConfig.ts +161 -0
- package/apps/web/hooks/usePlatformDetection.ts +80 -0
- package/apps/web/hooks/useStreaming.ts +199 -0
- package/apps/web/lib/api/errors.ts +386 -0
- package/apps/web/lib/api/index.ts +137 -0
- package/apps/web/lib/api/logger.ts +187 -0
- package/apps/web/lib/api/middleware.ts +364 -0
- package/apps/web/lib/api/openapi.ts +977 -0
- package/apps/web/lib/api/session-manager.ts +594 -0
- package/apps/web/lib/api/types.ts +433 -0
- package/apps/web/lib/api/validation.ts +523 -0
- package/apps/web/lib/constants.ts +114 -0
- package/apps/web/lib/mcp/client.ts +1137 -0
- package/apps/web/lib/mcp/events.ts +651 -0
- package/apps/web/lib/mcp/index.ts +347 -0
- package/apps/web/lib/mcp/logger.ts +428 -0
- package/apps/web/lib/mcp/metrics.ts +703 -0
- package/apps/web/lib/mcp/retry.ts +616 -0
- package/apps/web/lib/mcp/session-manager.ts +779 -0
- package/apps/web/lib/mcp/transports.ts +988 -0
- package/apps/web/lib/mcp/types.ts +594 -0
- package/apps/web/lib/mcp-client-enhanced.ts +871 -0
- package/apps/web/lib/mcp-client.ts +778 -0
- package/apps/web/lib/mcp-errors.ts +489 -0
- package/apps/web/lib/mcp-sandbox.ts +593 -0
- package/apps/web/lib/mcp-testing.ts +428 -0
- package/apps/web/lib/mcp-types.ts +448 -0
- package/apps/web/lib/playground-store.tsx +1147 -0
- package/apps/web/lib/utils.ts +439 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +23 -0
- package/apps/web/package.json +55 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/.well-known/ai-plugin.json +17 -0
- package/apps/web/public/logo.svg +6 -0
- package/apps/web/public/robots.txt +22 -0
- package/apps/web/public/schema.json +27 -0
- package/apps/web/tailwind.config.js +26 -0
- package/apps/web/tailwind.config.ts +123 -0
- package/apps/web/tsconfig.json +20 -0
- package/apps/web/types/deploy.ts +139 -0
- package/apps/web/types/index.ts +247 -0
- package/apps/web/vercel.json +39 -0
- package/eslint.config.mjs +23 -0
- package/llms.txt +102 -0
- package/mkdocs/docs/api/core.md +318 -0
- package/mkdocs/docs/api/index.md +128 -0
- package/mkdocs/docs/api/mcp-server.md +301 -0
- package/mkdocs/docs/api/openapi-parser.md +254 -0
- package/mkdocs/docs/assets/logo.svg +7 -0
- package/mkdocs/docs/changelog.md +118 -0
- package/mkdocs/docs/cli/generate.md +148 -0
- package/mkdocs/docs/cli/index.md +52 -0
- package/mkdocs/docs/cli/inspect.md +164 -0
- package/mkdocs/docs/cli/serve.md +136 -0
- package/mkdocs/docs/concepts/classification.md +254 -0
- package/mkdocs/docs/concepts/how-it-works.md +299 -0
- package/mkdocs/docs/concepts/index.md +77 -0
- package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
- package/mkdocs/docs/concepts/tool-types.md +382 -0
- package/mkdocs/docs/contributing/architecture.md +262 -0
- package/mkdocs/docs/contributing/development.md +245 -0
- package/mkdocs/docs/contributing/index.md +73 -0
- package/mkdocs/docs/contributing/testing.md +320 -0
- package/mkdocs/docs/getting-started/configuration.md +235 -0
- package/mkdocs/docs/getting-started/index.md +54 -0
- package/mkdocs/docs/getting-started/installation.md +145 -0
- package/mkdocs/docs/getting-started/quickstart.md +160 -0
- package/mkdocs/docs/guides/batch.md +375 -0
- package/mkdocs/docs/guides/claude-desktop.md +227 -0
- package/mkdocs/docs/guides/cursor.md +188 -0
- package/mkdocs/docs/guides/custom-tools.md +367 -0
- package/mkdocs/docs/guides/index.md +78 -0
- package/mkdocs/docs/guides/private-repos.md +221 -0
- package/mkdocs/docs/guides/vscode.md +247 -0
- package/mkdocs/docs/index.md +175 -0
- package/mkdocs/docs/reference/config.md +223 -0
- package/mkdocs/docs/reference/env.md +192 -0
- package/mkdocs/docs/reference/index.md +102 -0
- package/mkdocs/docs/reference/tools.md +309 -0
- package/mkdocs/docs/stylesheets/extra.css +231 -0
- package/mkdocs/mkdocs.yml +204 -0
- package/mkdocs/overrides/.gitkeep +1 -0
- package/mkdocs/overrides/main.html +7 -0
- package/mkdocs/python-deps.txt +7 -0
- package/mkdocs/vercel.json +11 -0
- package/package.json +63 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
- package/packages/core/src/__tests__/cli.test.ts +235 -0
- package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
- package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
- package/packages/core/src/__tests__/github-client.test.ts +390 -0
- package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
- package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
- package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
- package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
- package/packages/core/src/__tests__/plugins.test.ts +467 -0
- package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
- package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
- package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
- package/packages/core/src/__tests__/streaming.test.ts +251 -0
- package/packages/core/src/additional-extractors.ts +333 -0
- package/packages/core/src/cache/cache-interface.ts +179 -0
- package/packages/core/src/cache/index.ts +210 -0
- package/packages/core/src/cache/redis-cache.ts +291 -0
- package/packages/core/src/cache/upstash-cache.ts +379 -0
- package/packages/core/src/cache.ts +251 -0
- package/packages/core/src/cli.ts +822 -0
- package/packages/core/src/code-extractor.ts +696 -0
- package/packages/core/src/docker-generator.ts +470 -0
- package/packages/core/src/edge-compatible.ts +491 -0
- package/packages/core/src/extractors/go-extractor.ts +791 -0
- package/packages/core/src/extractors/index.ts +9 -0
- package/packages/core/src/extractors/java-extractor.ts +937 -0
- package/packages/core/src/extractors/rust-extractor.ts +744 -0
- package/packages/core/src/github-client.ts +319 -0
- package/packages/core/src/go-generator.ts +356 -0
- package/packages/core/src/graphql-extractor.ts +358 -0
- package/packages/core/src/index.ts +797 -0
- package/packages/core/src/langchain-exporter.ts +617 -0
- package/packages/core/src/language-parsers.ts +1114 -0
- package/packages/core/src/mcp-introspector.ts +279 -0
- package/packages/core/src/monorepo-detector.ts +378 -0
- package/packages/core/src/plugins/index.ts +370 -0
- package/packages/core/src/plugins/registry.ts +404 -0
- package/packages/core/src/plugins/types.ts +215 -0
- package/packages/core/src/providers/base-provider.ts +246 -0
- package/packages/core/src/providers/bitbucket-client.ts +464 -0
- package/packages/core/src/providers/gitlab-client.ts +388 -0
- package/packages/core/src/providers/index.ts +176 -0
- package/packages/core/src/python-generator.ts +260 -0
- package/packages/core/src/queue/index.ts +100 -0
- package/packages/core/src/queue/memory-queue.ts +445 -0
- package/packages/core/src/queue/redis-queue.ts +578 -0
- package/packages/core/src/queue/types.ts +251 -0
- package/packages/core/src/readme-extractor.ts +409 -0
- package/packages/core/src/schema-generator.ts +638 -0
- package/packages/core/src/streaming.ts +999 -0
- package/packages/core/src/types.ts +289 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/core/tsup.config.ts +25 -0
- package/packages/mcp-server/README.md +297 -0
- package/packages/mcp-server/package.json +55 -0
- package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
- package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
- package/packages/mcp-server/src/index.ts +1206 -0
- package/packages/mcp-server/src/prompts/index.ts +601 -0
- package/packages/mcp-server/src/tools/export-docker.ts +362 -0
- package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
- package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
- package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
- package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
- package/packages/mcp-server/tsconfig.json +12 -0
- package/packages/mcp-server/tsup.config.ts +14 -0
- package/packages/openapi-parser/package-lock.json +3028 -0
- package/packages/openapi-parser/package.json +41 -0
- package/packages/openapi-parser/src/analyzer.ts +700 -0
- package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
- package/packages/openapi-parser/src/cli.ts +302 -0
- package/packages/openapi-parser/src/generator.ts +570 -0
- package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
- package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
- package/packages/openapi-parser/src/generators/index.ts +200 -0
- package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
- package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
- package/packages/openapi-parser/src/generators/types.ts +298 -0
- package/packages/openapi-parser/src/graphql-parser.ts +462 -0
- package/packages/openapi-parser/src/grpc-parser.ts +649 -0
- package/packages/openapi-parser/src/har-parser.ts +723 -0
- package/packages/openapi-parser/src/index.ts +635 -0
- package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
- package/packages/openapi-parser/src/parser.ts +231 -0
- package/packages/openapi-parser/src/postman-parser.ts +611 -0
- package/packages/openapi-parser/src/ref-resolver.ts +313 -0
- package/packages/openapi-parser/src/transformer.ts +459 -0
- package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
- package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
- package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
- package/packages/openapi-parser/tests/parsers.test.ts +847 -0
- package/packages/openapi-parser/tsconfig.json +9 -0
- package/packages/openapi-parser/tsup.config.ts +11 -0
- package/packages/registry/package.json +59 -0
- package/packages/registry/src/cli.ts +456 -0
- package/packages/registry/src/index.ts +44 -0
- package/packages/registry/src/popular/github.json +47 -0
- package/packages/registry/src/popular/index.ts +55 -0
- package/packages/registry/src/popular/linear.json +42 -0
- package/packages/registry/src/popular/notion.json +42 -0
- package/packages/registry/src/popular/openai.json +40 -0
- package/packages/registry/src/popular/resend.json +38 -0
- package/packages/registry/src/popular/slack.json +42 -0
- package/packages/registry/src/popular/stripe.json +163 -0
- package/packages/registry/src/popular/supabase.json +42 -0
- package/packages/registry/src/popular/twilio.json +40 -0
- package/packages/registry/src/popular/vercel.json +40 -0
- package/packages/registry/src/registry.ts +492 -0
- package/packages/registry/src/storage.ts +334 -0
- package/packages/registry/src/types.ts +275 -0
- package/packages/registry/src/updater.ts +208 -0
- package/packages/registry/tsconfig.json +10 -0
- package/packages/registry/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-docs.sh +16 -0
- package/server.json +9 -0
- package/templates/Dockerfile.python.template +60 -0
- package/templates/Dockerfile.typescript.template +60 -0
- package/templates/docker-compose.template.yml +68 -0
- package/tests/fixtures/express-app/index.js +34 -0
- package/tests/fixtures/express-app/routes/posts.js +43 -0
- package/tests/fixtures/express-app/routes/users.js +58 -0
- package/tests/fixtures/fastapi-app/main.py +125 -0
- package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
- package/tests/fixtures/graphql/simple-schema.graphql +65 -0
- package/tests/fixtures/mocks/github-api-responses.json +63 -0
- package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
- package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
- package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
- package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
- package/tests/fixtures/openapi/petstore.yaml +179 -0
- package/tests/integration/langchain-export.test.ts +405 -0
- package/tests/integration/openapi-conversion.test.ts +221 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cache interface for pluggable cache backends
|
|
3
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cache entry with metadata
|
|
9
|
+
*/
|
|
10
|
+
export interface CacheEntry<T = unknown> {
|
|
11
|
+
/** The cached data */
|
|
12
|
+
data: T;
|
|
13
|
+
/** Timestamp when the entry was created (ms since epoch) */
|
|
14
|
+
timestamp: number;
|
|
15
|
+
/** Time-to-live in seconds */
|
|
16
|
+
ttl: number;
|
|
17
|
+
/** Optional ETag for HTTP caching */
|
|
18
|
+
etag?: string;
|
|
19
|
+
/** Optional version for cache invalidation */
|
|
20
|
+
version?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cache statistics
|
|
25
|
+
*/
|
|
26
|
+
export interface CacheStats {
|
|
27
|
+
/** Number of cache hits */
|
|
28
|
+
hits: number;
|
|
29
|
+
/** Number of cache misses */
|
|
30
|
+
misses: number;
|
|
31
|
+
/** Number of entries in cache */
|
|
32
|
+
size: number;
|
|
33
|
+
/** Total bytes used (if available) */
|
|
34
|
+
bytes?: number;
|
|
35
|
+
/** Cache hit ratio */
|
|
36
|
+
hitRatio: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Cache configuration options
|
|
41
|
+
*/
|
|
42
|
+
export interface CacheConfig {
|
|
43
|
+
/** Default TTL in seconds */
|
|
44
|
+
defaultTTL?: number;
|
|
45
|
+
/** Maximum entries (for memory cache) */
|
|
46
|
+
maxEntries?: number;
|
|
47
|
+
/** Prefix for cache keys */
|
|
48
|
+
keyPrefix?: string;
|
|
49
|
+
/** Enable stale-while-revalidate */
|
|
50
|
+
staleWhileRevalidate?: boolean;
|
|
51
|
+
/** Stale tolerance (multiple of TTL) */
|
|
52
|
+
staleTolerance?: number;
|
|
53
|
+
/** Serializer for complex objects */
|
|
54
|
+
serializer?: CacheSerializer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Custom serializer interface
|
|
59
|
+
*/
|
|
60
|
+
export interface CacheSerializer {
|
|
61
|
+
serialize<T>(data: T): string;
|
|
62
|
+
deserialize<T>(data: string): T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default JSON serializer
|
|
67
|
+
*/
|
|
68
|
+
export const defaultSerializer: CacheSerializer = {
|
|
69
|
+
serialize: <T>(data: T) => JSON.stringify(data),
|
|
70
|
+
deserialize: <T>(data: string) => JSON.parse(data) as T
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Base cache interface that all cache implementations must follow
|
|
75
|
+
*/
|
|
76
|
+
export interface CacheInterface {
|
|
77
|
+
/**
|
|
78
|
+
* Get a cached value
|
|
79
|
+
* @param key - Cache key
|
|
80
|
+
* @returns The cached entry or null if not found/expired
|
|
81
|
+
*/
|
|
82
|
+
get<T>(key: string): Promise<CacheEntry<T> | null>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set a cached value
|
|
86
|
+
* @param key - Cache key
|
|
87
|
+
* @param entry - The entry to cache
|
|
88
|
+
*/
|
|
89
|
+
set<T>(key: string, entry: CacheEntry<T>): Promise<void>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Delete a cached value
|
|
93
|
+
* @param key - Cache key
|
|
94
|
+
*/
|
|
95
|
+
delete(key: string): Promise<void>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Clear all cached values
|
|
99
|
+
*/
|
|
100
|
+
clear(): Promise<void>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a key exists
|
|
104
|
+
* @param key - Cache key
|
|
105
|
+
*/
|
|
106
|
+
has(key: string): Promise<boolean>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get multiple values at once
|
|
110
|
+
* @param keys - Array of cache keys
|
|
111
|
+
*/
|
|
112
|
+
getMany<T>(keys: string[]): Promise<Map<string, CacheEntry<T> | null>>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set multiple values at once
|
|
116
|
+
* @param entries - Map of key to entry
|
|
117
|
+
*/
|
|
118
|
+
setMany<T>(entries: Map<string, CacheEntry<T>>): Promise<void>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get cache statistics
|
|
122
|
+
*/
|
|
123
|
+
getStats(): Promise<CacheStats>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Close the cache connection (for Redis, etc.)
|
|
127
|
+
*/
|
|
128
|
+
close?(): Promise<void>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Helper to check if a cache entry is fresh
|
|
133
|
+
*/
|
|
134
|
+
export function isFresh(entry: CacheEntry): boolean {
|
|
135
|
+
const age = Date.now() - entry.timestamp;
|
|
136
|
+
return age < entry.ttl * 1000;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Helper to check if a cache entry is stale but within tolerance
|
|
141
|
+
*/
|
|
142
|
+
export function isStale(entry: CacheEntry, tolerance: number = 2): boolean {
|
|
143
|
+
const age = Date.now() - entry.timestamp;
|
|
144
|
+
return age >= entry.ttl * 1000 && age < entry.ttl * tolerance * 1000;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Helper to create a cache entry
|
|
149
|
+
*/
|
|
150
|
+
export function createCacheEntry<T>(data: T, ttl: number, etag?: string): CacheEntry<T> {
|
|
151
|
+
return {
|
|
152
|
+
data,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
ttl,
|
|
155
|
+
etag
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Cache key generator helper
|
|
161
|
+
*/
|
|
162
|
+
export function generateCacheKey(parts: string[], prefix?: string): string {
|
|
163
|
+
const key = parts.filter(Boolean).join(':');
|
|
164
|
+
return prefix ? `${prefix}:${key}` : key;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Type guard to check if an object is a CacheInterface
|
|
169
|
+
*/
|
|
170
|
+
export function isCacheInterface(obj: unknown): obj is CacheInterface {
|
|
171
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
172
|
+
const cache = obj as CacheInterface;
|
|
173
|
+
return (
|
|
174
|
+
typeof cache.get === 'function' &&
|
|
175
|
+
typeof cache.set === 'function' &&
|
|
176
|
+
typeof cache.delete === 'function' &&
|
|
177
|
+
typeof cache.clear === 'function'
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cache module exports
|
|
3
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Export interfaces and types
|
|
8
|
+
export {
|
|
9
|
+
CacheInterface,
|
|
10
|
+
CacheEntry,
|
|
11
|
+
CacheStats,
|
|
12
|
+
CacheConfig,
|
|
13
|
+
CacheSerializer,
|
|
14
|
+
defaultSerializer,
|
|
15
|
+
isFresh,
|
|
16
|
+
isStale,
|
|
17
|
+
createCacheEntry,
|
|
18
|
+
generateCacheKey,
|
|
19
|
+
isCacheInterface
|
|
20
|
+
} from './cache-interface';
|
|
21
|
+
|
|
22
|
+
// Export Redis cache
|
|
23
|
+
export {
|
|
24
|
+
RedisCache,
|
|
25
|
+
RedisCacheConfig,
|
|
26
|
+
RedisClientInterface,
|
|
27
|
+
createRedisCache,
|
|
28
|
+
createRedisCacheFromUrl
|
|
29
|
+
} from './redis-cache';
|
|
30
|
+
|
|
31
|
+
// Export Upstash cache
|
|
32
|
+
export {
|
|
33
|
+
UpstashCache,
|
|
34
|
+
UpstashCacheConfig,
|
|
35
|
+
createUpstashCache,
|
|
36
|
+
createUpstashCacheFromEnv
|
|
37
|
+
} from './upstash-cache';
|
|
38
|
+
|
|
39
|
+
// Re-export memory cache from parent
|
|
40
|
+
import { MemoryCacheAdapter, GitHubCache, DEFAULT_TTL } from '../cache';
|
|
41
|
+
export { MemoryCacheAdapter, GitHubCache, DEFAULT_TTL };
|
|
42
|
+
|
|
43
|
+
import type { CacheInterface, CacheConfig } from './cache-interface';
|
|
44
|
+
import type { RedisCacheConfig, RedisClientInterface } from './redis-cache';
|
|
45
|
+
import type { UpstashCacheConfig } from './upstash-cache';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Cache backend types
|
|
49
|
+
*/
|
|
50
|
+
export type CacheBackend = 'memory' | 'redis' | 'upstash';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Unified cache factory configuration
|
|
54
|
+
*/
|
|
55
|
+
export interface CacheFactoryConfig {
|
|
56
|
+
backend: CacheBackend;
|
|
57
|
+
/** Redis-specific configuration */
|
|
58
|
+
redis?: {
|
|
59
|
+
client: RedisClientInterface;
|
|
60
|
+
} & Omit<RedisCacheConfig, 'client'>;
|
|
61
|
+
/** Upstash-specific configuration */
|
|
62
|
+
upstash?: {
|
|
63
|
+
url: string;
|
|
64
|
+
token: string;
|
|
65
|
+
} & Omit<UpstashCacheConfig, 'url' | 'token'>;
|
|
66
|
+
/** Common cache options */
|
|
67
|
+
options?: CacheConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a cache instance based on configuration
|
|
72
|
+
*/
|
|
73
|
+
export function createCache(config: CacheFactoryConfig): CacheInterface {
|
|
74
|
+
const { backend, redis, upstash, options } = config;
|
|
75
|
+
|
|
76
|
+
switch (backend) {
|
|
77
|
+
case 'memory':
|
|
78
|
+
// Return memory cache wrapped as CacheInterface
|
|
79
|
+
return new MemoryCacheWrapper(options);
|
|
80
|
+
|
|
81
|
+
case 'redis':
|
|
82
|
+
if (!redis?.client) {
|
|
83
|
+
throw new Error('Redis client is required for redis backend');
|
|
84
|
+
}
|
|
85
|
+
const { createRedisCache } = require('./redis-cache');
|
|
86
|
+
return createRedisCache({
|
|
87
|
+
...options,
|
|
88
|
+
...redis
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
case 'upstash':
|
|
92
|
+
if (!upstash?.url || !upstash?.token) {
|
|
93
|
+
// Try environment variables
|
|
94
|
+
const url = upstash?.url || process.env.UPSTASH_REDIS_REST_URL;
|
|
95
|
+
const token = upstash?.token || process.env.UPSTASH_REDIS_REST_TOKEN;
|
|
96
|
+
|
|
97
|
+
if (!url || !token) {
|
|
98
|
+
throw new Error('Upstash URL and token are required for upstash backend');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { createUpstashCache } = require('./upstash-cache');
|
|
102
|
+
return createUpstashCache({
|
|
103
|
+
url,
|
|
104
|
+
token,
|
|
105
|
+
...options,
|
|
106
|
+
...upstash
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { createUpstashCache: createUpstash } = require('./upstash-cache');
|
|
111
|
+
return createUpstash({
|
|
112
|
+
...options,
|
|
113
|
+
...upstash
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unknown cache backend: ${backend}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Memory cache wrapper that implements CacheInterface
|
|
123
|
+
*/
|
|
124
|
+
class MemoryCacheWrapper implements CacheInterface {
|
|
125
|
+
private cache: Map<string, any> = new Map();
|
|
126
|
+
private config: CacheConfig;
|
|
127
|
+
private stats = { hits: 0, misses: 0 };
|
|
128
|
+
|
|
129
|
+
constructor(config?: CacheConfig) {
|
|
130
|
+
this.config = {
|
|
131
|
+
defaultTTL: 3600,
|
|
132
|
+
maxEntries: 1000,
|
|
133
|
+
...config
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async get<T>(key: string): Promise<{ data: T; timestamp: number; ttl: number } | null> {
|
|
138
|
+
const prefixedKey = this.config.keyPrefix ? `${this.config.keyPrefix}:${key}` : key;
|
|
139
|
+
const entry = this.cache.get(prefixedKey);
|
|
140
|
+
|
|
141
|
+
if (!entry) {
|
|
142
|
+
this.stats.misses++;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check expiration
|
|
147
|
+
if (Date.now() - entry.timestamp > entry.ttl * 1000) {
|
|
148
|
+
this.cache.delete(prefixedKey);
|
|
149
|
+
this.stats.misses++;
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.stats.hits++;
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async set<T>(key: string, entry: { data: T; timestamp: number; ttl: number }): Promise<void> {
|
|
158
|
+
const prefixedKey = this.config.keyPrefix ? `${this.config.keyPrefix}:${key}` : key;
|
|
159
|
+
|
|
160
|
+
// Enforce max entries
|
|
161
|
+
if (this.config.maxEntries && this.cache.size >= this.config.maxEntries) {
|
|
162
|
+
// Remove oldest entry
|
|
163
|
+
const oldestKey = this.cache.keys().next().value;
|
|
164
|
+
if (oldestKey) {
|
|
165
|
+
this.cache.delete(oldestKey);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.cache.set(prefixedKey, entry);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async delete(key: string): Promise<void> {
|
|
173
|
+
const prefixedKey = this.config.keyPrefix ? `${this.config.keyPrefix}:${key}` : key;
|
|
174
|
+
this.cache.delete(prefixedKey);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async clear(): Promise<void> {
|
|
178
|
+
this.cache.clear();
|
|
179
|
+
this.stats = { hits: 0, misses: 0 };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async has(key: string): Promise<boolean> {
|
|
183
|
+
const prefixedKey = this.config.keyPrefix ? `${this.config.keyPrefix}:${key}` : key;
|
|
184
|
+
return this.cache.has(prefixedKey);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async getMany<T>(keys: string[]): Promise<Map<string, { data: T; timestamp: number; ttl: number } | null>> {
|
|
188
|
+
const result = new Map();
|
|
189
|
+
for (const key of keys) {
|
|
190
|
+
result.set(key, await this.get<T>(key));
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async setMany<T>(entries: Map<string, { data: T; timestamp: number; ttl: number }>): Promise<void> {
|
|
196
|
+
for (const [key, entry] of entries) {
|
|
197
|
+
await this.set(key, entry);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getStats() {
|
|
202
|
+
const total = this.stats.hits + this.stats.misses;
|
|
203
|
+
return {
|
|
204
|
+
hits: this.stats.hits,
|
|
205
|
+
misses: this.stats.misses,
|
|
206
|
+
size: this.cache.size,
|
|
207
|
+
hitRatio: total > 0 ? this.stats.hits / total : 0
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Redis cache implementation
|
|
3
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
CacheInterface,
|
|
9
|
+
CacheEntry,
|
|
10
|
+
CacheStats,
|
|
11
|
+
CacheConfig,
|
|
12
|
+
CacheSerializer
|
|
13
|
+
} from './cache-interface';
|
|
14
|
+
import { defaultSerializer, generateCacheKey } from './cache-interface';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Redis client interface (compatible with ioredis and node-redis)
|
|
18
|
+
*/
|
|
19
|
+
export interface RedisClientInterface {
|
|
20
|
+
get(key: string): Promise<string | null>;
|
|
21
|
+
set(key: string, value: string, ...args: any[]): Promise<any>;
|
|
22
|
+
del(key: string | string[]): Promise<number>;
|
|
23
|
+
keys(pattern: string): Promise<string[]>;
|
|
24
|
+
exists(key: string | string[]): Promise<number>;
|
|
25
|
+
mget(...keys: string[]): Promise<(string | null)[]>;
|
|
26
|
+
mset(...keysAndValues: string[]): Promise<any>;
|
|
27
|
+
flushdb?(): Promise<any>;
|
|
28
|
+
quit(): Promise<any>;
|
|
29
|
+
info?(section?: string): Promise<string>;
|
|
30
|
+
dbsize?(): Promise<number>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Redis cache configuration
|
|
35
|
+
*/
|
|
36
|
+
export interface RedisCacheConfig extends CacheConfig {
|
|
37
|
+
/** Redis client instance */
|
|
38
|
+
client: RedisClientInterface;
|
|
39
|
+
/** Key prefix for namespacing */
|
|
40
|
+
keyPrefix?: string;
|
|
41
|
+
/** Use SETEX for automatic expiration */
|
|
42
|
+
useExpiration?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Redis-based cache implementation
|
|
47
|
+
*/
|
|
48
|
+
export class RedisCache implements CacheInterface {
|
|
49
|
+
private client: RedisClientInterface;
|
|
50
|
+
private config: RedisCacheConfig;
|
|
51
|
+
private serializer: CacheSerializer;
|
|
52
|
+
private stats: { hits: number; misses: number };
|
|
53
|
+
|
|
54
|
+
constructor(config: RedisCacheConfig) {
|
|
55
|
+
this.client = config.client;
|
|
56
|
+
this.config = {
|
|
57
|
+
keyPrefix: 'mcp-cache',
|
|
58
|
+
defaultTTL: 3600,
|
|
59
|
+
useExpiration: true,
|
|
60
|
+
...config
|
|
61
|
+
};
|
|
62
|
+
this.serializer = config.serializer || defaultSerializer;
|
|
63
|
+
this.stats = { hits: 0, misses: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate prefixed key
|
|
68
|
+
*/
|
|
69
|
+
private prefixKey(key: string): string {
|
|
70
|
+
return this.config.keyPrefix ? `${this.config.keyPrefix}:${key}` : key;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get a cached value
|
|
75
|
+
*/
|
|
76
|
+
async get<T>(key: string): Promise<CacheEntry<T> | null> {
|
|
77
|
+
const prefixedKey = this.prefixKey(key);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const value = await this.client.get(prefixedKey);
|
|
81
|
+
|
|
82
|
+
if (!value) {
|
|
83
|
+
this.stats.misses++;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const entry = this.serializer.deserialize<CacheEntry<T>>(value);
|
|
88
|
+
|
|
89
|
+
// Check if entry is expired (in case TTL wasn't set in Redis)
|
|
90
|
+
if (Date.now() - entry.timestamp > entry.ttl * 1000) {
|
|
91
|
+
this.stats.misses++;
|
|
92
|
+
// Delete expired entry
|
|
93
|
+
await this.delete(key);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.stats.hits++;
|
|
98
|
+
return entry;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.stats.misses++;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set a cached value
|
|
107
|
+
*/
|
|
108
|
+
async set<T>(key: string, entry: CacheEntry<T>): Promise<void> {
|
|
109
|
+
const prefixedKey = this.prefixKey(key);
|
|
110
|
+
const serialized = this.serializer.serialize(entry);
|
|
111
|
+
|
|
112
|
+
if (this.config.useExpiration && entry.ttl > 0) {
|
|
113
|
+
// Use SETEX or SET with EX option for automatic expiration
|
|
114
|
+
// Add extra time for stale-while-revalidate if enabled
|
|
115
|
+
const expirationSeconds = this.config.staleWhileRevalidate
|
|
116
|
+
? Math.ceil(entry.ttl * (this.config.staleTolerance || 2))
|
|
117
|
+
: entry.ttl;
|
|
118
|
+
|
|
119
|
+
await this.client.set(prefixedKey, serialized, 'EX', expirationSeconds);
|
|
120
|
+
} else {
|
|
121
|
+
await this.client.set(prefixedKey, serialized);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Delete a cached value
|
|
127
|
+
*/
|
|
128
|
+
async delete(key: string): Promise<void> {
|
|
129
|
+
const prefixedKey = this.prefixKey(key);
|
|
130
|
+
await this.client.del(prefixedKey);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clear all cached values with the prefix
|
|
135
|
+
*/
|
|
136
|
+
async clear(): Promise<void> {
|
|
137
|
+
const pattern = this.config.keyPrefix ? `${this.config.keyPrefix}:*` : '*';
|
|
138
|
+
const keys = await this.client.keys(pattern);
|
|
139
|
+
|
|
140
|
+
if (keys.length > 0) {
|
|
141
|
+
// Delete in batches to avoid blocking
|
|
142
|
+
const batchSize = 1000;
|
|
143
|
+
for (let i = 0; i < keys.length; i += batchSize) {
|
|
144
|
+
const batch = keys.slice(i, i + batchSize);
|
|
145
|
+
await this.client.del(batch);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Reset stats
|
|
150
|
+
this.stats = { hits: 0, misses: 0 };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if a key exists
|
|
155
|
+
*/
|
|
156
|
+
async has(key: string): Promise<boolean> {
|
|
157
|
+
const prefixedKey = this.prefixKey(key);
|
|
158
|
+
const exists = await this.client.exists(prefixedKey);
|
|
159
|
+
return exists > 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get multiple values at once
|
|
164
|
+
*/
|
|
165
|
+
async getMany<T>(keys: string[]): Promise<Map<string, CacheEntry<T> | null>> {
|
|
166
|
+
const result = new Map<string, CacheEntry<T> | null>();
|
|
167
|
+
|
|
168
|
+
if (keys.length === 0) {
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const prefixedKeys = keys.map(k => this.prefixKey(k));
|
|
173
|
+
const values = await this.client.mget(...prefixedKeys);
|
|
174
|
+
|
|
175
|
+
keys.forEach((key, index) => {
|
|
176
|
+
const value = values[index];
|
|
177
|
+
if (value) {
|
|
178
|
+
try {
|
|
179
|
+
const entry = this.serializer.deserialize<CacheEntry<T>>(value);
|
|
180
|
+
if (Date.now() - entry.timestamp <= entry.ttl * 1000) {
|
|
181
|
+
this.stats.hits++;
|
|
182
|
+
result.set(key, entry);
|
|
183
|
+
} else {
|
|
184
|
+
this.stats.misses++;
|
|
185
|
+
result.set(key, null);
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
this.stats.misses++;
|
|
189
|
+
result.set(key, null);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
this.stats.misses++;
|
|
193
|
+
result.set(key, null);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Set multiple values at once
|
|
202
|
+
*/
|
|
203
|
+
async setMany<T>(entries: Map<string, CacheEntry<T>>): Promise<void> {
|
|
204
|
+
if (entries.size === 0) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// For entries with TTL, we need to set them individually to use SETEX
|
|
209
|
+
if (this.config.useExpiration) {
|
|
210
|
+
const promises = Array.from(entries.entries()).map(([key, entry]) =>
|
|
211
|
+
this.set(key, entry)
|
|
212
|
+
);
|
|
213
|
+
await Promise.all(promises);
|
|
214
|
+
} else {
|
|
215
|
+
// Use MSET for entries without TTL
|
|
216
|
+
const args: string[] = [];
|
|
217
|
+
entries.forEach((entry, key) => {
|
|
218
|
+
args.push(this.prefixKey(key), this.serializer.serialize(entry));
|
|
219
|
+
});
|
|
220
|
+
await this.client.mset(...args);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get cache statistics
|
|
226
|
+
*/
|
|
227
|
+
async getStats(): Promise<CacheStats> {
|
|
228
|
+
const pattern = this.config.keyPrefix ? `${this.config.keyPrefix}:*` : '*';
|
|
229
|
+
let size = 0;
|
|
230
|
+
let bytes: number | undefined;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (this.client.dbsize) {
|
|
234
|
+
size = await this.client.dbsize();
|
|
235
|
+
} else {
|
|
236
|
+
const keys = await this.client.keys(pattern);
|
|
237
|
+
size = keys.length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Try to get memory usage if available
|
|
241
|
+
if (this.client.info) {
|
|
242
|
+
const info = await this.client.info('memory');
|
|
243
|
+
const match = info.match(/used_memory:(\d+)/);
|
|
244
|
+
if (match) {
|
|
245
|
+
bytes = parseInt(match[1], 10);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
// Ignore stats errors
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const total = this.stats.hits + this.stats.misses;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
hits: this.stats.hits,
|
|
256
|
+
misses: this.stats.misses,
|
|
257
|
+
size,
|
|
258
|
+
bytes,
|
|
259
|
+
hitRatio: total > 0 ? this.stats.hits / total : 0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Close the Redis connection
|
|
265
|
+
*/
|
|
266
|
+
async close(): Promise<void> {
|
|
267
|
+
await this.client.quit();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Create a Redis cache instance
|
|
273
|
+
*/
|
|
274
|
+
export function createRedisCache(config: RedisCacheConfig): RedisCache {
|
|
275
|
+
return new RedisCache(config);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Helper to create Redis cache from connection URL
|
|
280
|
+
* Note: This is a stub - actual implementation depends on the Redis client library used
|
|
281
|
+
*/
|
|
282
|
+
export async function createRedisCacheFromUrl(
|
|
283
|
+
url: string,
|
|
284
|
+
options?: Omit<RedisCacheConfig, 'client'>
|
|
285
|
+
): Promise<RedisCache> {
|
|
286
|
+
// This would need ioredis or node-redis to be installed
|
|
287
|
+
throw new Error(
|
|
288
|
+
'createRedisCacheFromUrl requires a Redis client library. ' +
|
|
289
|
+
'Please install ioredis or redis and pass the client directly.'
|
|
290
|
+
);
|
|
291
|
+
}
|