@bymax-one/nest-cache 1.2.5 → 1.2.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 CHANGED
@@ -6,6 +6,23 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.6] - 2026-10-31
10
+
11
+ ### Added
12
+
13
+ - **`createConnectionManager(options)`**, exported from the main entry. It takes the options
14
+ `BymaxCacheModule.forRoot` takes, validates and defaults them exactly as the module does, and returns a
15
+ `ConnectionManager` that Nest connects on `onModuleInit` and quits on `onModuleDestroy` when it is
16
+ registered as a provider. A consumer that needs a second, dedicated connection (for example a standalone
17
+ one beside a clustered cache) no longer has to hand-write `ResolvedOptions`, which skipped validation and
18
+ the defaults. The README has a section, "A second, dedicated connection", with a provider snippet.
19
+
20
+ ### Internal
21
+
22
+ - `forRoot`, `forRootAsync` and `createConnectionManager` share one `resolveOptions` function, so the
23
+ sequence "validate, then apply defaults" is written once. Nothing about `forRoot` or `forRootAsync`
24
+ changes.
25
+
9
26
  ## [1.2.5] - 2026-10-31
10
27
 
11
28
  A documentation and test release. Nothing you execute changes: `dist/` differs from 1.2.4 only in one comment, the
@@ -479,7 +496,8 @@ type or export moved.
479
496
  - Published with npm OIDC provenance — no long-lived tokens
480
497
  - Zero direct runtime dependencies (`dependencies: {}`) — `ioredis` and NestJS via peer deps
481
498
 
482
- [Unreleased]: https://github.com/bymaxone/nest-cache/compare/v1.2.5...HEAD
499
+ [Unreleased]: https://github.com/bymaxone/nest-cache/compare/v1.2.6...HEAD
500
+ [1.2.6]: https://github.com/bymaxone/nest-cache/compare/v1.2.5...v1.2.6
483
501
  [1.2.5]: https://github.com/bymaxone/nest-cache/compare/v1.2.4...v1.2.5
484
502
  [1.2.4]: https://github.com/bymaxone/nest-cache/compare/v1.2.3...v1.2.4
485
503
  [1.2.3]: https://github.com/bymaxone/nest-cache/compare/v1.2.2...v1.2.3
package/README.md CHANGED
@@ -279,6 +279,45 @@ Both `forRoot(options)` (synchronous) and `forRootAsync({ useFactory, inject, im
279
279
 
280
280
  ---
281
281
 
282
+ ## 🔌 A second, dedicated connection
283
+
284
+ `BymaxCacheModule` registers one connection. When another part of the application needs its own, with different options (for example a standalone connection beside a cache that runs in cluster mode), build it with `createConnectionManager` rather than hand-writing the resolved options, which would skip validation and the defaults.
285
+
286
+ ```ts
287
+ import { Inject, Injectable, Module } from '@nestjs/common'
288
+ import { ConnectionManager, createConnectionManager } from '@bymax-one/nest-cache'
289
+
290
+ export const AUTH_REDIS = Symbol('AUTH_REDIS')
291
+
292
+ @Module({
293
+ providers: [
294
+ {
295
+ provide: AUTH_REDIS,
296
+ useFactory: (): ConnectionManager =>
297
+ createConnectionManager({
298
+ mode: 'standalone',
299
+ connection: { url: process.env.AUTH_REDIS_URL ?? 'redis://localhost:6379' }
300
+ })
301
+ }
302
+ ],
303
+ exports: [AUTH_REDIS]
304
+ })
305
+ export class AuthRedisModule {}
306
+
307
+ @Injectable()
308
+ export class SessionStore {
309
+ constructor(@Inject(AUTH_REDIS) private readonly redis: ConnectionManager) {}
310
+
311
+ async ping(): Promise<string> {
312
+ return this.redis.getClient().ping()
313
+ }
314
+ }
315
+ ```
316
+
317
+ It takes the same options as `forRoot()` and runs the same validation and defaults, so an invalid set throws the same `CacheException` before any client exists. The manager owns its connection: Nest connects it on `onModuleInit` and quits it on `onModuleDestroy`, with the forced `disconnect()` after `shutdownTimeoutMs`, so you write neither hook. The connection is independent of the one the module registers, and the client you get from `getClient()` applies no namespace: its keys are not prefixed.
318
+
319
+ ---
320
+
282
321
  ## 🔑 Key Namespacing
283
322
 
284
323
  Every key is composed as `{namespace}{separator}{prefix}{separator}{id}` (default separator `:`).
@@ -290,6 +290,10 @@ function applyDefaults(options) {
290
290
  }
291
291
  return Object.freeze(resolved);
292
292
  }
293
+ function resolveOptions(options) {
294
+ validateOptions(options);
295
+ return applyDefaults(options);
296
+ }
293
297
  var _client, _redisOptionsResolved;
294
298
  exports.ConnectionManager = class ConnectionManager {
295
299
  /**
@@ -1647,8 +1651,7 @@ exports.BymaxCacheModule = class BymaxCacheModule extends ConfigurableModuleClas
1647
1651
  * fail bootstrap validation (e.g. missing connection, misconfigured mode).
1648
1652
  */
1649
1653
  static forRoot(options) {
1650
- validateOptions(options);
1651
- const resolved = applyDefaults(options);
1654
+ const resolved = resolveOptions(options);
1652
1655
  const providers = [
1653
1656
  { provide: MODULE_OPTIONS_TOKEN, useValue: options },
1654
1657
  { provide: BYMAX_CACHE_OPTIONS, useValue: resolved },
@@ -1705,10 +1708,7 @@ exports.BymaxCacheModule = class BymaxCacheModule extends ConfigurableModuleClas
1705
1708
  return [
1706
1709
  {
1707
1710
  provide: BYMAX_CACHE_OPTIONS,
1708
- useFactory: (raw) => {
1709
- validateOptions(raw);
1710
- return applyDefaults(raw);
1711
- },
1711
+ useFactory: (raw) => resolveOptions(raw),
1712
1712
  inject: [MODULE_OPTIONS_TOKEN]
1713
1713
  },
1714
1714
  {
@@ -1773,6 +1773,12 @@ exports.BymaxCacheModule = __decorateClass([
1773
1773
  common.Module({})
1774
1774
  ], exports.BymaxCacheModule);
1775
1775
 
1776
+ // src/server/connection/create-connection-manager.ts
1777
+ function createConnectionManager(options) {
1778
+ const resolved = resolveOptions(options);
1779
+ return new exports.ConnectionManager(resolved, resolved.events);
1780
+ }
1781
+
1776
1782
  // src/shared/constants/event-names.ts
1777
1783
  var CACHE_EVENT_NAMES = {
1778
1784
  CONNECT: "connect",
@@ -1796,3 +1802,4 @@ exports.CacheException = CacheException;
1796
1802
  exports.DEFAULT_KEY_SEPARATOR = DEFAULT_KEY_SEPARATOR;
1797
1803
  exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE;
1798
1804
  exports.DEFAULT_REDIS_PORT = DEFAULT_REDIS_PORT;
1805
+ exports.createConnectionManager = createConnectionManager;
@@ -433,6 +433,48 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
433
433
  private waitUntilReady;
434
434
  }
435
435
 
436
+ /**
437
+ * Factory for a dedicated, lifecycle-managed Redis connection.
438
+ *
439
+ * Layer: server. A consumer that needs a second connection with its own options (for
440
+ * example a standalone one beside a clustered cache) builds it here instead of
441
+ * hand-writing the resolved options, which would skip validation and the defaults.
442
+ */
443
+
444
+ /**
445
+ * Builds a {@link ConnectionManager} from the same options `BymaxCacheModule.forRoot`
446
+ * takes: the options are validated and defaulted exactly as the module does, so an invalid
447
+ * set throws the same `CacheException` before any client exists.
448
+ *
449
+ * The manager owns its connection. Register it as a provider and Nest connects it on
450
+ * `onModuleInit` and quits it on `onModuleDestroy`, with the forced `disconnect()` after
451
+ * `shutdownTimeoutMs`; the caller writes neither hook. Reach the client with
452
+ * `manager.getClient()`. This connection is independent of the one `BymaxCacheModule`
453
+ * registers, and it applies no namespace: keys written through its client are not
454
+ * prefixed.
455
+ *
456
+ * @param options - The connection options, as for `BymaxCacheModule.forRoot`.
457
+ * @returns A new manager that has not connected yet.
458
+ * @throws {CacheException} `SENTINEL_MISCONFIGURED`, `CLUSTER_MISCONFIGURED`,
459
+ * `CONNECTION_FAILED` or `INVALID_NAMESPACE` when the options are invalid.
460
+ * @example
461
+ * ```ts
462
+ * export const AUTH_REDIS = Symbol('AUTH_REDIS')
463
+ *
464
+ * @Module({
465
+ * providers: [
466
+ * {
467
+ * provide: AUTH_REDIS,
468
+ * useFactory: () => createConnectionManager({ connection: { url: 'redis://localhost:6379' } })
469
+ * }
470
+ * ],
471
+ * exports: [AUTH_REDIS]
472
+ * })
473
+ * export class AuthRedisModule {}
474
+ * ```
475
+ */
476
+ declare function createConnectionManager(options: BymaxCacheModuleOptions): ConnectionManager;
477
+
436
478
  /**
437
479
  * Composes Redis keys following `{namespace}{separator}{prefix}{separator}{id}`.
438
480
  *
@@ -1344,4 +1386,4 @@ declare const CACHE_EVENT_NAMES: {
1344
1386
  readonly END: "end";
1345
1387
  };
1346
1388
 
1347
- export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, type BymaxCacheClusterConnection, BymaxCacheModule, type BymaxCacheModuleAsyncOptions, type BymaxCacheModuleOptions, type BymaxCacheSentinelConnection, type BymaxCacheStandaloneConnection, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, type CacheConnectionStatus, type CacheErrorCode, type CacheEventName, CacheException, type CacheKeyPrefix, type CacheNamespace, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, type ResolvedOptions, ScriptManagerService, type SerializableValue, type Unsubscribe };
1389
+ export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, type BymaxCacheClusterConnection, BymaxCacheModule, type BymaxCacheModuleAsyncOptions, type BymaxCacheModuleOptions, type BymaxCacheSentinelConnection, type BymaxCacheStandaloneConnection, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, type CacheConnectionStatus, type CacheErrorCode, type CacheEventName, CacheException, type CacheKeyPrefix, type CacheNamespace, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, type ResolvedOptions, ScriptManagerService, type SerializableValue, type Unsubscribe, createConnectionManager };
@@ -433,6 +433,48 @@ declare class ConnectionManager implements OnModuleInit, OnModuleDestroy {
433
433
  private waitUntilReady;
434
434
  }
435
435
 
436
+ /**
437
+ * Factory for a dedicated, lifecycle-managed Redis connection.
438
+ *
439
+ * Layer: server. A consumer that needs a second connection with its own options (for
440
+ * example a standalone one beside a clustered cache) builds it here instead of
441
+ * hand-writing the resolved options, which would skip validation and the defaults.
442
+ */
443
+
444
+ /**
445
+ * Builds a {@link ConnectionManager} from the same options `BymaxCacheModule.forRoot`
446
+ * takes: the options are validated and defaulted exactly as the module does, so an invalid
447
+ * set throws the same `CacheException` before any client exists.
448
+ *
449
+ * The manager owns its connection. Register it as a provider and Nest connects it on
450
+ * `onModuleInit` and quits it on `onModuleDestroy`, with the forced `disconnect()` after
451
+ * `shutdownTimeoutMs`; the caller writes neither hook. Reach the client with
452
+ * `manager.getClient()`. This connection is independent of the one `BymaxCacheModule`
453
+ * registers, and it applies no namespace: keys written through its client are not
454
+ * prefixed.
455
+ *
456
+ * @param options - The connection options, as for `BymaxCacheModule.forRoot`.
457
+ * @returns A new manager that has not connected yet.
458
+ * @throws {CacheException} `SENTINEL_MISCONFIGURED`, `CLUSTER_MISCONFIGURED`,
459
+ * `CONNECTION_FAILED` or `INVALID_NAMESPACE` when the options are invalid.
460
+ * @example
461
+ * ```ts
462
+ * export const AUTH_REDIS = Symbol('AUTH_REDIS')
463
+ *
464
+ * @Module({
465
+ * providers: [
466
+ * {
467
+ * provide: AUTH_REDIS,
468
+ * useFactory: () => createConnectionManager({ connection: { url: 'redis://localhost:6379' } })
469
+ * }
470
+ * ],
471
+ * exports: [AUTH_REDIS]
472
+ * })
473
+ * export class AuthRedisModule {}
474
+ * ```
475
+ */
476
+ declare function createConnectionManager(options: BymaxCacheModuleOptions): ConnectionManager;
477
+
436
478
  /**
437
479
  * Composes Redis keys following `{namespace}{separator}{prefix}{separator}{id}`.
438
480
  *
@@ -1344,4 +1386,4 @@ declare const CACHE_EVENT_NAMES: {
1344
1386
  readonly END: "end";
1345
1387
  };
1346
1388
 
1347
- export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, type BymaxCacheClusterConnection, BymaxCacheModule, type BymaxCacheModuleAsyncOptions, type BymaxCacheModuleOptions, type BymaxCacheSentinelConnection, type BymaxCacheStandaloneConnection, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, type CacheConnectionStatus, type CacheErrorCode, type CacheEventName, CacheException, type CacheKeyPrefix, type CacheNamespace, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, type ResolvedOptions, ScriptManagerService, type SerializableValue, type Unsubscribe };
1389
+ export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, type BymaxCacheClusterConnection, BymaxCacheModule, type BymaxCacheModuleAsyncOptions, type BymaxCacheModuleOptions, type BymaxCacheSentinelConnection, type BymaxCacheStandaloneConnection, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, type CacheConnectionStatus, type CacheErrorCode, type CacheEventName, CacheException, type CacheKeyPrefix, type CacheNamespace, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, type ResolvedOptions, ScriptManagerService, type SerializableValue, type Unsubscribe, createConnectionManager };
@@ -288,6 +288,10 @@ function applyDefaults(options) {
288
288
  }
289
289
  return Object.freeze(resolved);
290
290
  }
291
+ function resolveOptions(options) {
292
+ validateOptions(options);
293
+ return applyDefaults(options);
294
+ }
291
295
  var _client, _redisOptionsResolved;
292
296
  var ConnectionManager = class {
293
297
  /**
@@ -1645,8 +1649,7 @@ var BymaxCacheModule = class extends ConfigurableModuleClass {
1645
1649
  * fail bootstrap validation (e.g. missing connection, misconfigured mode).
1646
1650
  */
1647
1651
  static forRoot(options) {
1648
- validateOptions(options);
1649
- const resolved = applyDefaults(options);
1652
+ const resolved = resolveOptions(options);
1650
1653
  const providers = [
1651
1654
  { provide: MODULE_OPTIONS_TOKEN, useValue: options },
1652
1655
  { provide: BYMAX_CACHE_OPTIONS, useValue: resolved },
@@ -1703,10 +1706,7 @@ var BymaxCacheModule = class extends ConfigurableModuleClass {
1703
1706
  return [
1704
1707
  {
1705
1708
  provide: BYMAX_CACHE_OPTIONS,
1706
- useFactory: (raw) => {
1707
- validateOptions(raw);
1708
- return applyDefaults(raw);
1709
- },
1709
+ useFactory: (raw) => resolveOptions(raw),
1710
1710
  inject: [MODULE_OPTIONS_TOKEN]
1711
1711
  },
1712
1712
  {
@@ -1771,6 +1771,12 @@ BymaxCacheModule = __decorateClass([
1771
1771
  Module({})
1772
1772
  ], BymaxCacheModule);
1773
1773
 
1774
+ // src/server/connection/create-connection-manager.ts
1775
+ function createConnectionManager(options) {
1776
+ const resolved = resolveOptions(options);
1777
+ return new ConnectionManager(resolved, resolved.events);
1778
+ }
1779
+
1774
1780
  // src/shared/constants/event-names.ts
1775
1781
  var CACHE_EVENT_NAMES = {
1776
1782
  CONNECT: "connect",
@@ -1781,4 +1787,4 @@ var CACHE_EVENT_NAMES = {
1781
1787
  END: "end"
1782
1788
  };
1783
1789
 
1784
- export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, BymaxCacheModule, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, CacheException, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, JsonSerializer, KeyBuilder, PubSubService, ScriptManagerService };
1790
+ export { BYMAX_CACHE_CONNECTION, BYMAX_CACHE_EVENTS, BYMAX_CACHE_KEY_BUILDER, BYMAX_CACHE_OPTIONS, BYMAX_CACHE_SCRIPT_REGISTRY, BYMAX_CACHE_SERIALIZER, BymaxCacheModule, CACHE_ERROR_CODES, CACHE_ERROR_MESSAGES, CACHE_EVENT_NAMES, CacheException, CacheService, ConnectionManager, DEFAULT_KEY_SEPARATOR, DEFAULT_NAMESPACE, DEFAULT_REDIS_PORT, JsonSerializer, KeyBuilder, PubSubService, ScriptManagerService, createConnectionManager };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-cache",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Typed Redis cache for NestJS based on ioredis 6, with namespace strategy, Pub/Sub and Lua script management.",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",