@bymax-one/nest-cache 1.0.6 → 1.2.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/CHANGELOG.md +115 -1
- package/README.md +118 -9
- package/dist/admin/index.cjs +741 -0
- package/dist/admin/index.d.cts +967 -0
- package/dist/admin/index.d.ts +967 -0
- package/dist/admin/index.mjs +723 -0
- package/dist/server/index.cjs +82 -28
- package/dist/server/index.d.cts +25 -1
- package/dist/server/index.d.ts +25 -1
- package/dist/server/index.mjs +80 -29
- package/dist/shared/index.cjs +5 -1
- package/dist/shared/index.d.cts +4 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.mjs +5 -1
- package/package.json +20 -6
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
import { DynamicModule, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
2
|
+
import { CacheService, ResolvedOptions, ISerializer } from '@bymax-one/nest-cache';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The keyspaces an administration surface is allowed to read, and what may be
|
|
6
|
+
* read from each.
|
|
7
|
+
*
|
|
8
|
+
* Layer: admin. A scope is declared by the *application*, not by the library and
|
|
9
|
+
* not by a caller. The library owns the mechanism — validating the declarations,
|
|
10
|
+
* scanning against them, withholding what they mark unreadable — while which
|
|
11
|
+
* keyspaces exist is a fact about a deployment's wiring that no library can
|
|
12
|
+
* learn. A cache configured with `namespace: 'app'` may still share its Redis
|
|
13
|
+
* with keys another library writes at root through `CacheService.getClient()`,
|
|
14
|
+
* and only the application knows that.
|
|
15
|
+
*
|
|
16
|
+
* @see `docs/technical_specification.md` §7 — Namespace Strategy
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* One keyspace an administration surface may read.
|
|
20
|
+
*
|
|
21
|
+
* Declared once at module wiring and frozen. A caller names a scope by
|
|
22
|
+
* {@link CacheScope.id} and never supplies a pattern — a pattern composed from
|
|
23
|
+
* request input is the vulnerability class this type exists to close.
|
|
24
|
+
*/
|
|
25
|
+
interface CacheScope {
|
|
26
|
+
/** The identifier a caller names. Unique within a deployment; never a pattern. */
|
|
27
|
+
readonly id: string;
|
|
28
|
+
/** Human-readable name for an operator-facing surface. */
|
|
29
|
+
readonly label: string;
|
|
30
|
+
/**
|
|
31
|
+
* The `SCAN` match pattern this scope resolves to.
|
|
32
|
+
*
|
|
33
|
+
* Composed by the application and validated here: it must carry at least one
|
|
34
|
+
* literal character before its first glob metacharacter, so a scope can never
|
|
35
|
+
* resolve to the whole keyspace. That is an anchoring guarantee, not a
|
|
36
|
+
* narrowness one — the library cannot judge whether `a*` is too broad for a
|
|
37
|
+
* given deployment, only that it is anchored somewhere.
|
|
38
|
+
*/
|
|
39
|
+
readonly pattern: string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a key's **value** may be returned from this scope.
|
|
42
|
+
*
|
|
43
|
+
* `false` means the value alone is withheld. Listing, types, TTLs and sizes
|
|
44
|
+
* stay available, and that split is the whole point: a surface that renders an
|
|
45
|
+
* unreadable keyspace as empty tells an operator the region holds nothing when
|
|
46
|
+
* it is full — the same defect as a blank log page during an outage, where a
|
|
47
|
+
* reading meaning "I may not tell you" is drawn identically to one meaning
|
|
48
|
+
* "there is nothing here".
|
|
49
|
+
*/
|
|
50
|
+
readonly isReadable: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Why this keyspace exists, in the application's own words.
|
|
53
|
+
*
|
|
54
|
+
* Rendered **verbatim**, as plain text — the library imposes no length limit
|
|
55
|
+
* and interprets no markup. An application with one scope wants a sentence and
|
|
56
|
+
* one with eight wants a phrase, and a truncation rule imposed here would cut
|
|
57
|
+
* an explanation mid-clause on the screen where the explanation is the point.
|
|
58
|
+
*/
|
|
59
|
+
readonly origin: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Options for the administration module.
|
|
64
|
+
*
|
|
65
|
+
* Layer: admin. The application declares which keyspaces exist and how patient
|
|
66
|
+
* this deployment is; the library owns everything else.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/** Consumer-supplied options for `BymaxCacheAdminModule`. */
|
|
70
|
+
interface BymaxCacheAdminModuleOptions {
|
|
71
|
+
/**
|
|
72
|
+
* The keyspaces this deployment exposes, in the order a surface should offer
|
|
73
|
+
* them. Validated and frozen at wiring — see `validateScopes`.
|
|
74
|
+
*/
|
|
75
|
+
scopes: readonly CacheScope[];
|
|
76
|
+
/** Round trip above which health reports `degraded`. Default: 250 ms. */
|
|
77
|
+
degradedAboveMs?: number;
|
|
78
|
+
/** How long a health probe waits before reporting `down`. Default: 2000 ms. */
|
|
79
|
+
probeTimeoutMs?: number;
|
|
80
|
+
/** Most keys one listing reads before reporting the page incomplete. Default: 500. */
|
|
81
|
+
scanLimit?: number;
|
|
82
|
+
/** Most commands one pipeline flush sends. Default: 100. */
|
|
83
|
+
commandBatchLimit?: number;
|
|
84
|
+
/** Most members or hash fields one value reveal returns. Default: 200. */
|
|
85
|
+
revealLimit?: number;
|
|
86
|
+
/** Most characters one revealed string value carries. Default: 4096. */
|
|
87
|
+
revealStringLimit?: number;
|
|
88
|
+
/** Register the module globally. Default: `false` — an admin surface is wired where it is used. */
|
|
89
|
+
isGlobal?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Async wiring for {@link BymaxCacheAdminModule.forRootAsync}.
|
|
94
|
+
*
|
|
95
|
+
* `isGlobal` is a top-level field rather than part of the factory's result
|
|
96
|
+
* because Nest needs the `global` flag while building the module definition,
|
|
97
|
+
* which is before any async factory has resolved.
|
|
98
|
+
*/
|
|
99
|
+
interface BymaxCacheAdminModuleAsyncOptions {
|
|
100
|
+
/** Modules exporting whatever the factory injects. */
|
|
101
|
+
imports?: DynamicModule['imports'];
|
|
102
|
+
/** Providers injected into the factory. */
|
|
103
|
+
inject?: (InjectionToken | OptionalFactoryDependency)[];
|
|
104
|
+
/** Builds the administration options. */
|
|
105
|
+
useFactory: (...args: never[]) => BymaxCacheAdminModuleOptions | Promise<BymaxCacheAdminModuleOptions>;
|
|
106
|
+
/** Register globally. Decided synchronously; default `false`. */
|
|
107
|
+
isGlobal?: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** Read-only administration for `@bymax-one/nest-cache`. */
|
|
110
|
+
declare class BymaxCacheAdminModule {
|
|
111
|
+
/**
|
|
112
|
+
* Registers the administration surface with statically known options.
|
|
113
|
+
*
|
|
114
|
+
* Options are validated here, at wiring — a malformed scope or a non-positive
|
|
115
|
+
* threshold fails the boot rather than the first request.
|
|
116
|
+
*
|
|
117
|
+
* @param options - The scopes this deployment exposes, plus any thresholds.
|
|
118
|
+
* @returns The dynamic module.
|
|
119
|
+
* @throws {CacheException} `INVALID_SCOPE` when the declaration is malformed.
|
|
120
|
+
*/
|
|
121
|
+
static forRoot(options: BymaxCacheAdminModuleOptions): DynamicModule;
|
|
122
|
+
/**
|
|
123
|
+
* Registers the administration surface with options built asynchronously.
|
|
124
|
+
*
|
|
125
|
+
* Validation runs inside the factory, so the same rules apply on both paths
|
|
126
|
+
* and neither can drift into accepting what the other rejects.
|
|
127
|
+
*
|
|
128
|
+
* @param options - Factory wiring plus the synchronous `isGlobal` flag.
|
|
129
|
+
* @returns The dynamic module.
|
|
130
|
+
*/
|
|
131
|
+
static forRootAsync(options: BymaxCacheAdminModuleAsyncOptions): DynamicModule;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Administration options after defaults are merged and validated. */
|
|
135
|
+
interface ResolvedAdminOptions {
|
|
136
|
+
readonly scopes: readonly CacheScope[];
|
|
137
|
+
readonly degradedAboveMs: number;
|
|
138
|
+
readonly probeTimeoutMs: number;
|
|
139
|
+
readonly scanLimit: number;
|
|
140
|
+
readonly commandBatchLimit: number;
|
|
141
|
+
readonly revealLimit: number;
|
|
142
|
+
readonly revealStringLimit: number;
|
|
143
|
+
readonly isGlobal: boolean;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Merges administration options with defaults, validates them, and freezes the
|
|
147
|
+
* result.
|
|
148
|
+
*
|
|
149
|
+
* @param options - The raw consumer options.
|
|
150
|
+
* @returns The frozen resolved options.
|
|
151
|
+
* @throws {CacheException} `INVALID_SCOPE` when a scope is malformed or a
|
|
152
|
+
* threshold is not a positive integer.
|
|
153
|
+
*/
|
|
154
|
+
declare function resolveAdminOptions(options: BymaxCacheAdminModuleOptions): Readonly<ResolvedAdminOptions>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* What an administration surface reads to decide what it can say about the cache.
|
|
158
|
+
*
|
|
159
|
+
* Layer: admin.
|
|
160
|
+
*/
|
|
161
|
+
/** How this deployment connects to Redis. */
|
|
162
|
+
type CacheMode = 'standalone' | 'sentinel' | 'cluster';
|
|
163
|
+
/**
|
|
164
|
+
* Why a probe did not answer.
|
|
165
|
+
*
|
|
166
|
+
* A closed union rather than the underlying error's text: an error message is
|
|
167
|
+
* free-form and may carry connection detail the library is careful never to
|
|
168
|
+
* echo (CLAUDE.md §4). The syscall {@link CacheProbeDown.code} carries the part
|
|
169
|
+
* an operator acts on; the full error reaches the application through the
|
|
170
|
+
* `ICacheEvents` hooks the module already provides.
|
|
171
|
+
*/
|
|
172
|
+
type CacheProbeFailure = 'timeout' | 'error';
|
|
173
|
+
/** A probe that answered within the degraded threshold. */
|
|
174
|
+
interface CacheProbeUp {
|
|
175
|
+
readonly status: 'up';
|
|
176
|
+
readonly latencyMs: number;
|
|
177
|
+
}
|
|
178
|
+
/** A probe that answered, but slowly. */
|
|
179
|
+
interface CacheProbeDegraded {
|
|
180
|
+
readonly status: 'degraded';
|
|
181
|
+
readonly latencyMs: number;
|
|
182
|
+
}
|
|
183
|
+
/** A probe that did not answer. */
|
|
184
|
+
interface CacheProbeDown {
|
|
185
|
+
readonly status: 'down';
|
|
186
|
+
readonly reason: CacheProbeFailure;
|
|
187
|
+
/** The syscall code when the driver reported one (`ECONNREFUSED`), else `null`. */
|
|
188
|
+
readonly code: string | null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The result of pinging Redis.
|
|
192
|
+
*
|
|
193
|
+
* **Three states, not two.** A server answering `PING` slowly is neither up nor
|
|
194
|
+
* down, and collapsing it into either hides the state an operator most wants to
|
|
195
|
+
* catch early — by the time a slow cache becomes an unreachable one, the choice
|
|
196
|
+
* has been made for them.
|
|
197
|
+
*
|
|
198
|
+
* **A union, so `latencyMs` cannot exist without a measurement.** The invariant
|
|
199
|
+
* is that a latency is reported if and only if the ping answered; expressed as
|
|
200
|
+
* `latencyMs: number | null` it would be a convention, and a handler that caught
|
|
201
|
+
* a throwing ping and fell back to a default shape would hand a surface a
|
|
202
|
+
* confident status that really means "I did not ask" — undetectable from the
|
|
203
|
+
* payload. Here that shape does not typecheck.
|
|
204
|
+
*/
|
|
205
|
+
type CacheProbe = CacheProbeUp | CacheProbeDegraded | CacheProbeDown;
|
|
206
|
+
/**
|
|
207
|
+
* The cache's health, as an administration surface reads it.
|
|
208
|
+
*
|
|
209
|
+
* `mode`, `isScanSupported` and `degradedAboveMs` sit OUTSIDE the discriminated
|
|
210
|
+
* part on purpose: a cluster deployment that is down should still report that
|
|
211
|
+
* scanning was never going to work, and folding those into the answering
|
|
212
|
+
* branches would make the `down` payload quieter about facts that have nothing
|
|
213
|
+
* to do with being down.
|
|
214
|
+
*/
|
|
215
|
+
type CacheHealth = CacheProbe & {
|
|
216
|
+
readonly mode: CacheMode;
|
|
217
|
+
/**
|
|
218
|
+
* Whether this deployment's mode supports `SCAN`.
|
|
219
|
+
*
|
|
220
|
+
* On the wire beside `mode` deliberately. The library refuses `scan` under
|
|
221
|
+
* cluster, and applying that rule is the library's job — a surface
|
|
222
|
+
* re-deriving it from `mode` would hold a copy of a rule it cannot see change.
|
|
223
|
+
*/
|
|
224
|
+
readonly isScanSupported: boolean;
|
|
225
|
+
/** The round trip above which this deployment calls the cache degraded. */
|
|
226
|
+
readonly degradedAboveMs: number;
|
|
227
|
+
};
|
|
228
|
+
/** The resolved wiring, with every credential-bearing field withheld. */
|
|
229
|
+
interface CacheConfig {
|
|
230
|
+
readonly mode: CacheMode;
|
|
231
|
+
readonly namespace: string;
|
|
232
|
+
readonly keySeparator: string;
|
|
233
|
+
readonly shutdownTimeoutMs: number;
|
|
234
|
+
readonly isFlushAllowedInProduction: boolean;
|
|
235
|
+
/** The serializer's constructor name, or `null` when it does not report one. */
|
|
236
|
+
readonly serializer: string | null;
|
|
237
|
+
/** The Lua scripts registered at wiring, by name only — never their bodies. */
|
|
238
|
+
readonly scripts: readonly string[];
|
|
239
|
+
/**
|
|
240
|
+
* Where this deployment connects, with the connection URL withheld.
|
|
241
|
+
*
|
|
242
|
+
* `ResolvedOptions.connection` is `{ url, password, ... }`, so serving the
|
|
243
|
+
* resolved options directly — the obvious implementation — ships a Redis URL
|
|
244
|
+
* with its password to whatever reads this. Host, port and a TLS flag carry
|
|
245
|
+
* everything an operator-facing panel needs and none of the credential.
|
|
246
|
+
*
|
|
247
|
+
* `host` and `port` are `null` under sentinel and cluster, where there is no
|
|
248
|
+
* single endpoint to name; `mode` alongside says why.
|
|
249
|
+
*/
|
|
250
|
+
readonly connection: {
|
|
251
|
+
readonly host: string | null;
|
|
252
|
+
readonly port: number | null;
|
|
253
|
+
readonly isTls: boolean;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The `INFO` subset an administration surface reads.
|
|
259
|
+
*
|
|
260
|
+
* Layer: admin. Every field here is reported, never defaulted: a value this
|
|
261
|
+
* server does not publish arrives as `null` and a surface renders "not
|
|
262
|
+
* reported". Substituting a zero would draw a reading that means "I could not
|
|
263
|
+
* measure" identically to one that means "I measured, and it is nothing".
|
|
264
|
+
*
|
|
265
|
+
* Where a single `null` would carry two different meanings, the type is a
|
|
266
|
+
* discriminated union instead — see {@link MaxMemory}.
|
|
267
|
+
*/
|
|
268
|
+
/**
|
|
269
|
+
* The configured memory ceiling.
|
|
270
|
+
*
|
|
271
|
+
* A union rather than `number | null` because `maxmemory` has three states and
|
|
272
|
+
* only two of them are a number. Redis spells "no ceiling" as `maxmemory:0`,
|
|
273
|
+
* which read as a literal ceiling makes a saturation bar show full on the least
|
|
274
|
+
* constrained server there is; and a server that does not publish the field at
|
|
275
|
+
* all has told us nothing. Collapsing "unbounded" and "unreported" into one
|
|
276
|
+
* `null` leaves a surface unable to tell an operator which of those it is
|
|
277
|
+
* looking at, and they call for opposite actions.
|
|
278
|
+
*/
|
|
279
|
+
type MaxMemory =
|
|
280
|
+
/** A ceiling is configured. */
|
|
281
|
+
{
|
|
282
|
+
readonly kind: 'limited';
|
|
283
|
+
readonly bytes: number;
|
|
284
|
+
}
|
|
285
|
+
/** `maxmemory:0` — the server will grow until the host stops it. */
|
|
286
|
+
| {
|
|
287
|
+
readonly kind: 'unbounded';
|
|
288
|
+
}
|
|
289
|
+
/** The server does not publish `maxmemory`. */
|
|
290
|
+
| {
|
|
291
|
+
readonly kind: 'unreported';
|
|
292
|
+
};
|
|
293
|
+
/** The outcome of the most recent background save, or `null` when unreported. */
|
|
294
|
+
type BgsaveStatus = 'ok' | 'err' | null;
|
|
295
|
+
/** Whether this server is a primary or a replica. */
|
|
296
|
+
type ReplicationRole = 'master' | 'replica';
|
|
297
|
+
/** A parsed reading of Redis `INFO`. */
|
|
298
|
+
interface RedisStats {
|
|
299
|
+
/**
|
|
300
|
+
* When this reading was taken, by the **reading host's** clock — not the
|
|
301
|
+
* Redis server's.
|
|
302
|
+
*
|
|
303
|
+
* Named for the side that measured it. Under clock skew between the
|
|
304
|
+
* application host and the Redis host the two differ, and this value is used
|
|
305
|
+
* to place a reading on an incident timeline, which is exactly where the
|
|
306
|
+
* difference shows.
|
|
307
|
+
*/
|
|
308
|
+
readonly readAt: string;
|
|
309
|
+
readonly server: {
|
|
310
|
+
readonly redisVersion: string | null;
|
|
311
|
+
readonly mode: string | null;
|
|
312
|
+
readonly uptimeSeconds: number | null;
|
|
313
|
+
};
|
|
314
|
+
readonly clients: {
|
|
315
|
+
readonly connected: number | null;
|
|
316
|
+
readonly blocked: number | null;
|
|
317
|
+
};
|
|
318
|
+
readonly memory: {
|
|
319
|
+
readonly usedBytes: number | null;
|
|
320
|
+
readonly peakBytes: number | null;
|
|
321
|
+
/** Three-state: a ceiling, no ceiling, or no answer. See {@link MaxMemory}. */
|
|
322
|
+
readonly max: MaxMemory;
|
|
323
|
+
/**
|
|
324
|
+
* Allocator fragmentation, reported raw.
|
|
325
|
+
*
|
|
326
|
+
* Interpretation is deliberately left to the caller: on an instance holding
|
|
327
|
+
* very little, allocator and copy-on-write overhead dominate and this reads
|
|
328
|
+
* far above 1 without indicating a problem — 9.07 was measured on an
|
|
329
|
+
* instance holding 1.1 MiB. A threshold that turns this into a health
|
|
330
|
+
* verdict is deployment policy, not a property of the reading.
|
|
331
|
+
*/
|
|
332
|
+
readonly fragmentationRatio: number | null;
|
|
333
|
+
readonly evictionPolicy: string | null;
|
|
334
|
+
};
|
|
335
|
+
readonly stats: {
|
|
336
|
+
readonly keyspaceHits: number | null;
|
|
337
|
+
readonly keyspaceMisses: number | null;
|
|
338
|
+
readonly expiredKeys: number | null;
|
|
339
|
+
readonly evictedKeys: number | null;
|
|
340
|
+
readonly instantaneousOpsPerSec: number | null;
|
|
341
|
+
readonly totalCommandsProcessed: number | null;
|
|
342
|
+
readonly totalConnectionsReceived: number | null;
|
|
343
|
+
readonly rejectedConnections: number | null;
|
|
344
|
+
};
|
|
345
|
+
readonly persistence: {
|
|
346
|
+
readonly rdbLastSaveAt: string | null;
|
|
347
|
+
readonly rdbChangesSinceLastSave: number | null;
|
|
348
|
+
readonly rdbLastBgsaveStatus: BgsaveStatus;
|
|
349
|
+
/**
|
|
350
|
+
* Whether append-only persistence is on, or `null` when the server does not
|
|
351
|
+
* publish `aof_enabled`.
|
|
352
|
+
*
|
|
353
|
+
* Nullable rather than defaulting to `false`, because `false` here is a
|
|
354
|
+
* claim about durability made in the absence of evidence — "AOF is off" and
|
|
355
|
+
* "I could not determine whether AOF is on" are different sentences to
|
|
356
|
+
* someone deciding whether a restart loses writes.
|
|
357
|
+
*/
|
|
358
|
+
readonly aofEnabled: boolean | null;
|
|
359
|
+
};
|
|
360
|
+
readonly replication: {
|
|
361
|
+
readonly role: ReplicationRole;
|
|
362
|
+
readonly connectedReplicas: number | null;
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The cache capabilities this service reads through.
|
|
368
|
+
*
|
|
369
|
+
* A `Pick` of the real facade rather than the class itself: Nest still injects
|
|
370
|
+
* the concrete `CacheService`, while a test supplies a plain object with two
|
|
371
|
+
* methods and no cast. A cast would be a blocking finding under this
|
|
372
|
+
* repository's suppression policy, so the narrow type is not a convenience.
|
|
373
|
+
*/
|
|
374
|
+
type ICacheProbe = Pick<CacheService, 'ping' | 'info'>;
|
|
375
|
+
/** Reports whether the cache is answering, what it is doing, and how it is wired. */
|
|
376
|
+
declare class CacheStatusService {
|
|
377
|
+
private readonly cache;
|
|
378
|
+
private readonly options;
|
|
379
|
+
private readonly adminOptions;
|
|
380
|
+
private readonly serializer;
|
|
381
|
+
/**
|
|
382
|
+
* @param cache - The cache facade this service probes through.
|
|
383
|
+
* @param options - The resolved cache module options.
|
|
384
|
+
* @param adminOptions - The resolved administration options.
|
|
385
|
+
* @param serializer - The wired serializer, named on the config payload.
|
|
386
|
+
*/
|
|
387
|
+
constructor(cache: ICacheProbe, options: ResolvedOptions, adminOptions: ResolvedAdminOptions, serializer: ISerializer);
|
|
388
|
+
/**
|
|
389
|
+
* Pings Redis and reports the result in three states.
|
|
390
|
+
*
|
|
391
|
+
* @returns The probe outcome, plus the mode facts that hold whether or not it
|
|
392
|
+
* answered.
|
|
393
|
+
*/
|
|
394
|
+
health(): Promise<CacheHealth>;
|
|
395
|
+
/**
|
|
396
|
+
* Reads Redis `INFO` into a typed reading.
|
|
397
|
+
*
|
|
398
|
+
* @returns The parsed statistics, stamped with the reading host's clock.
|
|
399
|
+
*/
|
|
400
|
+
stats(): Promise<RedisStats>;
|
|
401
|
+
/**
|
|
402
|
+
* Reports how this deployment is wired, with every credential withheld.
|
|
403
|
+
*
|
|
404
|
+
* @returns The resolved configuration, safe to serve to an operator surface.
|
|
405
|
+
*/
|
|
406
|
+
config(): CacheConfig;
|
|
407
|
+
/**
|
|
408
|
+
* Measures one `PING`.
|
|
409
|
+
*
|
|
410
|
+
* `latencyMs` is produced on exactly one path — a reply that arrived. Every
|
|
411
|
+
* other outcome returns the `down` branch, which has no latency field to fill
|
|
412
|
+
* in, so a confident status without a measurement cannot be constructed here.
|
|
413
|
+
*
|
|
414
|
+
* @returns The probe outcome.
|
|
415
|
+
*/
|
|
416
|
+
private probe;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* The Redis commands the administration surface issues.
|
|
421
|
+
*
|
|
422
|
+
* Layer: admin. Declared as capabilities rather than taken as ioredis's `Redis`
|
|
423
|
+
* so a unit test can supply a plain object with no cast — a cast would be a
|
|
424
|
+
* blocking finding under this repository's suppression policy, making the narrow
|
|
425
|
+
* interface a correctness requirement rather than a convenience. The real client
|
|
426
|
+
* satisfies these structurally, which a compile-time check in the service pins.
|
|
427
|
+
*
|
|
428
|
+
* **Every command named here is read-only.** No `DEL`, no `SET`, no `EXPIRE`, no
|
|
429
|
+
* `UNLINK`, no `FLUSHDB`. That is worth stating because the client this surface
|
|
430
|
+
* holds can do all of them — and it is enforced rather than asserted: the
|
|
431
|
+
* `check:admin-readonly` gate fails the build if a mutating command appears in
|
|
432
|
+
* this subpath.
|
|
433
|
+
*/
|
|
434
|
+
/** One entry of a pipeline's result: an error, or a value. */
|
|
435
|
+
type PipelineReply = [Error | null, unknown];
|
|
436
|
+
/** A pipeline being composed, chainable the way ioredis's commander is. */
|
|
437
|
+
interface IRedisPipeline {
|
|
438
|
+
/** Queues `TYPE`. */
|
|
439
|
+
type(key: string): IRedisPipeline;
|
|
440
|
+
/** Queues `TTL`. */
|
|
441
|
+
ttl(key: string): IRedisPipeline;
|
|
442
|
+
/** Queues `MEMORY USAGE`. */
|
|
443
|
+
memory(subcommand: 'USAGE', key: string): IRedisPipeline;
|
|
444
|
+
/** Sends the batch. */
|
|
445
|
+
exec(): Promise<PipelineReply[] | null>;
|
|
446
|
+
}
|
|
447
|
+
/** The subset of the Redis client this surface reads through. */
|
|
448
|
+
interface IRedisReader {
|
|
449
|
+
/** Cursor-based key iteration. */
|
|
450
|
+
scan(cursor: string | number, matchToken: 'MATCH', pattern: string, countToken: 'COUNT', count: number | string): Promise<[string, string[]]>;
|
|
451
|
+
/** Opens a batch. */
|
|
452
|
+
pipeline(): IRedisPipeline;
|
|
453
|
+
/** Reads one key's type. */
|
|
454
|
+
type(key: string): Promise<string>;
|
|
455
|
+
/** Reads one key's remaining life in seconds. */
|
|
456
|
+
ttl(key: string): Promise<number>;
|
|
457
|
+
/** Reads a string's value. */
|
|
458
|
+
get(key: string): Promise<string | null>;
|
|
459
|
+
/** Reads a hash's fields. */
|
|
460
|
+
hgetall(key: string): Promise<Record<string, string>>;
|
|
461
|
+
/** Reads a set's members. */
|
|
462
|
+
smembers(key: string): Promise<string[]>;
|
|
463
|
+
/** Reads a slice of a list. */
|
|
464
|
+
lrange(key: string, start: number, stop: number): Promise<string[]>;
|
|
465
|
+
/**
|
|
466
|
+
* Sends a command by name.
|
|
467
|
+
*
|
|
468
|
+
* Present for the one read this interface cannot express as a method:
|
|
469
|
+
* ioredis's `zrange` overload set cannot be narrowed to a four-argument
|
|
470
|
+
* signature (measured — TypeScript resolves the assignment against a nine
|
|
471
|
+
* argument overload and rejects it), so the sorted-set read goes through here
|
|
472
|
+
* as `ZRANGE key start stop WITHSCORES`.
|
|
473
|
+
*
|
|
474
|
+
* It is a general escape hatch, which is exactly why the `check:admin-readonly`
|
|
475
|
+
* gate inspects the command names this subpath passes to it rather than
|
|
476
|
+
* trusting that only reads are sent.
|
|
477
|
+
*/
|
|
478
|
+
call(command: string, ...args: (string | number)[]): Promise<unknown>;
|
|
479
|
+
}
|
|
480
|
+
/** How the administration surface obtains its reader. */
|
|
481
|
+
interface IRedisReaderSource {
|
|
482
|
+
/**
|
|
483
|
+
* Returns the client.
|
|
484
|
+
*
|
|
485
|
+
* The library's own `CacheService.getClient()` refuses cluster mode with
|
|
486
|
+
* `cache.unsupported_in_cluster`, so the cluster refusal this surface needs
|
|
487
|
+
* comes from the same rule the rest of the library applies rather than a
|
|
488
|
+
* second copy of it.
|
|
489
|
+
*/
|
|
490
|
+
getClient(): IRedisReader;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The shapes an administration surface reads about keys.
|
|
495
|
+
*
|
|
496
|
+
* Layer: admin. As in {@link ../types/redis-stats.types}, a value that would
|
|
497
|
+
* carry two meanings under one `null` is a discriminated union instead.
|
|
498
|
+
*/
|
|
499
|
+
/**
|
|
500
|
+
* The Redis data types this surface names, plus an escape hatch.
|
|
501
|
+
*
|
|
502
|
+
* `other` is what makes the union safe to declare closed. Redis may report a
|
|
503
|
+
* type this build has never heard of — a module type, or one added by a later
|
|
504
|
+
* server — and mapping the unrecognised case to a named member means a
|
|
505
|
+
* consumer's exhaustive switch can never be wrong about a real reply. Passing
|
|
506
|
+
* the raw string through instead would put an open set into a closed type.
|
|
507
|
+
*/
|
|
508
|
+
type RedisKeyType = 'string' | 'hash' | 'set' | 'list' | 'zset' | 'stream' | 'other';
|
|
509
|
+
/**
|
|
510
|
+
* How much life a key has left.
|
|
511
|
+
*
|
|
512
|
+
* A union rather than `number | null`, because Redis answers `TTL` with three
|
|
513
|
+
* distinct facts and two of them are not a number: `-1` means the key exists
|
|
514
|
+
* with no expiry, and `-2` means the key does not exist. Collapsing those into
|
|
515
|
+
* one `null` forces the caller to recover the difference from somewhere else —
|
|
516
|
+
* and the case where it matters most is a key that expired between the scan and
|
|
517
|
+
* the read, which is exactly the key an operator is staring at when they care.
|
|
518
|
+
*/
|
|
519
|
+
type KeyTtl =
|
|
520
|
+
/** The key expires in `seconds`. */
|
|
521
|
+
{
|
|
522
|
+
readonly kind: 'expiring';
|
|
523
|
+
readonly seconds: number;
|
|
524
|
+
}
|
|
525
|
+
/** The key exists and has no expiry set (`TTL` → `-1`). */
|
|
526
|
+
| {
|
|
527
|
+
readonly kind: 'persistent';
|
|
528
|
+
}
|
|
529
|
+
/** The key does not exist (`TTL` → `-2`). */
|
|
530
|
+
| {
|
|
531
|
+
readonly kind: 'missing';
|
|
532
|
+
};
|
|
533
|
+
/** One key as it appears in a listing. */
|
|
534
|
+
interface KeyEntry {
|
|
535
|
+
/** The full key, exactly as Redis stores it. */
|
|
536
|
+
readonly key: string;
|
|
537
|
+
readonly type: RedisKeyType;
|
|
538
|
+
readonly ttl: KeyTtl;
|
|
539
|
+
/**
|
|
540
|
+
* Serialized size in bytes, or `null` when not measured.
|
|
541
|
+
*
|
|
542
|
+
* `null` covers two cases the caller can already tell apart from the request
|
|
543
|
+
* it made: sizing was not requested, or the server declined to answer for this
|
|
544
|
+
* key. Reporting `0` instead would make a key of unknown size and a key
|
|
545
|
+
* occupying nothing render identically, and only one of them is a measurement.
|
|
546
|
+
*/
|
|
547
|
+
readonly sizeBytes: number | null;
|
|
548
|
+
}
|
|
549
|
+
/** One page of a keyspace listing. */
|
|
550
|
+
interface KeyspacePage {
|
|
551
|
+
/** The scope this page was read from. */
|
|
552
|
+
readonly scopeId: string;
|
|
553
|
+
readonly entries: readonly KeyEntry[];
|
|
554
|
+
/**
|
|
555
|
+
* How many keys this page actually read.
|
|
556
|
+
*
|
|
557
|
+
* Named `sampled` rather than `count` because it is a sum over a capped
|
|
558
|
+
* `SCAN`, not a measurement of the keyspace. {@link KeyspacePage.isComplete}
|
|
559
|
+
* is the fact; the name is the guard — a caller reaching for one of these does
|
|
560
|
+
* not necessarily read the other, and a bare `count` would look measured.
|
|
561
|
+
*/
|
|
562
|
+
readonly sampledCount: number;
|
|
563
|
+
/**
|
|
564
|
+
* Total serialized bytes across the keys whose size was measured, or `null`
|
|
565
|
+
* when sizing was not requested.
|
|
566
|
+
*
|
|
567
|
+
* Sampled for the same reason as {@link KeyspacePage.sampledCount}, and
|
|
568
|
+
* renamed with it or not at all — moving only one of the pair relocates the
|
|
569
|
+
* trap instead of closing it.
|
|
570
|
+
*/
|
|
571
|
+
readonly sampledBytes: number | null;
|
|
572
|
+
/** Whether the scan reached the end of the scope rather than the cap. */
|
|
573
|
+
readonly isComplete: boolean;
|
|
574
|
+
/** The cap this page was read under, so a caller can say why it stopped. */
|
|
575
|
+
readonly scanLimit: number;
|
|
576
|
+
/** Cursor to continue from, or `null` when the scope was exhausted. */
|
|
577
|
+
readonly cursor: string | null;
|
|
578
|
+
}
|
|
579
|
+
/** One key, described without reading its value. */
|
|
580
|
+
interface KeyDetail extends KeyEntry {
|
|
581
|
+
/** The scope the key was resolved through. */
|
|
582
|
+
readonly scopeId: string;
|
|
583
|
+
/**
|
|
584
|
+
* Whether this scope permits reading the key's value.
|
|
585
|
+
*
|
|
586
|
+
* Present on the detail so a surface can render the listing honestly and
|
|
587
|
+
* disable only the reveal, rather than discovering the refusal by attempting it.
|
|
588
|
+
*/
|
|
589
|
+
readonly isReadable: boolean;
|
|
590
|
+
}
|
|
591
|
+
/** A revealed value, shaped by the key's Redis type. */
|
|
592
|
+
type RevealedValue = {
|
|
593
|
+
readonly kind: 'string';
|
|
594
|
+
readonly value: string;
|
|
595
|
+
readonly isComplete: boolean;
|
|
596
|
+
} | {
|
|
597
|
+
readonly kind: 'hash';
|
|
598
|
+
readonly fields: readonly {
|
|
599
|
+
readonly name: string;
|
|
600
|
+
readonly value: string;
|
|
601
|
+
}[];
|
|
602
|
+
readonly isComplete: boolean;
|
|
603
|
+
}
|
|
604
|
+
/** Sets and lists: a flat member list. The enclosing `type` says which it is. */
|
|
605
|
+
| {
|
|
606
|
+
readonly kind: 'members';
|
|
607
|
+
readonly members: readonly string[];
|
|
608
|
+
readonly isComplete: boolean;
|
|
609
|
+
} | {
|
|
610
|
+
readonly kind: 'scored';
|
|
611
|
+
readonly members: readonly {
|
|
612
|
+
readonly member: string;
|
|
613
|
+
readonly score: string;
|
|
614
|
+
}[];
|
|
615
|
+
readonly isComplete: boolean;
|
|
616
|
+
}
|
|
617
|
+
/** A type this surface does not render — streams, module types. */
|
|
618
|
+
| {
|
|
619
|
+
readonly kind: 'unsupported';
|
|
620
|
+
readonly type: RedisKeyType;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* The outcome of asking for a key's value.
|
|
624
|
+
*
|
|
625
|
+
* A discriminated union rather than a nullable value, because `withheld` and
|
|
626
|
+
* `missing` are different answers and rendering them identically is the defect
|
|
627
|
+
* this whole surface exists to avoid: one means "I may not tell you", the other
|
|
628
|
+
* means "there is nothing here".
|
|
629
|
+
*/
|
|
630
|
+
type RevealResult = {
|
|
631
|
+
readonly status: 'revealed';
|
|
632
|
+
readonly key: string;
|
|
633
|
+
readonly type: RedisKeyType;
|
|
634
|
+
readonly value: RevealedValue;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* The scope withholds values. Listing, types, TTLs and sizes remain available
|
|
638
|
+
* for this key — only the value is refused, and `origin` says why in the
|
|
639
|
+
* application's own words.
|
|
640
|
+
*/
|
|
641
|
+
| {
|
|
642
|
+
readonly status: 'withheld';
|
|
643
|
+
readonly key: string;
|
|
644
|
+
readonly scopeId: string;
|
|
645
|
+
readonly origin: string;
|
|
646
|
+
}
|
|
647
|
+
/** The key does not exist. */
|
|
648
|
+
| {
|
|
649
|
+
readonly status: 'missing';
|
|
650
|
+
readonly key: string;
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
/** Options for one keyspace listing. */
|
|
654
|
+
interface ListKeysOptions {
|
|
655
|
+
/** Cursor from a previous page; omit to start at the beginning of the scope. */
|
|
656
|
+
cursor?: string;
|
|
657
|
+
/**
|
|
658
|
+
* Measure each key's serialized size.
|
|
659
|
+
*
|
|
660
|
+
* Opt-in because it is not free: it adds a `MEMORY USAGE` per key, and Redis
|
|
661
|
+
* is single-threaded, so the cost lands on every other client waiting behind
|
|
662
|
+
* the batch — on a server an operator is inspecting precisely because it is
|
|
663
|
+
* unwell.
|
|
664
|
+
*/
|
|
665
|
+
includeSize?: boolean;
|
|
666
|
+
}
|
|
667
|
+
/** Reads the keyspace an administration surface is allowed to see. */
|
|
668
|
+
declare class CacheAdminService {
|
|
669
|
+
private readonly cache;
|
|
670
|
+
private readonly options;
|
|
671
|
+
/**
|
|
672
|
+
* @param cache - Supplies the client. `CacheService.getClient()` refuses
|
|
673
|
+
* cluster mode, so this surface inherits that rule rather than restating it.
|
|
674
|
+
* @param options - The resolved administration options.
|
|
675
|
+
*/
|
|
676
|
+
constructor(cache: IRedisReaderSource, options: ResolvedAdminOptions);
|
|
677
|
+
/**
|
|
678
|
+
* Returns the declared scopes.
|
|
679
|
+
*
|
|
680
|
+
* **Never touches the connection**, and that is a property rather than an
|
|
681
|
+
* accident of the current implementation. A surface reads this to decide what
|
|
682
|
+
* it may offer at all, so it must answer during an outage — an operator whose
|
|
683
|
+
* cache is down is exactly the person who needs to be told what the inspector
|
|
684
|
+
* covers. The tempting change is a liveness check to make the list "more
|
|
685
|
+
* accurate"; it would turn a declaration into a measurement, and a scope that
|
|
686
|
+
* vanished during an outage reads as a scope that was removed.
|
|
687
|
+
*
|
|
688
|
+
* @returns The frozen allowlist, in declaration order.
|
|
689
|
+
*/
|
|
690
|
+
listScopes(): readonly CacheScope[];
|
|
691
|
+
/**
|
|
692
|
+
* Lists one page of the keys in a scope.
|
|
693
|
+
*
|
|
694
|
+
* The page may carry slightly MORE than `scanLimit` entries: the limit stops
|
|
695
|
+
* the scan loop, and the batch that crosses it is kept whole rather than
|
|
696
|
+
* trimmed, because the cursor has already moved past those keys and trimming
|
|
697
|
+
* would drop them from every subsequent page too.
|
|
698
|
+
*
|
|
699
|
+
* @param scopeId - The scope a caller named.
|
|
700
|
+
* @param listOptions - Cursor and whether to measure sizes.
|
|
701
|
+
* @returns The page, with the sample it read and whether it reached the end.
|
|
702
|
+
* @throws {CacheException} `SCOPE_NOT_FOUND` for an undeclared id, or
|
|
703
|
+
* `UNSUPPORTED_IN_CLUSTER` in cluster mode.
|
|
704
|
+
*/
|
|
705
|
+
listKeys(scopeId: string, listOptions?: ListKeysOptions): Promise<KeyspacePage>;
|
|
706
|
+
/**
|
|
707
|
+
* Describes one key without reading its value.
|
|
708
|
+
*
|
|
709
|
+
* Works on an unreadable scope: only the value is withheld, never the
|
|
710
|
+
* description. {@link KeyDetail.isReadable} lets a surface disable the reveal
|
|
711
|
+
* rather than discovering the refusal by attempting it.
|
|
712
|
+
*
|
|
713
|
+
* @param scopeId - The scope a caller named.
|
|
714
|
+
* @param key - The key, which must belong to that scope.
|
|
715
|
+
* @returns The key's type, remaining life, and whether its value may be read.
|
|
716
|
+
* @throws {CacheException} `SCOPE_NOT_FOUND` or `KEY_NOT_IN_SCOPE`.
|
|
717
|
+
*/
|
|
718
|
+
describeKey(scopeId: string, key: string): Promise<KeyDetail>;
|
|
719
|
+
/**
|
|
720
|
+
* Reads a key's value, or explains why it will not.
|
|
721
|
+
*
|
|
722
|
+
* @param scopeId - The scope a caller named.
|
|
723
|
+
* @param key - The key, which must belong to that scope.
|
|
724
|
+
* @returns The value, a refusal carrying the scope's `origin`, or `missing`.
|
|
725
|
+
* @throws {CacheException} `SCOPE_NOT_FOUND` or `KEY_NOT_IN_SCOPE`.
|
|
726
|
+
*/
|
|
727
|
+
revealValue(scopeId: string, key: string): Promise<RevealResult>;
|
|
728
|
+
/**
|
|
729
|
+
* Resolves a scope and confirms the key belongs to it.
|
|
730
|
+
*
|
|
731
|
+
* The membership check is the reason scope patterns are restricted to a
|
|
732
|
+
* literal prefix: it makes this decidable exactly, rather than by a matcher
|
|
733
|
+
* that could be more permissive than the server's.
|
|
734
|
+
*
|
|
735
|
+
* @param scopeId - The scope a caller named.
|
|
736
|
+
* @param key - The key a caller named.
|
|
737
|
+
* @returns The resolved scope.
|
|
738
|
+
* @throws {CacheException} `SCOPE_NOT_FOUND` or `KEY_NOT_IN_SCOPE`.
|
|
739
|
+
*/
|
|
740
|
+
private resolve;
|
|
741
|
+
/**
|
|
742
|
+
* Describes every key on a page, in batches bounded by command count.
|
|
743
|
+
*
|
|
744
|
+
* The bound is in commands rather than keys because that is the resource
|
|
745
|
+
* actually spent: describing a key costs two commands, or three when sized,
|
|
746
|
+
* and Redis works through a flush single-threaded while every other client
|
|
747
|
+
* waits. At least one key goes per batch, so a limit below one key's cost
|
|
748
|
+
* still makes progress instead of dividing to nothing.
|
|
749
|
+
*
|
|
750
|
+
* @param client - The reader.
|
|
751
|
+
* @param keys - The page's keys.
|
|
752
|
+
* @param includeSize - Whether to add `MEMORY USAGE`.
|
|
753
|
+
* @returns One entry per key, in page order.
|
|
754
|
+
*/
|
|
755
|
+
private describeAll;
|
|
756
|
+
/**
|
|
757
|
+
* Reads a value according to its Redis type, truncating to the configured caps.
|
|
758
|
+
*
|
|
759
|
+
* @param client - The reader.
|
|
760
|
+
* @param key - The key to read.
|
|
761
|
+
* @param type - The key's type.
|
|
762
|
+
* @returns The value, or `null` when the key vanished between reads.
|
|
763
|
+
*/
|
|
764
|
+
private readValue;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Splits a validated pattern into the literal prefix a key must carry and
|
|
769
|
+
* whether that prefix may be extended.
|
|
770
|
+
*
|
|
771
|
+
* @param pattern - A pattern that already satisfies {@link SCOPE_PATTERN_SHAPE}.
|
|
772
|
+
* @returns The literal prefix, and whether a trailing `*` allows a suffix.
|
|
773
|
+
*/
|
|
774
|
+
declare function readScopePattern(pattern: string): {
|
|
775
|
+
readonly prefix: string;
|
|
776
|
+
readonly isPrefixMatch: boolean;
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* Reports whether a key falls inside a scope's declared region.
|
|
780
|
+
*
|
|
781
|
+
* Exact by construction rather than by matching — see {@link SCOPE_PATTERN_SHAPE}
|
|
782
|
+
* for why the pattern shape is restricted to make this possible.
|
|
783
|
+
*
|
|
784
|
+
* @param pattern - The scope's validated match pattern.
|
|
785
|
+
* @param key - The key a caller named.
|
|
786
|
+
* @returns `true` when the key belongs to the scope.
|
|
787
|
+
*/
|
|
788
|
+
declare function isKeyInScope(pattern: string, key: string): boolean;
|
|
789
|
+
/**
|
|
790
|
+
* Validates an administration scope allowlist and returns a frozen copy.
|
|
791
|
+
*
|
|
792
|
+
* Every rule here closes a way the allowlist could authorise more than it
|
|
793
|
+
* appears to: an unanchored pattern reaches the whole instance, a duplicate id
|
|
794
|
+
* makes one declaration silently shadow another, and an empty field produces a
|
|
795
|
+
* scope a surface can name but not explain. Freezing the result keeps the
|
|
796
|
+
* decision made at wiring from being edited by anything that later holds it.
|
|
797
|
+
*
|
|
798
|
+
* @param scopes - The scopes the application declares.
|
|
799
|
+
* @returns A frozen list of frozen scopes, in declaration order.
|
|
800
|
+
* @throws {CacheException} `INVALID_SCOPE` when the list is empty, a required
|
|
801
|
+
* field is empty, an id repeats, or a pattern has no literal prefix.
|
|
802
|
+
*/
|
|
803
|
+
declare function validateScopes(scopes: readonly CacheScope[]): readonly CacheScope[];
|
|
804
|
+
/**
|
|
805
|
+
* Finds a declared scope by the identifier a caller named.
|
|
806
|
+
*
|
|
807
|
+
* Exact string equality against the frozen list, never interpolation — the
|
|
808
|
+
* caller's input selects a declaration, it never contributes to one.
|
|
809
|
+
*
|
|
810
|
+
* @param scopes - The validated allowlist.
|
|
811
|
+
* @param id - The identifier from the request.
|
|
812
|
+
* @returns The scope.
|
|
813
|
+
* @throws {CacheException} `SCOPE_NOT_FOUND` when no declaration has that id.
|
|
814
|
+
*/
|
|
815
|
+
declare function findScope(scopes: readonly CacheScope[], id: string): CacheScope;
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Reads Redis `INFO` text into a typed reading.
|
|
819
|
+
*
|
|
820
|
+
* Layer: admin. `INFO` is section headers (`# Memory`), `field:value` lines and
|
|
821
|
+
* blank lines separated by CRLF. Every value arrives as text — a count, a ratio
|
|
822
|
+
* and a Unix timestamp are indistinguishable until something decides which is
|
|
823
|
+
* which, and that decision is this module's whole content.
|
|
824
|
+
*
|
|
825
|
+
* Parsed here rather than by each consumer: the format is a wire detail, and
|
|
826
|
+
* interpreting it once on the side that can be tested against a real server
|
|
827
|
+
* beats every surface holding its own copy of the rules.
|
|
828
|
+
*
|
|
829
|
+
* The field names are asserted against a real Redis in the E2E suite rather than
|
|
830
|
+
* taken from documentation. A Redis test double returns whatever its fixture
|
|
831
|
+
* decided, so every name here would pass a unit suite while being wrong — a
|
|
832
|
+
* double cannot falsify an assumption about the thing it stands in for.
|
|
833
|
+
*/
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Splits `INFO` text into its fields.
|
|
837
|
+
*
|
|
838
|
+
* Section headers are dropped rather than used to namespace the fields: `INFO`
|
|
839
|
+
* field names are already unique across sections, and keying by section would
|
|
840
|
+
* make this depend on header spellings that vary between versions while the
|
|
841
|
+
* field names do not.
|
|
842
|
+
*
|
|
843
|
+
* @param text - The raw `INFO` output.
|
|
844
|
+
* @returns Field name to raw text value.
|
|
845
|
+
*/
|
|
846
|
+
declare function parseInfoFields(text: string): ReadonlyMap<string, string>;
|
|
847
|
+
/**
|
|
848
|
+
* Turns `INFO` text into a typed reading.
|
|
849
|
+
*
|
|
850
|
+
* @param infoText - The raw `INFO` output.
|
|
851
|
+
* @param readAt - When the reading was taken, by the reading host's clock.
|
|
852
|
+
* @returns The parsed statistics.
|
|
853
|
+
*/
|
|
854
|
+
declare function readRedisStats(infoText: string, readAt: Date): RedisStats;
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Narrowing of raw Redis replies into the admin surface's typed readings.
|
|
858
|
+
*
|
|
859
|
+
* Layer: admin. ioredis hands back `unknown` for the commands this surface
|
|
860
|
+
* issues through a pipeline, and every reply that reaches a caller passes
|
|
861
|
+
* through here first. Each helper reports absence rather than substituting a
|
|
862
|
+
* value that would read as a measurement.
|
|
863
|
+
*/
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Narrows a `TYPE` reply to the closed union.
|
|
867
|
+
*
|
|
868
|
+
* @param reply - Whatever the server answered.
|
|
869
|
+
* @returns The recognised type, or `other`.
|
|
870
|
+
*/
|
|
871
|
+
declare function readKeyType(reply: unknown): RedisKeyType;
|
|
872
|
+
/**
|
|
873
|
+
* Narrows a `TTL` reply to its three distinct meanings.
|
|
874
|
+
*
|
|
875
|
+
* A reply that is not a number, or is a negative value Redis does not document,
|
|
876
|
+
* is reported as `missing` rather than as a remaining life — a negative
|
|
877
|
+
* countdown on a screen is a reading no server produced.
|
|
878
|
+
*
|
|
879
|
+
* @param reply - Whatever the server answered.
|
|
880
|
+
* @returns Whether the key expires, is persistent, or is gone.
|
|
881
|
+
*/
|
|
882
|
+
declare function readKeyTtl(reply: unknown): KeyTtl;
|
|
883
|
+
/**
|
|
884
|
+
* Narrows a `MEMORY USAGE` reply.
|
|
885
|
+
*
|
|
886
|
+
* Reports `null` rather than zero when the server declines to answer: a key
|
|
887
|
+
* whose size is genuinely unknown and one that occupies nothing render the same
|
|
888
|
+
* under a zero, and only one of them is a measurement.
|
|
889
|
+
*
|
|
890
|
+
* @param reply - Whatever the server answered.
|
|
891
|
+
* @returns The size in bytes, or `null` when no size was reported.
|
|
892
|
+
*/
|
|
893
|
+
declare function readSizeBytes(reply: unknown): number | null;
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* NestJS injection tokens for the `@bymax-one/nest-cache/admin` subpath.
|
|
897
|
+
*
|
|
898
|
+
* Layer: admin. `Symbol`-based for the same reason as the server tokens: two
|
|
899
|
+
* consumers cannot collide by accident, and a string typo cannot resolve a
|
|
900
|
+
* foreign provider.
|
|
901
|
+
*/
|
|
902
|
+
/** Resolved administration options (scopes, thresholds, limits). */
|
|
903
|
+
declare const BYMAX_CACHE_ADMIN_OPTIONS: unique symbol;
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Defaults for the administration surface.
|
|
907
|
+
*
|
|
908
|
+
* Layer: admin. Each value is a threshold a deployment may override; they live
|
|
909
|
+
* together so a consumer reading one can see what else is tunable.
|
|
910
|
+
*/
|
|
911
|
+
/**
|
|
912
|
+
* Round trip above which the cache is answering but not well.
|
|
913
|
+
*
|
|
914
|
+
* A round number rather than a measured percentile, and deliberately so: this is
|
|
915
|
+
* the threshold at which a surface changes what it reports, not a service-level
|
|
916
|
+
* objective. A cache taking this long has stopped being the thing that makes
|
|
917
|
+
* requests fast.
|
|
918
|
+
*/
|
|
919
|
+
declare const DEFAULT_DEGRADED_ABOVE_MS = 250;
|
|
920
|
+
/**
|
|
921
|
+
* How long a health probe waits before reporting the cache down.
|
|
922
|
+
*
|
|
923
|
+
* Without this, a `PING` against a wedged connection never settles and a health
|
|
924
|
+
* route hangs instead of answering — the failure mode a health route exists to
|
|
925
|
+
* report becomes the failure mode it exhibits.
|
|
926
|
+
*/
|
|
927
|
+
declare const DEFAULT_PROBE_TIMEOUT_MS = 2000;
|
|
928
|
+
/**
|
|
929
|
+
* Most keys a single listing reads before reporting the page incomplete.
|
|
930
|
+
*
|
|
931
|
+
* A cap rather than a full scan: an administration surface asks about keyspaces
|
|
932
|
+
* whose size is exactly what it does not know yet.
|
|
933
|
+
*/
|
|
934
|
+
declare const DEFAULT_SCAN_LIMIT = 500;
|
|
935
|
+
/**
|
|
936
|
+
* Most commands one pipeline flush sends.
|
|
937
|
+
*
|
|
938
|
+
* Bounds the BATCH, in commands, rather than the key count — that is the
|
|
939
|
+
* resource actually spent. Redis is single-threaded, so a pipeline converts a
|
|
940
|
+
* network cost into a server-blocking one: describing N keys is two or three
|
|
941
|
+
* commands each, and sending them as one flush blocks every other client for
|
|
942
|
+
* the whole burst — on a server someone is inspecting precisely because it is
|
|
943
|
+
* unwell. Pipelining is the aggravating factor here, not the mitigation.
|
|
944
|
+
*
|
|
945
|
+
* Every batch chunks to this size regardless of how many keys the page allows,
|
|
946
|
+
* so a permitted maximum arrives as several small blocks rather than one large
|
|
947
|
+
* one.
|
|
948
|
+
*/
|
|
949
|
+
declare const DEFAULT_COMMAND_BATCH_LIMIT = 100;
|
|
950
|
+
/**
|
|
951
|
+
* Most members or hash fields one value reveal returns.
|
|
952
|
+
*
|
|
953
|
+
* A reveal is a debugging read, not an export. Returning a million-member set
|
|
954
|
+
* would block the server assembling it and hand a surface a payload it cannot
|
|
955
|
+
* draw; the truncation is reported so nobody mistakes the page for the whole.
|
|
956
|
+
*/
|
|
957
|
+
declare const DEFAULT_REVEAL_LIMIT = 200;
|
|
958
|
+
/**
|
|
959
|
+
* Most characters one revealed string value carries.
|
|
960
|
+
*
|
|
961
|
+
* Same reasoning as {@link DEFAULT_REVEAL_LIMIT}: a cached HTML document or a
|
|
962
|
+
* serialized blob is measured in megabytes, and a surface asking "what is in
|
|
963
|
+
* this key" does not need all of it to answer the question.
|
|
964
|
+
*/
|
|
965
|
+
declare const DEFAULT_REVEAL_STRING_LIMIT = 4096;
|
|
966
|
+
|
|
967
|
+
export { BYMAX_CACHE_ADMIN_OPTIONS, type BgsaveStatus, BymaxCacheAdminModule, type BymaxCacheAdminModuleAsyncOptions, type BymaxCacheAdminModuleOptions, CacheAdminService, type CacheConfig, type CacheHealth, type CacheMode, type CacheProbe, type CacheProbeDegraded, type CacheProbeDown, type CacheProbeFailure, type CacheProbeUp, type CacheScope, CacheStatusService, DEFAULT_COMMAND_BATCH_LIMIT, DEFAULT_DEGRADED_ABOVE_MS, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REVEAL_LIMIT, DEFAULT_REVEAL_STRING_LIMIT, DEFAULT_SCAN_LIMIT, type ICacheProbe, type IRedisPipeline, type IRedisReader, type IRedisReaderSource, type KeyDetail, type KeyEntry, type KeyTtl, type KeyspacePage, type ListKeysOptions, type MaxMemory, type PipelineReply, type RedisKeyType, type RedisStats, type ReplicationRole, type ResolvedAdminOptions, type RevealResult, type RevealedValue, findScope, isKeyInScope, parseInfoFields, readKeyTtl, readKeyType, readRedisStats, readScopePattern, readSizeBytes, resolveAdminOptions, validateScopes };
|