@travetto/cache 8.0.0-alpha.23 → 8.0.0-alpha.25
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 +17 -21
- package/__index__.ts +2 -2
- package/package.json +5 -5
- package/src/decorator.ts +17 -5
- package/src/error.ts +1 -1
- package/src/service.ts +12 -10
- package/src/types.ts +0 -1
- package/src/util.ts +1 -2
- package/support/test/service.ts +18 -20
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.
|
|
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.
|
|
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.
|
|
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.
|
|
78
|
-
* `key` the function
|
|
79
|
-
* `params` the function used to determine the inputs for computing the cache key.
|
|
80
|
-
* `maxAge` the number of milliseconds will hold the value before considering the cache entry to be invalid.
|
|
81
|
-
* `extendOnAccess` determines if the cache timeout should be extended on access.
|
|
82
|
-
* `serialize` the function to execute before storing a cacheable value.
|
|
83
|
-
* `reinstate` the function to execute on return of a cached value.
|
|
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#
|
|
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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cache",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.25",
|
|
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.
|
|
30
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
29
|
+
"@travetto/di": "^8.0.0-alpha.21",
|
|
30
|
+
"@travetto/model": "^8.0.0-alpha.24"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
34
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
33
|
+
"@travetto/test": "^8.0.0-alpha.22",
|
|
34
|
+
"@travetto/transformer": "^8.0.0-alpha.15"
|
|
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
|
|
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>>(
|
|
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,
|
|
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>>(
|
|
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
package/src/service.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
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) {
|
|
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(
|
|
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) {
|
|
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
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
|
+
}
|
package/support/test/service.ts
CHANGED
|
@@ -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 {
|
|
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 <
|
|
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 <
|
|
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(
|
|
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(
|
|
209
|
+
assert(Date.now() - start2 >= 100);
|
|
212
210
|
|
|
213
211
|
// First time is free
|
|
214
212
|
await service.deleteUser('200');
|
|
@@ -225,33 +223,33 @@ export abstract class CacheServiceSuite {
|
|
|
225
223
|
const service = this.testService;
|
|
226
224
|
|
|
227
225
|
// Prime cache
|
|
228
|
-
for (let i = 0; i < 10; i
|
|
226
|
+
for (let i = 0; i < 10; i += 1) {
|
|
229
227
|
const start = Date.now();
|
|
230
228
|
await service.getUser(`${i}`);
|
|
231
|
-
assert(
|
|
229
|
+
assert(Date.now() - start >= 100);
|
|
232
230
|
}
|
|
233
231
|
|
|
234
232
|
// Read cache
|
|
235
|
-
for (let i = 0; i < 10; i
|
|
233
|
+
for (let i = 0; i < 10; i += 1) {
|
|
236
234
|
const start = Date.now();
|
|
237
235
|
await service.getUser(`${i}`);
|
|
238
|
-
assert(
|
|
236
|
+
assert(Date.now() - start <= this.baseLatency + 100);
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
await service.deleteAllUsers();
|
|
242
240
|
|
|
243
241
|
// Prime cache
|
|
244
|
-
for (let i = 0; i < 10; i
|
|
242
|
+
for (let i = 0; i < 10; i += 1) {
|
|
245
243
|
const start = Date.now();
|
|
246
244
|
await service.getUser(`${i}`);
|
|
247
|
-
assert(
|
|
245
|
+
assert(Date.now() - start >= 100);
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
// Read cache
|
|
251
|
-
for (let i = 0; i < 10; i
|
|
249
|
+
for (let i = 0; i < 10; i += 1) {
|
|
252
250
|
const start = Date.now();
|
|
253
251
|
await service.getUser(`${i}`);
|
|
254
|
-
assert(
|
|
252
|
+
assert(Date.now() - start <= this.baseLatency + 100);
|
|
255
253
|
}
|
|
256
254
|
}
|
|
257
|
-
}
|
|
255
|
+
}
|