@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/admin/index.d.cts
CHANGED
|
@@ -132,6 +132,9 @@ declare class BymaxCacheAdminModule {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/** Administration options after defaults are merged and validated. */
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
137
|
+
*/
|
|
135
138
|
interface ResolvedAdminOptions {
|
|
136
139
|
readonly scopes: readonly CacheScope[];
|
|
137
140
|
readonly degradedAboveMs: number;
|
|
@@ -150,6 +153,8 @@ interface ResolvedAdminOptions {
|
|
|
150
153
|
* @returns The frozen resolved options.
|
|
151
154
|
* @throws {CacheException} `INVALID_SCOPE` when a scope is malformed or a
|
|
152
155
|
* threshold is not a positive integer.
|
|
156
|
+
*
|
|
157
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
153
158
|
*/
|
|
154
159
|
declare function resolveAdminOptions(options: BymaxCacheAdminModuleOptions): Readonly<ResolvedAdminOptions>;
|
|
155
160
|
|
|
@@ -370,6 +375,8 @@ interface RedisStats {
|
|
|
370
375
|
* the concrete `CacheService`, while a test supplies a plain object with two
|
|
371
376
|
* methods and no cast. A cast would be a blocking finding under this
|
|
372
377
|
* repository's suppression policy, so the narrow type is not a convenience.
|
|
378
|
+
*
|
|
379
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
373
380
|
*/
|
|
374
381
|
type ICacheProbe = Pick<CacheService, 'ping' | 'info'>;
|
|
375
382
|
/** Reports whether the cache is answering, what it is doing, and how it is wired. */
|
|
@@ -432,8 +439,14 @@ declare class CacheStatusService {
|
|
|
432
439
|
* this subpath.
|
|
433
440
|
*/
|
|
434
441
|
/** One entry of a pipeline's result: an error, or a value. */
|
|
442
|
+
/**
|
|
443
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
444
|
+
*/
|
|
435
445
|
type PipelineReply = [Error | null, unknown];
|
|
436
446
|
/** A pipeline being composed, chainable the way ioredis's commander is. */
|
|
447
|
+
/**
|
|
448
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
449
|
+
*/
|
|
437
450
|
interface IRedisPipeline {
|
|
438
451
|
/** Queues `TYPE`. */
|
|
439
452
|
type(key: string): IRedisPipeline;
|
|
@@ -445,6 +458,9 @@ interface IRedisPipeline {
|
|
|
445
458
|
exec(): Promise<PipelineReply[] | null>;
|
|
446
459
|
}
|
|
447
460
|
/** The subset of the Redis client this surface reads through. */
|
|
461
|
+
/**
|
|
462
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
463
|
+
*/
|
|
448
464
|
interface IRedisReader {
|
|
449
465
|
/** Cursor-based key iteration. */
|
|
450
466
|
scan(cursor: string | number, matchToken: 'MATCH', pattern: string, countToken: 'COUNT', count: number | string): Promise<[string, string[]]>;
|
|
@@ -478,6 +494,9 @@ interface IRedisReader {
|
|
|
478
494
|
call(command: string, ...args: (string | number)[]): Promise<unknown>;
|
|
479
495
|
}
|
|
480
496
|
/** How the administration surface obtains its reader. */
|
|
497
|
+
/**
|
|
498
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
499
|
+
*/
|
|
481
500
|
interface IRedisReaderSource {
|
|
482
501
|
/**
|
|
483
502
|
* Returns the client.
|
|
@@ -770,6 +789,8 @@ declare class CacheAdminService {
|
|
|
770
789
|
*
|
|
771
790
|
* @param pattern - A pattern that already satisfies {@link SCOPE_PATTERN_SHAPE}.
|
|
772
791
|
* @returns The literal prefix, and whether a trailing `*` allows a suffix.
|
|
792
|
+
*
|
|
793
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
773
794
|
*/
|
|
774
795
|
declare function readScopePattern(pattern: string): {
|
|
775
796
|
readonly prefix: string;
|
|
@@ -784,6 +805,8 @@ declare function readScopePattern(pattern: string): {
|
|
|
784
805
|
* @param pattern - The scope's validated match pattern.
|
|
785
806
|
* @param key - The key a caller named.
|
|
786
807
|
* @returns `true` when the key belongs to the scope.
|
|
808
|
+
*
|
|
809
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
787
810
|
*/
|
|
788
811
|
declare function isKeyInScope(pattern: string, key: string): boolean;
|
|
789
812
|
/**
|
|
@@ -799,6 +822,8 @@ declare function isKeyInScope(pattern: string, key: string): boolean;
|
|
|
799
822
|
* @returns A frozen list of frozen scopes, in declaration order.
|
|
800
823
|
* @throws {CacheException} `INVALID_SCOPE` when the list is empty, a required
|
|
801
824
|
* field is empty, an id repeats, or a pattern has no literal prefix.
|
|
825
|
+
*
|
|
826
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
802
827
|
*/
|
|
803
828
|
declare function validateScopes(scopes: readonly CacheScope[]): readonly CacheScope[];
|
|
804
829
|
/**
|
|
@@ -811,6 +836,8 @@ declare function validateScopes(scopes: readonly CacheScope[]): readonly CacheSc
|
|
|
811
836
|
* @param id - The identifier from the request.
|
|
812
837
|
* @returns The scope.
|
|
813
838
|
* @throws {CacheException} `SCOPE_NOT_FOUND` when no declaration has that id.
|
|
839
|
+
*
|
|
840
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
814
841
|
*/
|
|
815
842
|
declare function findScope(scopes: readonly CacheScope[], id: string): CacheScope;
|
|
816
843
|
|
|
@@ -842,6 +869,8 @@ declare function findScope(scopes: readonly CacheScope[], id: string): CacheScop
|
|
|
842
869
|
*
|
|
843
870
|
* @param text - The raw `INFO` output.
|
|
844
871
|
* @returns Field name to raw text value.
|
|
872
|
+
*
|
|
873
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
845
874
|
*/
|
|
846
875
|
declare function parseInfoFields(text: string): ReadonlyMap<string, string>;
|
|
847
876
|
/**
|
|
@@ -850,6 +879,8 @@ declare function parseInfoFields(text: string): ReadonlyMap<string, string>;
|
|
|
850
879
|
* @param infoText - The raw `INFO` output.
|
|
851
880
|
* @param readAt - When the reading was taken, by the reading host's clock.
|
|
852
881
|
* @returns The parsed statistics.
|
|
882
|
+
*
|
|
883
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
853
884
|
*/
|
|
854
885
|
declare function readRedisStats(infoText: string, readAt: Date): RedisStats;
|
|
855
886
|
|
|
@@ -867,6 +898,8 @@ declare function readRedisStats(infoText: string, readAt: Date): RedisStats;
|
|
|
867
898
|
*
|
|
868
899
|
* @param reply - Whatever the server answered.
|
|
869
900
|
* @returns The recognised type, or `other`.
|
|
901
|
+
*
|
|
902
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
870
903
|
*/
|
|
871
904
|
declare function readKeyType(reply: unknown): RedisKeyType;
|
|
872
905
|
/**
|
|
@@ -878,6 +911,8 @@ declare function readKeyType(reply: unknown): RedisKeyType;
|
|
|
878
911
|
*
|
|
879
912
|
* @param reply - Whatever the server answered.
|
|
880
913
|
* @returns Whether the key expires, is persistent, or is gone.
|
|
914
|
+
*
|
|
915
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
881
916
|
*/
|
|
882
917
|
declare function readKeyTtl(reply: unknown): KeyTtl;
|
|
883
918
|
/**
|
|
@@ -889,6 +924,8 @@ declare function readKeyTtl(reply: unknown): KeyTtl;
|
|
|
889
924
|
*
|
|
890
925
|
* @param reply - Whatever the server answered.
|
|
891
926
|
* @returns The size in bytes, or `null` when no size was reported.
|
|
927
|
+
*
|
|
928
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
892
929
|
*/
|
|
893
930
|
declare function readSizeBytes(reply: unknown): number | null;
|
|
894
931
|
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -132,6 +132,9 @@ declare class BymaxCacheAdminModule {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/** Administration options after defaults are merged and validated. */
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
137
|
+
*/
|
|
135
138
|
interface ResolvedAdminOptions {
|
|
136
139
|
readonly scopes: readonly CacheScope[];
|
|
137
140
|
readonly degradedAboveMs: number;
|
|
@@ -150,6 +153,8 @@ interface ResolvedAdminOptions {
|
|
|
150
153
|
* @returns The frozen resolved options.
|
|
151
154
|
* @throws {CacheException} `INVALID_SCOPE` when a scope is malformed or a
|
|
152
155
|
* threshold is not a positive integer.
|
|
156
|
+
*
|
|
157
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
153
158
|
*/
|
|
154
159
|
declare function resolveAdminOptions(options: BymaxCacheAdminModuleOptions): Readonly<ResolvedAdminOptions>;
|
|
155
160
|
|
|
@@ -370,6 +375,8 @@ interface RedisStats {
|
|
|
370
375
|
* the concrete `CacheService`, while a test supplies a plain object with two
|
|
371
376
|
* methods and no cast. A cast would be a blocking finding under this
|
|
372
377
|
* repository's suppression policy, so the narrow type is not a convenience.
|
|
378
|
+
*
|
|
379
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
373
380
|
*/
|
|
374
381
|
type ICacheProbe = Pick<CacheService, 'ping' | 'info'>;
|
|
375
382
|
/** Reports whether the cache is answering, what it is doing, and how it is wired. */
|
|
@@ -432,8 +439,14 @@ declare class CacheStatusService {
|
|
|
432
439
|
* this subpath.
|
|
433
440
|
*/
|
|
434
441
|
/** One entry of a pipeline's result: an error, or a value. */
|
|
442
|
+
/**
|
|
443
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
444
|
+
*/
|
|
435
445
|
type PipelineReply = [Error | null, unknown];
|
|
436
446
|
/** A pipeline being composed, chainable the way ioredis's commander is. */
|
|
447
|
+
/**
|
|
448
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
449
|
+
*/
|
|
437
450
|
interface IRedisPipeline {
|
|
438
451
|
/** Queues `TYPE`. */
|
|
439
452
|
type(key: string): IRedisPipeline;
|
|
@@ -445,6 +458,9 @@ interface IRedisPipeline {
|
|
|
445
458
|
exec(): Promise<PipelineReply[] | null>;
|
|
446
459
|
}
|
|
447
460
|
/** The subset of the Redis client this surface reads through. */
|
|
461
|
+
/**
|
|
462
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
463
|
+
*/
|
|
448
464
|
interface IRedisReader {
|
|
449
465
|
/** Cursor-based key iteration. */
|
|
450
466
|
scan(cursor: string | number, matchToken: 'MATCH', pattern: string, countToken: 'COUNT', count: number | string): Promise<[string, string[]]>;
|
|
@@ -478,6 +494,9 @@ interface IRedisReader {
|
|
|
478
494
|
call(command: string, ...args: (string | number)[]): Promise<unknown>;
|
|
479
495
|
}
|
|
480
496
|
/** How the administration surface obtains its reader. */
|
|
497
|
+
/**
|
|
498
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
499
|
+
*/
|
|
481
500
|
interface IRedisReaderSource {
|
|
482
501
|
/**
|
|
483
502
|
* Returns the client.
|
|
@@ -770,6 +789,8 @@ declare class CacheAdminService {
|
|
|
770
789
|
*
|
|
771
790
|
* @param pattern - A pattern that already satisfies {@link SCOPE_PATTERN_SHAPE}.
|
|
772
791
|
* @returns The literal prefix, and whether a trailing `*` allows a suffix.
|
|
792
|
+
*
|
|
793
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
773
794
|
*/
|
|
774
795
|
declare function readScopePattern(pattern: string): {
|
|
775
796
|
readonly prefix: string;
|
|
@@ -784,6 +805,8 @@ declare function readScopePattern(pattern: string): {
|
|
|
784
805
|
* @param pattern - The scope's validated match pattern.
|
|
785
806
|
* @param key - The key a caller named.
|
|
786
807
|
* @returns `true` when the key belongs to the scope.
|
|
808
|
+
*
|
|
809
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
787
810
|
*/
|
|
788
811
|
declare function isKeyInScope(pattern: string, key: string): boolean;
|
|
789
812
|
/**
|
|
@@ -799,6 +822,8 @@ declare function isKeyInScope(pattern: string, key: string): boolean;
|
|
|
799
822
|
* @returns A frozen list of frozen scopes, in declaration order.
|
|
800
823
|
* @throws {CacheException} `INVALID_SCOPE` when the list is empty, a required
|
|
801
824
|
* field is empty, an id repeats, or a pattern has no literal prefix.
|
|
825
|
+
*
|
|
826
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
802
827
|
*/
|
|
803
828
|
declare function validateScopes(scopes: readonly CacheScope[]): readonly CacheScope[];
|
|
804
829
|
/**
|
|
@@ -811,6 +836,8 @@ declare function validateScopes(scopes: readonly CacheScope[]): readonly CacheSc
|
|
|
811
836
|
* @param id - The identifier from the request.
|
|
812
837
|
* @returns The scope.
|
|
813
838
|
* @throws {CacheException} `SCOPE_NOT_FOUND` when no declaration has that id.
|
|
839
|
+
*
|
|
840
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
814
841
|
*/
|
|
815
842
|
declare function findScope(scopes: readonly CacheScope[], id: string): CacheScope;
|
|
816
843
|
|
|
@@ -842,6 +869,8 @@ declare function findScope(scopes: readonly CacheScope[], id: string): CacheScop
|
|
|
842
869
|
*
|
|
843
870
|
* @param text - The raw `INFO` output.
|
|
844
871
|
* @returns Field name to raw text value.
|
|
872
|
+
*
|
|
873
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
845
874
|
*/
|
|
846
875
|
declare function parseInfoFields(text: string): ReadonlyMap<string, string>;
|
|
847
876
|
/**
|
|
@@ -850,6 +879,8 @@ declare function parseInfoFields(text: string): ReadonlyMap<string, string>;
|
|
|
850
879
|
* @param infoText - The raw `INFO` output.
|
|
851
880
|
* @param readAt - When the reading was taken, by the reading host's clock.
|
|
852
881
|
* @returns The parsed statistics.
|
|
882
|
+
*
|
|
883
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
853
884
|
*/
|
|
854
885
|
declare function readRedisStats(infoText: string, readAt: Date): RedisStats;
|
|
855
886
|
|
|
@@ -867,6 +898,8 @@ declare function readRedisStats(infoText: string, readAt: Date): RedisStats;
|
|
|
867
898
|
*
|
|
868
899
|
* @param reply - Whatever the server answered.
|
|
869
900
|
* @returns The recognised type, or `other`.
|
|
901
|
+
*
|
|
902
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
870
903
|
*/
|
|
871
904
|
declare function readKeyType(reply: unknown): RedisKeyType;
|
|
872
905
|
/**
|
|
@@ -878,6 +911,8 @@ declare function readKeyType(reply: unknown): RedisKeyType;
|
|
|
878
911
|
*
|
|
879
912
|
* @param reply - Whatever the server answered.
|
|
880
913
|
* @returns Whether the key expires, is persistent, or is gone.
|
|
914
|
+
*
|
|
915
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
881
916
|
*/
|
|
882
917
|
declare function readKeyTtl(reply: unknown): KeyTtl;
|
|
883
918
|
/**
|
|
@@ -889,6 +924,8 @@ declare function readKeyTtl(reply: unknown): KeyTtl;
|
|
|
889
924
|
*
|
|
890
925
|
* @param reply - Whatever the server answered.
|
|
891
926
|
* @returns The size in bytes, or `null` when no size was reported.
|
|
927
|
+
*
|
|
928
|
+
* @deprecated Implementation helper; will leave the public surface in 2.0.0. Do not depend on it.
|
|
892
929
|
*/
|
|
893
930
|
declare function readSizeBytes(reply: unknown): number | null;
|
|
894
931
|
|
package/dist/server/index.cjs
CHANGED
|
@@ -67,7 +67,8 @@ var CACHE_ERROR_CODES = {
|
|
|
67
67
|
INVALID_SCOPE: "cache.invalid_scope",
|
|
68
68
|
SCOPE_NOT_FOUND: "cache.scope_not_found",
|
|
69
69
|
SCOPE_NOT_READABLE: "cache.scope_not_readable",
|
|
70
|
-
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope"
|
|
70
|
+
KEY_NOT_IN_SCOPE: "cache.key_not_in_scope",
|
|
71
|
+
INVALID_TTL: "cache.invalid_ttl"
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
// src/server/errors/cache-error-codes.ts
|
|
@@ -108,12 +109,17 @@ var CACHE_ERROR_MESSAGES = /* @__PURE__ */ new Map([
|
|
|
108
109
|
[
|
|
109
110
|
CACHE_ERROR_CODES.KEY_NOT_IN_SCOPE,
|
|
110
111
|
"That key does not belong to the named administration scope."
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
CACHE_ERROR_CODES.INVALID_TTL,
|
|
115
|
+
"A TTL in seconds must be a safe integer, and at least 1 for a write that sets one."
|
|
111
116
|
]
|
|
112
117
|
]);
|
|
113
118
|
var CACHE_ERROR_STATUS = /* @__PURE__ */ new Map([
|
|
114
119
|
[CACHE_ERROR_CODES.COMMAND_TIMEOUT, common.HttpStatus.GATEWAY_TIMEOUT],
|
|
115
120
|
[CACHE_ERROR_CODES.CONNECTION_LOST, common.HttpStatus.SERVICE_UNAVAILABLE],
|
|
116
121
|
[CACHE_ERROR_CODES.INVALID_KEY, common.HttpStatus.BAD_REQUEST],
|
|
122
|
+
[CACHE_ERROR_CODES.INVALID_TTL, common.HttpStatus.BAD_REQUEST],
|
|
117
123
|
[CACHE_ERROR_CODES.FLUSH_DISABLED_IN_PRODUCTION, common.HttpStatus.FORBIDDEN]
|
|
118
124
|
]);
|
|
119
125
|
var CacheException = class extends common.HttpException {
|
|
@@ -711,7 +717,7 @@ exports.KeyBuilder = __decorateClass([
|
|
|
711
717
|
common.Injectable(),
|
|
712
718
|
__decorateParam(0, common.Inject(BYMAX_CACHE_OPTIONS))
|
|
713
719
|
], exports.KeyBuilder);
|
|
714
|
-
var
|
|
720
|
+
var DESERIALIZATION_ERROR = "stored value is not valid JSON";
|
|
715
721
|
var extractErrorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
716
722
|
exports.JsonSerializer = class JsonSerializer {
|
|
717
723
|
/**
|
|
@@ -753,17 +759,17 @@ exports.JsonSerializer = class JsonSerializer {
|
|
|
753
759
|
* @param raw - The JSON string to decode.
|
|
754
760
|
* @returns The decoded value, typed as `T`.
|
|
755
761
|
* @throws {CacheException} `DESERIALIZATION_FAILED` when `raw` is not valid
|
|
756
|
-
* JSON. `details
|
|
757
|
-
*
|
|
758
|
-
*
|
|
762
|
+
* JSON. `details` holds a fixed `error` text and the `length` of `raw`; no
|
|
763
|
+
* part of the stored value is echoed, as it may carry PII or a credential
|
|
764
|
+
* (CLAUDE.md §4).
|
|
759
765
|
*/
|
|
760
766
|
deserialize(raw) {
|
|
761
767
|
try {
|
|
762
768
|
return JSON.parse(raw);
|
|
763
|
-
} catch
|
|
769
|
+
} catch {
|
|
764
770
|
throw new CacheException(CACHE_ERROR_CODES.DESERIALIZATION_FAILED, {
|
|
765
|
-
error:
|
|
766
|
-
|
|
771
|
+
error: DESERIALIZATION_ERROR,
|
|
772
|
+
length: raw.length
|
|
767
773
|
});
|
|
768
774
|
}
|
|
769
775
|
}
|
|
@@ -779,6 +785,17 @@ var resolveSerializer = (options, injected) => (
|
|
|
779
785
|
options.serializer ?? injected ?? new exports.JsonSerializer()
|
|
780
786
|
);
|
|
781
787
|
|
|
788
|
+
// src/server/utils/ttl.ts
|
|
789
|
+
var MIN_WRITE_TTL_SECONDS = 1;
|
|
790
|
+
function assertTtlSeconds(ttlSeconds, minimum) {
|
|
791
|
+
if (!Number.isSafeInteger(ttlSeconds)) {
|
|
792
|
+
throw new CacheException(CACHE_ERROR_CODES.INVALID_TTL, { reason: "not_a_safe_integer" });
|
|
793
|
+
}
|
|
794
|
+
if (minimum !== void 0 && ttlSeconds < minimum) {
|
|
795
|
+
throw new CacheException(CACHE_ERROR_CODES.INVALID_TTL, { reason: "below_minimum", minimum });
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
782
799
|
// src/server/services/cache.service.ts
|
|
783
800
|
var PRODUCTION_ENV = "production";
|
|
784
801
|
var SCAN_COUNT = 100;
|
|
@@ -839,14 +856,17 @@ exports.CacheService = class CacheService {
|
|
|
839
856
|
* @param prefix - Entity-group prefix.
|
|
840
857
|
* @param id - Entity id.
|
|
841
858
|
* @param value - The value to store (passed through the serializer).
|
|
842
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
859
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
860
|
+
* no expiration.
|
|
843
861
|
* @returns Resolves once the write completes.
|
|
844
862
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
863
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
845
864
|
*/
|
|
846
865
|
async set(prefix, id, value, ttlSeconds) {
|
|
847
866
|
const key = this.keyBuilder.build(prefix, id);
|
|
848
867
|
const raw = this.serializer.serialize(value);
|
|
849
868
|
if (ttlSeconds !== void 0) {
|
|
869
|
+
assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
850
870
|
await this.connection.getClient().set(key, raw, "EX", ttlSeconds);
|
|
851
871
|
} else {
|
|
852
872
|
await this.connection.getClient().set(key, raw);
|
|
@@ -858,12 +878,15 @@ exports.CacheService = class CacheService {
|
|
|
858
878
|
* @param prefix - Entity-group prefix.
|
|
859
879
|
* @param id - Entity id.
|
|
860
880
|
* @param value - The raw string to store as-is.
|
|
861
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
881
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
882
|
+
* no expiration.
|
|
862
883
|
* @returns Resolves once the write completes.
|
|
884
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
863
885
|
*/
|
|
864
886
|
async setRaw(prefix, id, value, ttlSeconds) {
|
|
865
887
|
const key = this.keyBuilder.build(prefix, id);
|
|
866
888
|
if (ttlSeconds !== void 0) {
|
|
889
|
+
assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
867
890
|
await this.connection.getClient().set(key, value, "EX", ttlSeconds);
|
|
868
891
|
} else {
|
|
869
892
|
await this.connection.getClient().set(key, value);
|
|
@@ -876,13 +899,16 @@ exports.CacheService = class CacheService {
|
|
|
876
899
|
* @param prefix - Entity-group prefix.
|
|
877
900
|
* @param id - Entity id.
|
|
878
901
|
* @param value - The value to store (passed through the serializer).
|
|
879
|
-
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write
|
|
902
|
+
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write, a safe
|
|
903
|
+
* integer of at least 1.
|
|
880
904
|
* @returns `true` when the value was stored, `false` when the key already existed.
|
|
881
905
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
906
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
882
907
|
*/
|
|
883
908
|
async setNx(prefix, id, value, ttlSeconds) {
|
|
884
909
|
const key = this.keyBuilder.build(prefix, id);
|
|
885
910
|
const raw = this.serializer.serialize(value);
|
|
911
|
+
if (ttlSeconds !== void 0) assertTtlSeconds(ttlSeconds, MIN_WRITE_TTL_SECONDS);
|
|
886
912
|
const result = ttlSeconds !== void 0 ? await this.connection.getClient().set(key, raw, "EX", ttlSeconds, "NX") : await this.connection.getClient().set(key, raw, "NX");
|
|
887
913
|
return result === "OK";
|
|
888
914
|
}
|
|
@@ -954,10 +980,13 @@ exports.CacheService = class CacheService {
|
|
|
954
980
|
*
|
|
955
981
|
* @param prefix - Entity-group prefix.
|
|
956
982
|
* @param id - Entity id.
|
|
957
|
-
* @param ttlSeconds - Expiry in seconds.
|
|
983
|
+
* @param ttlSeconds - Expiry in seconds, a safe integer. As in Redis, zero or a negative
|
|
984
|
+
* value deletes the key.
|
|
958
985
|
* @returns `true` when the timeout was set, `false` when the key does not exist.
|
|
986
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer.
|
|
959
987
|
*/
|
|
960
988
|
async expire(prefix, id, ttlSeconds) {
|
|
989
|
+
assertTtlSeconds(ttlSeconds, void 0);
|
|
961
990
|
const result = await this.connection.getClient().expire(this.keyBuilder.build(prefix, id), ttlSeconds);
|
|
962
991
|
return result === 1;
|
|
963
992
|
}
|
|
@@ -1165,6 +1194,9 @@ exports.CacheService = class CacheService {
|
|
|
1165
1194
|
* @param prefix - Entity-group prefix.
|
|
1166
1195
|
* @param pattern - Glob pattern for the id segment, e.g. `'*'`.
|
|
1167
1196
|
* @returns The matching fully-namespaced keys.
|
|
1197
|
+
* @throws {CacheException} `UNSUPPORTED_IN_CLUSTER` when called in cluster mode:
|
|
1198
|
+
* `KEYS` carries no key, so the cluster client sends it to one sampled node and the
|
|
1199
|
+
* reply would cover only that node's share of the keyspace.
|
|
1168
1200
|
* @example
|
|
1169
1201
|
* ```ts
|
|
1170
1202
|
* await cache.keys('users', '*') // ['app:users:u_1', 'app:users:u_2']
|
|
@@ -1172,7 +1204,11 @@ exports.CacheService = class CacheService {
|
|
|
1172
1204
|
*/
|
|
1173
1205
|
async keys(prefix, pattern) {
|
|
1174
1206
|
const fullPattern = this.keyBuilder.build(prefix, pattern);
|
|
1175
|
-
|
|
1207
|
+
const client = this.connection.getClient();
|
|
1208
|
+
if (!isScannableClient(client)) {
|
|
1209
|
+
throw new CacheException(CACHE_ERROR_CODES.UNSUPPORTED_IN_CLUSTER, { operation: "keys" });
|
|
1210
|
+
}
|
|
1211
|
+
return client.keys(fullPattern);
|
|
1176
1212
|
}
|
|
1177
1213
|
/**
|
|
1178
1214
|
* Iterates keys matching a pattern under a prefix using a non-blocking cursor.
|
package/dist/server/index.d.cts
CHANGED
|
@@ -591,9 +591,11 @@ declare class CacheService {
|
|
|
591
591
|
* @param prefix - Entity-group prefix.
|
|
592
592
|
* @param id - Entity id.
|
|
593
593
|
* @param value - The value to store (passed through the serializer).
|
|
594
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
594
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
595
|
+
* no expiration.
|
|
595
596
|
* @returns Resolves once the write completes.
|
|
596
597
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
598
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
597
599
|
*/
|
|
598
600
|
set<T>(prefix: string, id: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
599
601
|
/**
|
|
@@ -602,8 +604,10 @@ declare class CacheService {
|
|
|
602
604
|
* @param prefix - Entity-group prefix.
|
|
603
605
|
* @param id - Entity id.
|
|
604
606
|
* @param value - The raw string to store as-is.
|
|
605
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
607
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
608
|
+
* no expiration.
|
|
606
609
|
* @returns Resolves once the write completes.
|
|
610
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
607
611
|
*/
|
|
608
612
|
setRaw(prefix: string, id: string, value: string, ttlSeconds?: number): Promise<void>;
|
|
609
613
|
/**
|
|
@@ -613,9 +617,11 @@ declare class CacheService {
|
|
|
613
617
|
* @param prefix - Entity-group prefix.
|
|
614
618
|
* @param id - Entity id.
|
|
615
619
|
* @param value - The value to store (passed through the serializer).
|
|
616
|
-
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write
|
|
620
|
+
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write, a safe
|
|
621
|
+
* integer of at least 1.
|
|
617
622
|
* @returns `true` when the value was stored, `false` when the key already existed.
|
|
618
623
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
624
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
619
625
|
*/
|
|
620
626
|
setNx<T>(prefix: string, id: string, value: T, ttlSeconds?: number): Promise<boolean>;
|
|
621
627
|
/**
|
|
@@ -667,8 +673,10 @@ declare class CacheService {
|
|
|
667
673
|
*
|
|
668
674
|
* @param prefix - Entity-group prefix.
|
|
669
675
|
* @param id - Entity id.
|
|
670
|
-
* @param ttlSeconds - Expiry in seconds.
|
|
676
|
+
* @param ttlSeconds - Expiry in seconds, a safe integer. As in Redis, zero or a negative
|
|
677
|
+
* value deletes the key.
|
|
671
678
|
* @returns `true` when the timeout was set, `false` when the key does not exist.
|
|
679
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer.
|
|
672
680
|
*/
|
|
673
681
|
expire(prefix: string, id: string, ttlSeconds: number): Promise<boolean>;
|
|
674
682
|
/**
|
|
@@ -809,6 +817,9 @@ declare class CacheService {
|
|
|
809
817
|
* @param prefix - Entity-group prefix.
|
|
810
818
|
* @param pattern - Glob pattern for the id segment, e.g. `'*'`.
|
|
811
819
|
* @returns The matching fully-namespaced keys.
|
|
820
|
+
* @throws {CacheException} `UNSUPPORTED_IN_CLUSTER` when called in cluster mode:
|
|
821
|
+
* `KEYS` carries no key, so the cluster client sends it to one sampled node and the
|
|
822
|
+
* reply would cover only that node's share of the keyspace.
|
|
812
823
|
* @example
|
|
813
824
|
* ```ts
|
|
814
825
|
* await cache.keys('users', '*') // ['app:users:u_1', 'app:users:u_2']
|
|
@@ -971,9 +982,9 @@ declare class JsonSerializer implements ISerializer {
|
|
|
971
982
|
* @param raw - The JSON string to decode.
|
|
972
983
|
* @returns The decoded value, typed as `T`.
|
|
973
984
|
* @throws {CacheException} `DESERIALIZATION_FAILED` when `raw` is not valid
|
|
974
|
-
* JSON. `details
|
|
975
|
-
*
|
|
976
|
-
*
|
|
985
|
+
* JSON. `details` holds a fixed `error` text and the `length` of `raw`; no
|
|
986
|
+
* part of the stored value is echoed, as it may carry PII or a credential
|
|
987
|
+
* (CLAUDE.md §4).
|
|
977
988
|
*/
|
|
978
989
|
deserialize<T>(raw: string): T;
|
|
979
990
|
}
|
|
@@ -1199,6 +1210,7 @@ declare const CACHE_ERROR_CODES: {
|
|
|
1199
1210
|
readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
|
|
1200
1211
|
readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
|
|
1201
1212
|
readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
|
|
1213
|
+
readonly INVALID_TTL: "cache.invalid_ttl";
|
|
1202
1214
|
};
|
|
1203
1215
|
/** Union of every cache error code string value. */
|
|
1204
1216
|
type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
|
package/dist/server/index.d.ts
CHANGED
|
@@ -591,9 +591,11 @@ declare class CacheService {
|
|
|
591
591
|
* @param prefix - Entity-group prefix.
|
|
592
592
|
* @param id - Entity id.
|
|
593
593
|
* @param value - The value to store (passed through the serializer).
|
|
594
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
594
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
595
|
+
* no expiration.
|
|
595
596
|
* @returns Resolves once the write completes.
|
|
596
597
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
598
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
597
599
|
*/
|
|
598
600
|
set<T>(prefix: string, id: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
599
601
|
/**
|
|
@@ -602,8 +604,10 @@ declare class CacheService {
|
|
|
602
604
|
* @param prefix - Entity-group prefix.
|
|
603
605
|
* @param id - Entity id.
|
|
604
606
|
* @param value - The raw string to store as-is.
|
|
605
|
-
* @param ttlSeconds - Optional expiry in seconds; omit for
|
|
607
|
+
* @param ttlSeconds - Optional expiry in seconds, a safe integer of at least 1; omit for
|
|
608
|
+
* no expiration.
|
|
606
609
|
* @returns Resolves once the write completes.
|
|
610
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
607
611
|
*/
|
|
608
612
|
setRaw(prefix: string, id: string, value: string, ttlSeconds?: number): Promise<void>;
|
|
609
613
|
/**
|
|
@@ -613,9 +617,11 @@ declare class CacheService {
|
|
|
613
617
|
* @param prefix - Entity-group prefix.
|
|
614
618
|
* @param id - Entity id.
|
|
615
619
|
* @param value - The value to store (passed through the serializer).
|
|
616
|
-
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write
|
|
620
|
+
* @param ttlSeconds - Optional expiry in seconds applied on the same atomic write, a safe
|
|
621
|
+
* integer of at least 1.
|
|
617
622
|
* @returns `true` when the value was stored, `false` when the key already existed.
|
|
618
623
|
* @throws {CacheException} `SERIALIZATION_FAILED` when `value` cannot be encoded.
|
|
624
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer of at least 1.
|
|
619
625
|
*/
|
|
620
626
|
setNx<T>(prefix: string, id: string, value: T, ttlSeconds?: number): Promise<boolean>;
|
|
621
627
|
/**
|
|
@@ -667,8 +673,10 @@ declare class CacheService {
|
|
|
667
673
|
*
|
|
668
674
|
* @param prefix - Entity-group prefix.
|
|
669
675
|
* @param id - Entity id.
|
|
670
|
-
* @param ttlSeconds - Expiry in seconds.
|
|
676
|
+
* @param ttlSeconds - Expiry in seconds, a safe integer. As in Redis, zero or a negative
|
|
677
|
+
* value deletes the key.
|
|
671
678
|
* @returns `true` when the timeout was set, `false` when the key does not exist.
|
|
679
|
+
* @throws {CacheException} `INVALID_TTL` when `ttlSeconds` is not a safe integer.
|
|
672
680
|
*/
|
|
673
681
|
expire(prefix: string, id: string, ttlSeconds: number): Promise<boolean>;
|
|
674
682
|
/**
|
|
@@ -809,6 +817,9 @@ declare class CacheService {
|
|
|
809
817
|
* @param prefix - Entity-group prefix.
|
|
810
818
|
* @param pattern - Glob pattern for the id segment, e.g. `'*'`.
|
|
811
819
|
* @returns The matching fully-namespaced keys.
|
|
820
|
+
* @throws {CacheException} `UNSUPPORTED_IN_CLUSTER` when called in cluster mode:
|
|
821
|
+
* `KEYS` carries no key, so the cluster client sends it to one sampled node and the
|
|
822
|
+
* reply would cover only that node's share of the keyspace.
|
|
812
823
|
* @example
|
|
813
824
|
* ```ts
|
|
814
825
|
* await cache.keys('users', '*') // ['app:users:u_1', 'app:users:u_2']
|
|
@@ -971,9 +982,9 @@ declare class JsonSerializer implements ISerializer {
|
|
|
971
982
|
* @param raw - The JSON string to decode.
|
|
972
983
|
* @returns The decoded value, typed as `T`.
|
|
973
984
|
* @throws {CacheException} `DESERIALIZATION_FAILED` when `raw` is not valid
|
|
974
|
-
* JSON. `details
|
|
975
|
-
*
|
|
976
|
-
*
|
|
985
|
+
* JSON. `details` holds a fixed `error` text and the `length` of `raw`; no
|
|
986
|
+
* part of the stored value is echoed, as it may carry PII or a credential
|
|
987
|
+
* (CLAUDE.md §4).
|
|
977
988
|
*/
|
|
978
989
|
deserialize<T>(raw: string): T;
|
|
979
990
|
}
|
|
@@ -1199,6 +1210,7 @@ declare const CACHE_ERROR_CODES: {
|
|
|
1199
1210
|
readonly SCOPE_NOT_FOUND: "cache.scope_not_found";
|
|
1200
1211
|
readonly SCOPE_NOT_READABLE: "cache.scope_not_readable";
|
|
1201
1212
|
readonly KEY_NOT_IN_SCOPE: "cache.key_not_in_scope";
|
|
1213
|
+
readonly INVALID_TTL: "cache.invalid_ttl";
|
|
1202
1214
|
};
|
|
1203
1215
|
/** Union of every cache error code string value. */
|
|
1204
1216
|
type CacheErrorCode = (typeof CACHE_ERROR_CODES)[keyof typeof CACHE_ERROR_CODES];
|