@travetto/cache 8.0.0-alpha.23 → 8.0.0-alpha.24

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 CHANGED
@@ -13,7 +13,7 @@ npm install @travetto/cache
13
13
  yarn add @travetto/cache
14
14
  ```
15
15
 
16
- Provides a foundational structure for integrating caching at the method level. This allows for easy extension with a variety of providers, and is usable with or without [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."). The code aims to handle use cases surrounding common/basic usage.
16
+ Provides a foundational structure for integrating caching at the method level. This allows for easy extension with a variety of providers, and is usable with or without [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."). The code aims to handle use cases surrounding common/basic usage.
17
17
 
18
18
  The cache module requires an [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10) to provide functionality for reading and writing streams. You can use any existing providers to serve as your [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10), or you can roll your own.
19
19
 
@@ -38,16 +38,16 @@ Currently, the following are packages that provide [Expiry](https://github.com/t
38
38
  * [File Model Support](https://github.com/travetto/travetto/tree/main/module/model-file#readme "File system backing for the travetto model module.") - @travetto/model-file
39
39
 
40
40
  ## Decorators
41
- 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.
41
+ 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.
42
42
 
43
43
  Additionally, to use the decorators you will need to have a [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35) object accessible on the class instance. This can be dependency injected, or manually constructed. The decorators will detect the field at time of method execution, which decouples construction of your class from the cache construction.
44
44
 
45
- [@Cache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L12) is a decorator that will cache all successful results, keyed by a computation based on the method arguments. Given the desire for supporting remote caches (e.g. [redis](https://redis.io), [memcached](https://memcached.org)), only asynchronous methods are supported.
45
+ [@Cache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L12) is a decorator that will cache all successful results, keyed by a computation based on the method arguments. Given the desire for supporting remote caches (e.g. [redis](https://redis.io), [memcached](https://memcached.org)), only asynchronous methods are supported.
46
46
 
47
47
  **Code: Using decorators to cache expensive async call**
48
48
  ```typescript
49
- import { MemoryModelService } from '@travetto/model-memory';
50
49
  import { Cache, CacheService } from '@travetto/cache';
50
+ import { MemoryModelService } from '@travetto/model-memory';
51
51
 
52
52
  async function request(url: string): Promise<string> {
53
53
  let value: string;
@@ -56,10 +56,7 @@ async function request(url: string): Promise<string> {
56
56
  }
57
57
 
58
58
  export class Worker {
59
-
60
- myCache = new CacheService(
61
- new MemoryModelService({ namespace: '' })
62
- );
59
+ myCache = new CacheService(new MemoryModelService({ namespace: '' }));
63
60
 
64
61
  @Cache('myCache', '1s')
65
62
  async calculateExpensiveResult(expression: string): Promise<string> {
@@ -74,26 +71,25 @@ The [@Cache](https://github.com/travetto/travetto/tree/main/module/cache/src/dec
74
71
  * `name` the field name of the current class which points to the desired cache source.
75
72
  * `config` the additional/optional config options, on a per invocation basis
76
73
 
77
- * `keySpace` the key space within the cache. Defaults to class name plus method name.
78
- * `key` the function will use the inputs to determine the cache key, defaults to all params `JSON.stringify`ied
79
- * `params` the function used to determine the inputs for computing the cache key. This is an easier place to start to define what parameters are important in ,caching. This defaults to all inputs.
80
- * `maxAge` the number of milliseconds will hold the value before considering the cache entry to be invalid. By default values will live infinitely.
81
- * `extendOnAccess` determines if the cache timeout should be extended on access. This only applies to cache values that have specified a `maxAge`.
82
- * `serialize` the function to execute before storing a cacheable value. This allows for any custom data modification needed to persist as a string properly.
83
- * `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.
74
+ * `keySpace` the key space within the cache. Defaults to class name plus method name.
75
+ * `key` the function will use the inputs to determine the cache key, defaults to all params `JSON.stringify`ied
76
+ * `params` the function used to determine the inputs for computing the cache key. This is an easier place to start to define what parameters are important in ,caching. This defaults to all inputs.
77
+ * `maxAge` the number of milliseconds will hold the value before considering the cache entry to be invalid. By default values will live infinitely.
78
+ * `extendOnAccess` determines if the cache timeout should be extended on access. This only applies to cache values that have specified a `maxAge`.
79
+ * `serialize` the function to execute before storing a cacheable value. This allows for any custom data modification needed to persist as a string properly.
80
+ * `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.
84
81
 
85
82
  ### EvictCache
86
- Additionally, there is support for planned eviction via the [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L44) 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.
83
+ Additionally, there is support for planned eviction via the [@EvictCache](https://github.com/travetto/travetto/tree/main/module/cache/src/decorator.ts#L55) 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.
87
84
 
88
85
  **Code: Using decorators to cache/evict user access**
89
86
  ```typescript
87
+ import { Cache, CacheService, EvictCache } from '@travetto/cache';
90
88
  import { MemoryModelService } from '@travetto/model-memory';
91
- import { Cache, EvictCache, CacheService } from '@travetto/cache';
92
89
 
93
- class User { }
90
+ class User {}
94
91
 
95
92
  export class UserService {
96
-
97
93
  myCache = new CacheService(new MemoryModelService({ namespace: '' }));
98
94
  database: {
99
95
  lookupUser(id: string): Promise<User>;
@@ -119,14 +115,14 @@ export class UserService {
119
115
  ```
120
116
 
121
117
  ## Extending the Cache Service
122
- By design, the [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35) relies solely on the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. Specifically on the [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10). This combines basic support for CRUD as well as knowledge of how to manage expirable content. Any model service that honors these contracts is a valid candidate to power the [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35). The [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35) is expecting the model service to be registered using the @travetto/cache:model:
118
+ By design, the [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35) relies solely on the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module. Specifically on the [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10). This combines basic support for CRUD as well as knowledge of how to manage expirable content. Any model service that honors these contracts is a valid candidate to power the [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35). The [CacheService](https://github.com/travetto/travetto/tree/main/module/cache/src/service.ts#L35) is expecting the model service to be registered using the @travetto/cache:model:
123
119
 
124
120
  **Code: Registering a Custom Model Source**
125
121
  ```typescript
122
+ import { CacheModelSymbol } from '@travetto/cache';
126
123
  import { InjectableFactory } from '@travetto/di';
127
124
  import type { ModelExpirySupport } from '@travetto/model';
128
125
  import { MemoryModelService } from '@travetto/model-memory';
129
- import { CacheModelSymbol } from '@travetto/cache';
130
126
 
131
127
  class Config {
132
128
  @InjectableFactory(CacheModelSymbol)
package/__index__.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './src/types.ts';
2
1
  export * from './src/decorator.ts';
3
2
  export * from './src/service.ts';
4
- export * from './src/util.ts';
3
+ export * from './src/types.ts';
4
+ export * from './src/util.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cache",
3
- "version": "8.0.0-alpha.23",
3
+ "version": "8.0.0-alpha.24",
4
4
  "type": "module",
5
5
  "description": "Caching functionality with decorators for declarative use.",
6
6
  "keywords": [
@@ -26,12 +26,12 @@
26
26
  "directory": "module/cache"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/di": "^8.0.0-alpha.19",
30
- "@travetto/model": "^8.0.0-alpha.22"
29
+ "@travetto/di": "^8.0.0-alpha.20",
30
+ "@travetto/model": "^8.0.0-alpha.23"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/test": "^8.0.0-alpha.20",
34
- "@travetto/transformer": "^8.0.0-alpha.13"
33
+ "@travetto/test": "^8.0.0-alpha.21",
34
+ "@travetto/transformer": "^8.0.0-alpha.14"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/transformer": {
package/src/decorator.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { castTo, type MethodDescriptor, type TimeSpan, TimeUtil } from '@travetto/runtime';
2
2
 
3
3
  import type { CacheService } from './service.ts';
4
- import { type CoreCacheConfig, type CacheConfig, type CacheAware, CacheConfigSymbol, EvictConfigSymbol } from './types.ts';
4
+ import { type CacheAware, type CacheConfig, CacheConfigSymbol, type CoreCacheConfig, EvictConfigSymbol } from './types.ts';
5
5
 
6
6
  /**
7
7
  * Indicates a method is intended to cache. The return type must be properly serializable
@@ -9,10 +9,16 @@ import { type CoreCacheConfig, type CacheConfig, type CacheAware, CacheConfigSym
9
9
  * @param config The additional cache configuration
10
10
  * @kind decorator
11
11
  */
12
- export function Cache<F extends string, U extends Record<F, CacheService>>(field: F, maxAge: number | TimeSpan, config?: Omit<CacheConfig, 'maxAge'>): MethodDecorator;
12
+ export function Cache<F extends string, U extends Record<F, CacheService>>(
13
+ field: F,
14
+ maxAge: number | TimeSpan,
15
+ config?: Omit<CacheConfig, 'maxAge'>
16
+ ): MethodDecorator;
13
17
  export function Cache<F extends string, U extends Record<F, CacheService>>(field: F, input?: CacheConfig): MethodDecorator;
14
18
  export function Cache<F extends string, U extends Record<F, CacheService>>(
15
- field: F, input?: number | TimeSpan | CacheConfig, config: Exclude<CacheConfig, 'maxAge'> = {}
19
+ field: F,
20
+ input?: number | TimeSpan | CacheConfig,
21
+ config: Exclude<CacheConfig, 'maxAge'> = {}
16
22
  ): MethodDecorator {
17
23
  if (input !== undefined) {
18
24
  if (typeof input === 'string' || typeof input === 'number') {
@@ -21,12 +27,17 @@ export function Cache<F extends string, U extends Record<F, CacheService>>(
21
27
  config = input;
22
28
  }
23
29
  }
24
- const decorator = function <R extends Promise<unknown>>(target: U & CacheAware, propertyKey: string, descriptor: MethodDescriptor<R>): void {
30
+ const decorator = function <R extends Promise<unknown>>(
31
+ target: U & CacheAware,
32
+ propertyKey: string,
33
+ descriptor: MethodDescriptor<R>
34
+ ): void {
25
35
  config.keySpace ??= `${target.constructor.name}.${propertyKey}`;
26
36
  (target[CacheConfigSymbol] ??= {})[propertyKey] = config;
27
37
  const handler = descriptor.value!;
28
38
  // Allows for DI to run, as the service will not be bound until after the decorator is run
29
39
  descriptor.value = castTo(function (this: typeof target) {
40
+ // biome-ignore lint/complexity/noArguments: We want to use arguments here
30
41
  return this[field].cache(this, propertyKey, handler, [...arguments]);
31
42
  });
32
43
  Object.defineProperty(descriptor.value, 'name', { value: propertyKey, writable: false });
@@ -48,8 +59,9 @@ export function EvictCache<F extends string, U extends Record<F, CacheService>>(
48
59
  const handler = descriptor.value!;
49
60
  // Allows for DI to run, as the service will not be bound until after the decorator is run
50
61
  descriptor.value = castTo(function (this: typeof target) {
62
+ // biome-ignore lint/complexity/noArguments: We want to use arguments here
51
63
  return this[field].evict(this, propertyKey, handler, [...arguments]);
52
64
  });
53
65
  Object.defineProperty(descriptor.value, 'name', { value: propertyKey, writable: false });
54
66
  };
55
- }
67
+ }
package/src/error.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { RuntimeError } from '@travetto/runtime';
2
2
 
3
3
  /** Cache Error Class */
4
- export class CacheError extends RuntimeError { }
4
+ export class CacheError extends RuntimeError {}
package/src/service.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { ExpiresAt, Model, type ModelExpirySupport, NotFoundError, ModelStorageUtil } from '@travetto/model';
1
+ import { Inject, Injectable } from '@travetto/di';
2
+ import { ExpiresAt, Model, type ModelExpirySupport, ModelStorageUtil, NotFoundError } from '@travetto/model';
2
3
  import { ModelIndexedUtil, sortedIndex } from '@travetto/model-indexed';
4
+ import { JSONUtil, RuntimeError, TimeUtil } from '@travetto/runtime';
3
5
  import { MaxLength, Text } from '@travetto/schema';
4
- import { Inject, Injectable } from '@travetto/di';
5
- import { RuntimeError, JSONUtil, TimeUtil } from '@travetto/runtime';
6
6
 
7
7
  import { CacheError } from './error.ts';
8
- import { CacheUtil } from './util.ts';
9
8
  import { type CacheAware, CacheConfigSymbol, CacheModelSymbol, EvictConfigSymbol } from './types.ts';
9
+ import { CacheUtil } from './util.ts';
10
10
 
11
11
  const INFINITE_MAX_AGE = TimeUtil.duration('10y', 'ms');
12
12
 
@@ -33,7 +33,6 @@ const keySpaceIndex = sortedIndex(CacheRecord, {
33
33
  */
34
34
  @Injectable()
35
35
  export class CacheService {
36
-
37
36
  #modelService: ModelExpirySupport;
38
37
 
39
38
  constructor(@Inject({ qualifier: CacheModelSymbol, resolution: 'loose' }) modelService: ModelExpirySupport) {
@@ -51,7 +50,8 @@ export class CacheService {
51
50
  const delta = expiresAt.getTime() - Date.now();
52
51
  const maxAge = expiresAt.getTime() - issuedAt.getTime();
53
52
 
54
- if (delta < 0) { // Expired
53
+ if (delta < 0) {
54
+ // Expired
55
55
  await this.#modelService.delete(CacheRecord, id);
56
56
  throw new CacheError('Key expired', { category: 'data' });
57
57
  }
@@ -79,14 +79,15 @@ export class CacheService {
79
79
  async set(id: string, keySpace: string, entry: unknown, maxAge?: number): Promise<unknown> {
80
80
  const entryText = JSONUtil.toBase64(entry);
81
81
 
82
- const store = await this.#modelService.upsert(CacheRecord,
82
+ const store = await this.#modelService.upsert(
83
+ CacheRecord,
83
84
  CacheRecord.from({
84
85
  id,
85
86
  entry: entryText!,
86
87
  keySpace,
87
88
  expiresAt: TimeUtil.fromNow(maxAge || INFINITE_MAX_AGE),
88
89
  issuedAt: new Date()
89
- }),
90
+ })
90
91
  );
91
92
 
92
93
  return JSONUtil.fromBase64(store.entry);
@@ -167,7 +168,8 @@ export class CacheService {
167
168
  result = await this.set(id, config.keySpace!, data, config.maxAge);
168
169
  }
169
170
 
170
- if (config.reinstate) { // Reinstate result value if needed
171
+ if (config.reinstate) {
172
+ // Reinstate result value if needed
171
173
  result = config.reinstate(result);
172
174
  }
173
175
 
@@ -195,4 +197,4 @@ export class CacheService {
195
197
  }
196
198
  return result;
197
199
  }
198
- }
200
+ }
package/src/types.ts CHANGED
@@ -48,4 +48,3 @@ export interface CacheAware {
48
48
  [CacheConfigSymbol]?: Record<string, CacheConfig>;
49
49
  [EvictConfigSymbol]?: Record<string, CoreCacheConfig>;
50
50
  }
51
-
package/src/util.ts CHANGED
@@ -6,7 +6,6 @@ import type { CoreCacheConfig } from './types.ts';
6
6
  * Standard cache utilities
7
7
  */
8
8
  export class CacheUtil {
9
-
10
9
  /**
11
10
  * Generate key given config, cache source and input params
12
11
  */
@@ -16,4 +15,4 @@ export class CacheUtil {
16
15
  const key = `${config.keySpace!}_${JSONUtil.toBase64(keyParams)}`;
17
16
  return BinaryMetadataUtil.hash(key, { length: 32 });
18
17
  }
19
- }
18
+ }
@@ -1,12 +1,12 @@
1
1
  import assert from 'node:assert';
2
2
  import timers from 'node:timers/promises';
3
3
 
4
- import { Suite, Test } from '@travetto/test';
5
- import { type ModelExpirySupport } from '@travetto/model';
6
- import { ModelIndexedUtil } from '@travetto/model-indexed';
7
4
  import { Inject, Injectable } from '@travetto/di';
8
- import { castTo, type Class } from '@travetto/runtime';
5
+ import type { ModelExpirySupport } from '@travetto/model';
6
+ import { ModelIndexedUtil } from '@travetto/model-indexed';
7
+ import { type Class, castTo } from '@travetto/runtime';
9
8
  import { Schema } from '@travetto/schema';
9
+ import { Suite, Test } from '@travetto/test';
10
10
 
11
11
  import { InjectableSuite } from '@travetto/di/support/test/suite.ts';
12
12
  import { ModelSuite } from '@travetto/model/support/test/suite.ts';
@@ -16,11 +16,10 @@ import type { CacheService } from '../../src/service.ts';
16
16
  import { CacheModelSymbol } from '../../src/types.ts';
17
17
 
18
18
  @Schema()
19
- class User { }
19
+ class User {}
20
20
 
21
21
  @Injectable()
22
22
  class SampleService {
23
-
24
23
  @Inject()
25
24
  source: CacheService;
26
25
 
@@ -86,7 +85,6 @@ class SampleService {
86
85
  @ModelSuite(CacheModelSymbol)
87
86
  @InjectableSuite()
88
87
  export abstract class CacheServiceSuite {
89
-
90
88
  serviceClass: Class<ModelExpirySupport>;
91
89
  configClass: Class;
92
90
 
@@ -107,7 +105,7 @@ export abstract class CacheServiceSuite {
107
105
  start = Date.now();
108
106
  res = await service.basic(10);
109
107
  diff = Date.now() - start;
110
- assert(diff < (100 + this.baseLatency));
108
+ assert(diff < 100 + this.baseLatency);
111
109
  assert(res === 20);
112
110
  }
113
111
 
@@ -146,7 +144,7 @@ export abstract class CacheServiceSuite {
146
144
  start = Date.now();
147
145
  res = await service.ageExtension(10);
148
146
  diff = Date.now() - start;
149
- assert(diff < (100 + this.baseLatency));
147
+ assert(diff < 100 + this.baseLatency);
150
148
  assert(res === 30);
151
149
  }
152
150
 
@@ -203,12 +201,12 @@ export abstract class CacheServiceSuite {
203
201
  await service.getUser('200');
204
202
  const start = Date.now();
205
203
  await service.getUser('200');
206
- assert((Date.now() - start) <= (this.baseLatency + 100));
204
+ assert(Date.now() - start <= this.baseLatency + 100);
207
205
 
208
206
  await service.deleteUser('200');
209
207
  const start2 = Date.now();
210
208
  await service.getUser('200');
211
- assert((Date.now() - start2) >= 100);
209
+ assert(Date.now() - start2 >= 100);
212
210
 
213
211
  // First time is free
214
212
  await service.deleteUser('200');
@@ -228,14 +226,14 @@ export abstract class CacheServiceSuite {
228
226
  for (let i = 0; i < 10; i++) {
229
227
  const start = Date.now();
230
228
  await service.getUser(`${i}`);
231
- assert((Date.now() - start) >= 100);
229
+ assert(Date.now() - start >= 100);
232
230
  }
233
231
 
234
232
  // Read cache
235
233
  for (let i = 0; i < 10; i++) {
236
234
  const start = Date.now();
237
235
  await service.getUser(`${i}`);
238
- assert((Date.now() - start) <= (this.baseLatency + 100));
236
+ assert(Date.now() - start <= this.baseLatency + 100);
239
237
  }
240
238
 
241
239
  await service.deleteAllUsers();
@@ -244,14 +242,14 @@ export abstract class CacheServiceSuite {
244
242
  for (let i = 0; i < 10; i++) {
245
243
  const start = Date.now();
246
244
  await service.getUser(`${i}`);
247
- assert((Date.now() - start) >= 100);
245
+ assert(Date.now() - start >= 100);
248
246
  }
249
247
 
250
248
  // Read cache
251
249
  for (let i = 0; i < 10; i++) {
252
250
  const start = Date.now();
253
251
  await service.getUser(`${i}`);
254
- assert((Date.now() - start) <= (this.baseLatency + 100));
252
+ assert(Date.now() - start <= this.baseLatency + 100);
255
253
  }
256
254
  }
257
- }
255
+ }