@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.
@@ -35,6 +35,7 @@ var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS
35
35
  // src/server/constants/default-namespace.ts
36
36
  var DEFAULT_NAMESPACE = "app";
37
37
  var DEFAULT_KEY_SEPARATOR = ":";
38
+ var DEFAULT_REDIS_PORT = 6379;
38
39
 
39
40
  // src/server/constants/default-timeouts.ts
40
41
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
@@ -62,7 +63,11 @@ var CACHE_ERROR_CODES = {
62
63
  CLUSTER_MISCONFIGURED: "cache.cluster_misconfigured",
63
64
  SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured",
64
65
  SHUTDOWN_TIMEOUT: "cache.shutdown_timeout",
65
- UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster"
66
+ UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster",
67
+ INVALID_SCOPE: "cache.invalid_scope",
68
+ SCOPE_NOT_FOUND: "cache.scope_not_found",
69
+ SCOPE_NOT_READABLE: "cache.scope_not_readable",
70
+ KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
66
71
  };
67
72
 
68
73
  // src/server/errors/cache-error-codes.ts
@@ -72,7 +77,10 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
72
77
  [CACHE_ERROR_CODES.CONNECTION_LOST, "Redis connection was lost during the operation."],
73
78
  [CACHE_ERROR_CODES.SERIALIZATION_FAILED, "Failed to serialize the value."],
74
79
  [CACHE_ERROR_CODES.DESERIALIZATION_FAILED, "Failed to deserialize the cached value."],
75
- [CACHE_ERROR_CODES.INVALID_NAMESPACE, "Namespace is empty or contains the reserved separator."],
80
+ [
81
+ CACHE_ERROR_CODES.INVALID_NAMESPACE,
82
+ "Namespace or key separator is invalid: the namespace is empty, contains the separator, or contains a Redis glob metacharacter."
83
+ ],
76
84
  [CACHE_ERROR_CODES.INVALID_KEY, "Cache key prefix or id is empty."],
77
85
  [CACHE_ERROR_CODES.SCRIPT_NOT_REGISTERED, "Tried to execute an unregistered Lua script."],
78
86
  [CACHE_ERROR_CODES.SCRIPT_EXECUTION_FAILED, "Lua script returned a Redis error."],
@@ -87,6 +95,19 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
87
95
  [
88
96
  CACHE_ERROR_CODES.UNSUPPORTED_IN_CLUSTER,
89
97
  "This operation requires standalone or sentinel mode; it is not supported in cluster mode."
98
+ ],
99
+ [
100
+ CACHE_ERROR_CODES.INVALID_SCOPE,
101
+ "An administration scope is malformed: empty field, duplicate id, or a match pattern with no literal prefix."
102
+ ],
103
+ [CACHE_ERROR_CODES.SCOPE_NOT_FOUND, "No administration scope is declared under that id."],
104
+ [
105
+ CACHE_ERROR_CODES.SCOPE_NOT_READABLE,
106
+ "This administration scope withholds values; listing, types, TTLs and sizes remain available."
107
+ ],
108
+ [
109
+ CACHE_ERROR_CODES.KEY_NOT_IN_SCOPE,
110
+ "That key does not belong to the named administration scope."
90
111
  ]
91
112
  ]);
92
113
  var CACHE_ERROR_STATUS = /* @__PURE__ */ new Map([
@@ -130,7 +151,7 @@ function parseRedisUrl(url) {
130
151
  }
131
152
  const result = {
132
153
  host: parsed.hostname,
133
- port: parsed.port ? Number.parseInt(parsed.port, 10) : 6379
154
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : DEFAULT_REDIS_PORT
134
155
  };
135
156
  if (parsed.username) {
136
157
  result.username = decodeURIComponent(parsed.username);
@@ -149,40 +170,50 @@ function parseRedisUrl(url) {
149
170
  }
150
171
 
151
172
  // src/server/config/default-options.ts
152
- function validateOptions(options) {
153
- const mode = options.mode ?? "standalone";
154
- if (mode === "sentinel") {
155
- if (!options.sentinel || !options.sentinel.sentinels?.length || !options.sentinel.name) {
156
- throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode });
173
+ var GLOB_METACHARACTERS = /* @__PURE__ */ new Set(["*", "?", "[", "\\"]);
174
+ function findGlobMetacharacter(namespace) {
175
+ for (const character of namespace) {
176
+ if (GLOB_METACHARACTERS.has(character)) {
177
+ return character;
157
178
  }
158
179
  }
159
- if (mode === "cluster") {
160
- if (!options.cluster || !options.cluster.nodes?.length) {
161
- throw new CacheException(CACHE_ERROR_CODES.CLUSTER_MISCONFIGURED, { mode });
162
- }
180
+ return null;
181
+ }
182
+ function validateStandaloneConnection(options) {
183
+ const connection = options.connection;
184
+ if (!connection || !connection.url && !connection.host) {
185
+ throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
186
+ reason: "missing connection.url or connection.host"
187
+ });
163
188
  }
164
- if (mode === "standalone") {
165
- const connection = options.connection;
166
- if (!connection || !connection.url && !connection.host) {
189
+ if (connection.url) {
190
+ try {
191
+ parseRedisUrl(connection.url);
192
+ } catch {
167
193
  throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
168
- reason: "missing connection.url or connection.host"
194
+ reason: "invalid connection.url"
169
195
  });
170
196
  }
171
- if (connection.url) {
172
- try {
173
- parseRedisUrl(connection.url);
174
- } catch {
175
- throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
176
- reason: "invalid connection.url"
177
- });
178
- }
179
- }
180
197
  }
181
- const namespace = options.namespace ?? DEFAULT_NAMESPACE;
182
- const separator = options.keySeparator ?? DEFAULT_KEY_SEPARATOR;
198
+ }
199
+ function validateNamespace(namespace, separator) {
183
200
  if (!namespace || namespace.trim() === "") {
184
201
  throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, { namespace });
185
202
  }
203
+ if (separator === "") {
204
+ throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
205
+ reason: "empty key separator",
206
+ separator
207
+ });
208
+ }
209
+ const metacharacter = findGlobMetacharacter(namespace);
210
+ if (metacharacter !== null) {
211
+ throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
212
+ reason: "namespace contains glob metacharacter",
213
+ namespace,
214
+ metacharacter
215
+ });
216
+ }
186
217
  if (namespace.includes(separator)) {
187
218
  throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
188
219
  reason: "namespace contains key separator",
@@ -190,6 +221,26 @@ function validateOptions(options) {
190
221
  separator
191
222
  });
192
223
  }
224
+ }
225
+ function validateOptions(options) {
226
+ const mode = options.mode ?? "standalone";
227
+ if (mode === "sentinel") {
228
+ if (!options.sentinel || !options.sentinel.sentinels?.length || !options.sentinel.name) {
229
+ throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode });
230
+ }
231
+ }
232
+ if (mode === "cluster") {
233
+ if (!options.cluster || !options.cluster.nodes?.length) {
234
+ throw new CacheException(CACHE_ERROR_CODES.CLUSTER_MISCONFIGURED, { mode });
235
+ }
236
+ }
237
+ if (mode === "standalone") {
238
+ validateStandaloneConnection(options);
239
+ }
240
+ validateNamespace(
241
+ options.namespace ?? DEFAULT_NAMESPACE,
242
+ options.keySeparator ?? DEFAULT_KEY_SEPARATOR
243
+ );
193
244
  const shutdown = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
194
245
  if (shutdown < MIN_SHUTDOWN_TIMEOUT_MS) {
195
246
  throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
@@ -376,7 +427,7 @@ exports.ConnectionManager = class ConnectionManager {
376
427
  sentinelPassword: sentinel.sentinelPassword
377
428
  },
378
429
  ...sentinel.password !== void 0 && { password: sentinel.password },
379
- // Normalize 'replica' → 'slave' — ioredis 5 only accepts 'slave' at the
430
+ // Normalize 'replica' → 'slave' — ioredis only accepts 'slave' at the
380
431
  // wire level; our public interface accepts 'replica' per Redis 7 naming.
381
432
  ...sentinel.role !== void 0 && {
382
433
  role: sentinel.role === "replica" ? "slave" : sentinel.role
@@ -1705,3 +1756,6 @@ exports.CACHE_ERROR_CODES = CACHE_ERROR_CODES;
1705
1756
  exports.CACHE_ERROR_MESSAGES = CACHE_ERROR_MESSAGES;
1706
1757
  exports.CACHE_EVENT_NAMES = CACHE_EVENT_NAMES;
1707
1758
  exports.CacheException = CacheException;
1759
+ exports.DEFAULT_KEY_SEPARATOR = DEFAULT_KEY_SEPARATOR;
1760
+ exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE;
1761
+ exports.DEFAULT_REDIS_PORT = DEFAULT_REDIS_PORT;
@@ -1146,6 +1146,26 @@ declare const BYMAX_CACHE_SERIALIZER: unique symbol;
1146
1146
  /** The key builder that composes `{namespace}{sep}{prefix}{sep}{id}`. */
1147
1147
  declare const BYMAX_CACHE_KEY_BUILDER: unique symbol;
1148
1148
 
1149
+ /**
1150
+ * Default namespace and key-separator values.
1151
+ *
1152
+ * Layer: server. Applied by `applyDefaults` when the consumer omits the
1153
+ * corresponding option. Kept separate from timeout defaults so each concern has
1154
+ * a single, greppable home.
1155
+ */
1156
+ /** Default global namespace when the consumer does not override it. */
1157
+ declare const DEFAULT_NAMESPACE: "app";
1158
+ /** Default separator between namespace/prefix/id segments. */
1159
+ declare const DEFAULT_KEY_SEPARATOR: ":";
1160
+ /**
1161
+ * The port Redis listens on when a connection URL does not name one.
1162
+ *
1163
+ * Shared by the URL parser that builds connect options and by the admin surface
1164
+ * that reports the effective endpoint, so the two cannot drift into disagreeing
1165
+ * about where this deployment actually connects.
1166
+ */
1167
+ declare const DEFAULT_REDIS_PORT = 6379;
1168
+
1149
1169
  /**
1150
1170
  * Canonical cache error codes for `@bymax-one/nest-cache`.
1151
1171
  *
@@ -1175,6 +1195,10 @@ declare const CACHE_ERROR_CODES: {
1175
1195
  readonly SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured";
1176
1196
  readonly SHUTDOWN_TIMEOUT: "cache.shutdown_timeout";
1177
1197
  readonly UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster";
1198
+ readonly INVALID_SCOPE: "cache.invalid_scope";
1199
+ readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
1200
+ readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
1201
+ readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
1178
1202
  };
1179
1203
  /** Union of every cache error code string value. */
1180
1204
  type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
@@ -1307,4 +1331,4 @@ declare const CACHE_EVENT_NAMES: {
1307
1331
  readonly END: "end";
1308
1332
  };
1309
1333
 
1310
- 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, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, ScriptManagerService, type SerializableValue, type Unsubscribe };
1334
+ 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 };
@@ -1146,6 +1146,26 @@ declare const BYMAX_CACHE_SERIALIZER: unique symbol;
1146
1146
  /** The key builder that composes `{namespace}{sep}{prefix}{sep}{id}`. */
1147
1147
  declare const BYMAX_CACHE_KEY_BUILDER: unique symbol;
1148
1148
 
1149
+ /**
1150
+ * Default namespace and key-separator values.
1151
+ *
1152
+ * Layer: server. Applied by `applyDefaults` when the consumer omits the
1153
+ * corresponding option. Kept separate from timeout defaults so each concern has
1154
+ * a single, greppable home.
1155
+ */
1156
+ /** Default global namespace when the consumer does not override it. */
1157
+ declare const DEFAULT_NAMESPACE: "app";
1158
+ /** Default separator between namespace/prefix/id segments. */
1159
+ declare const DEFAULT_KEY_SEPARATOR: ":";
1160
+ /**
1161
+ * The port Redis listens on when a connection URL does not name one.
1162
+ *
1163
+ * Shared by the URL parser that builds connect options and by the admin surface
1164
+ * that reports the effective endpoint, so the two cannot drift into disagreeing
1165
+ * about where this deployment actually connects.
1166
+ */
1167
+ declare const DEFAULT_REDIS_PORT = 6379;
1168
+
1149
1169
  /**
1150
1170
  * Canonical cache error codes for `@bymax-one/nest-cache`.
1151
1171
  *
@@ -1175,6 +1195,10 @@ declare const CACHE_ERROR_CODES: {
1175
1195
  readonly SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured";
1176
1196
  readonly SHUTDOWN_TIMEOUT: "cache.shutdown_timeout";
1177
1197
  readonly UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster";
1198
+ readonly INVALID_SCOPE: "cache.invalid_scope";
1199
+ readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
1200
+ readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
1201
+ readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
1178
1202
  };
1179
1203
  /** Union of every cache error code string value. */
1180
1204
  type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
@@ -1307,4 +1331,4 @@ declare const CACHE_EVENT_NAMES: {
1307
1331
  readonly END: "end";
1308
1332
  };
1309
1333
 
1310
- 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, type ICacheEvents, type IPubSubHandler, type IPubSubPatternHandler, type IScriptDefinition, type ISerializer, JsonSerializer, KeyBuilder, PubSubService, ScriptManagerService, type SerializableValue, type Unsubscribe };
1334
+ 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 };
@@ -33,6 +33,7 @@ var { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS
33
33
  // src/server/constants/default-namespace.ts
34
34
  var DEFAULT_NAMESPACE = "app";
35
35
  var DEFAULT_KEY_SEPARATOR = ":";
36
+ var DEFAULT_REDIS_PORT = 6379;
36
37
 
37
38
  // src/server/constants/default-timeouts.ts
38
39
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
@@ -60,7 +61,11 @@ var CACHE_ERROR_CODES = {
60
61
  CLUSTER_MISCONFIGURED: "cache.cluster_misconfigured",
61
62
  SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured",
62
63
  SHUTDOWN_TIMEOUT: "cache.shutdown_timeout",
63
- UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster"
64
+ UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster",
65
+ INVALID_SCOPE: "cache.invalid_scope",
66
+ SCOPE_NOT_FOUND: "cache.scope_not_found",
67
+ SCOPE_NOT_READABLE: "cache.scope_not_readable",
68
+ KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
64
69
  };
65
70
 
66
71
  // src/server/errors/cache-error-codes.ts
@@ -70,7 +75,10 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
70
75
  [CACHE_ERROR_CODES.CONNECTION_LOST, "Redis connection was lost during the operation."],
71
76
  [CACHE_ERROR_CODES.SERIALIZATION_FAILED, "Failed to serialize the value."],
72
77
  [CACHE_ERROR_CODES.DESERIALIZATION_FAILED, "Failed to deserialize the cached value."],
73
- [CACHE_ERROR_CODES.INVALID_NAMESPACE, "Namespace is empty or contains the reserved separator."],
78
+ [
79
+ CACHE_ERROR_CODES.INVALID_NAMESPACE,
80
+ "Namespace or key separator is invalid: the namespace is empty, contains the separator, or contains a Redis glob metacharacter."
81
+ ],
74
82
  [CACHE_ERROR_CODES.INVALID_KEY, "Cache key prefix or id is empty."],
75
83
  [CACHE_ERROR_CODES.SCRIPT_NOT_REGISTERED, "Tried to execute an unregistered Lua script."],
76
84
  [CACHE_ERROR_CODES.SCRIPT_EXECUTION_FAILED, "Lua script returned a Redis error."],
@@ -85,6 +93,19 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
85
93
  [
86
94
  CACHE_ERROR_CODES.UNSUPPORTED_IN_CLUSTER,
87
95
  "This operation requires standalone or sentinel mode; it is not supported in cluster mode."
96
+ ],
97
+ [
98
+ CACHE_ERROR_CODES.INVALID_SCOPE,
99
+ "An administration scope is malformed: empty field, duplicate id, or a match pattern with no literal prefix."
100
+ ],
101
+ [CACHE_ERROR_CODES.SCOPE_NOT_FOUND, "No administration scope is declared under that id."],
102
+ [
103
+ CACHE_ERROR_CODES.SCOPE_NOT_READABLE,
104
+ "This administration scope withholds values; listing, types, TTLs and sizes remain available."
105
+ ],
106
+ [
107
+ CACHE_ERROR_CODES.KEY_NOT_IN_SCOPE,
108
+ "That key does not belong to the named administration scope."
88
109
  ]
89
110
  ]);
90
111
  var CACHE_ERROR_STATUS = /* @__PURE__ */ new Map([
@@ -128,7 +149,7 @@ function parseRedisUrl(url) {
128
149
  }
129
150
  const result = {
130
151
  host: parsed.hostname,
131
- port: parsed.port ? Number.parseInt(parsed.port, 10) : 6379
152
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : DEFAULT_REDIS_PORT
132
153
  };
133
154
  if (parsed.username) {
134
155
  result.username = decodeURIComponent(parsed.username);
@@ -147,40 +168,50 @@ function parseRedisUrl(url) {
147
168
  }
148
169
 
149
170
  // src/server/config/default-options.ts
150
- function validateOptions(options) {
151
- const mode = options.mode ?? "standalone";
152
- if (mode === "sentinel") {
153
- if (!options.sentinel || !options.sentinel.sentinels?.length || !options.sentinel.name) {
154
- throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode });
171
+ var GLOB_METACHARACTERS = /* @__PURE__ */ new Set(["*", "?", "[", "\\"]);
172
+ function findGlobMetacharacter(namespace) {
173
+ for (const character of namespace) {
174
+ if (GLOB_METACHARACTERS.has(character)) {
175
+ return character;
155
176
  }
156
177
  }
157
- if (mode === "cluster") {
158
- if (!options.cluster || !options.cluster.nodes?.length) {
159
- throw new CacheException(CACHE_ERROR_CODES.CLUSTER_MISCONFIGURED, { mode });
160
- }
178
+ return null;
179
+ }
180
+ function validateStandaloneConnection(options) {
181
+ const connection = options.connection;
182
+ if (!connection || !connection.url && !connection.host) {
183
+ throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
184
+ reason: "missing connection.url or connection.host"
185
+ });
161
186
  }
162
- if (mode === "standalone") {
163
- const connection = options.connection;
164
- if (!connection || !connection.url && !connection.host) {
187
+ if (connection.url) {
188
+ try {
189
+ parseRedisUrl(connection.url);
190
+ } catch {
165
191
  throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
166
- reason: "missing connection.url or connection.host"
192
+ reason: "invalid connection.url"
167
193
  });
168
194
  }
169
- if (connection.url) {
170
- try {
171
- parseRedisUrl(connection.url);
172
- } catch {
173
- throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
174
- reason: "invalid connection.url"
175
- });
176
- }
177
- }
178
195
  }
179
- const namespace = options.namespace ?? DEFAULT_NAMESPACE;
180
- const separator = options.keySeparator ?? DEFAULT_KEY_SEPARATOR;
196
+ }
197
+ function validateNamespace(namespace, separator) {
181
198
  if (!namespace || namespace.trim() === "") {
182
199
  throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, { namespace });
183
200
  }
201
+ if (separator === "") {
202
+ throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
203
+ reason: "empty key separator",
204
+ separator
205
+ });
206
+ }
207
+ const metacharacter = findGlobMetacharacter(namespace);
208
+ if (metacharacter !== null) {
209
+ throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
210
+ reason: "namespace contains glob metacharacter",
211
+ namespace,
212
+ metacharacter
213
+ });
214
+ }
184
215
  if (namespace.includes(separator)) {
185
216
  throw new CacheException(CACHE_ERROR_CODES.INVALID_NAMESPACE, {
186
217
  reason: "namespace contains key separator",
@@ -188,6 +219,26 @@ function validateOptions(options) {
188
219
  separator
189
220
  });
190
221
  }
222
+ }
223
+ function validateOptions(options) {
224
+ const mode = options.mode ?? "standalone";
225
+ if (mode === "sentinel") {
226
+ if (!options.sentinel || !options.sentinel.sentinels?.length || !options.sentinel.name) {
227
+ throw new CacheException(CACHE_ERROR_CODES.SENTINEL_MISCONFIGURED, { mode });
228
+ }
229
+ }
230
+ if (mode === "cluster") {
231
+ if (!options.cluster || !options.cluster.nodes?.length) {
232
+ throw new CacheException(CACHE_ERROR_CODES.CLUSTER_MISCONFIGURED, { mode });
233
+ }
234
+ }
235
+ if (mode === "standalone") {
236
+ validateStandaloneConnection(options);
237
+ }
238
+ validateNamespace(
239
+ options.namespace ?? DEFAULT_NAMESPACE,
240
+ options.keySeparator ?? DEFAULT_KEY_SEPARATOR
241
+ );
191
242
  const shutdown = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
192
243
  if (shutdown < MIN_SHUTDOWN_TIMEOUT_MS) {
193
244
  throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
@@ -374,7 +425,7 @@ var ConnectionManager = class {
374
425
  sentinelPassword: sentinel.sentinelPassword
375
426
  },
376
427
  ...sentinel.password !== void 0 && { password: sentinel.password },
377
- // Normalize 'replica' → 'slave' — ioredis 5 only accepts 'slave' at the
428
+ // Normalize 'replica' → 'slave' — ioredis only accepts 'slave' at the
378
429
  // wire level; our public interface accepts 'replica' per Redis 7 naming.
379
430
  ...sentinel.role !== void 0 && {
380
431
  role: sentinel.role === "replica" ? "slave" : sentinel.role
@@ -1693,4 +1744,4 @@ var CACHE_EVENT_NAMES = {
1693
1744
  END: "end"
1694
1745
  };
1695
1746
 
1696
- 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, JsonSerializer, KeyBuilder, PubSubService, ScriptManagerService };
1747
+ 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 };
@@ -16,7 +16,11 @@ var CACHE_ERROR_CODES = {
16
16
  CLUSTER_MISCONFIGURED: "cache.cluster_misconfigured",
17
17
  SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured",
18
18
  SHUTDOWN_TIMEOUT: "cache.shutdown_timeout",
19
- UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster"
19
+ UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster",
20
+ INVALID_SCOPE: "cache.invalid_scope",
21
+ SCOPE_NOT_FOUND: "cache.scope_not_found",
22
+ SCOPE_NOT_READABLE: "cache.scope_not_readable",
23
+ KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
20
24
  };
21
25
 
22
26
  // src/shared/constants/event-names.ts
@@ -93,6 +93,10 @@ declare const CACHE_ERROR_CODES: {
93
93
  readonly SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured";
94
94
  readonly SHUTDOWN_TIMEOUT: "cache.shutdown_timeout";
95
95
  readonly UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster";
96
+ readonly INVALID_SCOPE: "cache.invalid_scope";
97
+ readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
98
+ readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
99
+ readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
96
100
  };
97
101
  /** Union of every cache error code string value. */
98
102
  type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
@@ -93,6 +93,10 @@ declare const CACHE_ERROR_CODES: {
93
93
  readonly SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured";
94
94
  readonly SHUTDOWN_TIMEOUT: "cache.shutdown_timeout";
95
95
  readonly UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster";
96
+ readonly INVALID_SCOPE: "cache.invalid_scope";
97
+ readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
98
+ readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
99
+ readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
96
100
  };
97
101
  /** Union of every cache error code string value. */
98
102
  type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
@@ -14,7 +14,11 @@ var CACHE_ERROR_CODES = {
14
14
  CLUSTER_MISCONFIGURED: "cache.cluster_misconfigured",
15
15
  SENTINEL_MISCONFIGURED: "cache.sentinel_misconfigured",
16
16
  SHUTDOWN_TIMEOUT: "cache.shutdown_timeout",
17
- UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster"
17
+ UNSUPPORTED_IN_CLUSTER: "cache.unsupported_in_cluster",
18
+ INVALID_SCOPE: "cache.invalid_scope",
19
+ SCOPE_NOT_FOUND: "cache.scope_not_found",
20
+ SCOPE_NOT_READABLE: "cache.scope_not_readable",
21
+ KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
18
22
  };
19
23
 
20
24
  // src/shared/constants/event-names.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-cache",
3
- "version": "1.0.6",
4
- "description": "Typed Redis cache for NestJS based on ioredis 5, with namespace strategy, Pub/Sub and Lua script management.",
3
+ "version": "1.2.0",
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",
7
7
  "homepage": "https://github.com/bymaxone/nest-cache#readme",
@@ -31,6 +31,16 @@
31
31
  "default": "./dist/server/index.cjs"
32
32
  }
33
33
  },
34
+ "./admin": {
35
+ "import": {
36
+ "types": "./dist/admin/index.d.ts",
37
+ "default": "./dist/admin/index.mjs"
38
+ },
39
+ "require": {
40
+ "types": "./dist/admin/index.d.cts",
41
+ "default": "./dist/admin/index.cjs"
42
+ }
43
+ },
34
44
  "./shared": {
35
45
  "import": {
36
46
  "types": "./dist/shared/index.d.ts",
@@ -50,6 +60,9 @@
50
60
  "*": {
51
61
  "shared": [
52
62
  "./dist/shared/index.d.cts"
63
+ ],
64
+ "admin": [
65
+ "./dist/admin/index.d.cts"
53
66
  ]
54
67
  }
55
68
  },
@@ -57,15 +70,16 @@
57
70
  "build": "pnpm clean && tsup",
58
71
  "check:exports": "attw --pack .",
59
72
  "check:mutants": "node scripts/check-mutation-directives.mjs",
73
+ "check:admin-readonly": "node scripts/check-admin-readonly.mjs",
60
74
  "check:published": "node scripts/check-published-surface.mjs",
61
75
  "clean": "rm -rf dist coverage",
62
76
  "lint": "eslint src scripts",
63
77
  "lint:fix": "eslint src scripts --fix",
64
78
  "mutation": "stryker run",
79
+ "mutation:full": "node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run",
65
80
  "mutation:dry-run": "stryker run --dryRunOnly",
66
- "mutation:incremental": "stryker run --incremental",
67
81
  "prepare": "husky",
68
- "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published",
82
+ "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:admin-readonly && pnpm test:cov:all && pnpm build && pnpm check:published",
69
83
  "release": "npm publish --provenance --access public",
70
84
  "size": "node scripts/check-size.mjs",
71
85
  "test": "jest",
@@ -90,7 +104,7 @@
90
104
  "peerDependencies": {
91
105
  "@nestjs/common": "^11.0.16",
92
106
  "@nestjs/core": "^11.1.18",
93
- "ioredis": "^5.0.0",
107
+ "ioredis": "^6.0.0",
94
108
  "reflect-metadata": "^0.2.0"
95
109
  },
96
110
  "devDependencies": {
@@ -116,7 +130,7 @@
116
130
  "eslint-plugin-security": "^4.0.0",
117
131
  "globals": "^17.6.0",
118
132
  "husky": "^9.1.7",
119
- "ioredis": "^5.10.1",
133
+ "ioredis": "^6.0.0",
120
134
  "ioredis-mock": "^8.13.1",
121
135
  "jest": "^30.4.2",
122
136
  "lint-staged": "^17.2.0",