@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
package/dist/server/index.cjs
CHANGED
|
@@ -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
|
-
[
|
|
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) :
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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 (
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
if (connection.url) {
|
|
190
|
+
try {
|
|
191
|
+
parseRedisUrl(connection.url);
|
|
192
|
+
} catch {
|
|
167
193
|
throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
|
|
168
|
-
reason: "
|
|
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
|
-
|
|
182
|
-
|
|
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
|
|
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;
|
package/dist/server/index.d.cts
CHANGED
|
@@ -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 };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/server/index.mjs
CHANGED
|
@@ -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
|
-
[
|
|
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) :
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 (
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
if (connection.url) {
|
|
188
|
+
try {
|
|
189
|
+
parseRedisUrl(connection.url);
|
|
190
|
+
} catch {
|
|
165
191
|
throw new CacheException(CACHE_ERROR_CODES.CONNECTION_FAILED, {
|
|
166
|
-
reason: "
|
|
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
|
-
|
|
180
|
-
|
|
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
|
|
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 };
|
package/dist/shared/index.cjs
CHANGED
|
@@ -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
|
package/dist/shared/index.d.cts
CHANGED
|
@@ -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];
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -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];
|
package/dist/shared/index.mjs
CHANGED
|
@@ -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
|
|
4
|
-
"description": "Typed Redis cache for NestJS based on ioredis
|
|
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": "^
|
|
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": "^
|
|
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",
|