@travetto/cache 2.1.5 → 2.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/README.md +15 -15
- package/package.json +4 -4
- package/src/decorator.ts +9 -5
- package/src/service.ts +11 -11
- package/src/util.ts +4 -4
- package/support/transformer.cache.ts +5 -4
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ npm install @travetto/model-{provider}
|
|
|
19
19
|
|
|
20
20
|
Currently, the following are packages that provide [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11):
|
|
21
21
|
|
|
22
|
-
* [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") - [FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#
|
|
23
|
-
* [DynamoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-dynamodb#readme "DynamoDB backing for the travetto model module.") - [DynamoDBModelService](https://github.com/travetto/travetto/tree/main/module/model-dynamodb/src/service.ts#
|
|
24
|
-
* [Elasticsearch Model Source](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch#readme "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.") - [ElasticsearchModelService](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/service.ts#
|
|
25
|
-
* [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module.") - [MongoModelService](https://github.com/travetto/travetto/tree/main/module/model-mongo/src/service.ts#
|
|
22
|
+
* [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") - [FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L49), [MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L54)
|
|
23
|
+
* [DynamoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-dynamodb#readme "DynamoDB backing for the travetto model module.") - [DynamoDBModelService](https://github.com/travetto/travetto/tree/main/module/model-dynamodb/src/service.ts#L58)
|
|
24
|
+
* [Elasticsearch Model Source](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch#readme "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.") - [ElasticsearchModelService](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/service.ts#L42)
|
|
25
|
+
* [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module.") - [MongoModelService](https://github.com/travetto/travetto/tree/main/module/model-mongo/src/service.ts#L49)
|
|
26
26
|
* [Redis Model Support](https://github.com/travetto/travetto/tree/main/module/model-redis#readme "Redis backing for the travetto model module.") - [RedisModelService](https://github.com/travetto/travetto/tree/main/module/model-redis/src/service.ts#L26)
|
|
27
|
-
* [S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.") - [S3ModelService](https://github.com/travetto/travetto/tree/main/module/model-s3/src/service.ts#
|
|
28
|
-
* [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") - [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#
|
|
27
|
+
* [S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.") - [S3ModelService](https://github.com/travetto/travetto/tree/main/module/model-s3/src/service.ts#L34)
|
|
28
|
+
* [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") - [SQLModelService](https://github.com/travetto/travetto/tree/main/module/model-sql/src/service.ts#L38)
|
|
29
29
|
|
|
30
30
|
## Decorators
|
|
31
31
|
The caching framework provides method decorators that enables simple use cases. One of the requirements to use the caching decorators is that the method arguments, and return values need to be serializable into [JSON](https://www.json.org). Any other data types are not currently supported and would require either manual usage of the caching services directly, or specification of serialization/deserialization routines in the cache config.
|
|
@@ -39,10 +39,10 @@ Additionally, to use the decorators you will need to have a [CacheService](https
|
|
|
39
39
|
import { MemoryModelService } from '@travetto/model';
|
|
40
40
|
import { Cache, CacheService } from '@travetto/cache';
|
|
41
41
|
|
|
42
|
-
async function request(url: string) {
|
|
43
|
-
let value;
|
|
42
|
+
async function request(url: string): Promise<string> {
|
|
43
|
+
let value: string;
|
|
44
44
|
// ...fetch content
|
|
45
|
-
return value
|
|
45
|
+
return value!;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export class Worker {
|
|
@@ -52,7 +52,7 @@ export class Worker {
|
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
@Cache('myCache', '1s')
|
|
55
|
-
async calculateExpensiveResult(expression: string) {
|
|
55
|
+
async calculateExpensiveResult(expression: string): Promise<string> {
|
|
56
56
|
const value = await request(`https://google.com?q=${expression}`);
|
|
57
57
|
return value;
|
|
58
58
|
}
|
|
@@ -74,9 +74,9 @@ The [@Cache](https://github.com/travetto/travetto/tree/main/module/cache/src/dec
|
|
|
74
74
|
* `serialize` the function to execute before storing a cacheable value. This allows for any custom data modification needed to persist as a string properly.
|
|
75
75
|
* `reinstate` the function to execute on return of a cached value. This allows for any necessary operations to conform to expected output (e.g. re-establishing class instances, etc.). This method should not be used often, as the return values of the methods should naturally serialize to/from `JSON` and the values should be usable either way.
|
|
76
76
|
|
|
77
|
-
### [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#
|
|
77
|
+
### [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L41)
|
|
78
78
|
|
|
79
|
-
Additionally, there is support for planned eviction via the [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#
|
|
79
|
+
Additionally, there is support for planned eviction via the [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L41) decorator. On successful execution of a method with this decorator, the matching keySpace/key value will be evicted from the cache. This requires coordination between multiple methods, to use the same `keySpace` and `key` to compute the expected key.
|
|
80
80
|
|
|
81
81
|
**Code: Using decorators to cache/evict user access**
|
|
82
82
|
```typescript
|
|
@@ -95,17 +95,17 @@ export class UserService {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
@Cache('myCache', '5m', { keySpace: 'user.id' })
|
|
98
|
-
async getUser(id: string) {
|
|
98
|
+
async getUser(id: string): Promise<User> {
|
|
99
99
|
return this.database.lookupUser(id);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
@EvictCache('myCache', { keySpace: 'user.id', params: user => [user.id] })
|
|
103
|
-
async updateUser(user: User) {
|
|
103
|
+
async updateUser(user: User): Promise<void> {
|
|
104
104
|
this.database.updateUser(user);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
@EvictCache('myCache', { keySpace: 'user.id' })
|
|
108
|
-
async deleteUser(userId: string) {
|
|
108
|
+
async deleteUser(userId: string): Promise<void> {
|
|
109
109
|
this.database.deleteUser(userId);
|
|
110
110
|
}
|
|
111
111
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cache",
|
|
3
3
|
"displayName": "Caching",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Caching functionality with decorators for declarative use.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typescript",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"directory": "module/cache"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/transformer": "^2.
|
|
31
|
-
"@travetto/di": "^2.
|
|
32
|
-
"@travetto/model": "^2.
|
|
30
|
+
"@travetto/transformer": "^2.2.0",
|
|
31
|
+
"@travetto/di": "^2.2.0",
|
|
32
|
+
"@travetto/model": "^2.2.0"
|
|
33
33
|
},
|
|
34
34
|
"docDependencies": {
|
|
35
35
|
"@travetto/model-dynamodb": true,
|
package/src/decorator.ts
CHANGED
|
@@ -13,7 +13,9 @@ import { CacheAware, CacheConfigⲐ, EvictConfigⲐ } from './internal/types';
|
|
|
13
13
|
*/
|
|
14
14
|
export function Cache<F extends string, U extends Record<F, CacheService>>(field: F, maxAge: number | TimeSpan, config?: Omit<CacheConfig, 'maxAge'>): MethodDecorator;
|
|
15
15
|
export function Cache<F extends string, U extends Record<F, CacheService>>(field: F, cfg?: CacheConfig): MethodDecorator;
|
|
16
|
-
export function Cache<F extends string, U extends Record<F, CacheService>>(
|
|
16
|
+
export function Cache<F extends string, U extends Record<F, CacheService>>(
|
|
17
|
+
field: F, cfg?: number | TimeSpan | CacheConfig, config: Exclude<CacheConfig, 'maxAge'> = {}
|
|
18
|
+
): MethodDecorator {
|
|
17
19
|
if (cfg !== undefined) {
|
|
18
20
|
if (typeof cfg === 'string' || typeof cfg === 'number') {
|
|
19
21
|
config.maxAge = Util.timeToMs(cfg);
|
|
@@ -21,10 +23,12 @@ export function Cache<F extends string, U extends Record<F, CacheService>>(field
|
|
|
21
23
|
config = cfg;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
27
|
+
const dec = function <R extends Promise<unknown>>(target: U & CacheAware, propertyKey: string, descriptor: MethodDescriptor<R>): void {
|
|
25
28
|
config.keySpace ??= `${target.constructor.name}.${propertyKey}`;
|
|
26
|
-
(target[CacheConfigⲐ] ??= {})[propertyKey] = config
|
|
27
|
-
};
|
|
29
|
+
(target[CacheConfigⲐ] ??= {})[propertyKey] = config;
|
|
30
|
+
} as MethodDecorator;
|
|
31
|
+
return dec;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -35,7 +39,7 @@ export function Cache<F extends string, U extends Record<F, CacheService>>(field
|
|
|
35
39
|
* @augments `@trv:cache/Evict`
|
|
36
40
|
*/
|
|
37
41
|
export function EvictCache<F extends string, U extends Record<F, CacheService>>(field: F, config: CoreCacheConfig = {}) {
|
|
38
|
-
return function <R extends Promise<unknown>>(target: U & CacheAware, propertyKey: string, descriptor: MethodDescriptor<R>) {
|
|
42
|
+
return function <R extends Promise<unknown>>(target: U & CacheAware, propertyKey: string, descriptor: MethodDescriptor<R>): void {
|
|
39
43
|
config.keySpace ??= `${target.constructor.name}.${propertyKey}`;
|
|
40
44
|
(target[EvictConfigⲐ] ??= {})[propertyKey] = config;
|
|
41
45
|
};
|
package/src/service.ts
CHANGED
|
@@ -34,18 +34,18 @@ export class CacheService {
|
|
|
34
34
|
this.#modelService = modelService;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async postConstruct() {
|
|
37
|
+
async postConstruct(): Promise<void> {
|
|
38
38
|
if (isStorageSupported(this.#modelService) && EnvUtil.isDynamic()) {
|
|
39
39
|
await this.#modelService.createModel?.(CacheRecord);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Get an item
|
|
44
|
+
* Get an item throwing an error if missing or expired. Allows for extending expiry based on access
|
|
45
45
|
* @param id Record identifier
|
|
46
|
-
* @param extendOnAccess should the expiry be extended on
|
|
46
|
+
* @param extendOnAccess should the expiry be extended on access
|
|
47
47
|
*/
|
|
48
|
-
async get(id: string, extendOnAccess = true) {
|
|
48
|
+
async get(id: string, extendOnAccess = true): Promise<unknown> {
|
|
49
49
|
const { expiresAt, issuedAt } = await this.#modelService.get(CacheRecord, id);
|
|
50
50
|
|
|
51
51
|
const delta = expiresAt.getTime() - Date.now();
|
|
@@ -77,7 +77,7 @@ export class CacheService {
|
|
|
77
77
|
* @param maxAge Max age in ms
|
|
78
78
|
* @returns
|
|
79
79
|
*/
|
|
80
|
-
async set(id: string, entry: unknown, maxAge?: number) {
|
|
80
|
+
async set(id: string, entry: unknown, maxAge?: number): Promise<unknown> {
|
|
81
81
|
const entryText = CacheUtil.toSafeJSON(entry);
|
|
82
82
|
|
|
83
83
|
const store = await this.#modelService.upsert(CacheRecord,
|
|
@@ -96,14 +96,14 @@ export class CacheService {
|
|
|
96
96
|
* Remove an item by id
|
|
97
97
|
* @param id
|
|
98
98
|
*/
|
|
99
|
-
async delete(id: string) {
|
|
99
|
+
async delete(id: string): Promise<void> {
|
|
100
100
|
await this.#modelService.delete(CacheRecord, id);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Purge the cache store of all data, if supported
|
|
105
105
|
*/
|
|
106
|
-
async purge() {
|
|
106
|
+
async purge(): Promise<void> {
|
|
107
107
|
if (isStorageSupported(this.#modelService) && this.#modelService.truncateModel) {
|
|
108
108
|
await this.#modelService.truncateModel(CacheRecord);
|
|
109
109
|
} else {
|
|
@@ -114,9 +114,9 @@ export class CacheService {
|
|
|
114
114
|
/**
|
|
115
115
|
* Get an item optionally, returning undefined if missing. Allows for extending expiry based on access
|
|
116
116
|
* @param id Record identifier
|
|
117
|
-
* @param extendOnAccess should the expiry be extended on
|
|
117
|
+
* @param extendOnAccess should the expiry be extended on access
|
|
118
118
|
*/
|
|
119
|
-
async getOptional(id: string, extendOnAccess = true) {
|
|
119
|
+
async getOptional(id: string, extendOnAccess = true): Promise<unknown | undefined> {
|
|
120
120
|
let res: unknown;
|
|
121
121
|
|
|
122
122
|
try {
|
|
@@ -137,7 +137,7 @@ export class CacheService {
|
|
|
137
137
|
* @param fn Function to execute
|
|
138
138
|
* @param params input parameters
|
|
139
139
|
*/
|
|
140
|
-
async cache(target: CacheAware, method: string, fn: Function, params: unknown[]) {
|
|
140
|
+
async cache(target: CacheAware, method: string, fn: Function, params: unknown[]): Promise<unknown | undefined> {
|
|
141
141
|
const config = target[CacheConfigⲐ]![method];
|
|
142
142
|
|
|
143
143
|
const id = CacheUtil.generateKey(config, params);
|
|
@@ -164,7 +164,7 @@ export class CacheService {
|
|
|
164
164
|
* @param fn Function to execute
|
|
165
165
|
* @param params Input params to the function
|
|
166
166
|
*/
|
|
167
|
-
async evict(target: CacheAware, method: string, fn: Function, params: unknown[]) {
|
|
167
|
+
async evict(target: CacheAware, method: string, fn: Function, params: unknown[]): Promise<unknown> {
|
|
168
168
|
const config = target[EvictConfigⲐ]![method];
|
|
169
169
|
const id = CacheUtil.generateKey(config, params);
|
|
170
170
|
const val = await fn.apply(target, params);
|
package/src/util.ts
CHANGED
|
@@ -13,9 +13,9 @@ export class CacheUtil {
|
|
|
13
13
|
* @param value The value to make safe for storage
|
|
14
14
|
* @param all Should functions and regex be included
|
|
15
15
|
*/
|
|
16
|
-
static toSafeJSON(value: unknown, all = false) {
|
|
16
|
+
static toSafeJSON(value: unknown, all = false): string {
|
|
17
17
|
const replacer = all ?
|
|
18
|
-
((key: string, val: unknown) => (
|
|
18
|
+
((key: string, val: unknown): unknown => (val && val instanceof RegExp) ? val.source : (Util.isFunction(val) ? val.toString() : val)) :
|
|
19
19
|
undefined;
|
|
20
20
|
|
|
21
21
|
return Buffer.from(JSON.stringify(value, replacer)).toString('base64');
|
|
@@ -25,14 +25,14 @@ export class CacheUtil {
|
|
|
25
25
|
* Read safe JSON back into an object
|
|
26
26
|
* @param value The value to read as safe JSON
|
|
27
27
|
*/
|
|
28
|
-
static fromSafeJSON(value: string | undefined) {
|
|
28
|
+
static fromSafeJSON(value: string | undefined): unknown {
|
|
29
29
|
return value ? JSON.parse(Buffer.from(value, 'base64').toString('utf8')) : undefined;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Generate key given config, cache source and input params
|
|
34
34
|
*/
|
|
35
|
-
static generateKey(config: CoreCacheConfig, params: unknown[]) {
|
|
35
|
+
static generateKey(config: CoreCacheConfig, params: unknown[]): string {
|
|
36
36
|
const input = config.params?.(params) ?? params;
|
|
37
37
|
const keyParams = config.key?.(...input) ?? input;
|
|
38
38
|
const key = `${config.keySpace!}_${this.toSafeJSON(keyParams)}`;
|
|
@@ -13,7 +13,7 @@ export class CacheTransformer {
|
|
|
13
13
|
* When `@Cache` and `@Evict` are present
|
|
14
14
|
*/
|
|
15
15
|
@OnMethod('Cache', 'Evict')
|
|
16
|
-
static instrumentCache(state: TransformerState, node: ts.MethodDeclaration, dm?: DecoratorMeta) {
|
|
16
|
+
static instrumentCache(state: TransformerState, node: ts.MethodDeclaration, dm?: DecoratorMeta): ts.MethodDeclaration {
|
|
17
17
|
|
|
18
18
|
const isCache = !!state.findDecorator(this, node, 'Cache');
|
|
19
19
|
const dec = dm?.dec;
|
|
@@ -21,7 +21,7 @@ export class CacheTransformer {
|
|
|
21
21
|
// If valid function
|
|
22
22
|
if (dec && ts.isCallExpression(dec.expression)) {
|
|
23
23
|
const params = dec.expression.arguments;
|
|
24
|
-
const
|
|
24
|
+
const mainExpression = params[0];
|
|
25
25
|
|
|
26
26
|
const op = isCache ? 'cache' : 'evict';
|
|
27
27
|
|
|
@@ -50,7 +50,7 @@ export class CacheTransformer {
|
|
|
50
50
|
state.factory.createReturnStatement(
|
|
51
51
|
state.factory.createCallExpression(
|
|
52
52
|
state.factory.createPropertyAccessExpression(
|
|
53
|
-
state.factory.createElementAccessExpression(state.factory.createThis(),
|
|
53
|
+
state.factory.createElementAccessExpression(state.factory.createThis(), mainExpression), op
|
|
54
54
|
),
|
|
55
55
|
undefined,
|
|
56
56
|
[
|
|
@@ -60,7 +60,8 @@ export class CacheTransformer {
|
|
|
60
60
|
state.factory.createArrayLiteralExpression([
|
|
61
61
|
state.factory.createSpreadElement(state.createIdentifier('arguments'))
|
|
62
62
|
])
|
|
63
|
-
]
|
|
63
|
+
]
|
|
64
|
+
)
|
|
64
65
|
)
|
|
65
66
|
])
|
|
66
67
|
);
|