@bymax-one/nest-cache 1.0.5 → 1.0.6
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 +19 -0
- package/dist/server/index.cjs +27 -9
- package/dist/server/index.d.cts +17 -1
- package/dist/server/index.d.ts +17 -1
- package/dist/server/index.mjs +27 -9
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.0.6] - 2026-08-06
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `ConnectionManager.onModuleInit()` assigned a freshly created client over the field
|
|
14
|
+
unconditionally. When something had already opened the main client through
|
|
15
|
+
`getClient()`, that first socket was left connected with no reference left to close it —
|
|
16
|
+
`onModuleDestroy()` quits only the current client, so the abandoned one stayed in Redis's
|
|
17
|
+
`CLIENT LIST` until its own timeout. Init now adopts an existing client instead of
|
|
18
|
+
replacing it, and the assignment lives in one private accessor so no other path can
|
|
19
|
+
strand a live connection.
|
|
20
|
+
|
|
21
|
+
Reaching it takes touching the cache before the cache module's own init hook runs, which
|
|
22
|
+
is possible in two ordinary shapes: NestJS orders `onModuleInit` by module depth, so a
|
|
23
|
+
consumer module deeper in the graph runs first, and `app.get()` works between
|
|
24
|
+
`NestFactory.create()` and `app.init()`. The ordinary boot path opened exactly one client
|
|
25
|
+
before this change and still does.
|
|
26
|
+
|
|
9
27
|
## [1.0.5] - 2026-08-06
|
|
10
28
|
|
|
11
29
|
**Documentation, tests and E2E only.** `dist/` is byte-identical to `1.0.4`.
|
|
@@ -150,6 +168,7 @@ type or export moved.
|
|
|
150
168
|
- Published with npm OIDC provenance — no long-lived tokens
|
|
151
169
|
- Zero direct runtime dependencies (`dependencies: {}`) — `ioredis` and NestJS via peer deps
|
|
152
170
|
|
|
171
|
+
[1.0.6]: https://github.com/bymaxone/nest-cache/compare/v1.0.5...v1.0.6
|
|
153
172
|
[1.0.5]: https://github.com/bymaxone/nest-cache/compare/v1.0.4...v1.0.5
|
|
154
173
|
[1.0.4]: https://github.com/bymaxone/nest-cache/compare/v1.0.3...v1.0.4
|
|
155
174
|
[1.0.3]: https://github.com/bymaxone/nest-cache/releases/tag/v1.0.3
|
package/dist/server/index.cjs
CHANGED
|
@@ -227,6 +227,7 @@ function applyDefaults(options) {
|
|
|
227
227
|
Object.defineProperty(resolved, key, {
|
|
228
228
|
get: () => value,
|
|
229
229
|
enumerable: false,
|
|
230
|
+
// Stryker disable next-line BooleanLiteral: equivalent HERE — `resolved` is `Object.freeze`d on the way out and freezing makes every property non-configurable anyway, so flipping this flag changes nothing observable. It stays because it states the guarantee where the accessor is defined, and nest-storage withholds its credentials the same way WITHOUT freezing, where this flag is the only thing enforcing it.
|
|
230
231
|
configurable: false
|
|
231
232
|
});
|
|
232
233
|
}
|
|
@@ -259,12 +260,19 @@ exports.ConnectionManager = class ConnectionManager {
|
|
|
259
260
|
this.defaultReconnectOnError = (err) => err.message.includes("READONLY");
|
|
260
261
|
__privateSet(this, _redisOptionsResolved, this.buildRedisOptions(options));
|
|
261
262
|
}
|
|
262
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* Opens the main client and waits for readiness unless `lazyConnect`.
|
|
265
|
+
*
|
|
266
|
+
* Adopts a client {@link getClient} already opened rather than replacing it:
|
|
267
|
+
* NestJS orders `onModuleInit` by module depth, and `app.get()` works before
|
|
268
|
+
* `app.init()`, so a consumer can reach the cache first. Overwriting here
|
|
269
|
+
* would strand that socket with no reference left to close it — teardown
|
|
270
|
+
* quits only the current client.
|
|
271
|
+
*/
|
|
263
272
|
async onModuleInit() {
|
|
264
|
-
|
|
265
|
-
this.registerListeners(__privateGet(this, _client), "main");
|
|
273
|
+
const client = this.ensureClient();
|
|
266
274
|
if (!this.options.connection?.lazyConnect) {
|
|
267
|
-
await this.waitUntilReady(
|
|
275
|
+
await this.waitUntilReady(client);
|
|
268
276
|
}
|
|
269
277
|
}
|
|
270
278
|
/**
|
|
@@ -274,11 +282,7 @@ exports.ConnectionManager = class ConnectionManager {
|
|
|
274
282
|
* @returns The shared main client.
|
|
275
283
|
*/
|
|
276
284
|
getClient() {
|
|
277
|
-
|
|
278
|
-
__privateSet(this, _client, this.createClient());
|
|
279
|
-
this.registerListeners(__privateGet(this, _client), "main");
|
|
280
|
-
}
|
|
281
|
-
return __privateGet(this, _client);
|
|
285
|
+
return this.ensureClient();
|
|
282
286
|
}
|
|
283
287
|
/**
|
|
284
288
|
* Creates a brand-new dedicated connection for subscriber mode (a subscriber
|
|
@@ -336,6 +340,20 @@ exports.ConnectionManager = class ConnectionManager {
|
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
342
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
343
|
+
/**
|
|
344
|
+
* Returns the main client, opening and wiring it on the first call. The only
|
|
345
|
+
* place a live client is assigned, so no caller can replace one; teardown
|
|
346
|
+
* only clears the field back to `null`.
|
|
347
|
+
*
|
|
348
|
+
* @returns The shared main client.
|
|
349
|
+
*/
|
|
350
|
+
ensureClient() {
|
|
351
|
+
if (!__privateGet(this, _client)) {
|
|
352
|
+
__privateSet(this, _client, this.createClient());
|
|
353
|
+
this.registerListeners(__privateGet(this, _client), "main");
|
|
354
|
+
}
|
|
355
|
+
return __privateGet(this, _client);
|
|
356
|
+
}
|
|
339
357
|
/**
|
|
340
358
|
* Instantiates the client matching the configured mode.
|
|
341
359
|
*
|
package/dist/server/index.d.cts
CHANGED
|
@@ -369,7 +369,15 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
|
|
|
369
369
|
* module can provide `null` when the consumer omits `events`.
|
|
370
370
|
*/
|
|
371
371
|
constructor(options: ResolvedOptions, events?: ICacheEvents | undefined);
|
|
372
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* Opens the main client and waits for readiness unless `lazyConnect`.
|
|
374
|
+
*
|
|
375
|
+
* Adopts a client {@link getClient} already opened rather than replacing it:
|
|
376
|
+
* NestJS orders `onModuleInit` by module depth, and `app.get()` works before
|
|
377
|
+
* `app.init()`, so a consumer can reach the cache first. Overwriting here
|
|
378
|
+
* would strand that socket with no reference left to close it — teardown
|
|
379
|
+
* quits only the current client.
|
|
380
|
+
*/
|
|
373
381
|
onModuleInit(): Promise<void>;
|
|
374
382
|
/**
|
|
375
383
|
* Returns the singleton main client, creating it on first access if the
|
|
@@ -398,6 +406,14 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
|
|
|
398
406
|
createSubscriberClient(): AnyRedis;
|
|
399
407
|
/** Quits the main client gracefully, forcing `disconnect()` on timeout. */
|
|
400
408
|
onModuleDestroy(): Promise<void>;
|
|
409
|
+
/**
|
|
410
|
+
* Returns the main client, opening and wiring it on the first call. The only
|
|
411
|
+
* place a live client is assigned, so no caller can replace one; teardown
|
|
412
|
+
* only clears the field back to `null`.
|
|
413
|
+
*
|
|
414
|
+
* @returns The shared main client.
|
|
415
|
+
*/
|
|
416
|
+
private ensureClient;
|
|
401
417
|
/**
|
|
402
418
|
* Instantiates the client matching the configured mode.
|
|
403
419
|
*
|
package/dist/server/index.d.ts
CHANGED
|
@@ -369,7 +369,15 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
|
|
|
369
369
|
* module can provide `null` when the consumer omits `events`.
|
|
370
370
|
*/
|
|
371
371
|
constructor(options: ResolvedOptions, events?: ICacheEvents | undefined);
|
|
372
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* Opens the main client and waits for readiness unless `lazyConnect`.
|
|
374
|
+
*
|
|
375
|
+
* Adopts a client {@link getClient} already opened rather than replacing it:
|
|
376
|
+
* NestJS orders `onModuleInit` by module depth, and `app.get()` works before
|
|
377
|
+
* `app.init()`, so a consumer can reach the cache first. Overwriting here
|
|
378
|
+
* would strand that socket with no reference left to close it — teardown
|
|
379
|
+
* quits only the current client.
|
|
380
|
+
*/
|
|
373
381
|
onModuleInit(): Promise<void>;
|
|
374
382
|
/**
|
|
375
383
|
* Returns the singleton main client, creating it on first access if the
|
|
@@ -398,6 +406,14 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
|
|
|
398
406
|
createSubscriberClient(): AnyRedis;
|
|
399
407
|
/** Quits the main client gracefully, forcing `disconnect()` on timeout. */
|
|
400
408
|
onModuleDestroy(): Promise<void>;
|
|
409
|
+
/**
|
|
410
|
+
* Returns the main client, opening and wiring it on the first call. The only
|
|
411
|
+
* place a live client is assigned, so no caller can replace one; teardown
|
|
412
|
+
* only clears the field back to `null`.
|
|
413
|
+
*
|
|
414
|
+
* @returns The shared main client.
|
|
415
|
+
*/
|
|
416
|
+
private ensureClient;
|
|
401
417
|
/**
|
|
402
418
|
* Instantiates the client matching the configured mode.
|
|
403
419
|
*
|
package/dist/server/index.mjs
CHANGED
|
@@ -225,6 +225,7 @@ function applyDefaults(options) {
|
|
|
225
225
|
Object.defineProperty(resolved, key, {
|
|
226
226
|
get: () => value,
|
|
227
227
|
enumerable: false,
|
|
228
|
+
// Stryker disable next-line BooleanLiteral: equivalent HERE — `resolved` is `Object.freeze`d on the way out and freezing makes every property non-configurable anyway, so flipping this flag changes nothing observable. It stays because it states the guarantee where the accessor is defined, and nest-storage withholds its credentials the same way WITHOUT freezing, where this flag is the only thing enforcing it.
|
|
228
229
|
configurable: false
|
|
229
230
|
});
|
|
230
231
|
}
|
|
@@ -257,12 +258,19 @@ var ConnectionManager = class {
|
|
|
257
258
|
this.defaultReconnectOnError = (err) => err.message.includes("READONLY");
|
|
258
259
|
__privateSet(this, _redisOptionsResolved, this.buildRedisOptions(options));
|
|
259
260
|
}
|
|
260
|
-
/**
|
|
261
|
+
/**
|
|
262
|
+
* Opens the main client and waits for readiness unless `lazyConnect`.
|
|
263
|
+
*
|
|
264
|
+
* Adopts a client {@link getClient} already opened rather than replacing it:
|
|
265
|
+
* NestJS orders `onModuleInit` by module depth, and `app.get()` works before
|
|
266
|
+
* `app.init()`, so a consumer can reach the cache first. Overwriting here
|
|
267
|
+
* would strand that socket with no reference left to close it — teardown
|
|
268
|
+
* quits only the current client.
|
|
269
|
+
*/
|
|
261
270
|
async onModuleInit() {
|
|
262
|
-
|
|
263
|
-
this.registerListeners(__privateGet(this, _client), "main");
|
|
271
|
+
const client = this.ensureClient();
|
|
264
272
|
if (!this.options.connection?.lazyConnect) {
|
|
265
|
-
await this.waitUntilReady(
|
|
273
|
+
await this.waitUntilReady(client);
|
|
266
274
|
}
|
|
267
275
|
}
|
|
268
276
|
/**
|
|
@@ -272,11 +280,7 @@ var ConnectionManager = class {
|
|
|
272
280
|
* @returns The shared main client.
|
|
273
281
|
*/
|
|
274
282
|
getClient() {
|
|
275
|
-
|
|
276
|
-
__privateSet(this, _client, this.createClient());
|
|
277
|
-
this.registerListeners(__privateGet(this, _client), "main");
|
|
278
|
-
}
|
|
279
|
-
return __privateGet(this, _client);
|
|
283
|
+
return this.ensureClient();
|
|
280
284
|
}
|
|
281
285
|
/**
|
|
282
286
|
* Creates a brand-new dedicated connection for subscriber mode (a subscriber
|
|
@@ -334,6 +338,20 @@ var ConnectionManager = class {
|
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
340
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
341
|
+
/**
|
|
342
|
+
* Returns the main client, opening and wiring it on the first call. The only
|
|
343
|
+
* place a live client is assigned, so no caller can replace one; teardown
|
|
344
|
+
* only clears the field back to `null`.
|
|
345
|
+
*
|
|
346
|
+
* @returns The shared main client.
|
|
347
|
+
*/
|
|
348
|
+
ensureClient() {
|
|
349
|
+
if (!__privateGet(this, _client)) {
|
|
350
|
+
__privateSet(this, _client, this.createClient());
|
|
351
|
+
this.registerListeners(__privateGet(this, _client), "main");
|
|
352
|
+
}
|
|
353
|
+
return __privateGet(this, _client);
|
|
354
|
+
}
|
|
337
355
|
/**
|
|
338
356
|
* Instantiates the client matching the configured mode.
|
|
339
357
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-cache",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Typed Redis cache for NestJS based on ioredis 5, with namespace strategy, Pub/Sub and Lua script management.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "pnpm clean && tsup",
|
|
58
58
|
"check:exports": "attw --pack .",
|
|
59
|
+
"check:mutants": "node scripts/check-mutation-directives.mjs",
|
|
59
60
|
"check:published": "node scripts/check-published-surface.mjs",
|
|
60
61
|
"clean": "rm -rf dist coverage",
|
|
61
62
|
"lint": "eslint src scripts",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
65
66
|
"mutation:incremental": "stryker run --incremental",
|
|
66
67
|
"prepare": "husky",
|
|
67
|
-
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published",
|
|
68
|
+
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published",
|
|
68
69
|
"release": "npm publish --provenance --access public",
|
|
69
70
|
"size": "node scripts/check-size.mjs",
|
|
70
71
|
"test": "jest",
|