@loopback/example-greeting-app 3.0.1
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/.prettierignore +2 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +19 -0
- package/.vscode/tasks.json +29 -0
- package/CHANGELOG.md +552 -0
- package/LICENSE +25 -0
- package/README.md +61 -0
- package/dist/__tests__/integration/greeting-service.integration.d.ts +1 -0
- package/dist/__tests__/integration/greeting-service.integration.js +68 -0
- package/dist/__tests__/integration/greeting-service.integration.js.map +1 -0
- package/dist/application.d.ts +98 -0
- package/dist/application.js +29 -0
- package/dist/application.js.map +1 -0
- package/dist/caching-service.d.ts +64 -0
- package/dist/caching-service.js +133 -0
- package/dist/caching-service.js.map +1 -0
- package/dist/controllers/greeting.controller.d.ts +10 -0
- package/dist/controllers/greeting.controller.js +59 -0
- package/dist/controllers/greeting.controller.js.map +1 -0
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.js +9 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/caching.interceptor.d.ts +7 -0
- package/dist/interceptors/caching.interceptor.js +49 -0
- package/dist/interceptors/caching.interceptor.js.map +1 -0
- package/dist/interceptors/index.d.ts +1 -0
- package/dist/interceptors/index.js +9 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +13 -0
- package/dist/keys.js.map +1 -0
- package/dist/observers/cache.observer.d.ts +19 -0
- package/dist/observers/cache.observer.js +39 -0
- package/dist/observers/cache.observer.js.map +1 -0
- package/dist/observers/index.d.ts +1 -0
- package/dist/observers/index.js +9 -0
- package/dist/observers/index.js.map +1 -0
- package/dist/openapi-spec.d.ts +1 -0
- package/dist/openapi-spec.js +28 -0
- package/dist/openapi-spec.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/greeting-app.png +0 -0
- package/package.json +69 -0
- package/src/__tests__/integration/greeting-service.integration.ts +79 -0
- package/src/application.ts +27 -0
- package/src/caching-service.ts +146 -0
- package/src/controllers/greeting.controller.ts +50 -0
- package/src/controllers/index.ts +6 -0
- package/src/index.ts +33 -0
- package/src/interceptors/caching.interceptor.ts +55 -0
- package/src/interceptors/index.ts +6 -0
- package/src/keys.ts +14 -0
- package/src/observers/cache.observer.ts +34 -0
- package/src/observers/index.ts +6 -0
- package/src/openapi-spec.ts +28 -0
- package/src/types.ts +13 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Client,
|
|
8
|
+
createRestAppClient,
|
|
9
|
+
expect,
|
|
10
|
+
givenHttpServerConfig,
|
|
11
|
+
} from '@loopback/testlab';
|
|
12
|
+
import {promisify} from 'util';
|
|
13
|
+
import {GreetingApplication} from '../..';
|
|
14
|
+
import {CACHING_SERVICE} from '../../keys';
|
|
15
|
+
|
|
16
|
+
describe('GreetingApplication', () => {
|
|
17
|
+
let app: GreetingApplication;
|
|
18
|
+
let client: Client;
|
|
19
|
+
|
|
20
|
+
before(givenRunningApplicationWithCustomConfiguration);
|
|
21
|
+
after(() => app.stop());
|
|
22
|
+
|
|
23
|
+
before(() => {
|
|
24
|
+
client = createRestAppClient(app);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('gets a greeting in English', async function () {
|
|
28
|
+
const response = await client
|
|
29
|
+
.get('/greet/Raymond')
|
|
30
|
+
.set('Accept-Language', 'en')
|
|
31
|
+
.expect(200);
|
|
32
|
+
expect(response.body).to.containEql({
|
|
33
|
+
language: 'en',
|
|
34
|
+
greeting: 'Hello, Raymond!',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('gets a greeting in Chinese', async function () {
|
|
39
|
+
const response = await client
|
|
40
|
+
.get('/greet/Raymond')
|
|
41
|
+
.set('Accept-Language', 'zh')
|
|
42
|
+
.expect(200);
|
|
43
|
+
expect(response.body).to.containEql({
|
|
44
|
+
language: 'zh',
|
|
45
|
+
greeting: 'Raymond,你好!',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('gets a greeting from cache', async function () {
|
|
50
|
+
app.configure(CACHING_SERVICE).to({ttl: 100});
|
|
51
|
+
let response = await client
|
|
52
|
+
.get('/greet/Raymond')
|
|
53
|
+
.set('Accept-Language', 'en')
|
|
54
|
+
.expect(200);
|
|
55
|
+
const msg1 = response.body;
|
|
56
|
+
// Now the result should be cached
|
|
57
|
+
response = await client
|
|
58
|
+
.get('/greet/Raymond')
|
|
59
|
+
.set('Accept-Language', 'en')
|
|
60
|
+
.expect(200);
|
|
61
|
+
expect(response.body).to.eql(msg1);
|
|
62
|
+
// Cache should be expired now
|
|
63
|
+
await promisify(setTimeout)(200);
|
|
64
|
+
response = await client
|
|
65
|
+
.get('/greet/Raymond')
|
|
66
|
+
.set('Accept-Language', 'en')
|
|
67
|
+
.expect(200);
|
|
68
|
+
expect(response.body.timestamp).to.not.eql(msg1.timestamp);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
async function givenRunningApplicationWithCustomConfiguration() {
|
|
72
|
+
app = new GreetingApplication({
|
|
73
|
+
rest: givenHttpServerConfig(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Start Application
|
|
77
|
+
await app.main();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {BootMixin} from '@loopback/boot';
|
|
7
|
+
import {ApplicationConfig, createBindingFromClass} from '@loopback/core';
|
|
8
|
+
import {GreetingComponent} from '@loopback/example-greeter-extension';
|
|
9
|
+
import {RestApplication} from '@loopback/rest';
|
|
10
|
+
import {CachingService} from './caching-service';
|
|
11
|
+
import {CachingInterceptor} from './interceptors';
|
|
12
|
+
import {CACHING_SERVICE} from './keys';
|
|
13
|
+
|
|
14
|
+
export class GreetingApplication extends BootMixin(RestApplication) {
|
|
15
|
+
constructor(config: ApplicationConfig = {}) {
|
|
16
|
+
super(config);
|
|
17
|
+
this.projectRoot = __dirname;
|
|
18
|
+
this.add(createBindingFromClass(CachingService, {key: CACHING_SERVICE}));
|
|
19
|
+
this.add(createBindingFromClass(CachingInterceptor));
|
|
20
|
+
this.component(GreetingComponent);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async main() {
|
|
24
|
+
await this.boot();
|
|
25
|
+
await this.start();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {BindingScope, config, ContextView, injectable} from '@loopback/core';
|
|
7
|
+
import debugFactory from 'debug';
|
|
8
|
+
import {Message} from './types';
|
|
9
|
+
const debug = debugFactory('greeter-extension');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for CachingService
|
|
13
|
+
*/
|
|
14
|
+
export interface CachingServiceOptions {
|
|
15
|
+
// The time-to-live setting for a cache entry
|
|
16
|
+
ttl: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Message caching service
|
|
21
|
+
*/
|
|
22
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
23
|
+
export class CachingService {
|
|
24
|
+
private timer: NodeJS.Timer;
|
|
25
|
+
private store: Map<string, Message> = new Map();
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
@config.view()
|
|
29
|
+
private optionsView: ContextView<CachingServiceOptions>,
|
|
30
|
+
) {
|
|
31
|
+
// Use a view so that we can listen on `refresh` events, which are emitted
|
|
32
|
+
// when the configuration binding is updated in the context.
|
|
33
|
+
optionsView.on('refresh', () => {
|
|
34
|
+
debug('Restarting the service as configuration changes...');
|
|
35
|
+
this.restart().catch(err => {
|
|
36
|
+
console.error('Cannot restart the caching service.', err);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// We intentionally mark all operations `async` to reflect the commonly used
|
|
43
|
+
// caching infrastructure even though our in-memory implementation is based
|
|
44
|
+
// on a `Map` that provides synchronous APIs.
|
|
45
|
+
/**
|
|
46
|
+
* Store a message in the cache
|
|
47
|
+
* @param key - Key for caching
|
|
48
|
+
* @param message - Message
|
|
49
|
+
*/
|
|
50
|
+
async set(key: string, message: Message) {
|
|
51
|
+
this.store.set(key, message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load a message from the cache by key
|
|
56
|
+
* @param key - Key for caching
|
|
57
|
+
*/
|
|
58
|
+
async get(key: string) {
|
|
59
|
+
const expired = await this.isExpired(key);
|
|
60
|
+
debug('Getting cache for %s', key, expired);
|
|
61
|
+
return expired ? undefined : this.store.get(key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Delete a message from the cache by key
|
|
66
|
+
* @param key - Key for caching
|
|
67
|
+
*/
|
|
68
|
+
async delete(key: string) {
|
|
69
|
+
return this.store.delete(key);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clear the cache
|
|
74
|
+
*/
|
|
75
|
+
async clear() {
|
|
76
|
+
this.store.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if the cached item is expired by key
|
|
81
|
+
* @param key - Key for caching
|
|
82
|
+
* @param now - The current date
|
|
83
|
+
*/
|
|
84
|
+
async isExpired(key: string, now = new Date()): Promise<boolean> {
|
|
85
|
+
const ttl = await this.getTTL();
|
|
86
|
+
const msg = this.store.get(key);
|
|
87
|
+
if (!msg) return true;
|
|
88
|
+
return now.getTime() - msg.timestamp.getTime() > ttl;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the TTL setting
|
|
93
|
+
*/
|
|
94
|
+
async getTTL() {
|
|
95
|
+
const options = await this.optionsView.singleValue();
|
|
96
|
+
debug('Caching options: %j', options);
|
|
97
|
+
return options?.ttl ?? 5000;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove expired items from the cache
|
|
102
|
+
*/
|
|
103
|
+
async sweep() {
|
|
104
|
+
debug('Sweeping cache...');
|
|
105
|
+
for (const key of this.store.keys()) {
|
|
106
|
+
if (await this.isExpired(key)) {
|
|
107
|
+
debug('Cache for %s is swept.', key);
|
|
108
|
+
await this.delete(key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* This method will be invoked when the application starts
|
|
115
|
+
*/
|
|
116
|
+
async start(): Promise<void> {
|
|
117
|
+
debug('Starting caching service');
|
|
118
|
+
await this.clear();
|
|
119
|
+
const ttl = await this.getTTL();
|
|
120
|
+
debug('TTL: %d', ttl);
|
|
121
|
+
this.timer = setInterval(() => {
|
|
122
|
+
this.sweep().catch(console.warn);
|
|
123
|
+
}, ttl);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* This method will be invoked when the application stops
|
|
128
|
+
*/
|
|
129
|
+
async stop(): Promise<void> {
|
|
130
|
+
debug('Stopping caching service');
|
|
131
|
+
/* istanbul ignore if */
|
|
132
|
+
if (this.timer) {
|
|
133
|
+
clearInterval(this.timer);
|
|
134
|
+
}
|
|
135
|
+
await this.clear();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* This method may be used to restart the service (and may be triggered by a
|
|
140
|
+
* 'refresh' event)
|
|
141
|
+
*/
|
|
142
|
+
async restart(): Promise<void> {
|
|
143
|
+
await this.stop();
|
|
144
|
+
await this.start();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {inject} from '@loopback/core';
|
|
7
|
+
import {
|
|
8
|
+
GreetingService,
|
|
9
|
+
GREETING_SERVICE,
|
|
10
|
+
} from '@loopback/example-greeter-extension';
|
|
11
|
+
import {get, param, Request, RestBindings} from '@loopback/rest';
|
|
12
|
+
import {Message} from '../types';
|
|
13
|
+
|
|
14
|
+
/* istanbul ignore file */
|
|
15
|
+
export class GreetingController {
|
|
16
|
+
constructor(
|
|
17
|
+
@inject(GREETING_SERVICE) private greetingService: GreetingService,
|
|
18
|
+
@inject(RestBindings.Http.REQUEST) private request: Request,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
@get('/greet/{name}', {
|
|
22
|
+
responses: {
|
|
23
|
+
'200': {
|
|
24
|
+
description: '',
|
|
25
|
+
content: {
|
|
26
|
+
'application/json': {
|
|
27
|
+
schema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
timestamp: 'string',
|
|
31
|
+
language: 'string',
|
|
32
|
+
message: 'string',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
async greet(@param.path.string('name') name: string): Promise<Message> {
|
|
41
|
+
const language: string =
|
|
42
|
+
this.request.acceptsLanguages(['en', 'zh']) || 'en';
|
|
43
|
+
const greeting = await this.greetingService.greet(language, name);
|
|
44
|
+
return {
|
|
45
|
+
timestamp: new Date(),
|
|
46
|
+
language,
|
|
47
|
+
greeting,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
export * from './application';
|
|
7
|
+
export * from './keys';
|
|
8
|
+
export * from './types';
|
|
9
|
+
|
|
10
|
+
import {GreetingApplication} from './application';
|
|
11
|
+
export async function main() {
|
|
12
|
+
const config = {
|
|
13
|
+
rest: {
|
|
14
|
+
port: +(process.env.PORT ?? 3000),
|
|
15
|
+
host: process.env.HOST ?? '127.0.0.1',
|
|
16
|
+
openApiSpec: {
|
|
17
|
+
// useful when used with OpenAPI-to-GraphQL to locate your application
|
|
18
|
+
setServersFromRequest: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const app = new GreetingApplication(config);
|
|
23
|
+
await app.main();
|
|
24
|
+
const url = app.restServer.url;
|
|
25
|
+
console.log(`The service is running at ${url}/greet/world.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (require.main === module) {
|
|
29
|
+
main().catch(err => {
|
|
30
|
+
console.error('Cannot start the application.', err);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
asGlobalInterceptor,
|
|
8
|
+
inject,
|
|
9
|
+
injectable,
|
|
10
|
+
Interceptor,
|
|
11
|
+
InvocationContext,
|
|
12
|
+
InvocationResult,
|
|
13
|
+
Provider,
|
|
14
|
+
ValueOrPromise,
|
|
15
|
+
} from '@loopback/core';
|
|
16
|
+
import {RestBindings} from '@loopback/rest';
|
|
17
|
+
import debugFactory from 'debug';
|
|
18
|
+
import {CachingService} from '../caching-service';
|
|
19
|
+
import {CACHING_SERVICE} from '../keys';
|
|
20
|
+
|
|
21
|
+
const debug = debugFactory('greeter-extension');
|
|
22
|
+
|
|
23
|
+
@injectable(asGlobalInterceptor('caching'))
|
|
24
|
+
export class CachingInterceptor implements Provider<Interceptor> {
|
|
25
|
+
constructor(
|
|
26
|
+
@inject(CACHING_SERVICE) private cachingService: CachingService,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
value() {
|
|
30
|
+
return async (
|
|
31
|
+
ctx: InvocationContext,
|
|
32
|
+
next: () => ValueOrPromise<InvocationResult>,
|
|
33
|
+
) => {
|
|
34
|
+
const httpReq = await ctx.get(RestBindings.Http.REQUEST, {
|
|
35
|
+
optional: true,
|
|
36
|
+
});
|
|
37
|
+
/* istanbul ignore if */
|
|
38
|
+
if (!httpReq) {
|
|
39
|
+
// Not http request
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
const key = httpReq.path;
|
|
43
|
+
const lang = httpReq.acceptsLanguages(['en', 'zh']) || 'en';
|
|
44
|
+
const cachingKey = `${lang}:${key}`;
|
|
45
|
+
const cachedResult = await this.cachingService.get(cachingKey);
|
|
46
|
+
if (cachedResult) {
|
|
47
|
+
debug('Cache found for %s %j', cachingKey, cachedResult);
|
|
48
|
+
return cachedResult;
|
|
49
|
+
}
|
|
50
|
+
const result = await next();
|
|
51
|
+
await this.cachingService.set(cachingKey, result);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/keys.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {BindingKey} from '@loopback/core';
|
|
7
|
+
import {CachingService} from './caching-service';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Strongly-typed binding key for CachingService
|
|
11
|
+
*/
|
|
12
|
+
export const CACHING_SERVICE = BindingKey.create<CachingService>(
|
|
13
|
+
'services.CachingService',
|
|
14
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/example-greeting-app
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
|
|
7
|
+
import {CachingService} from '../caching-service';
|
|
8
|
+
import {CACHING_SERVICE} from '../keys';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This class will be bound to the application as a `LifeCycleObserver` during
|
|
12
|
+
* `boot`
|
|
13
|
+
*/
|
|
14
|
+
@lifeCycleObserver('caching')
|
|
15
|
+
export class CacheObserver implements LifeCycleObserver {
|
|
16
|
+
private timer: NodeJS.Timer;
|
|
17
|
+
constructor(
|
|
18
|
+
@inject(CACHING_SERVICE) private cachingService: CachingService,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This method will be invoked when the application starts
|
|
23
|
+
*/
|
|
24
|
+
async start(): Promise<void> {
|
|
25
|
+
await this.cachingService.start();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* This method will be invoked when the application stops
|
|
30
|
+
*/
|
|
31
|
+
async stop(): Promise<void> {
|
|
32
|
+
await this.cachingService.stop();
|
|
33
|
+
}
|
|
34
|
+
}
|