@bymax-one/nest-cache 1.2.2 → 1.2.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 +113 -0
- package/README.md +144 -58
- package/dist/admin/index.d.cts +37 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/server/index.cjs +49 -13
- package/dist/server/index.d.cts +19 -7
- package/dist/server/index.d.ts +19 -7
- package/dist/server/index.mjs +49 -13
- package/dist/shared/index.cjs +2 -1
- package/dist/shared/index.d.cts +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.mjs +2 -1
- package/package.json +1 -1
package/dist/server/index.mjs
CHANGED
|
@@ -65,7 +65,8 @@ var CACHE_ERROR_CODES = {
|
|
|
65
65
|
INVALID_SCOPE: "cache.invalid_scope",
|
|
66
66
|
SCOPE_NOT_FOUND: "cache.scope_not_found",
|
|
67
67
|
SCOPE_NOT_READABLE: "cache.scope_not_readable",
|
|
68
|
-
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
|
|
68
|
+
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope",
|
|
69
|
+
INVALID_TTL: "cache.invalid_ttl"
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
// src/server/errors/cache-error-codes.ts
|
|
@@ -106,12 +107,17 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
|
|
|
106
107
|
[
|
|
107
108
|
CACHE_ERROR_CODES.KEY_NOT_IN_SCOPE,
|
|
108
109
|
"That key does not belong to the named administration scope."
|
|
110
|
+
],
|
|
111
|
+
[
|
|
112
|
+
CACHE_ERROR_CODES.INVALID_TTL,
|
|
113
|
+
"A TTL in seconds must be a safe integer, and at least 1 for a write that sets one."
|
|
109
114
|
]
|
|
110
115
|
]);
|
|
111
116
|
var CACHE_ERROR_STATUS = /* @__PURE__ */ new Map([
|
|
112
117
|
[CACHE_ERROR_CODES.COMMAND_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT],
|
|
113
118
|
[CACHE_ERROR_CODES.CONNECTION_LOST, HttpStatus.SERVICE_UNAVAILABLE],
|
|
114
119
|
[CACHE_ERROR_CODES.INVALID_KEY, HttpStatus.BAD_REQUEST],
|
|
120
|
+
[CACHE_ERROR_CODES.INVALID_TTL, HttpStatus.BAD_REQUEST],
|
|
115
121
|
[CACHE_ERROR_CODES.FLUSH_DISABLED_IN_PRODUCTION, HttpStatus.FORBIDDEN]
|
|
116
122
|
]);
|
|
117
123
|
var CacheException = class extends HttpException {
|
|
@@ -709,7 +715,7 @@ KeyBuilder = __decorateClass([
|
|
|
709
715
|
Injectable(),
|
|
710
716
|
__decorateParam(0, Inject(BYMAX_CACHE_OPTIONS))
|
|
711
717
|
], KeyBuilder);
|
|
712
|
-
var
|
|
718
|
+
var DESERIALIZATION_ERROR = "stored value is not valid JSON";
|
|
713
719
|
var extractErrorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
714
720
|
var JsonSerializer = class {
|
|
715
721
|
/**
|
|
@@ -751,17 +757,17 @@ var JsonSerializer = class {
|
|
|
751
757
|
* @param raw - The JSON string to decode.
|
|
752
758
|
* @returns The decoded value, typed as `T`.
|
|
753
759
|
* @throws {CacheException} `DESERIALIZATION_FAILED` when `raw` is not valid
|
|
754
|
-
* JSON. `details
|
|
755
|
-
*
|
|
756
|
-
*
|
|
760
|
+
* JSON. `details` holds a fixed `error` text and the `length` of `raw`; no
|
|
761
|
+
* part of the stored value is echoed, as it may carry PII or a credential
|
|
762
|
+
* (CLAUDE.md §4).
|
|
757
763
|
*/
|
|
758
764
|
deserialize(raw) {
|
|
759
765
|
try {
|
|
760
766
|
return JSON.parse(raw);
|
|
761
|
-
} catch
|
|
767
|
+
} catch {
|
|
762
768
|
throw new CacheException(CACHE_ERROR_CODES.DESERIALIZATION_FAILED, {
|
|
763
|
-
error:
|
|
764
|
-
|
|
769
|
+
error: DESERIALIZATION_ERROR,
|
|
770
|
+
length: raw.length
|
|
765
771
|
});
|
|
766
772
|
}
|
|
767
773
|
}
|
|
@@ -777,6 +783,17 @@ var resolveSerializer = (options, injected) => (
|
|
|
777
783
|
options.serializer ?? injected ?? new JsonSerializer()
|
|
778
784
|
);
|
|
779
785
|
|
|
786
|
+
// src/server/utils/ttl.ts
|
|
787
|
+
var MIN_WRITE_TTL_SECONDS = 1;
|
|
788
|
+
function assertTtlSeconds(ttlSeconds, minimum) {
|
|
789
|
+
if (!Number.isSafeInteger(ttlSeconds)) {
|
|
790
|
+
throw new CacheException(CACHE_ERROR_CODES.INVALID_TTL, { reason: "not_a_safe_integer" });
|
|
791
|
+
}
|
|
792
|
+
if (minimum !== void 0 && ttlSeconds < minimum) {
|
|
793
|
+
throw new CacheException(CACHE_ERROR_CODES.INVALID_TTL, { reason: "below_minimum", minimum });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
780
797
|
// src/server/services/cache.service.ts
|
|
781
798
|
var PRODUCTION_ENV = "production";
|
|
782
799
|
var SCAN_COUNT = 100;
|
|
@@ -837,14 +854,17 @@ var CacheService = class {
|
|
|
837
854
|
* @param prefix - Entity-group prefix.
|
|
838
855
|
* @param id - Entity id.
|
|
839
856
|
* @param value - The value to store (passed through the serializer).
|
|
840
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
857
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
858
|
+
* no expiration.
|
|
841
859
|
* @returns Resolves once the write completes.
|
|
842
860
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
861
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
843
862
|
*/
|
|
844
863
|
async set(prefix, id, value, ttlSeconds) {
|
|
845
864
|
const key = this.keyBuilder.build(prefix, id);
|
|
846
865
|
const raw = this.serializer.serialize(value);
|
|
847
866
|
if (ttlSeconds !== void 0) {
|
|
867
|
+
assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
848
868
|
await this.connection.getClient().set(key, raw, "EX", ttlSeconds);
|
|
849
869
|
} else {
|
|
850
870
|
await this.connection.getClient().set(key, raw);
|
|
@@ -856,12 +876,15 @@ var CacheService = class {
|
|
|
856
876
|
* @param prefix - Entity-group prefix.
|
|
857
877
|
* @param id - Entity id.
|
|
858
878
|
* @param value - The raw string to store as-is.
|
|
859
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
879
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
880
|
+
* no expiration.
|
|
860
881
|
* @returns Resolves once the write completes.
|
|
882
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
861
883
|
*/
|
|
862
884
|
async setRaw(prefix, id, value, ttlSeconds) {
|
|
863
885
|
const key = this.keyBuilder.build(prefix, id);
|
|
864
886
|
if (ttlSeconds !== void 0) {
|
|
887
|
+
assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
865
888
|
await this.connection.getClient().set(key, value, "EX", ttlSeconds);
|
|
866
889
|
} else {
|
|
867
890
|
await this.connection.getClient().set(key, value);
|
|
@@ -874,13 +897,16 @@ var CacheService = class {
|
|
|
874
897
|
* @param prefix - Entity-group prefix.
|
|
875
898
|
* @param id - Entity id.
|
|
876
899
|
* @param value - The value to store (passed through the serializer).
|
|
877
|
-
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write
|
|
900
|
+
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write, a safe
|
|
901
|
+
* integer of at least 1.
|
|
878
902
|
* @returns `true` when the value was stored, `false` when the key already existed.
|
|
879
903
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
904
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
880
905
|
*/
|
|
881
906
|
async setNx(prefix, id, value, ttlSeconds) {
|
|
882
907
|
const key = this.keyBuilder.build(prefix, id);
|
|
883
908
|
const raw = this.serializer.serialize(value);
|
|
909
|
+
if (ttlSeconds !== void 0) assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
884
910
|
const result = ttlSeconds !== void 0 ? await this.connection.getClient().set(key, raw, "EX", ttlSeconds, "NX") : await this.connection.getClient().set(key, raw, "NX");
|
|
885
911
|
return result === "OK";
|
|
886
912
|
}
|
|
@@ -952,10 +978,13 @@ var CacheService = class {
|
|
|
952
978
|
*
|
|
953
979
|
* @param prefix - Entity-group prefix.
|
|
954
980
|
* @param id - Entity id.
|
|
955
|
-
* @param ttlSeconds - Expiry in seconds.
|
|
981
|
+
* @param ttlSeconds - Expiry in seconds, a safe integer. As in Redis, zero or a negative
|
|
982
|
+
* value deletes the key.
|
|
956
983
|
* @returns `true` when the timeout was set, `false` when the key does not exist.
|
|
984
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer.
|
|
957
985
|
*/
|
|
958
986
|
async expire(prefix, id, ttlSeconds) {
|
|
987
|
+
assertTtlSeconds(ttlSeconds, void 0);
|
|
959
988
|
const result = await this.connection.getClient().expire(this.keyBuilder.build(prefix, id), ttlSeconds);
|
|
960
989
|
return result === 1;
|
|
961
990
|
}
|
|
@@ -1163,6 +1192,9 @@ var CacheService = class {
|
|
|
1163
1192
|
* @param prefix - Entity-group prefix.
|
|
1164
1193
|
* @param pattern - Glob pattern for the id segment, e.g. `'*'`.
|
|
1165
1194
|
* @returns The matching fully-namespaced keys.
|
|
1195
|
+
* @throws {CacheException} `UNSUPPORTED_IN_CLUSTER` when called in cluster mode:
|
|
1196
|
+
* `KEYS` carries no key, so the cluster client sends it to one sampled node and the
|
|
1197
|
+
* reply would cover only that node's share of the keyspace.
|
|
1166
1198
|
* @example
|
|
1167
1199
|
* ```ts
|
|
1168
1200
|
* await cache.keys('users', '*') // ['app:users:u_1', 'app:users:u_2']
|
|
@@ -1170,7 +1202,11 @@ var CacheService = class {
|
|
|
1170
1202
|
*/
|
|
1171
1203
|
async keys(prefix, pattern) {
|
|
1172
1204
|
const fullPattern = this.keyBuilder.build(prefix, pattern);
|
|
1173
|
-
|
|
1205
|
+
const client = this.connection.getClient();
|
|
1206
|
+
if (!isScannableClient(client)) {
|
|
1207
|
+
throw new CacheException(CACHE_ERROR_CODES.UNSUPPORTED_IN_CLUSTER, { operation: "keys" });
|
|
1208
|
+
}
|
|
1209
|
+
return client.keys(fullPattern);
|
|
1174
1210
|
}
|
|
1175
1211
|
/**
|
|
1176
1212
|
* Iterates keys matching a pattern under a prefix using a non-blocking cursor.
|
package/dist/shared/index.cjs
CHANGED
|
@@ -20,7 +20,8 @@ var CACHE_ERROR_CODES = {
|
|
|
20
20
|
INVALID_SCOPE: "cache.invalid_scope",
|
|
21
21
|
SCOPE_NOT_FOUND: "cache.scope_not_found",
|
|
22
22
|
SCOPE_NOT_READABLE: "cache.scope_not_readable",
|
|
23
|
-
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
|
|
23
|
+
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope",
|
|
24
|
+
INVALID_TTL: "cache.invalid_ttl"
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
// src/shared/constants/event-names.ts
|
package/dist/shared/index.d.cts
CHANGED
|
@@ -97,6 +97,7 @@ declare const CACHE_ERROR_CODES: {
|
|
|
97
97
|
readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
|
|
98
98
|
readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
|
|
99
99
|
readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
|
|
100
|
+
readonly INVALID_TTL: "cache.invalid_ttl";
|
|
100
101
|
};
|
|
101
102
|
/** Union of every cache error code string value. */
|
|
102
103
|
type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ declare const CACHE_ERROR_CODES: {
|
|
|
97
97
|
readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
|
|
98
98
|
readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
|
|
99
99
|
readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
|
|
100
|
+
readonly INVALID_TTL: "cache.invalid_ttl";
|
|
100
101
|
};
|
|
101
102
|
/** Union of every cache error code string value. */
|
|
102
103
|
type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
|
package/dist/shared/index.mjs
CHANGED
|
@@ -18,7 +18,8 @@ var CACHE_ERROR_CODES = {
|
|
|
18
18
|
INVALID_SCOPE: "cache.invalid_scope",
|
|
19
19
|
SCOPE_NOT_FOUND: "cache.scope_not_found",
|
|
20
20
|
SCOPE_NOT_READABLE: "cache.scope_not_readable",
|
|
21
|
-
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
|
|
21
|
+
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope",
|
|
22
|
+
INVALID_TTL: "cache.invalid_ttl"
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
// src/shared/constants/event-names.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-cache",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|