@npm-safe/core-dsh 1.0.5
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/LICENSE +204 -0
- package/dist/index.d.ts +513 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +711 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +47 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +161 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/gemini.d.ts +47 -0
- package/dist/llm/gemini.d.ts.map +1 -0
- package/dist/llm/gemini.js +165 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/llm-config.d.ts +97 -0
- package/dist/llm/llm-config.d.ts.map +1 -0
- package/dist/llm/llm-config.js +188 -0
- package/dist/llm/llm-config.js.map +1 -0
- package/dist/llm/parse.d.ts +95 -0
- package/dist/llm/parse.d.ts.map +1 -0
- package/dist/llm/parse.js +158 -0
- package/dist/llm/parse.js.map +1 -0
- package/dist/llm/provider.d.ts +122 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +206 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/registry/client.d.ts +164 -0
- package/dist/registry/client.d.ts.map +1 -0
- package/dist/registry/client.js +378 -0
- package/dist/registry/client.js.map +1 -0
- package/dist/registry/types.d.ts +226 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +32 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/validator.d.ts +87 -0
- package/dist/registry/validator.d.ts.map +1 -0
- package/dist/registry/validator.js +214 -0
- package/dist/registry/validator.js.map +1 -0
- package/dist/scanner/ci-scan.d.ts +82 -0
- package/dist/scanner/ci-scan.d.ts.map +1 -0
- package/dist/scanner/ci-scan.js +130 -0
- package/dist/scanner/ci-scan.js.map +1 -0
- package/dist/scanner/rule-config.d.ts +61 -0
- package/dist/scanner/rule-config.d.ts.map +1 -0
- package/dist/scanner/rule-config.js +103 -0
- package/dist/scanner/rule-config.js.map +1 -0
- package/dist/scanner/rule-loader.d.ts +28 -0
- package/dist/scanner/rule-loader.d.ts.map +1 -0
- package/dist/scanner/rule-loader.js +67 -0
- package/dist/scanner/rule-loader.js.map +1 -0
- package/dist/scanner/static-rules.d.ts +88 -0
- package/dist/scanner/static-rules.d.ts.map +1 -0
- package/dist/scanner/static-rules.js +723 -0
- package/dist/scanner/static-rules.js.map +1 -0
- package/dist/scanner/types.d.ts +177 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +53 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scheduler/rate-limiter.d.ts +74 -0
- package/dist/scheduler/rate-limiter.d.ts.map +1 -0
- package/dist/scheduler/rate-limiter.js +182 -0
- package/dist/scheduler/rate-limiter.js.map +1 -0
- package/dist/scheduler/refresh-scheduler.d.ts +201 -0
- package/dist/scheduler/refresh-scheduler.d.ts.map +1 -0
- package/dist/scheduler/refresh-scheduler.js +295 -0
- package/dist/scheduler/refresh-scheduler.js.map +1 -0
- package/dist/store/cache-manager.d.ts +166 -0
- package/dist/store/cache-manager.d.ts.map +1 -0
- package/dist/store/cache-manager.js +356 -0
- package/dist/store/cache-manager.js.map +1 -0
- package/dist/store/database.d.ts +81 -0
- package/dist/store/database.d.ts.map +1 -0
- package/dist/store/database.js +182 -0
- package/dist/store/database.js.map +1 -0
- package/dist/store/schema.d.ts +42 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +126 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/translator/provider.d.ts +152 -0
- package/dist/translator/provider.d.ts.map +1 -0
- package/dist/translator/provider.js +159 -0
- package/dist/translator/provider.js.map +1 -0
- package/dist/translator/types.d.ts +83 -0
- package/dist/translator/types.d.ts.map +1 -0
- package/dist/translator/types.js +58 -0
- package/dist/translator/types.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-refresh scheduler with rate-limited registry polling.
|
|
3
|
+
*
|
|
4
|
+
* {@link RefreshScheduler} orchestrates periodic re-fetching of npm package
|
|
5
|
+
* metadata and re-running the static analyzer for packages on the user's
|
|
6
|
+
* watchlist or with stale cache entries. It is a pure scheduling +
|
|
7
|
+
* orchestration layer: it contains no GUI integration and no CLI command
|
|
8
|
+
* logic. All registry calls are gated through the injected {@link TokenBucket}
|
|
9
|
+
* so the scheduler respects the configured rate limit, and progress is
|
|
10
|
+
* surfaced via three {@link EventEmitter} events:
|
|
11
|
+
*
|
|
12
|
+
* - `refresh:start` — emitted before each package refresh begins.
|
|
13
|
+
* - `refresh:complete`— emitted after a package refresh succeeds, carrying
|
|
14
|
+
* the freshly produced {@link StaticScanReport}.
|
|
15
|
+
* - `refresh:error` — emitted when a package refresh fails. The scheduler
|
|
16
|
+
* does **not** throw on per-package failures; it emits the error and
|
|
17
|
+
* continues with the next package.
|
|
18
|
+
*
|
|
19
|
+
* @module scheduler/refresh-scheduler
|
|
20
|
+
*/
|
|
21
|
+
import { EventEmitter } from 'node:events';
|
|
22
|
+
import type { NpmRegistryClient } from '../registry/client.js';
|
|
23
|
+
import type { CacheManager } from '../store/cache-manager.js';
|
|
24
|
+
import type { TokenBucket } from './rate-limiter.js';
|
|
25
|
+
import type { StaticAnalyzer } from '../scanner/static-rules.js';
|
|
26
|
+
import type { StaticScanReport } from '../scanner/types.js';
|
|
27
|
+
import type { LlmScanReport } from '../scanner/types.js';
|
|
28
|
+
import type { LlmScanProvider } from '../llm/provider.js';
|
|
29
|
+
/**
|
|
30
|
+
* Payload of the `refresh:start` event.
|
|
31
|
+
*/
|
|
32
|
+
export interface RefreshStartPayload {
|
|
33
|
+
/** Name of the package about to be refreshed. */
|
|
34
|
+
readonly packageName: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Payload of the `refresh:complete` event.
|
|
38
|
+
*/
|
|
39
|
+
export interface RefreshCompletePayload {
|
|
40
|
+
/** Name of the package that was refreshed. */
|
|
41
|
+
readonly packageName: string;
|
|
42
|
+
/** The static scan report produced during the refresh. */
|
|
43
|
+
readonly report: StaticScanReport;
|
|
44
|
+
readonly llmScan?: LlmScanReport;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Payload of the `refresh:error` event.
|
|
48
|
+
*/
|
|
49
|
+
export interface RefreshErrorPayload {
|
|
50
|
+
/** Name of the package whose refresh failed. */
|
|
51
|
+
readonly packageName: string;
|
|
52
|
+
/** The error that caused the refresh to fail. */
|
|
53
|
+
readonly error: unknown;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Auto-refresh scheduler that periodically polls the npm registry for
|
|
57
|
+
* updated package metadata, re-caches it, and re-runs the static analyzer.
|
|
58
|
+
*
|
|
59
|
+
* The scheduler is constructed with four collaborators:
|
|
60
|
+
*
|
|
61
|
+
* - {@link NpmRegistryClient} — used to fetch fresh packuments.
|
|
62
|
+
* - {@link CacheManager} — used to read the watchlist/stale set and persist
|
|
63
|
+
* refreshed metadata + security reports.
|
|
64
|
+
* - {@link TokenBucket} — gates every registry call so the configured rate
|
|
65
|
+
* limit is respected.
|
|
66
|
+
* - {@link StaticAnalyzer} — re-evaluates the refreshed package's README and
|
|
67
|
+
* latest version manifest.
|
|
68
|
+
*
|
|
69
|
+
* Call {@link RefreshScheduler.start} to begin the refresh loop and
|
|
70
|
+
* {@link RefreshScheduler.stop} to tear it down.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const scheduler = new RefreshScheduler(client, cache, limiter, analyzer);
|
|
75
|
+
* scheduler.on('refresh:complete', ({ packageName, report }) => {
|
|
76
|
+
* console.log(`${packageName}: score=${report.score}`);
|
|
77
|
+
* });
|
|
78
|
+
* scheduler.start(); // refresh hourly
|
|
79
|
+
* // ... later
|
|
80
|
+
* scheduler.stop();
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare class RefreshScheduler extends EventEmitter {
|
|
84
|
+
/** Registry client used to fetch fresh packuments. */
|
|
85
|
+
private readonly client;
|
|
86
|
+
/** Cache manager used to read the watchlist and persist results. */
|
|
87
|
+
private readonly cache;
|
|
88
|
+
/** Rate limiter gating every registry call. */
|
|
89
|
+
private readonly limiter;
|
|
90
|
+
/** Static analyzer re-run on each refreshed package. */
|
|
91
|
+
private readonly analyzer;
|
|
92
|
+
/** Optional semantic analyzer for refreshed packages (mutable at runtime). */
|
|
93
|
+
private llmProvider;
|
|
94
|
+
/** Handle to the recurring refresh interval, or `null` when stopped. */
|
|
95
|
+
private intervalId;
|
|
96
|
+
/**
|
|
97
|
+
* @param client - Registry client for fetching package metadata.
|
|
98
|
+
* @param cache - Cache manager for reading the watchlist and persisting
|
|
99
|
+
* refreshed metadata + reports.
|
|
100
|
+
* @param limiter - Token bucket rate limiter; one token is consumed per
|
|
101
|
+
* registry fetch.
|
|
102
|
+
* @param analyzer - Static analyzer re-run on each refreshed package.
|
|
103
|
+
* @param llmProvider - Optional semantic analyzer for refreshed packages, or
|
|
104
|
+
* a getter that returns the current provider. A getter is preferred when the
|
|
105
|
+
* provider can be changed at runtime.
|
|
106
|
+
*/
|
|
107
|
+
constructor(client: NpmRegistryClient, cache: CacheManager, limiter: TokenBucket, analyzer: StaticAnalyzer, llmProvider?: LlmScanProvider | (() => LlmScanProvider | undefined));
|
|
108
|
+
/**
|
|
109
|
+
* Replace the LLM provider used during refreshes.
|
|
110
|
+
*
|
|
111
|
+
* Safe to call while the scheduler is running; the next refresh cycle uses
|
|
112
|
+
* the new provider.
|
|
113
|
+
*
|
|
114
|
+
* @param provider - The new provider, or `undefined` to disable LLM scanning.
|
|
115
|
+
*/
|
|
116
|
+
setLlmProvider(provider?: LlmScanProvider): void;
|
|
117
|
+
/** Resolve the current LLM provider. */
|
|
118
|
+
private getLlmProvider;
|
|
119
|
+
/**
|
|
120
|
+
* Start the periodic refresh loop.
|
|
121
|
+
*
|
|
122
|
+
* Immediately kicks off a background {@link RefreshScheduler.refreshWatchlist}
|
|
123
|
+
* call (without awaiting it), then schedules the same call to repeat every
|
|
124
|
+
* `intervalMs` milliseconds. Safe to call multiple times; calling while
|
|
125
|
+
* already running clears the previous interval before starting a new one.
|
|
126
|
+
*
|
|
127
|
+
* @param intervalMs - Milliseconds between refresh cycles. Defaults to
|
|
128
|
+
* {@link DEFAULT_INTERVAL_MS} (1 hour).
|
|
129
|
+
*/
|
|
130
|
+
start(intervalMs?: number): void;
|
|
131
|
+
/**
|
|
132
|
+
* Stop the periodic refresh loop.
|
|
133
|
+
*
|
|
134
|
+
* Clears the recurring interval. Any in-flight refresh that is already
|
|
135
|
+
* executing continues to completion; this method only prevents future
|
|
136
|
+
* cycles from being scheduled. Safe to call when not running.
|
|
137
|
+
*/
|
|
138
|
+
stop(): void;
|
|
139
|
+
/**
|
|
140
|
+
* Refresh a single package: fetch its latest metadata from the registry,
|
|
141
|
+
* persist it to the cache, re-run the static analyzer against the latest
|
|
142
|
+
* version's README and manifest, and persist the resulting report.
|
|
143
|
+
*
|
|
144
|
+
* One token is consumed from the rate limiter before the registry fetch,
|
|
145
|
+
* so concurrent callers are naturally throttled. Per-package failures are
|
|
146
|
+
* surfaced via the `refresh:error` event rather than thrown — the promise
|
|
147
|
+
* resolves normally after emitting the error so a failing package does not
|
|
148
|
+
* abort a batch.
|
|
149
|
+
*
|
|
150
|
+
* **Cooperative cancellation**: when `options.signal` is supplied it is
|
|
151
|
+
* forwarded to the registry client and the LLM scan. An external abort
|
|
152
|
+
* propagates as `DOMException('The operation was aborted.', 'AbortError')`
|
|
153
|
+
* (instead of the usual `false` return) so a caller awaiting refresh can
|
|
154
|
+
* observe the abort promptly.
|
|
155
|
+
*
|
|
156
|
+
* @param name - Fully-qualified package name (scope included when scoped).
|
|
157
|
+
* @param options - Optional refresh options. `options.signal` is an
|
|
158
|
+
* external abort signal forwarded to the registry fetch and LLM scan.
|
|
159
|
+
* @returns `true` when the refresh succeeds, or `false` after emitting
|
|
160
|
+
* `refresh:error`. Never rejects for per-package refresh failures —
|
|
161
|
+
* only rejects with `AbortError` when `options.signal` is aborted.
|
|
162
|
+
*/
|
|
163
|
+
refreshPackage(name: string, options?: {
|
|
164
|
+
readonly signal?: AbortSignal;
|
|
165
|
+
}): Promise<boolean>;
|
|
166
|
+
private scanWithLlm;
|
|
167
|
+
/**
|
|
168
|
+
* Refresh every package whose cached metadata has passed its TTL.
|
|
169
|
+
*
|
|
170
|
+
* Packages are processed sequentially (one after another) so the rate
|
|
171
|
+
* limiter is respected and the registry is not hammered in bursts. Each
|
|
172
|
+
* package's outcome is surfaced via the `refresh:complete` / `refresh:error`
|
|
173
|
+
* events emitted by {@link RefreshScheduler.refreshPackage}.
|
|
174
|
+
*
|
|
175
|
+
* **Cooperative cancellation**: when `options.signal` is supplied, the top
|
|
176
|
+
* of each loop iteration checks `signal.aborted` and throws
|
|
177
|
+
* `DOMException('The operation was aborted.', 'AbortError')` so a
|
|
178
|
+
* mid-batch abort settles promptly instead of continuing to iterate.
|
|
179
|
+
*
|
|
180
|
+
* @param options - Optional refresh options. `options.signal` is an
|
|
181
|
+
* external abort signal forwarded to each per-package refresh.
|
|
182
|
+
* @returns `true` when every stale package refresh succeeds, otherwise
|
|
183
|
+
* `false` after all stale packages have been attempted. Rejects with
|
|
184
|
+
* `AbortError` when `options.signal` is aborted.
|
|
185
|
+
*/
|
|
186
|
+
refreshAll(options?: {
|
|
187
|
+
readonly signal?: AbortSignal;
|
|
188
|
+
}): Promise<boolean>;
|
|
189
|
+
/**
|
|
190
|
+
* Refresh every package on the user's watchlist.
|
|
191
|
+
*
|
|
192
|
+
* Watched packages are processed sequentially so the rate limiter is
|
|
193
|
+
* respected. Each package's outcome is surfaced via the
|
|
194
|
+
* `refresh:complete` / `refresh:error` events emitted by
|
|
195
|
+
* {@link RefreshScheduler.refreshPackage}.
|
|
196
|
+
*
|
|
197
|
+
* @returns Resolves once all watched packages have been attempted.
|
|
198
|
+
*/
|
|
199
|
+
private refreshWatchlist;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=refresh-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler/refresh-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,sDAAsD;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,8EAA8E;IAC9E,OAAO,CAAC,WAAW,CAAkD;IACrE,wEAAwE;IACxE,OAAO,CAAC,UAAU,CAA+C;IAEjE;;;;;;;;;;OAUG;gBAED,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,cAAc,EACxB,WAAW,CAAC,EAAE,eAAe,GAAG,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC;IAcrE;;;;;;;OAOG;IACH,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI;IAIhD,wCAAwC;IACxC,OAAO,CAAC,cAAc;IAItB;;;;;;;;;;OAUG;IACH,KAAK,CAAC,UAAU,GAAE,MAA4B,GAAG,IAAI;IAerD;;;;;;OAMG;IACH,IAAI,IAAI,IAAI;IAOZ;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,OAAO,CAAC;YA0DL,WAAW;IAqCzB;;;;;;;;;;;;;;;;;;OAkBG;IACG,UAAU,CACd,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,OAAO,CAAC;IAiBnB;;;;;;;;;OASG;YACW,gBAAgB;CAM/B"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-refresh scheduler with rate-limited registry polling.
|
|
3
|
+
*
|
|
4
|
+
* {@link RefreshScheduler} orchestrates periodic re-fetching of npm package
|
|
5
|
+
* metadata and re-running the static analyzer for packages on the user's
|
|
6
|
+
* watchlist or with stale cache entries. It is a pure scheduling +
|
|
7
|
+
* orchestration layer: it contains no GUI integration and no CLI command
|
|
8
|
+
* logic. All registry calls are gated through the injected {@link TokenBucket}
|
|
9
|
+
* so the scheduler respects the configured rate limit, and progress is
|
|
10
|
+
* surfaced via three {@link EventEmitter} events:
|
|
11
|
+
*
|
|
12
|
+
* - `refresh:start` — emitted before each package refresh begins.
|
|
13
|
+
* - `refresh:complete`— emitted after a package refresh succeeds, carrying
|
|
14
|
+
* the freshly produced {@link StaticScanReport}.
|
|
15
|
+
* - `refresh:error` — emitted when a package refresh fails. The scheduler
|
|
16
|
+
* does **not** throw on per-package failures; it emits the error and
|
|
17
|
+
* continues with the next package.
|
|
18
|
+
*
|
|
19
|
+
* @module scheduler/refresh-scheduler
|
|
20
|
+
*/
|
|
21
|
+
import { EventEmitter } from 'node:events';
|
|
22
|
+
/**
|
|
23
|
+
* Default refresh interval: 1 hour in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_INTERVAL_MS = 3_600_000;
|
|
26
|
+
/**
|
|
27
|
+
* Auto-refresh scheduler that periodically polls the npm registry for
|
|
28
|
+
* updated package metadata, re-caches it, and re-runs the static analyzer.
|
|
29
|
+
*
|
|
30
|
+
* The scheduler is constructed with four collaborators:
|
|
31
|
+
*
|
|
32
|
+
* - {@link NpmRegistryClient} — used to fetch fresh packuments.
|
|
33
|
+
* - {@link CacheManager} — used to read the watchlist/stale set and persist
|
|
34
|
+
* refreshed metadata + security reports.
|
|
35
|
+
* - {@link TokenBucket} — gates every registry call so the configured rate
|
|
36
|
+
* limit is respected.
|
|
37
|
+
* - {@link StaticAnalyzer} — re-evaluates the refreshed package's README and
|
|
38
|
+
* latest version manifest.
|
|
39
|
+
*
|
|
40
|
+
* Call {@link RefreshScheduler.start} to begin the refresh loop and
|
|
41
|
+
* {@link RefreshScheduler.stop} to tear it down.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const scheduler = new RefreshScheduler(client, cache, limiter, analyzer);
|
|
46
|
+
* scheduler.on('refresh:complete', ({ packageName, report }) => {
|
|
47
|
+
* console.log(`${packageName}: score=${report.score}`);
|
|
48
|
+
* });
|
|
49
|
+
* scheduler.start(); // refresh hourly
|
|
50
|
+
* // ... later
|
|
51
|
+
* scheduler.stop();
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class RefreshScheduler extends EventEmitter {
|
|
55
|
+
/** Registry client used to fetch fresh packuments. */
|
|
56
|
+
client;
|
|
57
|
+
/** Cache manager used to read the watchlist and persist results. */
|
|
58
|
+
cache;
|
|
59
|
+
/** Rate limiter gating every registry call. */
|
|
60
|
+
limiter;
|
|
61
|
+
/** Static analyzer re-run on each refreshed package. */
|
|
62
|
+
analyzer;
|
|
63
|
+
/** Optional semantic analyzer for refreshed packages (mutable at runtime). */
|
|
64
|
+
llmProvider;
|
|
65
|
+
/** Handle to the recurring refresh interval, or `null` when stopped. */
|
|
66
|
+
intervalId = null;
|
|
67
|
+
/**
|
|
68
|
+
* @param client - Registry client for fetching package metadata.
|
|
69
|
+
* @param cache - Cache manager for reading the watchlist and persisting
|
|
70
|
+
* refreshed metadata + reports.
|
|
71
|
+
* @param limiter - Token bucket rate limiter; one token is consumed per
|
|
72
|
+
* registry fetch.
|
|
73
|
+
* @param analyzer - Static analyzer re-run on each refreshed package.
|
|
74
|
+
* @param llmProvider - Optional semantic analyzer for refreshed packages, or
|
|
75
|
+
* a getter that returns the current provider. A getter is preferred when the
|
|
76
|
+
* provider can be changed at runtime.
|
|
77
|
+
*/
|
|
78
|
+
constructor(client, cache, limiter, analyzer, llmProvider) {
|
|
79
|
+
super();
|
|
80
|
+
this.client = client;
|
|
81
|
+
this.cache = cache;
|
|
82
|
+
this.limiter = limiter;
|
|
83
|
+
this.analyzer = analyzer;
|
|
84
|
+
this.llmProvider = llmProvider
|
|
85
|
+
? typeof llmProvider === 'function'
|
|
86
|
+
? llmProvider
|
|
87
|
+
: () => llmProvider
|
|
88
|
+
: undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Replace the LLM provider used during refreshes.
|
|
92
|
+
*
|
|
93
|
+
* Safe to call while the scheduler is running; the next refresh cycle uses
|
|
94
|
+
* the new provider.
|
|
95
|
+
*
|
|
96
|
+
* @param provider - The new provider, or `undefined` to disable LLM scanning.
|
|
97
|
+
*/
|
|
98
|
+
setLlmProvider(provider) {
|
|
99
|
+
this.llmProvider = provider ? () => provider : undefined;
|
|
100
|
+
}
|
|
101
|
+
/** Resolve the current LLM provider. */
|
|
102
|
+
getLlmProvider() {
|
|
103
|
+
return this.llmProvider?.();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Start the periodic refresh loop.
|
|
107
|
+
*
|
|
108
|
+
* Immediately kicks off a background {@link RefreshScheduler.refreshWatchlist}
|
|
109
|
+
* call (without awaiting it), then schedules the same call to repeat every
|
|
110
|
+
* `intervalMs` milliseconds. Safe to call multiple times; calling while
|
|
111
|
+
* already running clears the previous interval before starting a new one.
|
|
112
|
+
*
|
|
113
|
+
* @param intervalMs - Milliseconds between refresh cycles. Defaults to
|
|
114
|
+
* {@link DEFAULT_INTERVAL_MS} (1 hour).
|
|
115
|
+
*/
|
|
116
|
+
start(intervalMs = DEFAULT_INTERVAL_MS) {
|
|
117
|
+
// Avoid double-starting: clear any existing interval first.
|
|
118
|
+
if (this.intervalId !== null) {
|
|
119
|
+
clearInterval(this.intervalId);
|
|
120
|
+
this.intervalId = null;
|
|
121
|
+
}
|
|
122
|
+
// Kick off the first cycle immediately in the background.
|
|
123
|
+
void this.refreshWatchlist();
|
|
124
|
+
this.intervalId = setInterval(() => {
|
|
125
|
+
void this.refreshWatchlist();
|
|
126
|
+
}, intervalMs);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Stop the periodic refresh loop.
|
|
130
|
+
*
|
|
131
|
+
* Clears the recurring interval. Any in-flight refresh that is already
|
|
132
|
+
* executing continues to completion; this method only prevents future
|
|
133
|
+
* cycles from being scheduled. Safe to call when not running.
|
|
134
|
+
*/
|
|
135
|
+
stop() {
|
|
136
|
+
if (this.intervalId !== null) {
|
|
137
|
+
clearInterval(this.intervalId);
|
|
138
|
+
this.intervalId = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Refresh a single package: fetch its latest metadata from the registry,
|
|
143
|
+
* persist it to the cache, re-run the static analyzer against the latest
|
|
144
|
+
* version's README and manifest, and persist the resulting report.
|
|
145
|
+
*
|
|
146
|
+
* One token is consumed from the rate limiter before the registry fetch,
|
|
147
|
+
* so concurrent callers are naturally throttled. Per-package failures are
|
|
148
|
+
* surfaced via the `refresh:error` event rather than thrown — the promise
|
|
149
|
+
* resolves normally after emitting the error so a failing package does not
|
|
150
|
+
* abort a batch.
|
|
151
|
+
*
|
|
152
|
+
* **Cooperative cancellation**: when `options.signal` is supplied it is
|
|
153
|
+
* forwarded to the registry client and the LLM scan. An external abort
|
|
154
|
+
* propagates as `DOMException('The operation was aborted.', 'AbortError')`
|
|
155
|
+
* (instead of the usual `false` return) so a caller awaiting refresh can
|
|
156
|
+
* observe the abort promptly.
|
|
157
|
+
*
|
|
158
|
+
* @param name - Fully-qualified package name (scope included when scoped).
|
|
159
|
+
* @param options - Optional refresh options. `options.signal` is an
|
|
160
|
+
* external abort signal forwarded to the registry fetch and LLM scan.
|
|
161
|
+
* @returns `true` when the refresh succeeds, or `false` after emitting
|
|
162
|
+
* `refresh:error`. Never rejects for per-package refresh failures —
|
|
163
|
+
* only rejects with `AbortError` when `options.signal` is aborted.
|
|
164
|
+
*/
|
|
165
|
+
async refreshPackage(name, options) {
|
|
166
|
+
this.emit('refresh:start', { packageName: name });
|
|
167
|
+
try {
|
|
168
|
+
// Gate the registry call through the rate limiter.
|
|
169
|
+
await this.limiter.consume(1);
|
|
170
|
+
const meta = await this.client.getPackageMetadata(name, {
|
|
171
|
+
signal: options?.signal,
|
|
172
|
+
});
|
|
173
|
+
// Persist the fresh packument before doing anything else so the
|
|
174
|
+
// cache is updated even if analysis fails downstream.
|
|
175
|
+
await this.cache.setPackage(meta);
|
|
176
|
+
// Derive a package.json-like object from the latest version manifest
|
|
177
|
+
// for the static analyzer. The analyzer reads `name`, `version`,
|
|
178
|
+
// `scripts`, `browser`, and `publishConfig.registry`; the manifest
|
|
179
|
+
// carries `name`/`version`/`scripts` directly, and any extra fields
|
|
180
|
+
// present on the registry payload (e.g. `browser`, `publishConfig`)
|
|
181
|
+
// are passed through via the spread.
|
|
182
|
+
const latestVersion = meta['dist-tags'].latest;
|
|
183
|
+
const manifest = meta.versions[latestVersion];
|
|
184
|
+
const packageJson = manifest
|
|
185
|
+
? { ...manifest }
|
|
186
|
+
: undefined;
|
|
187
|
+
const readme = meta.readme ?? '';
|
|
188
|
+
const report = this.analyzer.analyze(readme, packageJson);
|
|
189
|
+
await this.cache.setSecurityReport(report);
|
|
190
|
+
const llmProvider = this.getLlmProvider();
|
|
191
|
+
const llmScan = llmProvider
|
|
192
|
+
? await this.scanWithLlm(meta, latestVersion, options?.signal)
|
|
193
|
+
: undefined;
|
|
194
|
+
this.emit('refresh:complete', { packageName: name, report, llmScan });
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
// An external abort must propagate as AbortError instead of being
|
|
199
|
+
// swallowed as a per-package failure — the cooperative-cancellation
|
|
200
|
+
// contract requires the caller to observe the abort promptly.
|
|
201
|
+
if (options?.signal?.aborted) {
|
|
202
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
203
|
+
}
|
|
204
|
+
// Per-package failures are reported, not thrown, so a batch refresh
|
|
205
|
+
// continues with the remaining packages.
|
|
206
|
+
this.emit('refresh:error', { packageName: name, error });
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async scanWithLlm(meta, version, signal) {
|
|
211
|
+
const provider = this.getLlmProvider();
|
|
212
|
+
if (!provider) {
|
|
213
|
+
return { enabled: false, reason: 'LLM provider is not configured.' };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const manifest = meta.versions[version];
|
|
217
|
+
const report = await provider.scan({
|
|
218
|
+
packageName: meta.name,
|
|
219
|
+
version,
|
|
220
|
+
description: meta.description ?? '',
|
|
221
|
+
readme: meta.readme ?? '',
|
|
222
|
+
packageJson: manifest ? { ...manifest } : undefined,
|
|
223
|
+
signal,
|
|
224
|
+
});
|
|
225
|
+
await this.cache.setLlmScanReport(meta.name, version, report);
|
|
226
|
+
return report;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
// An external abort must propagate as AbortError so the caller
|
|
230
|
+
// settles promptly (cooperative cancellation).
|
|
231
|
+
if (signal?.aborted) {
|
|
232
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
233
|
+
}
|
|
234
|
+
const report = {
|
|
235
|
+
enabled: false,
|
|
236
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
237
|
+
scannedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
await this.cache.setLlmScanReport(meta.name, version, report);
|
|
240
|
+
return report;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Refresh every package whose cached metadata has passed its TTL.
|
|
245
|
+
*
|
|
246
|
+
* Packages are processed sequentially (one after another) so the rate
|
|
247
|
+
* limiter is respected and the registry is not hammered in bursts. Each
|
|
248
|
+
* package's outcome is surfaced via the `refresh:complete` / `refresh:error`
|
|
249
|
+
* events emitted by {@link RefreshScheduler.refreshPackage}.
|
|
250
|
+
*
|
|
251
|
+
* **Cooperative cancellation**: when `options.signal` is supplied, the top
|
|
252
|
+
* of each loop iteration checks `signal.aborted` and throws
|
|
253
|
+
* `DOMException('The operation was aborted.', 'AbortError')` so a
|
|
254
|
+
* mid-batch abort settles promptly instead of continuing to iterate.
|
|
255
|
+
*
|
|
256
|
+
* @param options - Optional refresh options. `options.signal` is an
|
|
257
|
+
* external abort signal forwarded to each per-package refresh.
|
|
258
|
+
* @returns `true` when every stale package refresh succeeds, otherwise
|
|
259
|
+
* `false` after all stale packages have been attempted. Rejects with
|
|
260
|
+
* `AbortError` when `options.signal` is aborted.
|
|
261
|
+
*/
|
|
262
|
+
async refreshAll(options) {
|
|
263
|
+
const stale = await this.cache.getStalePackages();
|
|
264
|
+
let allSucceeded = true;
|
|
265
|
+
for (const name of stale) {
|
|
266
|
+
// Short-circuit at the top of each iteration so an aborted batch
|
|
267
|
+
// settles promptly (cooperative cancellation).
|
|
268
|
+
if (options?.signal?.aborted) {
|
|
269
|
+
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
270
|
+
}
|
|
271
|
+
const succeeded = await this.refreshPackage(name, {
|
|
272
|
+
signal: options?.signal,
|
|
273
|
+
});
|
|
274
|
+
allSucceeded = allSucceeded && succeeded;
|
|
275
|
+
}
|
|
276
|
+
return allSucceeded;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Refresh every package on the user's watchlist.
|
|
280
|
+
*
|
|
281
|
+
* Watched packages are processed sequentially so the rate limiter is
|
|
282
|
+
* respected. Each package's outcome is surfaced via the
|
|
283
|
+
* `refresh:complete` / `refresh:error` events emitted by
|
|
284
|
+
* {@link RefreshScheduler.refreshPackage}.
|
|
285
|
+
*
|
|
286
|
+
* @returns Resolves once all watched packages have been attempted.
|
|
287
|
+
*/
|
|
288
|
+
async refreshWatchlist() {
|
|
289
|
+
const watchlist = await this.cache.getWatchlist();
|
|
290
|
+
for (const name of watchlist) {
|
|
291
|
+
await this.refreshPackage(name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=refresh-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-scheduler.js","sourceRoot":"","sources":["../../src/scheduler/refresh-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache read/write layer for @npm-safe/core.
|
|
3
|
+
*
|
|
4
|
+
* {@link CacheManager} sits on top of a {@link DatabaseManager} connection and
|
|
5
|
+
* provides typed accessors for the cached npm metadata, security scan reports,
|
|
6
|
+
* watchlist, and key-value settings tables. It owns the TTL policy for the
|
|
7
|
+
* `packages` table: every {@link CacheManager.setPackage} call stamps the row
|
|
8
|
+
* with an expiry timestamp (`ttl_until`) computed as `now + cacheTtlMs`, and
|
|
9
|
+
* {@link CacheManager.getPackage} refuses to return rows whose TTL has
|
|
10
|
+
* elapsed. Scheduling of refreshes is intentionally out of scope — that is
|
|
11
|
+
* the job of the refresh-scheduler; this module only reads and writes.
|
|
12
|
+
*
|
|
13
|
+
* All methods return `Promise`s for API consistency and future async backing
|
|
14
|
+
* stores, even though the underlying `better-sqlite3` calls are synchronous.
|
|
15
|
+
*
|
|
16
|
+
* @module store/cache-manager
|
|
17
|
+
*/
|
|
18
|
+
import { DatabaseManager } from "./database.js";
|
|
19
|
+
import type { PackageMetadata } from "../registry/types.js";
|
|
20
|
+
import { type LlmScanReport, type StaticScanReport } from "../scanner/types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Default cache time-to-live for package metadata: 1 hour in milliseconds.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DEFAULT_CACHE_TTL_MS = 3600000;
|
|
25
|
+
/**
|
|
26
|
+
* Options accepted by the {@link CacheManager} constructor.
|
|
27
|
+
*/
|
|
28
|
+
export interface CacheManagerOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Time-to-live for cached package metadata, in milliseconds. A row fetched
|
|
31
|
+
* via {@link CacheManager.getPackage} is considered stale once `ttl_until`
|
|
32
|
+
* is in the past. Sub-second TTLs (e.g. `500`) are supported and stored
|
|
33
|
+
* with millisecond precision. Defaults to {@link DEFAULT_CACHE_TTL_MS}
|
|
34
|
+
* (1 hour).
|
|
35
|
+
*/
|
|
36
|
+
readonly cacheTtlMs?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Cache read/write manager backed by a {@link DatabaseManager} connection.
|
|
40
|
+
*
|
|
41
|
+
* The manager is stateless beyond the TTL configuration and the injected
|
|
42
|
+
* database handle, so it is safe to construct one per request or to share a
|
|
43
|
+
* long-lived instance. All public methods are async-returning for API
|
|
44
|
+
* consistency; the underlying `better-sqlite3` calls are synchronous.
|
|
45
|
+
*/
|
|
46
|
+
export declare class CacheManager {
|
|
47
|
+
/** Underlying better-sqlite3 connection accessor. */
|
|
48
|
+
private readonly db;
|
|
49
|
+
/** Cache TTL in milliseconds applied to newly written package rows. */
|
|
50
|
+
private readonly cacheTtlMs;
|
|
51
|
+
/**
|
|
52
|
+
* @param database - The {@link DatabaseManager} supplying the connection.
|
|
53
|
+
* @param options - Optional configuration; see {@link CacheManagerOptions}.
|
|
54
|
+
*/
|
|
55
|
+
constructor(database: DatabaseManager, options?: CacheManagerOptions);
|
|
56
|
+
/**
|
|
57
|
+
* Returns cached package metadata for `name` if the row is still fresh
|
|
58
|
+
* (i.e. `ttl_until >= now`). Returns `null` when the row is missing or
|
|
59
|
+
* stale; callers are expected to re-fetch from the registry and call
|
|
60
|
+
* {@link CacheManager.setPackage} to refresh the cache.
|
|
61
|
+
*
|
|
62
|
+
* @param name - Fully-qualified package name (scope included when scoped).
|
|
63
|
+
* @returns The cached {@link PackageMetadata}, or `null` if absent/stale.
|
|
64
|
+
*/
|
|
65
|
+
getPackage(name: string): Promise<PackageMetadata | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Upserts a package metadata row, stamping it with a fresh TTL computed as
|
|
68
|
+
* `now + cacheTtlMs`. The full packument is serialized into the
|
|
69
|
+
* `registry_data` JSON column; the scalar columns (`latest_version`,
|
|
70
|
+
* `description`, `homepage`, `repository`) are denormalized for cheap
|
|
71
|
+
* listing queries.
|
|
72
|
+
*
|
|
73
|
+
* @param meta - The packument returned by the npm registry.
|
|
74
|
+
*/
|
|
75
|
+
setPackage(meta: PackageMetadata): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Returns the most recent static security report for `pkg`@`version`, or
|
|
78
|
+
* `null` if none is cached. The {@link SecurityLevel} is reconstructed from
|
|
79
|
+
* the persisted numeric score because the `security_reports` table stores
|
|
80
|
+
* only the score, not the enum.
|
|
81
|
+
*
|
|
82
|
+
* @param pkg - Fully-qualified package name.
|
|
83
|
+
* @param version - Semver version string.
|
|
84
|
+
* @returns The cached {@link StaticScanReport}, or `null` if absent.
|
|
85
|
+
*/
|
|
86
|
+
getSecurityReport(pkg: string, version: string): Promise<StaticScanReport | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Upserts a static security report into the `security_reports` table. The
|
|
89
|
+
* findings array is serialized to JSON in the `findings_json` column; the
|
|
90
|
+
* numeric score is stored in `overall_score`. The row is keyed by
|
|
91
|
+
* `(package_name, version, scan_type)` via the table's UNIQUE constraint.
|
|
92
|
+
*
|
|
93
|
+
* @param report - The static scan report to persist.
|
|
94
|
+
*/
|
|
95
|
+
setSecurityReport(report: StaticScanReport): Promise<void>;
|
|
96
|
+
getLlmScanReport(pkg: string, version: string): Promise<LlmScanReport | null>;
|
|
97
|
+
setLlmScanReport(packageName: string, version: string, report: LlmScanReport): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Returns the list of package names currently on the user's watchlist.
|
|
100
|
+
*
|
|
101
|
+
* @returns All watched package names, in insertion order.
|
|
102
|
+
*/
|
|
103
|
+
getWatchlist(): Promise<string[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Adds `name` to the watchlist. Idempotent: inserting a name that is
|
|
106
|
+
* already watched is a no-op (INSERT OR IGNORE).
|
|
107
|
+
*
|
|
108
|
+
* @param name - Fully-qualified package name to watch.
|
|
109
|
+
*/
|
|
110
|
+
addToWatchlist(name: string): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Removes `name` from the watchlist. No-op if the name was not watched.
|
|
113
|
+
*
|
|
114
|
+
* @param name - Fully-qualified package name to stop watching.
|
|
115
|
+
*/
|
|
116
|
+
removeFromWatchlist(name: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Returns the value stored under `key` in the settings table, or `null`
|
|
119
|
+
* if the key is unset.
|
|
120
|
+
*
|
|
121
|
+
* @param key - Settings key.
|
|
122
|
+
* @returns The stored value, or `null` if absent.
|
|
123
|
+
*/
|
|
124
|
+
getSetting(key: string): Promise<string | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Upserts `value` under `key` in the settings table (INSERT OR REPLACE).
|
|
127
|
+
*
|
|
128
|
+
* @param key - Settings key.
|
|
129
|
+
* @param value - Settings value to persist.
|
|
130
|
+
*/
|
|
131
|
+
setSetting(key: string, value: string): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Returns the names of all cached package rows whose TTL has elapsed
|
|
134
|
+
* (`ttl_until < now`). These are candidates for an incremental refresh by
|
|
135
|
+
* the refresh-scheduler.
|
|
136
|
+
*
|
|
137
|
+
* @returns Package names with expired cache entries.
|
|
138
|
+
*/
|
|
139
|
+
getStalePackages(): Promise<string[]>;
|
|
140
|
+
/**
|
|
141
|
+
* Append an entry to the persistent check history, keeping only the most
|
|
142
|
+
* recent {@link MAX_CHECK_HISTORY} entries.
|
|
143
|
+
*/
|
|
144
|
+
addHistoryEntry(entry: {
|
|
145
|
+
readonly packageName: string;
|
|
146
|
+
readonly level: string;
|
|
147
|
+
readonly score: number;
|
|
148
|
+
readonly timestamp: string;
|
|
149
|
+
}): Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Return the persistent check history, newest first.
|
|
152
|
+
*/
|
|
153
|
+
getHistory(limit?: number): Promise<ReadonlyArray<{
|
|
154
|
+
readonly packageName: string;
|
|
155
|
+
readonly level: string;
|
|
156
|
+
readonly score: number;
|
|
157
|
+
readonly timestamp: string;
|
|
158
|
+
}>>;
|
|
159
|
+
/**
|
|
160
|
+
* Remove every entry from the persistent check history.
|
|
161
|
+
*/
|
|
162
|
+
clearHistory(): Promise<void>;
|
|
163
|
+
}
|
|
164
|
+
/** Maximum number of check-history entries retained in the database. */
|
|
165
|
+
export declare const MAX_CHECK_HISTORY = 1000;
|
|
166
|
+
//# sourceMappingURL=cache-manager.d.ts.map
|