@tsed/platform-cache 8.0.0 → 8.0.2

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.
@@ -0,0 +1,204 @@
1
+ import {isClass, isFunction, isString, Type} from "@tsed/core";
2
+ import {Configuration, Inject, InjectorService, Module} from "@tsed/di";
3
+ import {deserialize, JsonDeserializerOptions, serialize} from "@tsed/json-mapper";
4
+ import {Logger} from "@tsed/logger";
5
+ import {AsyncLocalStorage} from "async_hooks";
6
+ import type {Cache, CachingConfig, MultiCache} from "cache-manager";
7
+
8
+ import {PlatformCacheSettings} from "../interfaces/interfaces.js";
9
+ import {PlatformCachedObject} from "../interfaces/PlatformCachedObject.js";
10
+ import {getPrefix} from "../utils/getPrefix.js";
11
+
12
+ const defaultKeyResolver = (args: any[]) => {
13
+ return args.map((arg: any) => (isClass(arg) ? JSON.stringify(serialize(arg)) : arg)).join(":");
14
+ };
15
+
16
+ export type CacheManager = Cache | MultiCache;
17
+ export type Ttl = number | ((result: any) => number);
18
+
19
+ const storage: AsyncLocalStorage<{forceRefresh: boolean}> = new AsyncLocalStorage();
20
+
21
+ /**
22
+ * @platform
23
+ */
24
+ @Module()
25
+ export class PlatformCache {
26
+ @Configuration()
27
+ protected settings: Configuration;
28
+
29
+ @Inject()
30
+ protected injector: InjectorService;
31
+
32
+ @Inject()
33
+ protected logger: Logger;
34
+
35
+ #cache: CacheManager | undefined;
36
+
37
+ get cache() {
38
+ return this.#cache;
39
+ }
40
+
41
+ async $onInit() {
42
+ const settings = this.settings.get<PlatformCacheSettings>("cache");
43
+
44
+ if (settings) {
45
+ this.#cache = await this.createCacheManager(settings);
46
+
47
+ await this.injector.emit("$onCreateCacheManager", this.#cache);
48
+ }
49
+ }
50
+
51
+ getKeysOf(target: Type<any>, propertyKey: string | symbol) {
52
+ const prefix = getPrefix(target, propertyKey);
53
+
54
+ return this.keys(`${prefix.join(":")}:*`);
55
+ }
56
+
57
+ disabled(): boolean {
58
+ return !this.settings.get<PlatformCacheSettings>("cache");
59
+ }
60
+
61
+ defaultKeyResolver() {
62
+ return this.settings.get<(args: any[], ctx?: any) => string>("cache.keyResolver", defaultKeyResolver);
63
+ }
64
+
65
+ defaultTtl() {
66
+ return this.settings.get<Ttl>("cache.ttl");
67
+ }
68
+
69
+ calculateTTL(result?: any, currentTtl?: Ttl): number {
70
+ const ttl = currentTtl === undefined ? this.defaultTtl() : currentTtl;
71
+
72
+ return isFunction(ttl) ? ttl(result) : ttl;
73
+ }
74
+
75
+ ttl(key: string) {
76
+ if (this.cache && "store" in this.cache && this.cache.store.ttl) {
77
+ return this.cache.store.ttl(key);
78
+ }
79
+
80
+ return Promise.resolve();
81
+ }
82
+
83
+ wrap<T>(key: string, fetch: () => Promise<T>, ttl?: number): Promise<T> {
84
+ if (!this.cache) {
85
+ return fetch();
86
+ }
87
+
88
+ return this.cache?.wrap<T>(key, fetch, ttl);
89
+ }
90
+
91
+ get<T>(key: string, options: JsonDeserializerOptions = {}): Promise<T | undefined> {
92
+ return Promise.resolve(deserialize(this.cache?.get<T>(key), options));
93
+ }
94
+
95
+ async set<T>(key: string, value: any, options?: CachingConfig<T>): Promise<T | undefined> {
96
+ await this.cache?.set(key, value, options?.ttl);
97
+ return;
98
+ }
99
+
100
+ async getCachedObject(key: string) {
101
+ try {
102
+ return await this.get<PlatformCachedObject>(key);
103
+ } catch (er) {
104
+ this.logger.error({
105
+ event: "CACHE_ERROR",
106
+ method: "getCachedObject",
107
+ error: er
108
+ });
109
+ }
110
+ }
111
+
112
+ async setCachedObject(key: string, data: any, opts: {ttl: number} & Record<string, any>) {
113
+ try {
114
+ await this.set<PlatformCachedObject>(
115
+ key,
116
+ {
117
+ ...opts,
118
+ data: JSON.stringify(data)
119
+ },
120
+ {
121
+ ttl: opts.ttl
122
+ }
123
+ );
124
+ } catch (er) {
125
+ this.logger.error({
126
+ event: "CACHE_ERROR",
127
+ method: "setCachedObject",
128
+ error: er
129
+ });
130
+ }
131
+ }
132
+
133
+ async del(key: string): Promise<void> {
134
+ await this.cache?.del(key);
135
+ }
136
+
137
+ async reset(): Promise<void> {
138
+ // @ts-ignore
139
+ await this.cache?.reset();
140
+ }
141
+
142
+ keys(...args: any[]): Promise<string[]> {
143
+ if (this.cache && "store" in this.cache && this.cache.store.keys) {
144
+ return this.cache.store.keys(...args);
145
+ }
146
+
147
+ return Promise.resolve([]);
148
+ }
149
+
150
+ async deleteKeys(patterns: string): Promise<string[]> {
151
+ const keys = await this.keys(patterns);
152
+
153
+ await Promise.all(keys.map((key: string) => this.del(key)));
154
+
155
+ return keys;
156
+ }
157
+
158
+ /**
159
+ * Use micromatch instead native patterns. Use this method if the native store method doesn't support glob patterns
160
+ * @param patterns
161
+ */
162
+ async getMatchingKeys(patterns: string): Promise<string[]> {
163
+ const [keys, {default: micromatch}] = await Promise.all([this.keys(), import("micromatch")]);
164
+
165
+ return micromatch(keys, patterns);
166
+ }
167
+
168
+ async deleteMatchingKeys(patterns: string): Promise<string[]> {
169
+ const keys = await this.getMatchingKeys(patterns);
170
+
171
+ await Promise.all(keys.map((key: string) => this.del(key)));
172
+
173
+ return keys;
174
+ }
175
+
176
+ refresh(callback: () => Promise<any> | any) {
177
+ return storage.run({forceRefresh: true}, callback);
178
+ }
179
+
180
+ isForceRefresh() {
181
+ return !!storage.getStore()?.forceRefresh;
182
+ }
183
+
184
+ protected async createCacheManager(settings: PlatformCacheSettings) {
185
+ const {caches, store = "memory", ttl, ...props} = settings;
186
+
187
+ const {multiCaching, caching} = await import("cache-manager");
188
+
189
+ return caches?.length
190
+ ? multiCaching(caches)
191
+ : caching(this.mapStore(store), {
192
+ ...props,
193
+ ttl
194
+ });
195
+ }
196
+
197
+ private mapStore(store: "memory" | Function | {create: Function}): any {
198
+ if (!isString(store) && "create" in store) {
199
+ return store.create;
200
+ }
201
+
202
+ return store;
203
+ }
204
+ }
@@ -0,0 +1,13 @@
1
+ import {nameOf, Type} from "@tsed/core";
2
+ import {getInterceptorOptions} from "@tsed/di";
3
+
4
+ import type {PlatformCacheOptions} from "../interfaces/PlatformCacheOptions.js";
5
+
6
+ export function getPrefix(target: Type<any>, propertyKey: string | symbol) {
7
+ const {prefix} = getInterceptorOptions<PlatformCacheOptions>(target, propertyKey);
8
+ if (prefix) {
9
+ return [prefix];
10
+ }
11
+
12
+ return [nameOf(target), propertyKey];
13
+ }
@@ -0,0 +1,6 @@
1
+ import {Store, Type} from "@tsed/core";
2
+ import {JsonEntityStore} from "@tsed/schema";
3
+
4
+ export function isEndpoint(target: Type<any>, propertyKey: string | symbol) {
5
+ return Store.fromMethod(target, propertyKey).has(JsonEntityStore);
6
+ }