@tsed/platform-cache 8.25.2 → 8.25.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/lib/esm/interceptors/PlatformCacheInterceptor.js +80 -39
- package/lib/esm/services/PlatformCache.js +16 -32
- package/lib/types/interceptors/PlatformCacheInterceptor.d.ts +8 -3
- package/lib/types/interfaces/PlatformCachedObject.d.ts +1 -1
- package/lib/types/services/PlatformCache.d.ts +3 -8
- package/package.json +7 -7
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { __decorate, __metadata } from "tslib";
|
|
2
1
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
2
|
import { isClass, isString, nameOf } from "@tsed/core";
|
|
4
|
-
import {
|
|
3
|
+
import { constant, context, DIContext, inject, interceptor, logger, runInContext } from "@tsed/di";
|
|
5
4
|
import { deserialize, serialize } from "@tsed/json-mapper";
|
|
6
|
-
import { Logger } from "@tsed/logger";
|
|
7
5
|
import { PlatformCache } from "../services/PlatformCache.js";
|
|
8
6
|
import { getPrefix } from "../utils/getPrefix.js";
|
|
9
7
|
import { isEndpoint } from "../utils/isEndpoint.js";
|
|
@@ -20,7 +18,19 @@ const cleanHeaders = (headers, blacklist) => {
|
|
|
20
18
|
/**
|
|
21
19
|
* @platform
|
|
22
20
|
*/
|
|
23
|
-
|
|
21
|
+
export class PlatformCacheInterceptor {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.cache = inject(PlatformCache);
|
|
24
|
+
this.prefix = constant("cache.prefix", "");
|
|
25
|
+
this.blacklist = constant("cache.ignoreHeaders", [
|
|
26
|
+
"content-length",
|
|
27
|
+
"x-request-id",
|
|
28
|
+
"cache-control",
|
|
29
|
+
"vary",
|
|
30
|
+
"content-encoding"
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
static #refreshLocks = new Map();
|
|
24
34
|
intercept(context, next) {
|
|
25
35
|
if (this.cache.disabled()) {
|
|
26
36
|
return next();
|
|
@@ -30,14 +40,24 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
30
40
|
}
|
|
31
41
|
return this.cacheResponse(context, next);
|
|
32
42
|
}
|
|
33
|
-
async canRefreshInBackground(key, { refreshThreshold, ttl }, next, $ctx) {
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
async canRefreshInBackground(key, { refreshThreshold, ttl, cachedTTL }, next, $ctx) {
|
|
44
|
+
if (!refreshThreshold) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const release = await this.acquireRefreshLock(key);
|
|
48
|
+
try {
|
|
49
|
+
const inQueue = await this.hasKeyInQueue(key);
|
|
50
|
+
const inCooldown = await this.hasRefreshCooldown(key);
|
|
51
|
+
if (inQueue || inCooldown) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
36
54
|
await this.addKeyToQueue(key);
|
|
37
55
|
const currentTTL = await this.cache.ttl(key);
|
|
38
|
-
const calculatedTTL =
|
|
56
|
+
const calculatedTTL = cachedTTL ?? (typeof ttl === "number" ? ttl : undefined);
|
|
57
|
+
const refreshThresholdWithJitter = this.getRefreshThresholdWithJitter(key, refreshThreshold);
|
|
39
58
|
try {
|
|
40
|
-
if (currentTTL === undefined || currentTTL < calculatedTTL -
|
|
59
|
+
if (calculatedTTL !== undefined && (currentTTL === undefined || currentTTL < calculatedTTL - refreshThresholdWithJitter)) {
|
|
60
|
+
await this.addRefreshCooldown(key, refreshThresholdWithJitter);
|
|
41
61
|
if ($ctx) {
|
|
42
62
|
await runInContext($ctx, () => next());
|
|
43
63
|
}
|
|
@@ -50,6 +70,9 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
50
70
|
await this.deleteKeyFromQueue(key);
|
|
51
71
|
}
|
|
52
72
|
}
|
|
73
|
+
finally {
|
|
74
|
+
release();
|
|
75
|
+
}
|
|
53
76
|
}
|
|
54
77
|
async cacheMethod(context, next) {
|
|
55
78
|
const { key, type, ttl, collectionType, refreshThreshold, args, canCache, $ctx, byPass } = this.getOptions(context, false);
|
|
@@ -60,7 +83,7 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
60
83
|
if (!canCache || (canCache && canCache(result))) {
|
|
61
84
|
const calculatedTTL = this.cache.calculateTTL(result, ttl);
|
|
62
85
|
const data = serialize(result, { type, collectionType });
|
|
63
|
-
this.cache.setCachedObject(key, data, { args, ttl: calculatedTTL });
|
|
86
|
+
this.cache.setCachedObject(key, data, { args, ...(calculatedTTL !== undefined && { ttl: calculatedTTL }) });
|
|
64
87
|
}
|
|
65
88
|
};
|
|
66
89
|
const cachedObject = await this.cache.getCachedObject(key);
|
|
@@ -71,10 +94,10 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
71
94
|
}
|
|
72
95
|
return result;
|
|
73
96
|
}
|
|
74
|
-
this.canRefreshInBackground(key, { refreshThreshold, ttl }, async () => {
|
|
97
|
+
this.canRefreshInBackground(key, { refreshThreshold, ttl, cachedTTL: cachedObject.ttl }, async () => {
|
|
75
98
|
const result = await next();
|
|
76
99
|
await set(result);
|
|
77
|
-
}, $ctx).catch((er) =>
|
|
100
|
+
}, $ctx).catch((er) => logger().error({
|
|
78
101
|
event: "CACHE_ERROR",
|
|
79
102
|
method: "cacheMethod",
|
|
80
103
|
concerned_key: key,
|
|
@@ -102,17 +125,19 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
102
125
|
}
|
|
103
126
|
const result = await next();
|
|
104
127
|
const calculatedTTL = this.cache.calculateTTL(result, ttl);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
if (calculatedTTL !== undefined) {
|
|
129
|
+
currentCtx.response?.setHeaders({
|
|
130
|
+
"cache-control": `max-age=${calculatedTTL}`
|
|
131
|
+
});
|
|
132
|
+
}
|
|
108
133
|
currentCtx.response?.onEnd(() => {
|
|
109
134
|
if (!useCache) {
|
|
110
135
|
return;
|
|
111
136
|
}
|
|
112
137
|
this.cache.setCachedObject(key, currentCtx.response.getBody(), {
|
|
113
|
-
ttl: calculatedTTL,
|
|
114
138
|
args,
|
|
115
|
-
headers: cleanHeaders(currentCtx.response.getHeaders(), this.blacklist)
|
|
139
|
+
headers: cleanHeaders(currentCtx.response.getHeaders(), this.blacklist),
|
|
140
|
+
...(calculatedTTL !== undefined && { ttl: calculatedTTL })
|
|
116
141
|
});
|
|
117
142
|
});
|
|
118
143
|
return result;
|
|
@@ -177,6 +202,41 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
177
202
|
async deleteKeyFromQueue(key) {
|
|
178
203
|
await this.cache.del(`$$queue:${key}`);
|
|
179
204
|
}
|
|
205
|
+
async acquireRefreshLock(key) {
|
|
206
|
+
for (;;) {
|
|
207
|
+
const lock = PlatformCacheInterceptor.#refreshLocks.get(key);
|
|
208
|
+
if (!lock) {
|
|
209
|
+
let releaseFn;
|
|
210
|
+
const currentLock = new Promise((resolve) => {
|
|
211
|
+
releaseFn = resolve;
|
|
212
|
+
});
|
|
213
|
+
PlatformCacheInterceptor.#refreshLocks.set(key, currentLock);
|
|
214
|
+
return () => {
|
|
215
|
+
if (PlatformCacheInterceptor.#refreshLocks.get(key) === currentLock) {
|
|
216
|
+
PlatformCacheInterceptor.#refreshLocks.delete(key);
|
|
217
|
+
}
|
|
218
|
+
releaseFn?.();
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
await lock;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
getRefreshCooldownKey(key) {
|
|
225
|
+
return `$$refresh-cooldown:${key}`;
|
|
226
|
+
}
|
|
227
|
+
getRefreshThresholdWithJitter(key, refreshThreshold) {
|
|
228
|
+
const maxJitter = Math.max(1, Math.floor(refreshThreshold * 0.1));
|
|
229
|
+
const hash = key.split("").reduce((acc, char) => (acc * 31 + char.charCodeAt(0)) | 0, 0);
|
|
230
|
+
const jitter = Math.abs(hash) % (maxJitter + 1);
|
|
231
|
+
return Math.max(1, refreshThreshold - jitter);
|
|
232
|
+
}
|
|
233
|
+
async hasRefreshCooldown(key) {
|
|
234
|
+
return !!(await this.cache.get(this.getRefreshCooldownKey(key)));
|
|
235
|
+
}
|
|
236
|
+
async addRefreshCooldown(key, refreshThreshold) {
|
|
237
|
+
const cooldownTTL = Math.max(1, Math.floor(refreshThreshold / 2));
|
|
238
|
+
await this.cache.set(this.getRefreshCooldownKey(key), true, { ttl: cooldownTTL });
|
|
239
|
+
}
|
|
180
240
|
sendResponse(cachedObject) {
|
|
181
241
|
const { headers, ttl } = cachedObject;
|
|
182
242
|
const $ctx = context();
|
|
@@ -191,31 +251,12 @@ let PlatformCacheInterceptor = class PlatformCacheInterceptor {
|
|
|
191
251
|
.setHeaders({
|
|
192
252
|
...headers,
|
|
193
253
|
"x-cached": "true",
|
|
194
|
-
"cache-control": `max-age=${ttl}`
|
|
254
|
+
...(ttl !== undefined && { "cache-control": `max-age=${ttl}` })
|
|
195
255
|
})
|
|
196
256
|
.body(data);
|
|
197
257
|
return cachedObject.data;
|
|
198
258
|
}
|
|
199
259
|
return cachedObject.data;
|
|
200
260
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
Inject(),
|
|
204
|
-
__metadata("design:type", PlatformCache)
|
|
205
|
-
], PlatformCacheInterceptor.prototype, "cache", void 0);
|
|
206
|
-
__decorate([
|
|
207
|
-
Inject(),
|
|
208
|
-
__metadata("design:type", Logger)
|
|
209
|
-
], PlatformCacheInterceptor.prototype, "logger", void 0);
|
|
210
|
-
__decorate([
|
|
211
|
-
Constant("cache.prefix", ""),
|
|
212
|
-
__metadata("design:type", String)
|
|
213
|
-
], PlatformCacheInterceptor.prototype, "prefix", void 0);
|
|
214
|
-
__decorate([
|
|
215
|
-
Constant("cache.ignoreHeaders", ["content-length", "x-request-id", "cache-control", "vary", "content-encoding"]),
|
|
216
|
-
__metadata("design:type", Array)
|
|
217
|
-
], PlatformCacheInterceptor.prototype, "blacklist", void 0);
|
|
218
|
-
PlatformCacheInterceptor = __decorate([
|
|
219
|
-
Interceptor()
|
|
220
|
-
], PlatformCacheInterceptor);
|
|
221
|
-
export { PlatformCacheInterceptor };
|
|
261
|
+
}
|
|
262
|
+
interceptor(PlatformCacheInterceptor);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { __decorate, __metadata } from "tslib";
|
|
2
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
2
|
import { isClass, isFunction, isString } from "@tsed/core";
|
|
4
|
-
import {
|
|
3
|
+
import { constant, injectable, logger, ProviderScope, ProviderType } from "@tsed/di";
|
|
4
|
+
import { $asyncEmit } from "@tsed/hooks";
|
|
5
5
|
import { deserialize, serialize } from "@tsed/json-mapper";
|
|
6
|
-
import { Logger } from "@tsed/logger";
|
|
7
6
|
import { getPrefix } from "../utils/getPrefix.js";
|
|
8
7
|
const defaultKeyResolver = (args) => {
|
|
9
8
|
return args.map((arg) => (isClass(arg) ? JSON.stringify(serialize(arg)) : arg)).join(":");
|
|
@@ -12,16 +11,16 @@ const storage = new AsyncLocalStorage();
|
|
|
12
11
|
/**
|
|
13
12
|
* @platform
|
|
14
13
|
*/
|
|
15
|
-
|
|
14
|
+
export class PlatformCache {
|
|
16
15
|
#cache;
|
|
17
16
|
get cache() {
|
|
18
17
|
return this.#cache;
|
|
19
18
|
}
|
|
20
19
|
async $onInit() {
|
|
21
|
-
const settings =
|
|
20
|
+
const settings = constant("cache");
|
|
22
21
|
if (settings) {
|
|
23
22
|
this.#cache = await this.createCacheManager(settings);
|
|
24
|
-
await
|
|
23
|
+
await $asyncEmit("$onCreateCacheManager", [this.#cache]);
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
getKeysOf(target, propertyKey) {
|
|
@@ -29,13 +28,13 @@ let PlatformCache = class PlatformCache {
|
|
|
29
28
|
return this.keys(`${prefix.join(":")}:*`);
|
|
30
29
|
}
|
|
31
30
|
disabled() {
|
|
32
|
-
return !
|
|
31
|
+
return !constant("cache");
|
|
33
32
|
}
|
|
34
33
|
defaultKeyResolver() {
|
|
35
|
-
return
|
|
34
|
+
return constant("cache.keyResolver", defaultKeyResolver);
|
|
36
35
|
}
|
|
37
36
|
defaultTtl() {
|
|
38
|
-
return
|
|
37
|
+
return constant("cache.ttl");
|
|
39
38
|
}
|
|
40
39
|
calculateTTL(result, currentTtl) {
|
|
41
40
|
const ttl = currentTtl === undefined ? this.defaultTtl() : currentTtl;
|
|
@@ -65,7 +64,7 @@ let PlatformCache = class PlatformCache {
|
|
|
65
64
|
return await this.get(key);
|
|
66
65
|
}
|
|
67
66
|
catch (er) {
|
|
68
|
-
|
|
67
|
+
logger().error({
|
|
69
68
|
event: "CACHE_ERROR",
|
|
70
69
|
method: "getCachedObject",
|
|
71
70
|
error: er
|
|
@@ -74,15 +73,15 @@ let PlatformCache = class PlatformCache {
|
|
|
74
73
|
}
|
|
75
74
|
async setCachedObject(key, data, opts) {
|
|
76
75
|
try {
|
|
76
|
+
const { ttl, ...rest } = opts;
|
|
77
77
|
await this.set(key, {
|
|
78
|
-
...
|
|
78
|
+
...rest,
|
|
79
|
+
...(ttl !== undefined && { ttl }),
|
|
79
80
|
data: JSON.stringify(data)
|
|
80
|
-
}, {
|
|
81
|
-
ttl: opts.ttl
|
|
82
|
-
});
|
|
81
|
+
}, ttl !== undefined ? { ttl } : undefined);
|
|
83
82
|
}
|
|
84
83
|
catch (er) {
|
|
85
|
-
|
|
84
|
+
logger().error({
|
|
86
85
|
event: "CACHE_ERROR",
|
|
87
86
|
method: "setCachedObject",
|
|
88
87
|
error: er
|
|
@@ -142,20 +141,5 @@ let PlatformCache = class PlatformCache {
|
|
|
142
141
|
}
|
|
143
142
|
return store;
|
|
144
143
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
Configuration(),
|
|
148
|
-
__metadata("design:type", Object)
|
|
149
|
-
], PlatformCache.prototype, "settings", void 0);
|
|
150
|
-
__decorate([
|
|
151
|
-
Inject(),
|
|
152
|
-
__metadata("design:type", InjectorService)
|
|
153
|
-
], PlatformCache.prototype, "injector", void 0);
|
|
154
|
-
__decorate([
|
|
155
|
-
Inject(),
|
|
156
|
-
__metadata("design:type", Logger)
|
|
157
|
-
], PlatformCache.prototype, "logger", void 0);
|
|
158
|
-
PlatformCache = __decorate([
|
|
159
|
-
Module()
|
|
160
|
-
], PlatformCache);
|
|
161
|
-
export { PlatformCache };
|
|
144
|
+
}
|
|
145
|
+
injectable(PlatformCache).type(ProviderType.MODULE).scope(ProviderScope.SINGLETON);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type BaseContext, DIContext, InterceptorContext, InterceptorMethods, InterceptorNext } from "@tsed/di";
|
|
2
|
-
import { Logger } from "@tsed/logger";
|
|
3
2
|
import { PlatformCachedObject } from "../interfaces/PlatformCachedObject.js";
|
|
4
3
|
import { PlatformCacheOptions } from "../interfaces/PlatformCacheOptions.js";
|
|
5
4
|
import { PlatformCache } from "../services/PlatformCache.js";
|
|
@@ -23,14 +22,15 @@ type Context = DIContext & {
|
|
|
23
22
|
* @platform
|
|
24
23
|
*/
|
|
25
24
|
export declare class PlatformCacheInterceptor implements InterceptorMethods {
|
|
25
|
+
#private;
|
|
26
26
|
protected cache: PlatformCache;
|
|
27
|
-
protected logger: Logger;
|
|
28
27
|
protected prefix: string;
|
|
29
28
|
protected blacklist: string[];
|
|
30
29
|
intercept(context: InterceptorContext<any, PlatformCacheOptions>, next: InterceptorNext): unknown;
|
|
31
|
-
canRefreshInBackground(key: string, { refreshThreshold, ttl }: {
|
|
30
|
+
canRefreshInBackground(key: string, { refreshThreshold, ttl, cachedTTL }: {
|
|
32
31
|
refreshThreshold?: number;
|
|
33
32
|
ttl: any;
|
|
33
|
+
cachedTTL?: number;
|
|
34
34
|
}, next: Function, $ctx?: Context): Promise<void>;
|
|
35
35
|
cacheMethod(context: InterceptorContext<any, PlatformCacheOptions>, next: InterceptorNext): Promise<any>;
|
|
36
36
|
cacheResponse(interceptorContext: InterceptorContext<any, PlatformCacheOptions>, next: InterceptorNext): Promise<any>;
|
|
@@ -54,6 +54,11 @@ export declare class PlatformCacheInterceptor implements InterceptorMethods {
|
|
|
54
54
|
protected hasKeyInQueue(key: string): Promise<boolean>;
|
|
55
55
|
protected addKeyToQueue(key: string): Promise<void>;
|
|
56
56
|
protected deleteKeyFromQueue(key: string): Promise<void>;
|
|
57
|
+
protected acquireRefreshLock(key: string): Promise<() => void>;
|
|
58
|
+
protected getRefreshCooldownKey(key: string): string;
|
|
59
|
+
protected getRefreshThresholdWithJitter(key: string, refreshThreshold: number): number;
|
|
60
|
+
protected hasRefreshCooldown(key: string): Promise<boolean>;
|
|
61
|
+
protected addRefreshCooldown(key: string, refreshThreshold: number): Promise<void>;
|
|
57
62
|
protected sendResponse(cachedObject: PlatformCachedObject): any;
|
|
58
63
|
}
|
|
59
64
|
export {};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Type } from "@tsed/core";
|
|
2
|
-
import { Configuration, InjectorService } from "@tsed/di";
|
|
3
2
|
import { JsonDeserializerOptions } from "@tsed/json-mapper";
|
|
4
|
-
import { Logger } from "@tsed/logger";
|
|
5
3
|
import type { Cache, CachingConfig, MultiCache } from "cache-manager";
|
|
6
4
|
import { PlatformCacheSettings } from "../interfaces/interfaces.js";
|
|
7
5
|
import { PlatformCachedObject } from "../interfaces/PlatformCachedObject.js";
|
|
@@ -12,23 +10,20 @@ export type Ttl = number | ((result: any) => number);
|
|
|
12
10
|
*/
|
|
13
11
|
export declare class PlatformCache {
|
|
14
12
|
#private;
|
|
15
|
-
protected settings: Configuration;
|
|
16
|
-
protected injector: InjectorService;
|
|
17
|
-
protected logger: Logger;
|
|
18
13
|
get cache(): CacheManager | undefined;
|
|
19
14
|
$onInit(): Promise<void>;
|
|
20
15
|
getKeysOf(target: Type<any>, propertyKey: string | symbol): Promise<string[]>;
|
|
21
16
|
disabled(): boolean;
|
|
22
17
|
defaultKeyResolver(): (args: any[], ctx?: any) => string;
|
|
23
|
-
defaultTtl(): Ttl;
|
|
24
|
-
calculateTTL(result?: any, currentTtl?: Ttl): number;
|
|
18
|
+
defaultTtl(): Ttl | undefined;
|
|
19
|
+
calculateTTL(result?: any, currentTtl?: Ttl): number | undefined;
|
|
25
20
|
ttl(key: string): Promise<void> | Promise<number>;
|
|
26
21
|
wrap<T>(key: string, fetch: () => Promise<T>, ttl?: number): Promise<T>;
|
|
27
22
|
get<T>(key: string, options?: JsonDeserializerOptions): Promise<T | undefined>;
|
|
28
23
|
set<T>(key: string, value: any, options?: CachingConfig<T>): Promise<T | undefined>;
|
|
29
24
|
getCachedObject(key: string): Promise<PlatformCachedObject | undefined>;
|
|
30
25
|
setCachedObject(key: string, data: any, opts: {
|
|
31
|
-
ttl
|
|
26
|
+
ttl?: number;
|
|
32
27
|
} & Record<string, any>): Promise<void>;
|
|
33
28
|
del(key: string): Promise<void>;
|
|
34
29
|
reset(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tsed/platform-cache",
|
|
3
3
|
"description": "Platform cache module for Ts.ED Framework",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.25.
|
|
5
|
+
"version": "8.25.4",
|
|
6
6
|
"source": "./src/index.ts",
|
|
7
7
|
"main": "./lib/esm/index.js",
|
|
8
8
|
"module": "./lib/esm/index.js",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"tslib": "2.7.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@tsed/barrels": "8.25.
|
|
32
|
-
"@tsed/core": "8.25.
|
|
33
|
-
"@tsed/di": "8.25.
|
|
34
|
-
"@tsed/json-mapper": "8.25.
|
|
35
|
-
"@tsed/schema": "8.25.
|
|
36
|
-
"@tsed/typescript": "8.25.
|
|
31
|
+
"@tsed/barrels": "8.25.4",
|
|
32
|
+
"@tsed/core": "8.25.4",
|
|
33
|
+
"@tsed/di": "8.25.4",
|
|
34
|
+
"@tsed/json-mapper": "8.25.4",
|
|
35
|
+
"@tsed/schema": "8.25.4",
|
|
36
|
+
"@tsed/typescript": "8.25.4",
|
|
37
37
|
"@types/micromatch": "^4.0.9",
|
|
38
38
|
"eslint": "9.12.0",
|
|
39
39
|
"typescript": "5.9.3",
|