/** * Auto-refresh scheduler with rate-limited registry polling. * * {@link RefreshScheduler} orchestrates periodic re-fetching of npm package * metadata and re-running the static analyzer for packages on the user's * watchlist or with stale cache entries. It is a pure scheduling + * orchestration layer: it contains no GUI integration and no CLI command * logic. All registry calls are gated through the injected {@link TokenBucket} * so the scheduler respects the configured rate limit, and progress is * surfaced via three {@link EventEmitter} events: * * - `refresh:start` — emitted before each package refresh begins. * - `refresh:complete`— emitted after a package refresh succeeds, carrying * the freshly produced {@link StaticScanReport}. * - `refresh:error` — emitted when a package refresh fails. The scheduler * does **not** throw on per-package failures; it emits the error and * continues with the next package. * * @module scheduler/refresh-scheduler */ import { EventEmitter } from 'node:events'; import type { NpmRegistryClient } from '../registry/client.js'; import type { CacheManager } from '../store/cache-manager.js'; import type { TokenBucket } from './rate-limiter.js'; import type { StaticAnalyzer } from '../scanner/static-rules.js'; import type { StaticScanReport } from '../scanner/types.js'; import type { LlmScanReport } from '../scanner/types.js'; import type { LlmScanProvider } from '../llm/provider.js'; /** * Payload of the `refresh:start` event. */ export interface RefreshStartPayload { /** Name of the package about to be refreshed. */ readonly packageName: string; } /** * Payload of the `refresh:complete` event. */ export interface RefreshCompletePayload { /** Name of the package that was refreshed. */ readonly packageName: string; /** The static scan report produced during the refresh. */ readonly report: StaticScanReport; readonly llmScan?: LlmScanReport; } /** * Payload of the `refresh:error` event. */ export interface RefreshErrorPayload { /** Name of the package whose refresh failed. */ readonly packageName: string; /** The error that caused the refresh to fail. */ readonly error: unknown; } /** * Auto-refresh scheduler that periodically polls the npm registry for * updated package metadata, re-caches it, and re-runs the static analyzer. * * The scheduler is constructed with four collaborators: * * - {@link NpmRegistryClient} — used to fetch fresh packuments. * - {@link CacheManager} — used to read the watchlist/stale set and persist * refreshed metadata + security reports. * - {@link TokenBucket} — gates every registry call so the configured rate * limit is respected. * - {@link StaticAnalyzer} — re-evaluates the refreshed package's README and * latest version manifest. * * Call {@link RefreshScheduler.start} to begin the refresh loop and * {@link RefreshScheduler.stop} to tear it down. * * @example * ```ts * const scheduler = new RefreshScheduler(client, cache, limiter, analyzer); * scheduler.on('refresh:complete', ({ packageName, report }) => { * console.log(`${packageName}: score=${report.score}`); * }); * scheduler.start(); // refresh hourly * // ... later * scheduler.stop(); * ``` */ export declare class RefreshScheduler extends EventEmitter { /** Registry client used to fetch fresh packuments. */ private readonly client; /** Cache manager used to read the watchlist and persist results. */ private readonly cache; /** Rate limiter gating every registry call. */ private readonly limiter; /** Static analyzer re-run on each refreshed package. */ private readonly analyzer; /** Optional semantic analyzer for refreshed packages (mutable at runtime). */ private llmProvider; /** Handle to the recurring refresh interval, or `null` when stopped. */ private intervalId; /** * @param client - Registry client for fetching package metadata. * @param cache - Cache manager for reading the watchlist and persisting * refreshed metadata + reports. * @param limiter - Token bucket rate limiter; one token is consumed per * registry fetch. * @param analyzer - Static analyzer re-run on each refreshed package. * @param llmProvider - Optional semantic analyzer for refreshed packages, or * a getter that returns the current provider. A getter is preferred when the * provider can be changed at runtime. */ constructor(client: NpmRegistryClient, cache: CacheManager, limiter: TokenBucket, analyzer: StaticAnalyzer, llmProvider?: LlmScanProvider | (() => LlmScanProvider | undefined)); /** * Replace the LLM provider used during refreshes. * * Safe to call while the scheduler is running; the next refresh cycle uses * the new provider. * * @param provider - The new provider, or `undefined` to disable LLM scanning. */ setLlmProvider(provider?: LlmScanProvider): void; /** Resolve the current LLM provider. */ private getLlmProvider; /** * Start the periodic refresh loop. * * Immediately kicks off a background {@link RefreshScheduler.refreshWatchlist} * call (without awaiting it), then schedules the same call to repeat every * `intervalMs` milliseconds. Safe to call multiple times; calling while * already running clears the previous interval before starting a new one. * * @param intervalMs - Milliseconds between refresh cycles. Defaults to * {@link DEFAULT_INTERVAL_MS} (1 hour). */ start(intervalMs?: number): void; /** * Stop the periodic refresh loop. * * Clears the recurring interval. Any in-flight refresh that is already * executing continues to completion; this method only prevents future * cycles from being scheduled. Safe to call when not running. */ stop(): void; /** * Refresh a single package: fetch its latest metadata from the registry, * persist it to the cache, re-run the static analyzer against the latest * version's README and manifest, and persist the resulting report. * * One token is consumed from the rate limiter before the registry fetch, * so concurrent callers are naturally throttled. Per-package failures are * surfaced via the `refresh:error` event rather than thrown — the promise * resolves normally after emitting the error so a failing package does not * abort a batch. * * **Cooperative cancellation**: when `options.signal` is supplied it is * forwarded to the registry client and the LLM scan. An external abort * propagates as `DOMException('The operation was aborted.', 'AbortError')` * (instead of the usual `false` return) so a caller awaiting refresh can * observe the abort promptly. * * @param name - Fully-qualified package name (scope included when scoped). * @param options - Optional refresh options. `options.signal` is an * external abort signal forwarded to the registry fetch and LLM scan. * @returns `true` when the refresh succeeds, or `false` after emitting * `refresh:error`. Never rejects for per-package refresh failures — * only rejects with `AbortError` when `options.signal` is aborted. */ refreshPackage(name: string, options?: { readonly signal?: AbortSignal; }): Promise; private scanWithLlm; /** * Refresh every package whose cached metadata has passed its TTL. * * Packages are processed sequentially (one after another) so the rate * limiter is respected and the registry is not hammered in bursts. Each * package's outcome is surfaced via the `refresh:complete` / `refresh:error` * events emitted by {@link RefreshScheduler.refreshPackage}. * * **Cooperative cancellation**: when `options.signal` is supplied, the top * of each loop iteration checks `signal.aborted` and throws * `DOMException('The operation was aborted.', 'AbortError')` so a * mid-batch abort settles promptly instead of continuing to iterate. * * @param options - Optional refresh options. `options.signal` is an * external abort signal forwarded to each per-package refresh. * @returns `true` when every stale package refresh succeeds, otherwise * `false` after all stale packages have been attempted. Rejects with * `AbortError` when `options.signal` is aborted. */ refreshAll(options?: { readonly signal?: AbortSignal; }): Promise; /** * Refresh every package on the user's watchlist. * * Watched packages are processed sequentially so the rate limiter is * respected. Each package's outcome is surfaced via the * `refresh:complete` / `refresh:error` events emitted by * {@link RefreshScheduler.refreshPackage}. * * @returns Resolves once all watched packages have been attempted. */ private refreshWatchlist; } //# sourceMappingURL=refresh-scheduler.d.ts.map