@bymax-one/nest-cache 1.0.2 → 1.0.4

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 CHANGED
@@ -6,6 +6,46 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.0.4] - 2026-08-04
10
+
11
+ ### Security
12
+
13
+ - The Redis credentials are no longer disclosed when a service that holds the resolved
14
+ options is serialized. The three connection shapes — `connection`, `sentinel` and
15
+ `cluster` — move from plain fields on the resolved options to non-enumerable accessors,
16
+ and `ConnectionManager` keeps its client and the driver options built from them, as does
17
+ `PubSubService` with its subscriber, in ECMAScript private fields. A `url` carries the
18
+ password inline, the discrete forms carry it as a field, and an ioredis instance carries
19
+ `options.password` as a plain field, so `JSON.stringify`, object spread and
20
+ `util.inspect` on any holder emitted it in plaintext — which is what a structured logger
21
+ does when it renders a provider it was handed, and what an error reporter does when it
22
+ captures the scope of a throw.
23
+
24
+ Reading on purpose is unchanged: `options.connection` resolves as before, and no public
25
+ type or export moved.
26
+
27
+ ## [1.0.3] - 2026-08-01
28
+
29
+ ### Security
30
+
31
+ - **Peer floors raised to exclude known-vulnerable NestJS versions.** The declared
32
+ ranges were `@nestjs/common ^11.0.0` and `@nestjs/core ^11.0.0`, and both
33
+ admitted versions carrying published advisories:
34
+
35
+ | Peer | Advisory | Vulnerable | New floor |
36
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ---------- |
37
+ | `@nestjs/common` | [GHSA-cj7v-w2c7-cp7c](https://github.com/advisories/GHSA-cj7v-w2c7-cp7c) — remote code execution via the `Content-Type` header | `>= 11.0.0-next.1, < 11.0.16` | `^11.0.16` |
38
+ | `@nestjs/core` | [GHSA-36xv-jgw5-4q75](https://github.com/advisories/GHSA-36xv-jgw5-4q75) — improper neutralization of special elements in downstream output | `<= 11.1.17` | `^11.1.18` |
39
+
40
+ A peer range is a statement about which versions this library supports. Leaving
41
+ the floor below a published advisory told a consumer that a vulnerable install
42
+ was a supported one, and nothing in their tooling contradicted it — the install
43
+ resolved cleanly and silently.
44
+
45
+ Shipped as a patch, which is where a security fix belongs. A minor would have
46
+ bought nothing: `^1.0.2` accepts `1.1.0` just as readily as `1.0.3`, so the same
47
+ installs are affected either way. No runtime behaviour changed.
48
+
9
49
  ## [1.0.2] - 2026-07-29
10
50
 
11
51
  ### Fixed
@@ -87,3 +127,9 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
87
127
  - E2E suite: `@nestjs/testing` with `ioredis-mock` + Testcontainers (standalone, sentinel, cluster, connection resilience)
88
128
  - Published with npm OIDC provenance — no long-lived tokens
89
129
  - Zero direct runtime dependencies (`dependencies: {}`) — `ioredis` and NestJS via peer deps
130
+
131
+ [1.0.4]: https://github.com/bymaxone/nest-cache/compare/v1.0.3...v1.0.4
132
+ [1.0.3]: https://github.com/bymaxone/nest-cache/releases/tag/v1.0.3
133
+ [1.0.2]: https://github.com/bymaxone/nest-cache/releases/tag/v1.0.2
134
+ [1.0.1]: https://github.com/bymaxone/nest-cache/releases/tag/v1.0.1
135
+ [1.0.0]: https://github.com/bymaxone/nest-cache/releases/tag/v1.0.0
@@ -4,6 +4,9 @@ var common = require('@nestjs/common');
4
4
  var ioredis = require('ioredis');
5
5
 
6
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
7
10
  var __decorateClass = (decorators, target, key, kind) => {
8
11
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
9
12
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
@@ -12,6 +15,10 @@ var __decorateClass = (decorators, target, key, kind) => {
12
15
  return result;
13
16
  };
14
17
  var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
18
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
19
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
20
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
21
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
15
22
 
16
23
  // src/server/bymax-cache.constants.ts
17
24
  var BYMAX_CACHE_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_CACHE_OPTIONS");
@@ -215,8 +222,17 @@ function applyDefaults(options) {
215
222
  isGlobal: options.isGlobal ?? true,
216
223
  scripts: options.scripts
217
224
  };
225
+ for (const key of ["connection", "sentinel", "cluster"]) {
226
+ const value = Object.getOwnPropertyDescriptor(resolved, key)?.value;
227
+ Object.defineProperty(resolved, key, {
228
+ get: () => value,
229
+ enumerable: false,
230
+ configurable: false
231
+ });
232
+ }
218
233
  return Object.freeze(resolved);
219
234
  }
235
+ var _client, _redisOptionsResolved;
220
236
  exports.ConnectionManager = class ConnectionManager {
221
237
  /**
222
238
  * @param options - Resolved module options (frozen).
@@ -226,19 +242,29 @@ exports.ConnectionManager = class ConnectionManager {
226
242
  constructor(options, events) {
227
243
  this.options = options;
228
244
  this.events = events;
229
- this.client = null;
245
+ /**
246
+ * The live Redis client.
247
+ *
248
+ * An ECMAScript private field rather than a TypeScript `private` one, which is
249
+ * erased at runtime: an ioredis instance carries `options.password` as a plain
250
+ * field, so leaving it enumerable puts the credentials one `JSON.stringify`
251
+ * away from any service that holds this manager.
252
+ */
253
+ __privateAdd(this, _client, null);
254
+ /** The driver options built from the connection config; they carry the password. */
255
+ __privateAdd(this, _redisOptionsResolved);
230
256
  /** Default backoff: grow 50 ms per attempt, capped at 2 s. */
231
257
  this.defaultRetryStrategy = (times) => Math.min(times * DEFAULT_RETRY_BASE_MS, DEFAULT_RETRY_MAX_MS);
232
258
  /** Default reconnect policy: reconnect only on a `READONLY` replica failover. */
233
259
  this.defaultReconnectOnError = (err) => err.message.includes("READONLY");
234
- this.redisOptionsResolved = this.buildRedisOptions(options);
260
+ __privateSet(this, _redisOptionsResolved, this.buildRedisOptions(options));
235
261
  }
236
262
  /** Opens the main client and waits for readiness unless `lazyConnect`. */
237
263
  async onModuleInit() {
238
- this.client = this.createClient();
239
- this.registerListeners(this.client, "main");
264
+ __privateSet(this, _client, this.createClient());
265
+ this.registerListeners(__privateGet(this, _client), "main");
240
266
  if (!this.options.connection?.lazyConnect) {
241
- await this.waitUntilReady(this.client);
267
+ await this.waitUntilReady(__privateGet(this, _client));
242
268
  }
243
269
  }
244
270
  /**
@@ -248,11 +274,11 @@ exports.ConnectionManager = class ConnectionManager {
248
274
  * @returns The shared main client.
249
275
  */
250
276
  getClient() {
251
- if (!this.client) {
252
- this.client = this.createClient();
253
- this.registerListeners(this.client, "main");
277
+ if (!__privateGet(this, _client)) {
278
+ __privateSet(this, _client, this.createClient());
279
+ this.registerListeners(__privateGet(this, _client), "main");
254
280
  }
255
- return this.client;
281
+ return __privateGet(this, _client);
256
282
  }
257
283
  /**
258
284
  * Creates a brand-new dedicated connection for subscriber mode (a subscriber
@@ -278,7 +304,7 @@ exports.ConnectionManager = class ConnectionManager {
278
304
  }
279
305
  /** Quits the main client gracefully, forcing `disconnect()` on timeout. */
280
306
  async onModuleDestroy() {
281
- const client = this.client;
307
+ const client = __privateGet(this, _client);
282
308
  if (!client) {
283
309
  return;
284
310
  }
@@ -306,7 +332,7 @@ exports.ConnectionManager = class ConnectionManager {
306
332
  client.disconnect();
307
333
  } finally {
308
334
  clearTimeout(timer);
309
- this.client = null;
335
+ __privateSet(this, _client, null);
310
336
  }
311
337
  }
312
338
  // ─── Private ──────────────────────────────────────────────────────────────
@@ -324,7 +350,7 @@ exports.ConnectionManager = class ConnectionManager {
324
350
  throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode: "sentinel" });
325
351
  }
326
352
  return new ioredis.Redis({
327
- ...this.redisOptionsResolved,
353
+ ...__privateGet(this, _redisOptionsResolved),
328
354
  ...overrides,
329
355
  sentinels: sentinel.sentinels,
330
356
  name: sentinel.name,
@@ -347,7 +373,7 @@ exports.ConnectionManager = class ConnectionManager {
347
373
  }
348
374
  return new ioredis.Cluster(cluster.nodes, cluster.options ?? {});
349
375
  }
350
- return new ioredis.Redis({ ...this.redisOptionsResolved, ...overrides });
376
+ return new ioredis.Redis({ ...__privateGet(this, _redisOptionsResolved), ...overrides });
351
377
  }
352
378
  /** Merges connection options with defaults; URL fields take precedence. */
353
379
  buildRedisOptions(opts) {
@@ -413,6 +439,8 @@ exports.ConnectionManager = class ConnectionManager {
413
439
  });
414
440
  }
415
441
  };
442
+ _client = new WeakMap();
443
+ _redisOptionsResolved = new WeakMap();
416
444
  exports.ConnectionManager = __decorateClass([
417
445
  common.Injectable(),
418
446
  __decorateParam(0, common.Inject(BYMAX_CACHE_OPTIONS)),
@@ -1259,6 +1287,7 @@ exports.CacheService = __decorateClass([
1259
1287
  __decorateParam(4, common.Optional()),
1260
1288
  __decorateParam(4, common.Inject(exports.ScriptManagerService))
1261
1289
  ], exports.CacheService);
1290
+ var _subscriber;
1262
1291
  exports.PubSubService = class PubSubService {
1263
1292
  /**
1264
1293
  * @param options - Resolved module options. Supplies the serializer.
@@ -1276,7 +1305,13 @@ exports.PubSubService = class PubSubService {
1276
1305
  this.keyBuilder = keyBuilder;
1277
1306
  this.events = events;
1278
1307
  /** Lazily-created dedicated subscriber connection (null until first subscribe). */
1279
- this.subscriber = null;
1308
+ /**
1309
+ * The dedicated subscriber connection.
1310
+ *
1311
+ * Private for the same reason as the manager's client: an ioredis instance
1312
+ * carries `options.password` as a plain field.
1313
+ */
1314
+ __privateAdd(this, _subscriber, null);
1280
1315
  /** Live-listener ref-count per namespaced channel; UNSUBSCRIBE fires on the last. */
1281
1316
  this.channelRefs = /* @__PURE__ */ new Map();
1282
1317
  /** Live-listener ref-count per namespaced pattern; PUNSUBSCRIBE fires on the last. */
@@ -1379,11 +1414,11 @@ exports.PubSubService = class PubSubService {
1379
1414
  }
1380
1415
  /** Closes the subscriber connection gracefully, forcing disconnect on failure. */
1381
1416
  async onModuleDestroy() {
1382
- if (!this.subscriber) {
1417
+ if (!__privateGet(this, _subscriber)) {
1383
1418
  return;
1384
1419
  }
1385
- const subscriber = this.subscriber;
1386
- this.subscriber = null;
1420
+ const subscriber = __privateGet(this, _subscriber);
1421
+ __privateSet(this, _subscriber, null);
1387
1422
  try {
1388
1423
  await subscriber.quit();
1389
1424
  } catch {
@@ -1401,10 +1436,10 @@ exports.PubSubService = class PubSubService {
1401
1436
  * experimental passthrough per the spec).
1402
1437
  */
1403
1438
  ensureSubscriber() {
1404
- if (!this.subscriber) {
1405
- this.subscriber = this.connection.createSubscriberClient();
1439
+ if (!__privateGet(this, _subscriber)) {
1440
+ __privateSet(this, _subscriber, this.connection.createSubscriberClient());
1406
1441
  }
1407
- return this.subscriber;
1442
+ return __privateGet(this, _subscriber);
1408
1443
  }
1409
1444
  /**
1410
1445
  * Subscribes the target (channel or pattern) only when it gains its FIRST
@@ -1483,6 +1518,7 @@ exports.PubSubService = class PubSubService {
1483
1518
  }
1484
1519
  }
1485
1520
  };
1521
+ _subscriber = new WeakMap();
1486
1522
  exports.PubSubService = __decorateClass([
1487
1523
  common.Injectable(),
1488
1524
  __decorateParam(0, common.Inject(BYMAX_CACHE_OPTIONS)),
@@ -356,10 +356,9 @@ type ResolvedOptions = Required<Pick<BymaxCacheModuleOptions, 'mode' | 'namespac
356
356
  /** The two client flavors this manager can produce. */
357
357
  type AnyRedis = Redis | Cluster;
358
358
  declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
359
+ #private;
359
360
  private readonly options;
360
361
  private readonly events?;
361
- private client;
362
- private readonly redisOptionsResolved;
363
362
  /** Default backoff: grow 50 ms per attempt, capped at 2 s. */
364
363
  private readonly defaultRetryStrategy;
365
364
  /** Default reconnect policy: reconnect only on a `READONLY` replica failover. */
@@ -991,11 +990,10 @@ type IPubSubPatternHandler<T> = (message: T, channel: string, pattern: string) =
991
990
  /** Detaches a subscription's listener and unsubscribes its channel/pattern. */
992
991
  type Unsubscribe = () => Promise<void>;
993
992
  declare class PubSubService implements OnModuleDestroy {
993
+ #private;
994
994
  private readonly connection;
995
995
  private readonly keyBuilder;
996
996
  private readonly events?;
997
- /** Lazily-created dedicated subscriber connection (null until first subscribe). */
998
- private subscriber;
999
997
  /** Live-listener ref-count per namespaced channel; UNSUBSCRIBE fires on the last. */
1000
998
  private readonly channelRefs;
1001
999
  /** Live-listener ref-count per namespaced pattern; PUNSUBSCRIBE fires on the last. */
@@ -356,10 +356,9 @@ type ResolvedOptions = Required<Pick<BymaxCacheModuleOptions, 'mode' | 'namespac
356
356
  /** The two client flavors this manager can produce. */
357
357
  type AnyRedis = Redis | Cluster;
358
358
  declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
359
+ #private;
359
360
  private readonly options;
360
361
  private readonly events?;
361
- private client;
362
- private readonly redisOptionsResolved;
363
362
  /** Default backoff: grow 50 ms per attempt, capped at 2 s. */
364
363
  private readonly defaultRetryStrategy;
365
364
  /** Default reconnect policy: reconnect only on a `READONLY` replica failover. */
@@ -991,11 +990,10 @@ type IPubSubPatternHandler<T> = (message: T, channel: string, pattern: string) =
991
990
  /** Detaches a subscription's listener and unsubscribes its channel/pattern. */
992
991
  type Unsubscribe = () => Promise<void>;
993
992
  declare class PubSubService implements OnModuleDestroy {
993
+ #private;
994
994
  private readonly connection;
995
995
  private readonly keyBuilder;
996
996
  private readonly events?;
997
- /** Lazily-created dedicated subscriber connection (null until first subscribe). */
998
- private subscriber;
999
997
  /** Live-listener ref-count per namespaced channel; UNSUBSCRIBE fires on the last. */
1000
998
  private readonly channelRefs;
1001
999
  /** Live-listener ref-count per namespaced pattern; PUNSUBSCRIBE fires on the last. */
@@ -2,6 +2,9 @@ import { ConfigurableModuleBuilder, HttpStatus, Injectable, Inject, Optional, Mo
2
2
  import { Redis, Cluster } from 'ioredis';
3
3
 
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
5
8
  var __decorateClass = (decorators, target, key, kind) => {
6
9
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
7
10
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
@@ -10,6 +13,10 @@ var __decorateClass = (decorators, target, key, kind) => {
10
13
  return result;
11
14
  };
12
15
  var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
16
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
17
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
18
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
19
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
13
20
 
14
21
  // src/server/bymax-cache.constants.ts
15
22
  var BYMAX_CACHE_OPTIONS = /* @__PURE__ */ Symbol("BYMAX_CACHE_OPTIONS");
@@ -213,8 +220,17 @@ function applyDefaults(options) {
213
220
  isGlobal: options.isGlobal ?? true,
214
221
  scripts: options.scripts
215
222
  };
223
+ for (const key of ["connection", "sentinel", "cluster"]) {
224
+ const value = Object.getOwnPropertyDescriptor(resolved, key)?.value;
225
+ Object.defineProperty(resolved, key, {
226
+ get: () => value,
227
+ enumerable: false,
228
+ configurable: false
229
+ });
230
+ }
216
231
  return Object.freeze(resolved);
217
232
  }
233
+ var _client, _redisOptionsResolved;
218
234
  var ConnectionManager = class {
219
235
  /**
220
236
  * @param options - Resolved module options (frozen).
@@ -224,19 +240,29 @@ var ConnectionManager = class {
224
240
  constructor(options, events) {
225
241
  this.options = options;
226
242
  this.events = events;
227
- this.client = null;
243
+ /**
244
+ * The live Redis client.
245
+ *
246
+ * An ECMAScript private field rather than a TypeScript `private` one, which is
247
+ * erased at runtime: an ioredis instance carries `options.password` as a plain
248
+ * field, so leaving it enumerable puts the credentials one `JSON.stringify`
249
+ * away from any service that holds this manager.
250
+ */
251
+ __privateAdd(this, _client, null);
252
+ /** The driver options built from the connection config; they carry the password. */
253
+ __privateAdd(this, _redisOptionsResolved);
228
254
  /** Default backoff: grow 50 ms per attempt, capped at 2 s. */
229
255
  this.defaultRetryStrategy = (times) => Math.min(times * DEFAULT_RETRY_BASE_MS, DEFAULT_RETRY_MAX_MS);
230
256
  /** Default reconnect policy: reconnect only on a `READONLY` replica failover. */
231
257
  this.defaultReconnectOnError = (err) => err.message.includes("READONLY");
232
- this.redisOptionsResolved = this.buildRedisOptions(options);
258
+ __privateSet(this, _redisOptionsResolved, this.buildRedisOptions(options));
233
259
  }
234
260
  /** Opens the main client and waits for readiness unless `lazyConnect`. */
235
261
  async onModuleInit() {
236
- this.client = this.createClient();
237
- this.registerListeners(this.client, "main");
262
+ __privateSet(this, _client, this.createClient());
263
+ this.registerListeners(__privateGet(this, _client), "main");
238
264
  if (!this.options.connection?.lazyConnect) {
239
- await this.waitUntilReady(this.client);
265
+ await this.waitUntilReady(__privateGet(this, _client));
240
266
  }
241
267
  }
242
268
  /**
@@ -246,11 +272,11 @@ var ConnectionManager = class {
246
272
  * @returns The shared main client.
247
273
  */
248
274
  getClient() {
249
- if (!this.client) {
250
- this.client = this.createClient();
251
- this.registerListeners(this.client, "main");
275
+ if (!__privateGet(this, _client)) {
276
+ __privateSet(this, _client, this.createClient());
277
+ this.registerListeners(__privateGet(this, _client), "main");
252
278
  }
253
- return this.client;
279
+ return __privateGet(this, _client);
254
280
  }
255
281
  /**
256
282
  * Creates a brand-new dedicated connection for subscriber mode (a subscriber
@@ -276,7 +302,7 @@ var ConnectionManager = class {
276
302
  }
277
303
  /** Quits the main client gracefully, forcing `disconnect()` on timeout. */
278
304
  async onModuleDestroy() {
279
- const client = this.client;
305
+ const client = __privateGet(this, _client);
280
306
  if (!client) {
281
307
  return;
282
308
  }
@@ -304,7 +330,7 @@ var ConnectionManager = class {
304
330
  client.disconnect();
305
331
  } finally {
306
332
  clearTimeout(timer);
307
- this.client = null;
333
+ __privateSet(this, _client, null);
308
334
  }
309
335
  }
310
336
  // ─── Private ──────────────────────────────────────────────────────────────
@@ -322,7 +348,7 @@ var ConnectionManager = class {
322
348
  throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode: "sentinel" });
323
349
  }
324
350
  return new Redis({
325
- ...this.redisOptionsResolved,
351
+ ...__privateGet(this, _redisOptionsResolved),
326
352
  ...overrides,
327
353
  sentinels: sentinel.sentinels,
328
354
  name: sentinel.name,
@@ -345,7 +371,7 @@ var ConnectionManager = class {
345
371
  }
346
372
  return new Cluster(cluster.nodes, cluster.options ?? {});
347
373
  }
348
- return new Redis({ ...this.redisOptionsResolved, ...overrides });
374
+ return new Redis({ ...__privateGet(this, _redisOptionsResolved), ...overrides });
349
375
  }
350
376
  /** Merges connection options with defaults; URL fields take precedence. */
351
377
  buildRedisOptions(opts) {
@@ -411,6 +437,8 @@ var ConnectionManager = class {
411
437
  });
412
438
  }
413
439
  };
440
+ _client = new WeakMap();
441
+ _redisOptionsResolved = new WeakMap();
414
442
  ConnectionManager = __decorateClass([
415
443
  Injectable(),
416
444
  __decorateParam(0, Inject(BYMAX_CACHE_OPTIONS)),
@@ -1257,6 +1285,7 @@ CacheService = __decorateClass([
1257
1285
  __decorateParam(4, Optional()),
1258
1286
  __decorateParam(4, Inject(ScriptManagerService))
1259
1287
  ], CacheService);
1288
+ var _subscriber;
1260
1289
  var PubSubService = class {
1261
1290
  /**
1262
1291
  * @param options - Resolved module options. Supplies the serializer.
@@ -1274,7 +1303,13 @@ var PubSubService = class {
1274
1303
  this.keyBuilder = keyBuilder;
1275
1304
  this.events = events;
1276
1305
  /** Lazily-created dedicated subscriber connection (null until first subscribe). */
1277
- this.subscriber = null;
1306
+ /**
1307
+ * The dedicated subscriber connection.
1308
+ *
1309
+ * Private for the same reason as the manager's client: an ioredis instance
1310
+ * carries `options.password` as a plain field.
1311
+ */
1312
+ __privateAdd(this, _subscriber, null);
1278
1313
  /** Live-listener ref-count per namespaced channel; UNSUBSCRIBE fires on the last. */
1279
1314
  this.channelRefs = /* @__PURE__ */ new Map();
1280
1315
  /** Live-listener ref-count per namespaced pattern; PUNSUBSCRIBE fires on the last. */
@@ -1377,11 +1412,11 @@ var PubSubService = class {
1377
1412
  }
1378
1413
  /** Closes the subscriber connection gracefully, forcing disconnect on failure. */
1379
1414
  async onModuleDestroy() {
1380
- if (!this.subscriber) {
1415
+ if (!__privateGet(this, _subscriber)) {
1381
1416
  return;
1382
1417
  }
1383
- const subscriber = this.subscriber;
1384
- this.subscriber = null;
1418
+ const subscriber = __privateGet(this, _subscriber);
1419
+ __privateSet(this, _subscriber, null);
1385
1420
  try {
1386
1421
  await subscriber.quit();
1387
1422
  } catch {
@@ -1399,10 +1434,10 @@ var PubSubService = class {
1399
1434
  * experimental passthrough per the spec).
1400
1435
  */
1401
1436
  ensureSubscriber() {
1402
- if (!this.subscriber) {
1403
- this.subscriber = this.connection.createSubscriberClient();
1437
+ if (!__privateGet(this, _subscriber)) {
1438
+ __privateSet(this, _subscriber, this.connection.createSubscriberClient());
1404
1439
  }
1405
- return this.subscriber;
1440
+ return __privateGet(this, _subscriber);
1406
1441
  }
1407
1442
  /**
1408
1443
  * Subscribes the target (channel or pattern) only when it gains its FIRST
@@ -1481,6 +1516,7 @@ var PubSubService = class {
1481
1516
  }
1482
1517
  }
1483
1518
  };
1519
+ _subscriber = new WeakMap();
1484
1520
  PubSubService = __decorateClass([
1485
1521
  Injectable(),
1486
1522
  __decorateParam(0, Inject(BYMAX_CACHE_OPTIONS)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-cache",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",
@@ -53,6 +53,29 @@
53
53
  ]
54
54
  }
55
55
  },
56
+ "scripts": {
57
+ "build": "pnpm clean && tsup",
58
+ "check:exports": "attw --pack .",
59
+ "check:published": "node scripts/check-published-surface.mjs",
60
+ "clean": "rm -rf dist coverage",
61
+ "lint": "eslint src scripts",
62
+ "lint:fix": "eslint src scripts --fix",
63
+ "mutation": "stryker run",
64
+ "mutation:dry-run": "stryker run --dryRunOnly",
65
+ "mutation:incremental": "stryker run --incremental",
66
+ "prepare": "husky",
67
+ "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published",
68
+ "release": "npm publish --provenance --access public",
69
+ "size": "node scripts/check-size.mjs",
70
+ "test": "jest",
71
+ "test:all": "pnpm test && pnpm test:e2e",
72
+ "test:cov": "jest --coverage",
73
+ "test:cov:all": "jest --config jest.coverage.config.ts --coverage",
74
+ "test:e2e": "jest --config jest.e2e.config.ts --runInBand",
75
+ "test:types": "tsc --noEmit -p tsconfig.typetest.json",
76
+ "test:watch": "jest --watch",
77
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
78
+ },
56
79
  "lint-staged": {
57
80
  "*.{ts,tsx,js,mjs,cjs}": [
58
81
  "eslint --fix",
@@ -64,8 +87,8 @@
64
87
  },
65
88
  "dependencies": {},
66
89
  "peerDependencies": {
67
- "@nestjs/common": "^11.0.0",
68
- "@nestjs/core": "^11.0.0",
90
+ "@nestjs/common": "^11.0.16",
91
+ "@nestjs/core": "^11.1.18",
69
92
  "ioredis": "^5.0.0",
70
93
  "reflect-metadata": "^0.2.0"
71
94
  },
@@ -116,6 +139,7 @@
116
139
  "pubsub",
117
140
  "lua"
118
141
  ],
142
+ "packageManager": "pnpm@10.8.1",
119
143
  "engines": {
120
144
  "node": ">=24.0.0"
121
145
  },
@@ -123,24 +147,18 @@
123
147
  "access": "public",
124
148
  "registry": "https://registry.npmjs.org/"
125
149
  },
126
- "scripts": {
127
- "build": "pnpm clean && tsup",
128
- "lint": "eslint src scripts",
129
- "lint:fix": "eslint src scripts --fix",
130
- "test": "jest",
131
- "test:cov": "jest --coverage",
132
- "test:watch": "jest --watch",
133
- "test:e2e": "jest --config jest.e2e.config.ts --runInBand",
134
- "test:all": "pnpm test && pnpm test:e2e",
135
- "test:cov:all": "jest --config jest.coverage.config.ts --coverage",
136
- "mutation": "stryker run",
137
- "mutation:incremental": "stryker run --incremental",
138
- "mutation:dry-run": "stryker run --dryRunOnly",
139
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json",
140
- "test:types": "tsc --noEmit -p tsconfig.typetest.json",
141
- "size": "node scripts/check-size.mjs",
142
- "check:exports": "attw --pack .",
143
- "clean": "rm -rf dist coverage",
144
- "release": "pnpm publish --provenance"
150
+ "pnpm": {
151
+ "overrides": {
152
+ "brace-expansion@1": "^1.1.18",
153
+ "brace-expansion@2": "^2.1.4",
154
+ "brace-expansion@5": "^5.0.9",
155
+ "esbuild": "^0.28.1",
156
+ "fast-uri": "^3.1.5",
157
+ "js-yaml@3": "^3.15.0",
158
+ "js-yaml@4": "^4.3.0",
159
+ "protobufjs": "^7.6.5",
160
+ "qs": "^6.15.2",
161
+ "undici": "^7.28.0"
162
+ }
145
163
  }
146
- }
164
+ }