@skillsmith/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/src/__tests__/errors.test.d.ts +5 -0
- package/dist/src/__tests__/errors.test.d.ts.map +1 -0
- package/dist/src/__tests__/errors.test.js +115 -0
- package/dist/src/__tests__/errors.test.js.map +1 -0
- package/dist/src/activation/ActivationManager.d.ts +141 -0
- package/dist/src/activation/ActivationManager.d.ts.map +1 -0
- package/dist/src/activation/ActivationManager.js +282 -0
- package/dist/src/activation/ActivationManager.js.map +1 -0
- package/dist/src/activation/ZeroConfigActivator.d.ts +126 -0
- package/dist/src/activation/ZeroConfigActivator.d.ts.map +1 -0
- package/dist/src/activation/ZeroConfigActivator.js +259 -0
- package/dist/src/activation/ZeroConfigActivator.js.map +1 -0
- package/dist/src/activation/index.d.ts +8 -0
- package/dist/src/activation/index.d.ts.map +1 -0
- package/dist/src/activation/index.js +8 -0
- package/dist/src/activation/index.js.map +1 -0
- package/dist/src/analysis/CodebaseAnalyzer.d.ts +175 -0
- package/dist/src/analysis/CodebaseAnalyzer.d.ts.map +1 -0
- package/dist/src/analysis/CodebaseAnalyzer.js +495 -0
- package/dist/src/analysis/CodebaseAnalyzer.js.map +1 -0
- package/dist/src/analysis/index.d.ts +10 -0
- package/dist/src/analysis/index.d.ts.map +1 -0
- package/dist/src/analysis/index.js +10 -0
- package/dist/src/analysis/index.js.map +1 -0
- package/dist/src/analytics/AnalyticsRepository.d.ts +97 -0
- package/dist/src/analytics/AnalyticsRepository.d.ts.map +1 -0
- package/dist/src/analytics/AnalyticsRepository.js +376 -0
- package/dist/src/analytics/AnalyticsRepository.js.map +1 -0
- package/dist/src/analytics/ExperimentService.d.ts +70 -0
- package/dist/src/analytics/ExperimentService.d.ts.map +1 -0
- package/dist/src/analytics/ExperimentService.js +251 -0
- package/dist/src/analytics/ExperimentService.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +55 -0
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.js +304 -0
- package/dist/src/analytics/ROIDashboardService.js.map +1 -0
- package/dist/src/analytics/UsageAnalyticsService.d.ts +55 -0
- package/dist/src/analytics/UsageAnalyticsService.d.ts.map +1 -0
- package/dist/src/analytics/UsageAnalyticsService.js +180 -0
- package/dist/src/analytics/UsageAnalyticsService.js.map +1 -0
- package/dist/src/analytics/anonymizer.d.ts +53 -0
- package/dist/src/analytics/anonymizer.d.ts.map +1 -0
- package/dist/src/analytics/anonymizer.js +107 -0
- package/dist/src/analytics/anonymizer.js.map +1 -0
- package/dist/src/analytics/constants.d.ts +19 -0
- package/dist/src/analytics/constants.d.ts.map +1 -0
- package/dist/src/analytics/constants.js +19 -0
- package/dist/src/analytics/constants.js.map +1 -0
- package/dist/src/analytics/index.d.ts +23 -0
- package/dist/src/analytics/index.d.ts.map +1 -0
- package/dist/src/analytics/index.js +23 -0
- package/dist/src/analytics/index.js.map +1 -0
- package/dist/src/analytics/metrics-aggregator.d.ts +98 -0
- package/dist/src/analytics/metrics-aggregator.d.ts.map +1 -0
- package/dist/src/analytics/metrics-aggregator.js +176 -0
- package/dist/src/analytics/metrics-aggregator.js.map +1 -0
- package/dist/src/analytics/metrics-exporter.d.ts +148 -0
- package/dist/src/analytics/metrics-exporter.d.ts.map +1 -0
- package/dist/src/analytics/metrics-exporter.js +244 -0
- package/dist/src/analytics/metrics-exporter.js.map +1 -0
- package/dist/src/analytics/schema.d.ts +20 -0
- package/dist/src/analytics/schema.d.ts.map +1 -0
- package/dist/src/analytics/schema.js +125 -0
- package/dist/src/analytics/schema.js.map +1 -0
- package/dist/src/analytics/storage.d.ts +76 -0
- package/dist/src/analytics/storage.d.ts.map +1 -0
- package/dist/src/analytics/storage.js +180 -0
- package/dist/src/analytics/storage.js.map +1 -0
- package/dist/src/analytics/types.d.ts +277 -0
- package/dist/src/analytics/types.d.ts.map +1 -0
- package/dist/src/analytics/types.js +10 -0
- package/dist/src/analytics/types.js.map +1 -0
- package/dist/src/analytics/usage-tracker.d.ts +132 -0
- package/dist/src/analytics/usage-tracker.d.ts.map +1 -0
- package/dist/src/analytics/usage-tracker.js +213 -0
- package/dist/src/analytics/usage-tracker.js.map +1 -0
- package/dist/src/benchmarks/BenchmarkRunner.d.ts +258 -0
- package/dist/src/benchmarks/BenchmarkRunner.d.ts.map +1 -0
- package/dist/src/benchmarks/BenchmarkRunner.js +453 -0
- package/dist/src/benchmarks/BenchmarkRunner.js.map +1 -0
- package/dist/src/benchmarks/IndexBenchmark.d.ts +101 -0
- package/dist/src/benchmarks/IndexBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/IndexBenchmark.js +314 -0
- package/dist/src/benchmarks/IndexBenchmark.js.map +1 -0
- package/dist/src/benchmarks/MemoryProfiler.d.ts +266 -0
- package/dist/src/benchmarks/MemoryProfiler.d.ts.map +1 -0
- package/dist/src/benchmarks/MemoryProfiler.js +404 -0
- package/dist/src/benchmarks/MemoryProfiler.js.map +1 -0
- package/dist/src/benchmarks/SearchBenchmark.d.ts +71 -0
- package/dist/src/benchmarks/SearchBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/SearchBenchmark.js +321 -0
- package/dist/src/benchmarks/SearchBenchmark.js.map +1 -0
- package/dist/src/benchmarks/cacheBenchmark.d.ts +75 -0
- package/dist/src/benchmarks/cacheBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/cacheBenchmark.js +325 -0
- package/dist/src/benchmarks/cacheBenchmark.js.map +1 -0
- package/dist/src/benchmarks/cli.d.ts +17 -0
- package/dist/src/benchmarks/cli.d.ts.map +1 -0
- package/dist/src/benchmarks/cli.js +140 -0
- package/dist/src/benchmarks/cli.js.map +1 -0
- package/dist/src/benchmarks/embeddingBenchmark.d.ts +86 -0
- package/dist/src/benchmarks/embeddingBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/embeddingBenchmark.js +329 -0
- package/dist/src/benchmarks/embeddingBenchmark.js.map +1 -0
- package/dist/src/benchmarks/index.d.ts +51 -0
- package/dist/src/benchmarks/index.d.ts.map +1 -0
- package/dist/src/benchmarks/index.js +201 -0
- package/dist/src/benchmarks/index.js.map +1 -0
- package/dist/src/benchmarks/stats.d.ts +56 -0
- package/dist/src/benchmarks/stats.d.ts.map +1 -0
- package/dist/src/benchmarks/stats.js +86 -0
- package/dist/src/benchmarks/stats.js.map +1 -0
- package/dist/src/cache/CacheEntry.d.ts +101 -0
- package/dist/src/cache/CacheEntry.d.ts.map +1 -0
- package/dist/src/cache/CacheEntry.js +238 -0
- package/dist/src/cache/CacheEntry.js.map +1 -0
- package/dist/src/cache/CacheManager.d.ts +167 -0
- package/dist/src/cache/CacheManager.d.ts.map +1 -0
- package/dist/src/cache/CacheManager.js +346 -0
- package/dist/src/cache/CacheManager.js.map +1 -0
- package/dist/src/cache/TieredCache.d.ts +97 -0
- package/dist/src/cache/TieredCache.d.ts.map +1 -0
- package/dist/src/cache/TieredCache.js +352 -0
- package/dist/src/cache/TieredCache.js.map +1 -0
- package/dist/src/cache/index.d.ts +63 -0
- package/dist/src/cache/index.d.ts.map +1 -0
- package/dist/src/cache/index.js +91 -0
- package/dist/src/cache/index.js.map +1 -0
- package/dist/src/cache/lru.d.ts +68 -0
- package/dist/src/cache/lru.d.ts.map +1 -0
- package/dist/src/cache/lru.js +105 -0
- package/dist/src/cache/lru.js.map +1 -0
- package/dist/src/cache/sqlite.d.ts +59 -0
- package/dist/src/cache/sqlite.d.ts.map +1 -0
- package/dist/src/cache/sqlite.js +159 -0
- package/dist/src/cache/sqlite.js.map +1 -0
- package/dist/src/db/quarantine-schema.d.ts +81 -0
- package/dist/src/db/quarantine-schema.d.ts.map +1 -0
- package/dist/src/db/quarantine-schema.js +110 -0
- package/dist/src/db/quarantine-schema.js.map +1 -0
- package/dist/src/db/schema.d.ts +65 -0
- package/dist/src/db/schema.d.ts.map +1 -0
- package/dist/src/db/schema.js +318 -0
- package/dist/src/db/schema.js.map +1 -0
- package/dist/src/embeddings/index.d.ts +158 -0
- package/dist/src/embeddings/index.d.ts.map +1 -0
- package/dist/src/embeddings/index.js +397 -0
- package/dist/src/embeddings/index.js.map +1 -0
- package/dist/src/errors/SkillsmithError.d.ts +118 -0
- package/dist/src/errors/SkillsmithError.d.ts.map +1 -0
- package/dist/src/errors/SkillsmithError.js +194 -0
- package/dist/src/errors/SkillsmithError.js.map +1 -0
- package/dist/src/errors/index.d.ts +36 -0
- package/dist/src/errors/index.d.ts.map +1 -0
- package/dist/src/errors/index.js +36 -0
- package/dist/src/errors/index.js.map +1 -0
- package/dist/src/errors.d.ts +72 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +123 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +56 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +73 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/index.test.d.ts +2 -0
- package/dist/src/index.test.d.ts.map +1 -0
- package/dist/src/index.test.js +35 -0
- package/dist/src/index.test.js.map +1 -0
- package/dist/src/indexer/GitHubIndexer.d.ts +107 -0
- package/dist/src/indexer/GitHubIndexer.d.ts.map +1 -0
- package/dist/src/indexer/GitHubIndexer.js +202 -0
- package/dist/src/indexer/GitHubIndexer.js.map +1 -0
- package/dist/src/indexer/PartitionStrategy.d.ts +115 -0
- package/dist/src/indexer/PartitionStrategy.d.ts.map +1 -0
- package/dist/src/indexer/PartitionStrategy.js +207 -0
- package/dist/src/indexer/PartitionStrategy.js.map +1 -0
- package/dist/src/indexer/SkillParser.d.ts +112 -0
- package/dist/src/indexer/SkillParser.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.js +281 -0
- package/dist/src/indexer/SkillParser.js.map +1 -0
- package/dist/src/indexer/SwarmIndexer.d.ts +163 -0
- package/dist/src/indexer/SwarmIndexer.d.ts.map +1 -0
- package/dist/src/indexer/SwarmIndexer.js +300 -0
- package/dist/src/indexer/SwarmIndexer.js.map +1 -0
- package/dist/src/indexer/index.d.ts +12 -0
- package/dist/src/indexer/index.d.ts.map +1 -0
- package/dist/src/indexer/index.js +16 -0
- package/dist/src/indexer/index.js.map +1 -0
- package/dist/src/learning/interfaces.d.ts +338 -0
- package/dist/src/learning/interfaces.d.ts.map +1 -0
- package/dist/src/learning/interfaces.js +13 -0
- package/dist/src/learning/interfaces.js.map +1 -0
- package/dist/src/learning/types.d.ts +284 -0
- package/dist/src/learning/types.d.ts.map +1 -0
- package/dist/src/learning/types.js +112 -0
- package/dist/src/learning/types.js.map +1 -0
- package/dist/src/matching/OverlapDetector.d.ts +152 -0
- package/dist/src/matching/OverlapDetector.d.ts.map +1 -0
- package/dist/src/matching/OverlapDetector.js +218 -0
- package/dist/src/matching/OverlapDetector.js.map +1 -0
- package/dist/src/matching/SkillMatcher.d.ts +125 -0
- package/dist/src/matching/SkillMatcher.d.ts.map +1 -0
- package/dist/src/matching/SkillMatcher.js +206 -0
- package/dist/src/matching/SkillMatcher.js.map +1 -0
- package/dist/src/matching/index.d.ts +14 -0
- package/dist/src/matching/index.d.ts.map +1 -0
- package/dist/src/matching/index.js +12 -0
- package/dist/src/matching/index.js.map +1 -0
- package/dist/src/pipeline/DailyIndexPipeline.d.ts +220 -0
- package/dist/src/pipeline/DailyIndexPipeline.d.ts.map +1 -0
- package/dist/src/pipeline/DailyIndexPipeline.js +320 -0
- package/dist/src/pipeline/DailyIndexPipeline.js.map +1 -0
- package/dist/src/pipeline/index.d.ts +9 -0
- package/dist/src/pipeline/index.d.ts.map +1 -0
- package/dist/src/pipeline/index.js +9 -0
- package/dist/src/pipeline/index.js.map +1 -0
- package/dist/src/repositories/CacheRepository.d.ts +60 -0
- package/dist/src/repositories/CacheRepository.d.ts.map +1 -0
- package/dist/src/repositories/CacheRepository.js +148 -0
- package/dist/src/repositories/CacheRepository.js.map +1 -0
- package/dist/src/repositories/IndexerRepository.d.ts +126 -0
- package/dist/src/repositories/IndexerRepository.d.ts.map +1 -0
- package/dist/src/repositories/IndexerRepository.js +270 -0
- package/dist/src/repositories/IndexerRepository.js.map +1 -0
- package/dist/src/repositories/QuarantineRepository.d.ts +255 -0
- package/dist/src/repositories/QuarantineRepository.d.ts.map +1 -0
- package/dist/src/repositories/QuarantineRepository.js +445 -0
- package/dist/src/repositories/QuarantineRepository.js.map +1 -0
- package/dist/src/repositories/SkillRepository.d.ts +78 -0
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -0
- package/dist/src/repositories/SkillRepository.js +208 -0
- package/dist/src/repositories/SkillRepository.js.map +1 -0
- package/dist/src/scoring/QualityScorer.d.ts +188 -0
- package/dist/src/scoring/QualityScorer.d.ts.map +1 -0
- package/dist/src/scoring/QualityScorer.js +342 -0
- package/dist/src/scoring/QualityScorer.js.map +1 -0
- package/dist/src/scoring/index.d.ts +9 -0
- package/dist/src/scoring/index.d.ts.map +1 -0
- package/dist/src/scoring/index.js +9 -0
- package/dist/src/scoring/index.js.map +1 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.d.ts +5 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.d.ts.map +1 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +365 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -0
- package/dist/src/scripts/import-github-skills.d.ts +24 -0
- package/dist/src/scripts/import-github-skills.d.ts.map +1 -0
- package/dist/src/scripts/import-github-skills.js +545 -0
- package/dist/src/scripts/import-github-skills.js.map +1 -0
- package/dist/src/scripts/import-to-database.d.ts +60 -0
- package/dist/src/scripts/import-to-database.d.ts.map +1 -0
- package/dist/src/scripts/import-to-database.js +307 -0
- package/dist/src/scripts/import-to-database.js.map +1 -0
- package/dist/src/scripts/scan-imported-skills.d.ts +15 -0
- package/dist/src/scripts/scan-imported-skills.d.ts.map +1 -0
- package/dist/src/scripts/scan-imported-skills.js +405 -0
- package/dist/src/scripts/scan-imported-skills.js.map +1 -0
- package/dist/src/scripts/validate-skills.d.ts +180 -0
- package/dist/src/scripts/validate-skills.d.ts.map +1 -0
- package/dist/src/scripts/validate-skills.js +572 -0
- package/dist/src/scripts/validate-skills.js.map +1 -0
- package/dist/src/search/hybrid.d.ts +85 -0
- package/dist/src/search/hybrid.d.ts.map +1 -0
- package/dist/src/search/hybrid.js +291 -0
- package/dist/src/search/hybrid.js.map +1 -0
- package/dist/src/search/index.d.ts +6 -0
- package/dist/src/search/index.d.ts.map +1 -0
- package/dist/src/search/index.js +5 -0
- package/dist/src/search/index.js.map +1 -0
- package/dist/src/security/AuditLogger.d.ts +197 -0
- package/dist/src/security/AuditLogger.d.ts.map +1 -0
- package/dist/src/security/AuditLogger.js +398 -0
- package/dist/src/security/AuditLogger.js.map +1 -0
- package/dist/src/security/RateLimiter.d.ts +337 -0
- package/dist/src/security/RateLimiter.d.ts.map +1 -0
- package/dist/src/security/RateLimiter.js +782 -0
- package/dist/src/security/RateLimiter.js.map +1 -0
- package/dist/src/security/__tests__/pathValidation.test.d.ts +8 -0
- package/dist/src/security/__tests__/pathValidation.test.d.ts.map +1 -0
- package/dist/src/security/__tests__/pathValidation.test.js +249 -0
- package/dist/src/security/__tests__/pathValidation.test.js.map +1 -0
- package/dist/src/security/index.d.ts +18 -0
- package/dist/src/security/index.d.ts.map +1 -0
- package/dist/src/security/index.js +14 -0
- package/dist/src/security/index.js.map +1 -0
- package/dist/src/security/pathValidation.d.ts +95 -0
- package/dist/src/security/pathValidation.d.ts.map +1 -0
- package/dist/src/security/pathValidation.js +216 -0
- package/dist/src/security/pathValidation.js.map +1 -0
- package/dist/src/security/sanitization.d.ts +123 -0
- package/dist/src/security/sanitization.d.ts.map +1 -0
- package/dist/src/security/sanitization.js +378 -0
- package/dist/src/security/sanitization.js.map +1 -0
- package/dist/src/security/scanner.d.ts +151 -0
- package/dist/src/security/scanner.d.ts.map +1 -0
- package/dist/src/security/scanner.js +599 -0
- package/dist/src/security/scanner.js.map +1 -0
- package/dist/src/services/SearchService.d.ts +88 -0
- package/dist/src/services/SearchService.d.ts.map +1 -0
- package/dist/src/services/SearchService.js +305 -0
- package/dist/src/services/SearchService.js.map +1 -0
- package/dist/src/session/SessionContext.d.ts +116 -0
- package/dist/src/session/SessionContext.d.ts.map +1 -0
- package/dist/src/session/SessionContext.js +134 -0
- package/dist/src/session/SessionContext.js.map +1 -0
- package/dist/src/session/SessionHealthMonitor.d.ts +249 -0
- package/dist/src/session/SessionHealthMonitor.d.ts.map +1 -0
- package/dist/src/session/SessionHealthMonitor.js +302 -0
- package/dist/src/session/SessionHealthMonitor.js.map +1 -0
- package/dist/src/session/SessionManager.d.ts +179 -0
- package/dist/src/session/SessionManager.d.ts.map +1 -0
- package/dist/src/session/SessionManager.js +451 -0
- package/dist/src/session/SessionManager.js.map +1 -0
- package/dist/src/session/SessionRecovery.d.ts +84 -0
- package/dist/src/session/SessionRecovery.d.ts.map +1 -0
- package/dist/src/session/SessionRecovery.js +257 -0
- package/dist/src/session/SessionRecovery.js.map +1 -0
- package/dist/src/session/index.d.ts +40 -0
- package/dist/src/session/index.d.ts.map +1 -0
- package/dist/src/session/index.js +44 -0
- package/dist/src/session/index.js.map +1 -0
- package/dist/src/sources/BaseSourceAdapter.d.ts +144 -0
- package/dist/src/sources/BaseSourceAdapter.d.ts.map +1 -0
- package/dist/src/sources/BaseSourceAdapter.js +287 -0
- package/dist/src/sources/BaseSourceAdapter.js.map +1 -0
- package/dist/src/sources/GitHubSourceAdapter.d.ts +98 -0
- package/dist/src/sources/GitHubSourceAdapter.d.ts.map +1 -0
- package/dist/src/sources/GitHubSourceAdapter.js +269 -0
- package/dist/src/sources/GitHubSourceAdapter.js.map +1 -0
- package/dist/src/sources/GitLabSourceAdapter.d.ts +102 -0
- package/dist/src/sources/GitLabSourceAdapter.d.ts.map +1 -0
- package/dist/src/sources/GitLabSourceAdapter.js +310 -0
- package/dist/src/sources/GitLabSourceAdapter.js.map +1 -0
- package/dist/src/sources/ISourceAdapter.d.ts +110 -0
- package/dist/src/sources/ISourceAdapter.d.ts.map +1 -0
- package/dist/src/sources/ISourceAdapter.js +19 -0
- package/dist/src/sources/ISourceAdapter.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.d.ts +112 -0
- package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.js +340 -0
- package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -0
- package/dist/src/sources/RawUrlSourceAdapter.d.ts +128 -0
- package/dist/src/sources/RawUrlSourceAdapter.d.ts.map +1 -0
- package/dist/src/sources/RawUrlSourceAdapter.js +282 -0
- package/dist/src/sources/RawUrlSourceAdapter.js.map +1 -0
- package/dist/src/sources/SourceAdapterRegistry.d.ts +156 -0
- package/dist/src/sources/SourceAdapterRegistry.d.ts.map +1 -0
- package/dist/src/sources/SourceAdapterRegistry.js +242 -0
- package/dist/src/sources/SourceAdapterRegistry.js.map +1 -0
- package/dist/src/sources/SourceIndexer.d.ts +119 -0
- package/dist/src/sources/SourceIndexer.d.ts.map +1 -0
- package/dist/src/sources/SourceIndexer.js +285 -0
- package/dist/src/sources/SourceIndexer.js.map +1 -0
- package/dist/src/sources/index.d.ts +45 -0
- package/dist/src/sources/index.d.ts.map +1 -0
- package/dist/src/sources/index.js +51 -0
- package/dist/src/sources/index.js.map +1 -0
- package/dist/src/sources/shared.d.ts +125 -0
- package/dist/src/sources/shared.d.ts.map +1 -0
- package/dist/src/sources/shared.js +191 -0
- package/dist/src/sources/shared.js.map +1 -0
- package/dist/src/sources/types.d.ts +204 -0
- package/dist/src/sources/types.d.ts.map +1 -0
- package/dist/src/sources/types.js +6 -0
- package/dist/src/sources/types.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +25 -0
- package/dist/src/telemetry/index.d.ts.map +1 -0
- package/dist/src/telemetry/index.js +32 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/telemetry/metrics.d.ts +171 -0
- package/dist/src/telemetry/metrics.d.ts.map +1 -0
- package/dist/src/telemetry/metrics.js +401 -0
- package/dist/src/telemetry/metrics.js.map +1 -0
- package/dist/src/telemetry/prometheus.d.ts +81 -0
- package/dist/src/telemetry/prometheus.d.ts.map +1 -0
- package/dist/src/telemetry/prometheus.js +252 -0
- package/dist/src/telemetry/prometheus.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts +151 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -0
- package/dist/src/telemetry/tracer.js +391 -0
- package/dist/src/telemetry/tracer.js.map +1 -0
- package/dist/src/triggers/ContextScorer.d.ts +118 -0
- package/dist/src/triggers/ContextScorer.d.ts.map +1 -0
- package/dist/src/triggers/ContextScorer.js +265 -0
- package/dist/src/triggers/ContextScorer.js.map +1 -0
- package/dist/src/triggers/TriggerDetector.d.ts +178 -0
- package/dist/src/triggers/TriggerDetector.d.ts.map +1 -0
- package/dist/src/triggers/TriggerDetector.js +390 -0
- package/dist/src/triggers/TriggerDetector.js.map +1 -0
- package/dist/src/triggers/__tests__/ContextScorer.test.d.ts +6 -0
- package/dist/src/triggers/__tests__/ContextScorer.test.d.ts.map +1 -0
- package/dist/src/triggers/__tests__/ContextScorer.test.js +307 -0
- package/dist/src/triggers/__tests__/ContextScorer.test.js.map +1 -0
- package/dist/src/triggers/__tests__/TriggerDetector.test.d.ts +6 -0
- package/dist/src/triggers/__tests__/TriggerDetector.test.d.ts.map +1 -0
- package/dist/src/triggers/__tests__/TriggerDetector.test.js +249 -0
- package/dist/src/triggers/__tests__/TriggerDetector.test.js.map +1 -0
- package/dist/src/triggers/index.d.ts +8 -0
- package/dist/src/triggers/index.d.ts.map +1 -0
- package/dist/src/triggers/index.js +8 -0
- package/dist/src/triggers/index.js.map +1 -0
- package/dist/src/types/skill.d.ts +80 -0
- package/dist/src/types/skill.d.ts.map +1 -0
- package/dist/src/types/skill.js +5 -0
- package/dist/src/types/skill.js.map +1 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +13 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +6 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/logger.d.ts +172 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +291 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/retry.d.ts +97 -0
- package/dist/src/utils/retry.d.ts.map +1 -0
- package/dist/src/utils/retry.js +267 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/validation/index.d.ts +118 -0
- package/dist/src/validation/index.d.ts.map +1 -0
- package/dist/src/validation/index.js +434 -0
- package/dist/src/validation/index.js.map +1 -0
- package/dist/src/webhooks/WebhookHandler.d.ts +117 -0
- package/dist/src/webhooks/WebhookHandler.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookHandler.js +349 -0
- package/dist/src/webhooks/WebhookHandler.js.map +1 -0
- package/dist/src/webhooks/WebhookPayload.d.ts +238 -0
- package/dist/src/webhooks/WebhookPayload.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookPayload.js +244 -0
- package/dist/src/webhooks/WebhookPayload.js.map +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts +227 -0
- package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookQueue.js +328 -0
- package/dist/src/webhooks/WebhookQueue.js.map +1 -0
- package/dist/src/webhooks/index.d.ts +12 -0
- package/dist/src/webhooks/index.d.ts.map +1 -0
- package/dist/src/webhooks/index.js +15 -0
- package/dist/src/webhooks/index.js.map +1 -0
- package/dist/tests/Analytics.integration.test.d.ts +7 -0
- package/dist/tests/Analytics.integration.test.d.ts.map +1 -0
- package/dist/tests/Analytics.integration.test.js +367 -0
- package/dist/tests/Analytics.integration.test.js.map +1 -0
- package/dist/tests/AnalyticsRepository.test.d.ts +8 -0
- package/dist/tests/AnalyticsRepository.test.d.ts.map +1 -0
- package/dist/tests/AnalyticsRepository.test.js +399 -0
- package/dist/tests/AnalyticsRepository.test.js.map +1 -0
- package/dist/tests/AnalyticsStorage.test.d.ts +8 -0
- package/dist/tests/AnalyticsStorage.test.d.ts.map +1 -0
- package/dist/tests/AnalyticsStorage.test.js +271 -0
- package/dist/tests/AnalyticsStorage.test.js.map +1 -0
- package/dist/tests/AuditLogger.test.d.ts +8 -0
- package/dist/tests/AuditLogger.test.d.ts.map +1 -0
- package/dist/tests/AuditLogger.test.js +699 -0
- package/dist/tests/AuditLogger.test.js.map +1 -0
- package/dist/tests/BenchmarkRunner.test.d.ts +11 -0
- package/dist/tests/BenchmarkRunner.test.d.ts.map +1 -0
- package/dist/tests/BenchmarkRunner.test.js +641 -0
- package/dist/tests/BenchmarkRunner.test.js.map +1 -0
- package/dist/tests/CacheRepository.test.d.ts +5 -0
- package/dist/tests/CacheRepository.test.d.ts.map +1 -0
- package/dist/tests/CacheRepository.test.js +184 -0
- package/dist/tests/CacheRepository.test.js.map +1 -0
- package/dist/tests/CacheSecurity.test.d.ts +8 -0
- package/dist/tests/CacheSecurity.test.d.ts.map +1 -0
- package/dist/tests/CacheSecurity.test.js +273 -0
- package/dist/tests/CacheSecurity.test.js.map +1 -0
- package/dist/tests/CodebaseAnalyzer.test.d.ts +2 -0
- package/dist/tests/CodebaseAnalyzer.test.d.ts.map +1 -0
- package/dist/tests/CodebaseAnalyzer.test.js +347 -0
- package/dist/tests/CodebaseAnalyzer.test.js.map +1 -0
- package/dist/tests/DailyIndexPipeline.test.d.ts +7 -0
- package/dist/tests/DailyIndexPipeline.test.d.ts.map +1 -0
- package/dist/tests/DailyIndexPipeline.test.js +539 -0
- package/dist/tests/DailyIndexPipeline.test.js.map +1 -0
- package/dist/tests/EmbeddingService.test.d.ts +2 -0
- package/dist/tests/EmbeddingService.test.d.ts.map +1 -0
- package/dist/tests/EmbeddingService.test.js +252 -0
- package/dist/tests/EmbeddingService.test.js.map +1 -0
- package/dist/tests/ExperimentService.test.d.ts +7 -0
- package/dist/tests/ExperimentService.test.d.ts.map +1 -0
- package/dist/tests/ExperimentService.test.js +293 -0
- package/dist/tests/ExperimentService.test.js.map +1 -0
- package/dist/tests/GitHubIndexer.test.d.ts +10 -0
- package/dist/tests/GitHubIndexer.test.d.ts.map +1 -0
- package/dist/tests/GitHubIndexer.test.js +524 -0
- package/dist/tests/GitHubIndexer.test.js.map +1 -0
- package/dist/tests/GitHubSourceAdapter.test.d.ts +5 -0
- package/dist/tests/GitHubSourceAdapter.test.d.ts.map +1 -0
- package/dist/tests/GitHubSourceAdapter.test.js +385 -0
- package/dist/tests/GitHubSourceAdapter.test.js.map +1 -0
- package/dist/tests/MemoryProfiler.test.d.ts +12 -0
- package/dist/tests/MemoryProfiler.test.d.ts.map +1 -0
- package/dist/tests/MemoryProfiler.test.js +402 -0
- package/dist/tests/MemoryProfiler.test.js.map +1 -0
- package/dist/tests/OverlapDetector.test.d.ts +2 -0
- package/dist/tests/OverlapDetector.test.d.ts.map +1 -0
- package/dist/tests/OverlapDetector.test.js +340 -0
- package/dist/tests/OverlapDetector.test.js.map +1 -0
- package/dist/tests/QualityScorer.test.d.ts +7 -0
- package/dist/tests/QualityScorer.test.d.ts.map +1 -0
- package/dist/tests/QualityScorer.test.js +340 -0
- package/dist/tests/QualityScorer.test.js.map +1 -0
- package/dist/tests/QuarantineRepository.test.d.ts +7 -0
- package/dist/tests/QuarantineRepository.test.d.ts.map +1 -0
- package/dist/tests/QuarantineRepository.test.js +582 -0
- package/dist/tests/QuarantineRepository.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.test.d.ts +7 -0
- package/dist/tests/ROIDashboardService.test.d.ts.map +1 -0
- package/dist/tests/ROIDashboardService.test.js +324 -0
- package/dist/tests/ROIDashboardService.test.js.map +1 -0
- package/dist/tests/RateLimiter.test.d.ts +7 -0
- package/dist/tests/RateLimiter.test.d.ts.map +1 -0
- package/dist/tests/RateLimiter.test.js +1017 -0
- package/dist/tests/RateLimiter.test.js.map +1 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.d.ts +7 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.d.ts.map +1 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.js +455 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.js.map +1 -0
- package/dist/tests/ScraperAdapters.test.d.ts +7 -0
- package/dist/tests/ScraperAdapters.test.d.ts.map +1 -0
- package/dist/tests/ScraperAdapters.test.js +748 -0
- package/dist/tests/ScraperAdapters.test.js.map +1 -0
- package/dist/tests/SearchQuality.test.d.ts +8 -0
- package/dist/tests/SearchQuality.test.d.ts.map +1 -0
- package/dist/tests/SearchQuality.test.js +397 -0
- package/dist/tests/SearchQuality.test.js.map +1 -0
- package/dist/tests/SearchService.test.d.ts +5 -0
- package/dist/tests/SearchService.test.d.ts.map +1 -0
- package/dist/tests/SearchService.test.js +218 -0
- package/dist/tests/SearchService.test.js.map +1 -0
- package/dist/tests/SecurityScanner.test.d.ts +6 -0
- package/dist/tests/SecurityScanner.test.d.ts.map +1 -0
- package/dist/tests/SecurityScanner.test.js +449 -0
- package/dist/tests/SecurityScanner.test.js.map +1 -0
- package/dist/tests/SessionHealthMonitor.test.d.ts +5 -0
- package/dist/tests/SessionHealthMonitor.test.d.ts.map +1 -0
- package/dist/tests/SessionHealthMonitor.test.js +449 -0
- package/dist/tests/SessionHealthMonitor.test.js.map +1 -0
- package/dist/tests/SessionManager.security.test.d.ts +10 -0
- package/dist/tests/SessionManager.security.test.d.ts.map +1 -0
- package/dist/tests/SessionManager.security.test.js +395 -0
- package/dist/tests/SessionManager.security.test.js.map +1 -0
- package/dist/tests/SessionManager.test.d.ts +8 -0
- package/dist/tests/SessionManager.test.d.ts.map +1 -0
- package/dist/tests/SessionManager.test.js +446 -0
- package/dist/tests/SessionManager.test.js.map +1 -0
- package/dist/tests/SkillMatcher.test.d.ts +2 -0
- package/dist/tests/SkillMatcher.test.d.ts.map +1 -0
- package/dist/tests/SkillMatcher.test.js +253 -0
- package/dist/tests/SkillMatcher.test.js.map +1 -0
- package/dist/tests/SkillRepository.test.d.ts +5 -0
- package/dist/tests/SkillRepository.test.d.ts.map +1 -0
- package/dist/tests/SkillRepository.test.js +237 -0
- package/dist/tests/SkillRepository.test.js.map +1 -0
- package/dist/tests/SwarmIndexer.test.d.ts +11 -0
- package/dist/tests/SwarmIndexer.test.d.ts.map +1 -0
- package/dist/tests/SwarmIndexer.test.js +374 -0
- package/dist/tests/SwarmIndexer.test.js.map +1 -0
- package/dist/tests/TieredCache.test.d.ts +7 -0
- package/dist/tests/TieredCache.test.d.ts.map +1 -0
- package/dist/tests/TieredCache.test.js +529 -0
- package/dist/tests/TieredCache.test.js.map +1 -0
- package/dist/tests/UsageAnalyticsService.test.d.ts +7 -0
- package/dist/tests/UsageAnalyticsService.test.d.ts.map +1 -0
- package/dist/tests/UsageAnalyticsService.test.js +238 -0
- package/dist/tests/UsageAnalyticsService.test.js.map +1 -0
- package/dist/tests/UsageTracker.test.d.ts +7 -0
- package/dist/tests/UsageTracker.test.d.ts.map +1 -0
- package/dist/tests/UsageTracker.test.js +196 -0
- package/dist/tests/UsageTracker.test.js.map +1 -0
- package/dist/tests/WebhookHandler.test.d.ts +10 -0
- package/dist/tests/WebhookHandler.test.d.ts.map +1 -0
- package/dist/tests/WebhookHandler.test.js +592 -0
- package/dist/tests/WebhookHandler.test.js.map +1 -0
- package/dist/tests/analytics/metrics-aggregator.test.d.ts +11 -0
- package/dist/tests/analytics/metrics-aggregator.test.d.ts.map +1 -0
- package/dist/tests/analytics/metrics-aggregator.test.js +273 -0
- package/dist/tests/analytics/metrics-aggregator.test.js.map +1 -0
- package/dist/tests/analytics/metrics-exporter.test.d.ts +11 -0
- package/dist/tests/analytics/metrics-exporter.test.d.ts.map +1 -0
- package/dist/tests/analytics/metrics-exporter.test.js +371 -0
- package/dist/tests/analytics/metrics-exporter.test.js.map +1 -0
- package/dist/tests/analytics/usage-tracker.test.d.ts +10 -0
- package/dist/tests/analytics/usage-tracker.test.d.ts.map +1 -0
- package/dist/tests/analytics/usage-tracker.test.js +151 -0
- package/dist/tests/analytics/usage-tracker.test.js.map +1 -0
- package/dist/tests/anonymizer.test.d.ts +8 -0
- package/dist/tests/anonymizer.test.d.ts.map +1 -0
- package/dist/tests/anonymizer.test.js +153 -0
- package/dist/tests/anonymizer.test.js.map +1 -0
- package/dist/tests/cache.test.d.ts +6 -0
- package/dist/tests/cache.test.d.ts.map +1 -0
- package/dist/tests/cache.test.js +170 -0
- package/dist/tests/cache.test.js.map +1 -0
- package/dist/tests/e2e/security/security.e2e.test.d.ts +8 -0
- package/dist/tests/e2e/security/security.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/security/security.e2e.test.js +448 -0
- package/dist/tests/e2e/security/security.e2e.test.js.map +1 -0
- package/dist/tests/edge-cases/EdgeCases.test.d.ts +13 -0
- package/dist/tests/edge-cases/EdgeCases.test.d.ts.map +1 -0
- package/dist/tests/edge-cases/EdgeCases.test.js +844 -0
- package/dist/tests/edge-cases/EdgeCases.test.js.map +1 -0
- package/dist/tests/import-github-skills.test.d.ts +8 -0
- package/dist/tests/import-github-skills.test.d.ts.map +1 -0
- package/dist/tests/import-github-skills.test.js +390 -0
- package/dist/tests/import-github-skills.test.js.map +1 -0
- package/dist/tests/logger.test.d.ts +2 -0
- package/dist/tests/logger.test.d.ts.map +1 -0
- package/dist/tests/logger.test.js +417 -0
- package/dist/tests/logger.test.js.map +1 -0
- package/dist/tests/performance/LargeScalePerformance.test.d.ts +14 -0
- package/dist/tests/performance/LargeScalePerformance.test.d.ts.map +1 -0
- package/dist/tests/performance/LargeScalePerformance.test.js +558 -0
- package/dist/tests/performance/LargeScalePerformance.test.js.map +1 -0
- package/dist/tests/retry.test.d.ts +7 -0
- package/dist/tests/retry.test.d.ts.map +1 -0
- package/dist/tests/retry.test.js +302 -0
- package/dist/tests/retry.test.js.map +1 -0
- package/dist/tests/sanitization.test.d.ts +8 -0
- package/dist/tests/sanitization.test.d.ts.map +1 -0
- package/dist/tests/sanitization.test.js +413 -0
- package/dist/tests/sanitization.test.js.map +1 -0
- package/dist/tests/schema.test.d.ts +5 -0
- package/dist/tests/schema.test.d.ts.map +1 -0
- package/dist/tests/schema.test.js +167 -0
- package/dist/tests/schema.test.js.map +1 -0
- package/dist/tests/scripts/import-to-database.test.d.ts +11 -0
- package/dist/tests/scripts/import-to-database.test.d.ts.map +1 -0
- package/dist/tests/scripts/import-to-database.test.js +325 -0
- package/dist/tests/scripts/import-to-database.test.js.map +1 -0
- package/dist/tests/security/ContinuousSecurity.test.d.ts +6 -0
- package/dist/tests/security/ContinuousSecurity.test.d.ts.map +1 -0
- package/dist/tests/security/ContinuousSecurity.test.js +595 -0
- package/dist/tests/security/ContinuousSecurity.test.js.map +1 -0
- package/dist/tests/security/ReDoS.test.d.ts +8 -0
- package/dist/tests/security/ReDoS.test.d.ts.map +1 -0
- package/dist/tests/security/ReDoS.test.js +213 -0
- package/dist/tests/security/ReDoS.test.js.map +1 -0
- package/dist/tests/security.test.d.ts +5 -0
- package/dist/tests/security.test.d.ts.map +1 -0
- package/dist/tests/security.test.js +134 -0
- package/dist/tests/security.test.js.map +1 -0
- package/dist/tests/shared.test.d.ts +7 -0
- package/dist/tests/shared.test.d.ts.map +1 -0
- package/dist/tests/shared.test.js +480 -0
- package/dist/tests/shared.test.js.map +1 -0
- package/dist/tests/sources.test.d.ts +5 -0
- package/dist/tests/sources.test.d.ts.map +1 -0
- package/dist/tests/sources.test.js +369 -0
- package/dist/tests/sources.test.js.map +1 -0
- package/dist/tests/stats.test.d.ts +11 -0
- package/dist/tests/stats.test.d.ts.map +1 -0
- package/dist/tests/stats.test.js +124 -0
- package/dist/tests/stats.test.js.map +1 -0
- package/dist/tests/telemetry.test.d.ts +11 -0
- package/dist/tests/telemetry.test.d.ts.map +1 -0
- package/dist/tests/telemetry.test.js +424 -0
- package/dist/tests/telemetry.test.js.map +1 -0
- package/dist/tests/test-utils.d.ts +74 -0
- package/dist/tests/test-utils.d.ts.map +1 -0
- package/dist/tests/test-utils.js +98 -0
- package/dist/tests/test-utils.js.map +1 -0
- package/dist/tests/validate-skills.test.d.ts +5 -0
- package/dist/tests/validate-skills.test.d.ts.map +1 -0
- package/dist/tests/validate-skills.test.js +649 -0
- package/dist/tests/validate-skills.test.js.map +1 -0
- package/dist/tests/validation.test.d.ts +7 -0
- package/dist/tests/validation.test.d.ts.map +1 -0
- package/dist/tests/validation.test.js +495 -0
- package/dist/tests/validation.test.js.map +1 -0
- package/dist/tests/webhooks/WebhookHandler.idempotency.test.d.ts +8 -0
- package/dist/tests/webhooks/WebhookHandler.idempotency.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookHandler.idempotency.test.js +190 -0
- package/dist/tests/webhooks/WebhookHandler.idempotency.test.js.map +1 -0
- package/dist/tests/webhooks/WebhookPayload.security.test.d.ts +8 -0
- package/dist/tests/webhooks/WebhookPayload.security.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookPayload.security.test.js +204 -0
- package/dist/tests/webhooks/WebhookPayload.security.test.js.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +13 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter - SMI-730, SMI-1013
|
|
3
|
+
*
|
|
4
|
+
* Token bucket algorithm for rate limiting API endpoints and adapters.
|
|
5
|
+
* Prevents abuse and DoS attacks with configurable limits and windows.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Token bucket algorithm for smooth rate limiting
|
|
9
|
+
* - Per-IP and per-user limits
|
|
10
|
+
* - Configurable limits and windows
|
|
11
|
+
* - In-memory storage (Redis-compatible interface)
|
|
12
|
+
* - Graceful degradation on errors
|
|
13
|
+
* - Request queue for waiting when rate limited (SMI-1013)
|
|
14
|
+
* - Configurable timeout for queued requests (SMI-1013)
|
|
15
|
+
*/
|
|
16
|
+
import { createLogger } from '../utils/logger.js';
|
|
17
|
+
import { randomUUID } from 'crypto';
|
|
18
|
+
const log = createLogger('RateLimiter');
|
|
19
|
+
/**
|
|
20
|
+
* Maximum number of unique keys to track in queues and metrics
|
|
21
|
+
* Prevents unbounded memory growth from malicious or misconfigured clients
|
|
22
|
+
*/
|
|
23
|
+
const MAX_UNIQUE_KEYS = 10000;
|
|
24
|
+
/**
|
|
25
|
+
* Metrics TTL in milliseconds (1 hour) - metrics older than this are cleaned up
|
|
26
|
+
*/
|
|
27
|
+
const METRICS_TTL_MS = 60 * 60 * 1000;
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when queue timeout is exceeded (SMI-1013)
|
|
30
|
+
*/
|
|
31
|
+
export class RateLimitQueueTimeoutError extends Error {
|
|
32
|
+
key;
|
|
33
|
+
timeoutMs;
|
|
34
|
+
constructor(key, timeoutMs) {
|
|
35
|
+
super(`Rate limit queue timeout exceeded for key '${key}' after ${timeoutMs}ms`);
|
|
36
|
+
this.key = key;
|
|
37
|
+
this.timeoutMs = timeoutMs;
|
|
38
|
+
this.name = 'RateLimitQueueTimeoutError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when queue is full (SMI-1013)
|
|
43
|
+
*/
|
|
44
|
+
export class RateLimitQueueFullError extends Error {
|
|
45
|
+
key;
|
|
46
|
+
maxQueueSize;
|
|
47
|
+
constructor(key, maxQueueSize) {
|
|
48
|
+
super(`Rate limit queue full for key '${key}' (max: ${maxQueueSize})`);
|
|
49
|
+
this.key = key;
|
|
50
|
+
this.maxQueueSize = maxQueueSize;
|
|
51
|
+
this.name = 'RateLimitQueueFullError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* In-memory storage implementation
|
|
56
|
+
*/
|
|
57
|
+
export class InMemoryRateLimitStorage {
|
|
58
|
+
store = new Map();
|
|
59
|
+
cleanupInterval = null;
|
|
60
|
+
constructor(cleanupIntervalMs = 60000) {
|
|
61
|
+
// Periodic cleanup of expired entries
|
|
62
|
+
this.cleanupInterval = setInterval(() => {
|
|
63
|
+
this.cleanup();
|
|
64
|
+
}, cleanupIntervalMs);
|
|
65
|
+
}
|
|
66
|
+
async get(key) {
|
|
67
|
+
const entry = this.store.get(key);
|
|
68
|
+
if (!entry)
|
|
69
|
+
return null;
|
|
70
|
+
// Check expiration
|
|
71
|
+
if (Date.now() > entry.expiresAt) {
|
|
72
|
+
this.store.delete(key);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return entry.bucket;
|
|
76
|
+
}
|
|
77
|
+
async set(key, value, ttlMs) {
|
|
78
|
+
this.store.set(key, {
|
|
79
|
+
bucket: value,
|
|
80
|
+
expiresAt: Date.now() + ttlMs,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async delete(key) {
|
|
84
|
+
this.store.delete(key);
|
|
85
|
+
}
|
|
86
|
+
async clear() {
|
|
87
|
+
this.store.clear();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clean up expired entries
|
|
91
|
+
*/
|
|
92
|
+
cleanup() {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
let cleaned = 0;
|
|
95
|
+
// Use Array.from to avoid downlevelIteration requirement
|
|
96
|
+
Array.from(this.store.entries()).forEach(([key, entry]) => {
|
|
97
|
+
if (now > entry.expiresAt) {
|
|
98
|
+
this.store.delete(key);
|
|
99
|
+
cleaned++;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (cleaned > 0) {
|
|
103
|
+
log.debug(`Cleaned up ${cleaned} expired rate limit entries`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Dispose of resources
|
|
108
|
+
*/
|
|
109
|
+
dispose() {
|
|
110
|
+
if (this.cleanupInterval) {
|
|
111
|
+
clearInterval(this.cleanupInterval);
|
|
112
|
+
this.cleanupInterval = null;
|
|
113
|
+
}
|
|
114
|
+
this.store.clear();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get storage stats (for testing/monitoring)
|
|
118
|
+
*/
|
|
119
|
+
getStats() {
|
|
120
|
+
return {
|
|
121
|
+
size: this.store.size,
|
|
122
|
+
keys: Array.from(this.store.keys()),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Rate Limiter using Token Bucket Algorithm
|
|
128
|
+
*
|
|
129
|
+
* The token bucket algorithm allows for burst traffic while maintaining
|
|
130
|
+
* a steady long-term rate. Each request consumes a token from the bucket.
|
|
131
|
+
* Tokens are refilled at a constant rate.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* // Create rate limiter: 100 requests per minute
|
|
136
|
+
* const limiter = new RateLimiter({
|
|
137
|
+
* maxTokens: 100,
|
|
138
|
+
* refillRate: 100 / 60, // ~1.67 tokens/sec
|
|
139
|
+
* windowMs: 60000,
|
|
140
|
+
* })
|
|
141
|
+
*
|
|
142
|
+
* // Check if request is allowed
|
|
143
|
+
* const result = await limiter.checkLimit('user:123')
|
|
144
|
+
* if (result.allowed) {
|
|
145
|
+
* // Process request
|
|
146
|
+
* } else {
|
|
147
|
+
* // Return 429 Too Many Requests
|
|
148
|
+
* // Retry after: result.retryAfterMs
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export class RateLimiter {
|
|
153
|
+
config;
|
|
154
|
+
storage;
|
|
155
|
+
metrics = new Map();
|
|
156
|
+
/** Queue of waiting requests per key (SMI-1013) */
|
|
157
|
+
queues = new Map();
|
|
158
|
+
/** Timer for processing queues (SMI-1013) */
|
|
159
|
+
queueProcessorInterval = null;
|
|
160
|
+
/** Timer for cleaning up stale metrics */
|
|
161
|
+
metricsCleanupInterval = null;
|
|
162
|
+
/** Lock for atomic token operations (prevents TOCTOU race conditions) */
|
|
163
|
+
operationLocks = new Map();
|
|
164
|
+
/** Flag to prevent concurrent queue processing */
|
|
165
|
+
isProcessingQueues = false;
|
|
166
|
+
constructor(config, storage = new InMemoryRateLimitStorage()) {
|
|
167
|
+
this.config = {
|
|
168
|
+
keyPrefix: 'ratelimit',
|
|
169
|
+
debug: false,
|
|
170
|
+
failMode: 'open',
|
|
171
|
+
enableQueue: false,
|
|
172
|
+
queueTimeoutMs: 30000,
|
|
173
|
+
maxQueueSize: 100,
|
|
174
|
+
...config,
|
|
175
|
+
};
|
|
176
|
+
this.storage = storage;
|
|
177
|
+
// Start queue processor if queuing is enabled (SMI-1013)
|
|
178
|
+
if (this.config.enableQueue) {
|
|
179
|
+
this.startQueueProcessor();
|
|
180
|
+
}
|
|
181
|
+
// Start metrics cleanup interval
|
|
182
|
+
this.startMetricsCleanup();
|
|
183
|
+
if (this.config.debug) {
|
|
184
|
+
log.info('Rate limiter initialized', {
|
|
185
|
+
maxTokens: this.config.maxTokens,
|
|
186
|
+
refillRate: this.config.refillRate,
|
|
187
|
+
windowMs: this.config.windowMs,
|
|
188
|
+
failMode: this.config.failMode,
|
|
189
|
+
enableQueue: this.config.enableQueue,
|
|
190
|
+
queueTimeoutMs: this.config.queueTimeoutMs,
|
|
191
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Start the queue processor that periodically checks for available tokens (SMI-1013)
|
|
197
|
+
*/
|
|
198
|
+
startQueueProcessor() {
|
|
199
|
+
// Check queue every 100ms
|
|
200
|
+
this.queueProcessorInterval = setInterval(() => {
|
|
201
|
+
this.processQueues();
|
|
202
|
+
}, 100);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Start periodic cleanup of stale metrics
|
|
206
|
+
*/
|
|
207
|
+
startMetricsCleanup() {
|
|
208
|
+
// Clean up stale metrics every 5 minutes
|
|
209
|
+
this.metricsCleanupInterval = setInterval(() => {
|
|
210
|
+
this.cleanupStaleMetrics();
|
|
211
|
+
}, 5 * 60 * 1000);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Clean up metrics older than METRICS_TTL_MS
|
|
215
|
+
*/
|
|
216
|
+
cleanupStaleMetrics() {
|
|
217
|
+
const now = new Date();
|
|
218
|
+
let cleaned = 0;
|
|
219
|
+
for (const [key, metrics] of this.metrics.entries()) {
|
|
220
|
+
if (now.getTime() - metrics.lastUpdated.getTime() > METRICS_TTL_MS) {
|
|
221
|
+
this.metrics.delete(key);
|
|
222
|
+
cleaned++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Also enforce MAX_UNIQUE_KEYS limit if somehow exceeded
|
|
226
|
+
if (this.metrics.size > MAX_UNIQUE_KEYS) {
|
|
227
|
+
// Sort by lastUpdated and remove oldest entries
|
|
228
|
+
const entries = Array.from(this.metrics.entries()).sort((a, b) => a[1].lastUpdated.getTime() - b[1].lastUpdated.getTime());
|
|
229
|
+
const toRemove = entries.slice(0, this.metrics.size - MAX_UNIQUE_KEYS);
|
|
230
|
+
for (const [key] of toRemove) {
|
|
231
|
+
this.metrics.delete(key);
|
|
232
|
+
cleaned++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (cleaned > 0 && this.config.debug) {
|
|
236
|
+
log.debug(`Cleaned up ${cleaned} stale metric entries`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Acquire a lock for atomic operations on a key
|
|
241
|
+
*/
|
|
242
|
+
async acquireLock(key) {
|
|
243
|
+
// Wait for any existing operation to complete
|
|
244
|
+
const existingLock = this.operationLocks.get(key);
|
|
245
|
+
if (existingLock) {
|
|
246
|
+
await existingLock;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Process all queues and release waiting requests when tokens become available (SMI-1013)
|
|
251
|
+
* Uses a flag to prevent concurrent processing
|
|
252
|
+
*/
|
|
253
|
+
async processQueues() {
|
|
254
|
+
// Prevent concurrent queue processing
|
|
255
|
+
if (this.isProcessingQueues) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.isProcessingQueues = true;
|
|
259
|
+
try {
|
|
260
|
+
for (const [key, queue] of this.queues.entries()) {
|
|
261
|
+
if (queue.length === 0) {
|
|
262
|
+
// Clean up empty queues to prevent memory leak
|
|
263
|
+
this.queues.delete(key);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Try to process the first request in the queue
|
|
267
|
+
const request = queue[0];
|
|
268
|
+
const result = await this.tryConsumeToken(key, request.cost);
|
|
269
|
+
if (result.allowed) {
|
|
270
|
+
// Remove from queue by ID (not by position for safety)
|
|
271
|
+
const index = queue.findIndex((r) => r.id === request.id);
|
|
272
|
+
if (index !== -1) {
|
|
273
|
+
queue.splice(index, 1);
|
|
274
|
+
}
|
|
275
|
+
// Clear timeout
|
|
276
|
+
clearTimeout(request.timeoutHandle);
|
|
277
|
+
// Resolve with queue info
|
|
278
|
+
const queueWaitMs = Date.now() - request.queuedAt;
|
|
279
|
+
request.resolve({
|
|
280
|
+
...result,
|
|
281
|
+
queued: true,
|
|
282
|
+
queueWaitMs,
|
|
283
|
+
});
|
|
284
|
+
if (this.config.debug) {
|
|
285
|
+
log.debug(`Queue request released for ${key}`, {
|
|
286
|
+
requestId: request.id,
|
|
287
|
+
queueWaitMs,
|
|
288
|
+
remaining: result.remaining,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
this.isProcessingQueues = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Try to consume a token without queuing (internal method)
|
|
300
|
+
*/
|
|
301
|
+
async tryConsumeToken(key, cost) {
|
|
302
|
+
const storageKey = `${this.config.keyPrefix}:${key}`;
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
try {
|
|
305
|
+
let bucket = await this.storage.get(storageKey);
|
|
306
|
+
if (!bucket) {
|
|
307
|
+
bucket = {
|
|
308
|
+
tokens: this.config.maxTokens,
|
|
309
|
+
lastRefill: now,
|
|
310
|
+
firstRequest: now,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Refill tokens based on elapsed time
|
|
314
|
+
const elapsedMs = now - bucket.lastRefill;
|
|
315
|
+
const elapsedSeconds = elapsedMs / 1000;
|
|
316
|
+
const tokensToAdd = elapsedSeconds * this.config.refillRate;
|
|
317
|
+
if (tokensToAdd > 0) {
|
|
318
|
+
bucket.tokens = Math.min(this.config.maxTokens, bucket.tokens + tokensToAdd);
|
|
319
|
+
bucket.lastRefill = now;
|
|
320
|
+
}
|
|
321
|
+
// Check if we have enough tokens
|
|
322
|
+
const allowed = bucket.tokens >= cost;
|
|
323
|
+
if (allowed) {
|
|
324
|
+
bucket.tokens -= cost;
|
|
325
|
+
await this.storage.set(storageKey, bucket, this.config.windowMs);
|
|
326
|
+
return {
|
|
327
|
+
allowed: true,
|
|
328
|
+
remaining: Math.floor(bucket.tokens),
|
|
329
|
+
limit: this.config.maxTokens,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
const tokensNeeded = cost - bucket.tokens;
|
|
334
|
+
const retryAfterMs = Math.ceil((tokensNeeded / this.config.refillRate) * 1000);
|
|
335
|
+
const resetAt = new Date(now + retryAfterMs).toISOString();
|
|
336
|
+
return {
|
|
337
|
+
allowed: false,
|
|
338
|
+
remaining: Math.floor(bucket.tokens),
|
|
339
|
+
limit: this.config.maxTokens,
|
|
340
|
+
retryAfterMs,
|
|
341
|
+
resetAt,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// On error, return based on fail mode
|
|
347
|
+
if (this.config.failMode === 'closed') {
|
|
348
|
+
return {
|
|
349
|
+
allowed: false,
|
|
350
|
+
remaining: 0,
|
|
351
|
+
limit: this.config.maxTokens,
|
|
352
|
+
retryAfterMs: this.config.windowMs,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
allowed: true,
|
|
357
|
+
remaining: this.config.maxTokens,
|
|
358
|
+
limit: this.config.maxTokens,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Update metrics for a key with bounds checking
|
|
364
|
+
*/
|
|
365
|
+
updateMetrics(key, allowed, error = false) {
|
|
366
|
+
// Check if we've hit the max unique keys limit
|
|
367
|
+
if (!this.metrics.has(key) && this.metrics.size >= MAX_UNIQUE_KEYS) {
|
|
368
|
+
// Evict oldest entry before adding new one
|
|
369
|
+
let oldestKey = null;
|
|
370
|
+
let oldestTime = Infinity;
|
|
371
|
+
for (const [k, m] of this.metrics.entries()) {
|
|
372
|
+
if (m.lastUpdated.getTime() < oldestTime) {
|
|
373
|
+
oldestTime = m.lastUpdated.getTime();
|
|
374
|
+
oldestKey = k;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (oldestKey) {
|
|
378
|
+
this.metrics.delete(oldestKey);
|
|
379
|
+
if (this.config.debug) {
|
|
380
|
+
log.debug(`Evicted oldest metrics entry: ${oldestKey}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const now = new Date();
|
|
385
|
+
const existing = this.metrics.get(key) || {
|
|
386
|
+
allowed: 0,
|
|
387
|
+
blocked: 0,
|
|
388
|
+
errors: 0,
|
|
389
|
+
lastReset: now,
|
|
390
|
+
lastUpdated: now,
|
|
391
|
+
};
|
|
392
|
+
if (error) {
|
|
393
|
+
existing.errors++;
|
|
394
|
+
// Also track allowed/blocked for error cases (fail-open vs fail-closed)
|
|
395
|
+
if (allowed) {
|
|
396
|
+
existing.allowed++;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
existing.blocked++;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (allowed) {
|
|
403
|
+
existing.allowed++;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
existing.blocked++;
|
|
407
|
+
}
|
|
408
|
+
existing.lastUpdated = now;
|
|
409
|
+
this.metrics.set(key, existing);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if a request is allowed under rate limit
|
|
413
|
+
*
|
|
414
|
+
* @param key - Unique identifier (e.g., 'ip:192.168.1.1' or 'user:123')
|
|
415
|
+
* @param cost - Number of tokens to consume (default: 1)
|
|
416
|
+
* @returns Rate limit result
|
|
417
|
+
*/
|
|
418
|
+
async checkLimit(key, cost = 1) {
|
|
419
|
+
const storageKey = `${this.config.keyPrefix}:${key}`;
|
|
420
|
+
const now = Date.now();
|
|
421
|
+
try {
|
|
422
|
+
// Get current bucket state
|
|
423
|
+
let bucket = await this.storage.get(storageKey);
|
|
424
|
+
if (!bucket) {
|
|
425
|
+
// Initialize new bucket
|
|
426
|
+
bucket = {
|
|
427
|
+
tokens: this.config.maxTokens,
|
|
428
|
+
lastRefill: now,
|
|
429
|
+
firstRequest: now,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
// Refill tokens based on elapsed time
|
|
433
|
+
const elapsedMs = now - bucket.lastRefill;
|
|
434
|
+
const elapsedSeconds = elapsedMs / 1000;
|
|
435
|
+
const tokensToAdd = elapsedSeconds * this.config.refillRate;
|
|
436
|
+
if (tokensToAdd > 0) {
|
|
437
|
+
bucket.tokens = Math.min(this.config.maxTokens, bucket.tokens + tokensToAdd);
|
|
438
|
+
bucket.lastRefill = now;
|
|
439
|
+
}
|
|
440
|
+
// Check if we have enough tokens
|
|
441
|
+
const allowed = bucket.tokens >= cost;
|
|
442
|
+
if (allowed) {
|
|
443
|
+
// Consume tokens
|
|
444
|
+
bucket.tokens -= cost;
|
|
445
|
+
// Save updated bucket
|
|
446
|
+
await this.storage.set(storageKey, bucket, this.config.windowMs);
|
|
447
|
+
if (this.config.debug) {
|
|
448
|
+
log.debug(`Rate limit check: ${key}`, {
|
|
449
|
+
allowed: true,
|
|
450
|
+
remaining: bucket.tokens,
|
|
451
|
+
cost,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
// Track metrics for allowed request
|
|
455
|
+
this.updateMetrics(key, true);
|
|
456
|
+
return {
|
|
457
|
+
allowed: true,
|
|
458
|
+
remaining: Math.floor(bucket.tokens),
|
|
459
|
+
limit: this.config.maxTokens,
|
|
460
|
+
metrics: this.metrics.get(key),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// Not enough tokens - calculate retry time
|
|
465
|
+
const tokensNeeded = cost - bucket.tokens;
|
|
466
|
+
const retryAfterMs = Math.ceil((tokensNeeded / this.config.refillRate) * 1000);
|
|
467
|
+
const resetAt = new Date(now + retryAfterMs).toISOString();
|
|
468
|
+
// Don't update bucket since we're denying the request
|
|
469
|
+
if (this.config.debug) {
|
|
470
|
+
log.debug(`Rate limit exceeded: ${key}`, {
|
|
471
|
+
allowed: false,
|
|
472
|
+
remaining: bucket.tokens,
|
|
473
|
+
cost,
|
|
474
|
+
retryAfterMs,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// Track metrics for blocked request
|
|
478
|
+
this.updateMetrics(key, false);
|
|
479
|
+
// Call onLimitExceeded callback if configured
|
|
480
|
+
const currentMetrics = this.metrics.get(key);
|
|
481
|
+
if (this.config.onLimitExceeded && currentMetrics) {
|
|
482
|
+
this.config.onLimitExceeded(key, currentMetrics);
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
allowed: false,
|
|
486
|
+
remaining: Math.floor(bucket.tokens),
|
|
487
|
+
limit: this.config.maxTokens,
|
|
488
|
+
retryAfterMs,
|
|
489
|
+
resetAt,
|
|
490
|
+
metrics: currentMetrics,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
// Track error in metrics
|
|
496
|
+
this.updateMetrics(key, this.config.failMode === 'open', true);
|
|
497
|
+
if (this.config.failMode === 'closed') {
|
|
498
|
+
// Fail-closed: deny requests on storage errors (for high-security endpoints)
|
|
499
|
+
log.error(`Rate limiter error (fail-closed) for ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
500
|
+
return {
|
|
501
|
+
allowed: false,
|
|
502
|
+
remaining: 0,
|
|
503
|
+
limit: this.config.maxTokens,
|
|
504
|
+
retryAfterMs: this.config.windowMs,
|
|
505
|
+
resetAt: new Date(Date.now() + this.config.windowMs).toISOString(),
|
|
506
|
+
metrics: this.metrics.get(key),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
// Fail-open: allow request on storage errors (graceful degradation)
|
|
510
|
+
log.error(`Rate limiter error (fail-open) for ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
511
|
+
return {
|
|
512
|
+
allowed: true,
|
|
513
|
+
remaining: this.config.maxTokens,
|
|
514
|
+
limit: this.config.maxTokens,
|
|
515
|
+
metrics: this.metrics.get(key),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Wait for a token to become available (SMI-1013)
|
|
521
|
+
*
|
|
522
|
+
* If tokens are available, consumes immediately.
|
|
523
|
+
* If not, queues the request and waits until tokens become available or timeout.
|
|
524
|
+
*
|
|
525
|
+
* @param key - Unique identifier (e.g., 'ip:192.168.1.1' or 'adapter:github')
|
|
526
|
+
* @param cost - Number of tokens to consume (default: 1)
|
|
527
|
+
* @returns Promise that resolves when token is available
|
|
528
|
+
* @throws RateLimitQueueTimeoutError if timeout is exceeded
|
|
529
|
+
* @throws RateLimitQueueFullError if queue is at capacity
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```typescript
|
|
533
|
+
* const limiter = new RateLimiter({
|
|
534
|
+
* maxTokens: 10,
|
|
535
|
+
* refillRate: 1,
|
|
536
|
+
* windowMs: 60000,
|
|
537
|
+
* enableQueue: true,
|
|
538
|
+
* queueTimeoutMs: 30000,
|
|
539
|
+
* })
|
|
540
|
+
*
|
|
541
|
+
* // Will wait up to 30s for token
|
|
542
|
+
* const result = await limiter.waitForToken('adapter:github')
|
|
543
|
+
* if (result.queued) {
|
|
544
|
+
* console.log(`Waited ${result.queueWaitMs}ms in queue`)
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
async waitForToken(key, cost = 1) {
|
|
549
|
+
if (!this.config.enableQueue) {
|
|
550
|
+
// Queue not enabled, fall back to checkLimit behavior
|
|
551
|
+
return this.checkLimit(key, cost);
|
|
552
|
+
}
|
|
553
|
+
// First, try to get token immediately
|
|
554
|
+
const immediateResult = await this.tryConsumeToken(key, cost);
|
|
555
|
+
if (immediateResult.allowed) {
|
|
556
|
+
this.updateMetrics(key, true);
|
|
557
|
+
return {
|
|
558
|
+
...immediateResult,
|
|
559
|
+
queued: false,
|
|
560
|
+
metrics: this.metrics.get(key),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
// Check queue size
|
|
564
|
+
const queue = this.queues.get(key) || [];
|
|
565
|
+
if (queue.length >= this.config.maxQueueSize) {
|
|
566
|
+
this.updateMetrics(key, false);
|
|
567
|
+
throw new RateLimitQueueFullError(key, this.config.maxQueueSize);
|
|
568
|
+
}
|
|
569
|
+
// Check if adding new queue would exceed max unique keys
|
|
570
|
+
if (!this.queues.has(key) && this.queues.size >= MAX_UNIQUE_KEYS) {
|
|
571
|
+
this.updateMetrics(key, false);
|
|
572
|
+
throw new RateLimitQueueFullError(key, this.config.maxQueueSize);
|
|
573
|
+
}
|
|
574
|
+
// Queue the request
|
|
575
|
+
return new Promise((resolve, reject) => {
|
|
576
|
+
// Use UUID for unique identification (not timestamp which can collide)
|
|
577
|
+
const requestId = randomUUID();
|
|
578
|
+
const queuedAt = Date.now();
|
|
579
|
+
// Set up timeout
|
|
580
|
+
const timeoutHandle = setTimeout(() => {
|
|
581
|
+
// Remove from queue by unique ID
|
|
582
|
+
const currentQueue = this.queues.get(key) || [];
|
|
583
|
+
const index = currentQueue.findIndex((r) => r.id === requestId);
|
|
584
|
+
if (index !== -1) {
|
|
585
|
+
currentQueue.splice(index, 1);
|
|
586
|
+
}
|
|
587
|
+
this.updateMetrics(key, false);
|
|
588
|
+
reject(new RateLimitQueueTimeoutError(key, this.config.queueTimeoutMs));
|
|
589
|
+
}, this.config.queueTimeoutMs);
|
|
590
|
+
const request = {
|
|
591
|
+
id: requestId,
|
|
592
|
+
resolve: (result) => {
|
|
593
|
+
this.updateMetrics(key, true);
|
|
594
|
+
resolve({
|
|
595
|
+
...result,
|
|
596
|
+
metrics: this.metrics.get(key),
|
|
597
|
+
});
|
|
598
|
+
},
|
|
599
|
+
reject,
|
|
600
|
+
cost,
|
|
601
|
+
queuedAt,
|
|
602
|
+
timeoutHandle,
|
|
603
|
+
};
|
|
604
|
+
// Add to queue
|
|
605
|
+
if (!this.queues.has(key)) {
|
|
606
|
+
this.queues.set(key, []);
|
|
607
|
+
}
|
|
608
|
+
this.queues.get(key).push(request);
|
|
609
|
+
if (this.config.debug) {
|
|
610
|
+
log.debug(`Request queued for ${key}`, {
|
|
611
|
+
requestId,
|
|
612
|
+
queueSize: this.queues.get(key).length,
|
|
613
|
+
cost,
|
|
614
|
+
timeoutMs: this.config.queueTimeoutMs,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Get queue status for a key (SMI-1013)
|
|
621
|
+
*
|
|
622
|
+
* @param key - Optional key to get queue status for
|
|
623
|
+
* @returns Queue size and waiting requests info
|
|
624
|
+
*/
|
|
625
|
+
getQueueStatus(key) {
|
|
626
|
+
if (key) {
|
|
627
|
+
return this.queues.get(key)?.length ?? 0;
|
|
628
|
+
}
|
|
629
|
+
const queues = new Map();
|
|
630
|
+
let totalQueued = 0;
|
|
631
|
+
for (const [k, q] of this.queues.entries()) {
|
|
632
|
+
queues.set(k, q.length);
|
|
633
|
+
totalQueued += q.length;
|
|
634
|
+
}
|
|
635
|
+
return { totalQueued, queues };
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Clear queue for a key (SMI-1013)
|
|
639
|
+
*
|
|
640
|
+
* Rejects all waiting requests with a timeout error.
|
|
641
|
+
*
|
|
642
|
+
* @param key - Optional key to clear queue for (clears all if not specified)
|
|
643
|
+
*/
|
|
644
|
+
clearQueue(key) {
|
|
645
|
+
const clearQueueForKey = (k) => {
|
|
646
|
+
const queue = this.queues.get(k);
|
|
647
|
+
if (queue) {
|
|
648
|
+
for (const request of queue) {
|
|
649
|
+
clearTimeout(request.timeoutHandle);
|
|
650
|
+
request.reject(new RateLimitQueueTimeoutError(k, 0));
|
|
651
|
+
}
|
|
652
|
+
this.queues.delete(k);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
if (key) {
|
|
656
|
+
clearQueueForKey(key);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
for (const k of this.queues.keys()) {
|
|
660
|
+
clearQueueForKey(k);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (this.config.debug) {
|
|
664
|
+
log.debug(`Queue cleared${key ? ` for key: ${key}` : ' (all)'}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Reset rate limit for a key (e.g., after authentication)
|
|
669
|
+
*/
|
|
670
|
+
async reset(key) {
|
|
671
|
+
const storageKey = `${this.config.keyPrefix}:${key}`;
|
|
672
|
+
await this.storage.delete(storageKey);
|
|
673
|
+
if (this.config.debug) {
|
|
674
|
+
log.debug(`Rate limit reset: ${key}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Get current state for a key (for monitoring/debugging)
|
|
679
|
+
*/
|
|
680
|
+
async getState(key) {
|
|
681
|
+
const storageKey = `${this.config.keyPrefix}:${key}`;
|
|
682
|
+
return await this.storage.get(storageKey);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Get metrics for a specific key or all keys
|
|
686
|
+
*
|
|
687
|
+
* @param key - Optional key to get metrics for
|
|
688
|
+
* @returns Metrics for the key, or all metrics if no key specified
|
|
689
|
+
*/
|
|
690
|
+
getMetrics(key) {
|
|
691
|
+
if (key) {
|
|
692
|
+
return this.metrics.get(key);
|
|
693
|
+
}
|
|
694
|
+
return new Map(this.metrics);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Reset metrics for a specific key or all keys
|
|
698
|
+
*
|
|
699
|
+
* @param key - Optional key to reset metrics for
|
|
700
|
+
*/
|
|
701
|
+
resetMetrics(key) {
|
|
702
|
+
if (key) {
|
|
703
|
+
this.metrics.delete(key);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
this.metrics.clear();
|
|
707
|
+
}
|
|
708
|
+
if (this.config.debug) {
|
|
709
|
+
log.debug(`Metrics reset${key ? ` for key: ${key}` : ' (all)'}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Dispose of resources
|
|
714
|
+
*/
|
|
715
|
+
dispose() {
|
|
716
|
+
// Stop queue processor (SMI-1013)
|
|
717
|
+
if (this.queueProcessorInterval) {
|
|
718
|
+
clearInterval(this.queueProcessorInterval);
|
|
719
|
+
this.queueProcessorInterval = null;
|
|
720
|
+
}
|
|
721
|
+
// Stop metrics cleanup
|
|
722
|
+
if (this.metricsCleanupInterval) {
|
|
723
|
+
clearInterval(this.metricsCleanupInterval);
|
|
724
|
+
this.metricsCleanupInterval = null;
|
|
725
|
+
}
|
|
726
|
+
// Clear all queues (SMI-1013)
|
|
727
|
+
this.clearQueue();
|
|
728
|
+
// Clear operation locks
|
|
729
|
+
this.operationLocks.clear();
|
|
730
|
+
if (this.storage instanceof InMemoryRateLimitStorage) {
|
|
731
|
+
this.storage.dispose();
|
|
732
|
+
}
|
|
733
|
+
this.metrics.clear();
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Preset rate limit configurations
|
|
738
|
+
*/
|
|
739
|
+
export const RATE_LIMIT_PRESETS = {
|
|
740
|
+
/** Very strict: 10 requests per minute, fail-closed for high security */
|
|
741
|
+
STRICT: {
|
|
742
|
+
maxTokens: 10,
|
|
743
|
+
refillRate: 10 / 60, // 0.167 tokens/sec
|
|
744
|
+
windowMs: 60000,
|
|
745
|
+
failMode: 'closed',
|
|
746
|
+
},
|
|
747
|
+
/** Standard: 30 requests per minute (default for adapters) */
|
|
748
|
+
STANDARD: {
|
|
749
|
+
maxTokens: 30,
|
|
750
|
+
refillRate: 30 / 60, // 0.5 tokens/sec
|
|
751
|
+
windowMs: 60000,
|
|
752
|
+
failMode: 'open',
|
|
753
|
+
},
|
|
754
|
+
/** Relaxed: 60 requests per minute */
|
|
755
|
+
RELAXED: {
|
|
756
|
+
maxTokens: 60,
|
|
757
|
+
refillRate: 60 / 60, // 1 token/sec
|
|
758
|
+
windowMs: 60000,
|
|
759
|
+
failMode: 'open',
|
|
760
|
+
},
|
|
761
|
+
/** Generous: 120 requests per minute */
|
|
762
|
+
GENEROUS: {
|
|
763
|
+
maxTokens: 120,
|
|
764
|
+
refillRate: 120 / 60, // 2 tokens/sec
|
|
765
|
+
windowMs: 60000,
|
|
766
|
+
failMode: 'open',
|
|
767
|
+
},
|
|
768
|
+
/** High throughput: 300 requests per minute */
|
|
769
|
+
HIGH_THROUGHPUT: {
|
|
770
|
+
maxTokens: 300,
|
|
771
|
+
refillRate: 300 / 60, // 5 tokens/sec
|
|
772
|
+
windowMs: 60000,
|
|
773
|
+
failMode: 'open',
|
|
774
|
+
},
|
|
775
|
+
};
|
|
776
|
+
/**
|
|
777
|
+
* Create a rate limiter from a preset
|
|
778
|
+
*/
|
|
779
|
+
export function createRateLimiterFromPreset(preset, storage) {
|
|
780
|
+
return new RateLimiter(RATE_LIMIT_PRESETS[preset], storage);
|
|
781
|
+
}
|
|
782
|
+
//# sourceMappingURL=RateLimiter.js.map
|