@twin.org/api-service 0.0.2-next.9 → 0.0.3-next.10
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/dist/es/index.js +8 -0
- package/dist/es/index.js.map +1 -0
- package/dist/{esm/index.mjs → es/informationRoutes.js} +80 -187
- package/dist/es/informationRoutes.js.map +1 -0
- package/dist/es/informationService.js +196 -0
- package/dist/es/informationService.js.map +1 -0
- package/dist/es/models/IInformationServiceConfig.js +2 -0
- package/dist/es/models/IInformationServiceConfig.js.map +1 -0
- package/dist/es/models/IInformationServiceConstructorOptions.js +2 -0
- package/dist/es/models/IInformationServiceConstructorOptions.js.map +1 -0
- package/dist/es/restEntryPoints.js +10 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/types/index.d.ts +5 -5
- package/dist/types/informationRoutes.d.ts +9 -1
- package/dist/types/informationService.d.ts +16 -4
- package/dist/types/models/IInformationServiceConstructorOptions.d.ts +1 -1
- package/docs/changelog.md +221 -0
- package/docs/reference/classes/InformationService.md +51 -7
- package/docs/reference/functions/serverLivez.md +31 -0
- package/docs/reference/index.md +1 -0
- package/package.json +23 -9
- package/dist/cjs/index.cjs +0 -447
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
export * from "./informationRoutes.js";
|
|
4
|
+
export * from "./informationService.js";
|
|
5
|
+
export * from "./models/IInformationServiceConfig.js";
|
|
6
|
+
export * from "./models/IInformationServiceConstructorOptions.js";
|
|
7
|
+
export * from "./restEntryPoints.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,sBAAsB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./restEntryPoints.js\";\n"]}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { ComponentFactory, Is
|
|
2
|
-
import {
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
|
|
1
|
+
import { ComponentFactory, Is } from "@twin.org/core";
|
|
2
|
+
import { HeaderTypes, HttpStatusCode, MimeTypes } from "@twin.org/web";
|
|
5
3
|
/**
|
|
6
4
|
* The tag to associate with the routes.
|
|
7
5
|
*/
|
|
8
|
-
const tagsInformation = [
|
|
6
|
+
export const tagsInformation = [
|
|
9
7
|
{
|
|
10
8
|
name: "Info",
|
|
11
9
|
description: "Information endpoints for the REST server."
|
|
@@ -17,14 +15,14 @@ const tagsInformation = [
|
|
|
17
15
|
* @param componentName The name of the component to use in the routes stored in the ComponentFactory.
|
|
18
16
|
* @returns The generated routes.
|
|
19
17
|
*/
|
|
20
|
-
function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
18
|
+
export function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
21
19
|
const rootRoute = {
|
|
22
20
|
operationId: "serverRoot",
|
|
23
21
|
summary: "Get the root text page",
|
|
24
22
|
tag: tagsInformation[0].name,
|
|
25
23
|
method: "GET",
|
|
26
24
|
path: `${baseRouteName}/`,
|
|
27
|
-
handler: async (httpRequestContext, request) => serverRoot(httpRequestContext, componentName),
|
|
25
|
+
handler: async (httpRequestContext, request) => serverRoot(httpRequestContext, componentName, request),
|
|
28
26
|
responseType: [
|
|
29
27
|
{
|
|
30
28
|
type: "IServerRootResponse",
|
|
@@ -34,13 +32,17 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
34
32
|
id: "serverRootResponse",
|
|
35
33
|
description: "The response for the root request.",
|
|
36
34
|
response: {
|
|
35
|
+
headers: {
|
|
36
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
37
|
+
},
|
|
37
38
|
body: "API Server - 1.0.0"
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
]
|
|
41
42
|
}
|
|
42
43
|
],
|
|
43
|
-
skipAuth: true
|
|
44
|
+
skipAuth: true,
|
|
45
|
+
skipTenant: true
|
|
44
46
|
};
|
|
45
47
|
const informationRoute = {
|
|
46
48
|
operationId: "serverInformation",
|
|
@@ -48,7 +50,7 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
48
50
|
tag: tagsInformation[0].name,
|
|
49
51
|
method: "GET",
|
|
50
52
|
path: `${baseRouteName}/info`,
|
|
51
|
-
handler: async (httpRequestContext, request) => serverInfo(httpRequestContext, componentName),
|
|
53
|
+
handler: async (httpRequestContext, request) => serverInfo(httpRequestContext, componentName, request),
|
|
52
54
|
responseType: [
|
|
53
55
|
{
|
|
54
56
|
type: "IServerInfoResponse",
|
|
@@ -74,14 +76,53 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
74
76
|
tag: tagsInformation[0].name,
|
|
75
77
|
method: "GET",
|
|
76
78
|
path: `${baseRouteName}/favicon.ico`,
|
|
77
|
-
handler: async (httpRequestContext, request) => serverFavIcon(httpRequestContext, componentName),
|
|
79
|
+
handler: async (httpRequestContext, request) => serverFavIcon(httpRequestContext, componentName, request),
|
|
78
80
|
responseType: [
|
|
79
81
|
{
|
|
80
82
|
type: "IServerFavIconResponse",
|
|
81
83
|
mimeType: "image/x-icon"
|
|
82
84
|
}
|
|
83
85
|
],
|
|
84
|
-
skipAuth: true
|
|
86
|
+
skipAuth: true,
|
|
87
|
+
skipTenant: true
|
|
88
|
+
};
|
|
89
|
+
const livezRoute = {
|
|
90
|
+
operationId: "serverLivez",
|
|
91
|
+
summary: "Get the livez status for the server",
|
|
92
|
+
tag: tagsInformation[0].name,
|
|
93
|
+
method: "GET",
|
|
94
|
+
path: `${baseRouteName}/livez`,
|
|
95
|
+
handler: async (httpRequestContext, request) => serverLivez(httpRequestContext, componentName, request),
|
|
96
|
+
responseType: [
|
|
97
|
+
{
|
|
98
|
+
type: "IServerLivezResponse",
|
|
99
|
+
mimeType: MimeTypes.PlainText,
|
|
100
|
+
examples: [
|
|
101
|
+
{
|
|
102
|
+
id: "livezResponseOK",
|
|
103
|
+
description: "The response for the liveness request.",
|
|
104
|
+
response: {
|
|
105
|
+
headers: {
|
|
106
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
107
|
+
},
|
|
108
|
+
body: "ok"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "livezResponseFailure",
|
|
113
|
+
description: "The response for the liveness request with errors.",
|
|
114
|
+
response: {
|
|
115
|
+
headers: {
|
|
116
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
117
|
+
},
|
|
118
|
+
body: "failed"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
skipAuth: true,
|
|
125
|
+
skipTenant: true
|
|
85
126
|
};
|
|
86
127
|
const healthRoute = {
|
|
87
128
|
operationId: "serverHealth",
|
|
@@ -89,7 +130,7 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
89
130
|
tag: tagsInformation[0].name,
|
|
90
131
|
method: "GET",
|
|
91
132
|
path: `${baseRouteName}/health`,
|
|
92
|
-
handler: async (httpRequestContext, request) => serverHealth(httpRequestContext, componentName),
|
|
133
|
+
handler: async (httpRequestContext, request) => serverHealth(httpRequestContext, componentName, request),
|
|
93
134
|
responseType: [
|
|
94
135
|
{
|
|
95
136
|
type: "IServerHealthResponse",
|
|
@@ -164,7 +205,7 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
164
205
|
tag: tagsInformation[0].name,
|
|
165
206
|
method: "GET",
|
|
166
207
|
path: `${baseRouteName}/spec`,
|
|
167
|
-
handler: async (httpRequestContext, request) => serverSpec(httpRequestContext, componentName),
|
|
208
|
+
handler: async (httpRequestContext, request) => serverSpec(httpRequestContext, componentName, request),
|
|
168
209
|
responseType: [
|
|
169
210
|
{
|
|
170
211
|
type: "IServerSpecResponse",
|
|
@@ -185,7 +226,7 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
185
226
|
],
|
|
186
227
|
skipAuth: true
|
|
187
228
|
};
|
|
188
|
-
return [rootRoute, favIconRoute, informationRoute, healthRoute, specRoute];
|
|
229
|
+
return [rootRoute, favIconRoute, informationRoute, livezRoute, healthRoute, specRoute];
|
|
189
230
|
}
|
|
190
231
|
/**
|
|
191
232
|
* Get the root for the server.
|
|
@@ -194,9 +235,12 @@ function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
|
194
235
|
* @param request The request.
|
|
195
236
|
* @returns The response object with additional http response properties.
|
|
196
237
|
*/
|
|
197
|
-
async function serverRoot(httpRequestContext, componentName, request) {
|
|
238
|
+
export async function serverRoot(httpRequestContext, componentName, request) {
|
|
198
239
|
const component = ComponentFactory.get(componentName);
|
|
199
240
|
return {
|
|
241
|
+
headers: {
|
|
242
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
243
|
+
},
|
|
200
244
|
body: await component.root()
|
|
201
245
|
};
|
|
202
246
|
}
|
|
@@ -207,12 +251,28 @@ async function serverRoot(httpRequestContext, componentName, request) {
|
|
|
207
251
|
* @param request The request.
|
|
208
252
|
* @returns The response object with additional http response properties.
|
|
209
253
|
*/
|
|
210
|
-
async function serverInfo(httpRequestContext, componentName, request) {
|
|
254
|
+
export async function serverInfo(httpRequestContext, componentName, request) {
|
|
211
255
|
const component = ComponentFactory.get(componentName);
|
|
212
256
|
return {
|
|
213
257
|
body: await component.info()
|
|
214
258
|
};
|
|
215
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Get the livez for the server.
|
|
262
|
+
* @param httpRequestContext The request context for the API.
|
|
263
|
+
* @param componentName The name of the component to use in the routes.
|
|
264
|
+
* @param request The request.
|
|
265
|
+
* @returns The response object with additional http response properties.
|
|
266
|
+
*/
|
|
267
|
+
export async function serverLivez(httpRequestContext, componentName, request) {
|
|
268
|
+
const component = ComponentFactory.get(componentName);
|
|
269
|
+
return {
|
|
270
|
+
headers: {
|
|
271
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
272
|
+
},
|
|
273
|
+
body: (await component.livez()) ? "ok" : "failed"
|
|
274
|
+
};
|
|
275
|
+
}
|
|
216
276
|
/**
|
|
217
277
|
* Get the health for the server.
|
|
218
278
|
* @param httpRequestContext The request context for the API.
|
|
@@ -220,7 +280,7 @@ async function serverInfo(httpRequestContext, componentName, request) {
|
|
|
220
280
|
* @param request The request.
|
|
221
281
|
* @returns The response object with additional http response properties.
|
|
222
282
|
*/
|
|
223
|
-
async function serverHealth(httpRequestContext, componentName, request) {
|
|
283
|
+
export async function serverHealth(httpRequestContext, componentName, request) {
|
|
224
284
|
const component = ComponentFactory.get(componentName);
|
|
225
285
|
return {
|
|
226
286
|
body: await component.health()
|
|
@@ -233,7 +293,7 @@ async function serverHealth(httpRequestContext, componentName, request) {
|
|
|
233
293
|
* @param request The request.
|
|
234
294
|
* @returns The response object with additional http response properties.
|
|
235
295
|
*/
|
|
236
|
-
async function serverFavIcon(httpRequestContext, componentName, request) {
|
|
296
|
+
export async function serverFavIcon(httpRequestContext, componentName, request) {
|
|
237
297
|
const component = ComponentFactory.get(componentName);
|
|
238
298
|
const favIcon = await component.favicon();
|
|
239
299
|
if (Is.uint8Array(favIcon)) {
|
|
@@ -255,7 +315,7 @@ async function serverFavIcon(httpRequestContext, componentName, request) {
|
|
|
255
315
|
* @param request The request.
|
|
256
316
|
* @returns The response object with additional http response properties.
|
|
257
317
|
*/
|
|
258
|
-
async function serverSpec(httpRequestContext, componentName, request) {
|
|
318
|
+
export async function serverSpec(httpRequestContext, componentName, request) {
|
|
259
319
|
const component = ComponentFactory.get(componentName);
|
|
260
320
|
const spec = await component.spec();
|
|
261
321
|
if (Is.objectValue(spec)) {
|
|
@@ -267,171 +327,4 @@ async function serverSpec(httpRequestContext, componentName, request) {
|
|
|
267
327
|
statusCode: HttpStatusCode.notFound
|
|
268
328
|
};
|
|
269
329
|
}
|
|
270
|
-
|
|
271
|
-
// Copyright 2024 IOTA Stiftung.
|
|
272
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
273
|
-
/**
|
|
274
|
-
* The information service for the server.
|
|
275
|
-
*/
|
|
276
|
-
class InformationService {
|
|
277
|
-
/**
|
|
278
|
-
* Runtime name for the class.
|
|
279
|
-
*/
|
|
280
|
-
CLASS_NAME = "InformationService";
|
|
281
|
-
/**
|
|
282
|
-
* The server information.
|
|
283
|
-
* @internal
|
|
284
|
-
*/
|
|
285
|
-
_serverInfo;
|
|
286
|
-
/**
|
|
287
|
-
* The server health.
|
|
288
|
-
* @internal
|
|
289
|
-
*/
|
|
290
|
-
_healthInfo;
|
|
291
|
-
/**
|
|
292
|
-
* The path to the favicon Spec.
|
|
293
|
-
* @internal
|
|
294
|
-
*/
|
|
295
|
-
_faviconPath;
|
|
296
|
-
/**
|
|
297
|
-
* The favicon.
|
|
298
|
-
* @internal
|
|
299
|
-
*/
|
|
300
|
-
_favicon;
|
|
301
|
-
/**
|
|
302
|
-
* The path to the OpenAPI Spec.
|
|
303
|
-
* @internal
|
|
304
|
-
*/
|
|
305
|
-
_openApiSpecPath;
|
|
306
|
-
/**
|
|
307
|
-
* The OpenAPI spec.
|
|
308
|
-
* @internal
|
|
309
|
-
*/
|
|
310
|
-
_openApiSpec;
|
|
311
|
-
/**
|
|
312
|
-
* Create a new instance of InformationService.
|
|
313
|
-
* @param options The options to create the service.
|
|
314
|
-
*/
|
|
315
|
-
constructor(options) {
|
|
316
|
-
Guards.object(this.CLASS_NAME, "options", options);
|
|
317
|
-
Guards.object(this.CLASS_NAME, "options.config", options.config);
|
|
318
|
-
Guards.object(this.CLASS_NAME, "options.config.serverInfo", options.config.serverInfo);
|
|
319
|
-
this._serverInfo = options.config.serverInfo;
|
|
320
|
-
this._healthInfo = {
|
|
321
|
-
status: "ok"
|
|
322
|
-
};
|
|
323
|
-
this._faviconPath = options.config.favIconPath;
|
|
324
|
-
this._openApiSpecPath = options.config.openApiSpecPath;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* The service needs to be started when the application is initialized.
|
|
328
|
-
* @returns Nothing.
|
|
329
|
-
*/
|
|
330
|
-
async start() {
|
|
331
|
-
const openApiPath = this._openApiSpecPath;
|
|
332
|
-
if (Is.stringValue(openApiPath)) {
|
|
333
|
-
const contentBuffer = await readFile(openApiPath, "utf8");
|
|
334
|
-
this._openApiSpec = JSON.parse(contentBuffer);
|
|
335
|
-
}
|
|
336
|
-
const favIconPath = this._faviconPath;
|
|
337
|
-
if (Is.stringValue(favIconPath)) {
|
|
338
|
-
this._favicon = await readFile(favIconPath);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Get the root information.
|
|
343
|
-
* @returns The root information.
|
|
344
|
-
*/
|
|
345
|
-
async root() {
|
|
346
|
-
return `${this._serverInfo.name} - ${this._serverInfo.version}`;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Get the server information.
|
|
350
|
-
* @returns The service information.
|
|
351
|
-
*/
|
|
352
|
-
async info() {
|
|
353
|
-
return this._serverInfo;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Get the favicon.
|
|
357
|
-
* @returns The favicon.
|
|
358
|
-
*/
|
|
359
|
-
async favicon() {
|
|
360
|
-
return this._favicon;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Get the OpenAPI spec.
|
|
364
|
-
* @returns The OpenAPI spec.
|
|
365
|
-
*/
|
|
366
|
-
async spec() {
|
|
367
|
-
return this._openApiSpec;
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Get the server health.
|
|
371
|
-
* @returns The service health.
|
|
372
|
-
*/
|
|
373
|
-
async health() {
|
|
374
|
-
let errorCount = 0;
|
|
375
|
-
let warningCount = 0;
|
|
376
|
-
if (Is.arrayValue(this._healthInfo.components)) {
|
|
377
|
-
errorCount = this._healthInfo.components.filter(c => c.status === "error").length;
|
|
378
|
-
warningCount = this._healthInfo.components.filter(c => c.status === "warning").length;
|
|
379
|
-
}
|
|
380
|
-
if (errorCount > 0) {
|
|
381
|
-
this._healthInfo.status = "error";
|
|
382
|
-
}
|
|
383
|
-
else if (warningCount > 0) {
|
|
384
|
-
this._healthInfo.status = "warning";
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
this._healthInfo.status = "ok";
|
|
388
|
-
}
|
|
389
|
-
return this._healthInfo;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Set the status of a component.
|
|
393
|
-
* @param name The component name.
|
|
394
|
-
* @param status The status of the component.
|
|
395
|
-
* @param details The details for the status.
|
|
396
|
-
* @returns Nothing.
|
|
397
|
-
*/
|
|
398
|
-
async setComponentHealth(name, status, details) {
|
|
399
|
-
const component = this._healthInfo.components?.find(c => c.name === name);
|
|
400
|
-
if (Is.undefined(component)) {
|
|
401
|
-
this._healthInfo.components ??= [];
|
|
402
|
-
this._healthInfo.components.push({
|
|
403
|
-
name,
|
|
404
|
-
status,
|
|
405
|
-
details
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
component.status = status;
|
|
410
|
-
component.details = details;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Remove the status of a component.
|
|
415
|
-
* @param name The component name.
|
|
416
|
-
* @returns Nothing.
|
|
417
|
-
*/
|
|
418
|
-
async removeComponentHealth(name) {
|
|
419
|
-
if (Is.arrayValue(this._healthInfo.components)) {
|
|
420
|
-
const componentIndex = this._healthInfo.components.findIndex(c => c.name === name);
|
|
421
|
-
if (componentIndex !== -1) {
|
|
422
|
-
this._healthInfo.components.splice(componentIndex, 1);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const restEntryPoints = [
|
|
429
|
-
{
|
|
430
|
-
name: "information",
|
|
431
|
-
defaultBaseRoute: "",
|
|
432
|
-
tags: tagsInformation,
|
|
433
|
-
generateRoutes: generateRestRoutesInformation
|
|
434
|
-
}
|
|
435
|
-
];
|
|
436
|
-
|
|
437
|
-
export { InformationService, generateRestRoutesInformation, restEntryPoints, serverFavIcon, serverHealth, serverInfo, serverRoot, serverSpec, tagsInformation };
|
|
330
|
+
//# sourceMappingURL=informationRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"informationRoutes.js","sourceRoot":"","sources":["../../src/informationRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHttpRequestContext,\n\tIInformationComponent,\n\tINoContentRequest,\n\tIRestRoute,\n\tIServerFavIconResponse,\n\tIServerHealthResponse,\n\tIServerInfoResponse,\n\tIServerLivezResponse,\n\tIServerRootResponse,\n\tIServerSpecResponse,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@twin.org/web\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsInformation: ITag[] = [\n\t{\n\t\tname: \"Info\",\n\t\tdescription: \"Information endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for server information.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesInformation(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst rootRoute: IRestRoute = {\n\t\toperationId: \"serverRoot\",\n\t\tsummary: \"Get the root text page\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverRoot(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerRootResponse>(),\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"serverRootResponse\",\n\t\t\t\t\t\tdescription: \"The response for the root request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"API Server - 1.0.0\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst informationRoute: IRestRoute<INoContentRequest, IServerInfoResponse> = {\n\t\toperationId: \"serverInformation\",\n\t\tsummary: \"Get the information for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/info`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverInfo(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerInfoResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"informationResponse\",\n\t\t\t\t\t\tdescription: \"The response for the information request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tname: \"API Server\",\n\t\t\t\t\t\t\t\tversion: \"1.0.0\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst favIconRoute: IRestRoute<INoContentRequest, IServerFavIconResponse> = {\n\t\toperationId: \"serverFavIcon\",\n\t\tsummary: \"Get the favicon for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/favicon.ico`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverFavIcon(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerFavIconResponse>(),\n\t\t\t\tmimeType: \"image/x-icon\"\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst livezRoute: IRestRoute<INoContentRequest, IServerLivezResponse> = {\n\t\toperationId: \"serverLivez\",\n\t\tsummary: \"Get the livez status for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/livez`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverLivez(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerLivezResponse>(),\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"ok\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseFailure\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"failed\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst healthRoute: IRestRoute<INoContentRequest, IServerHealthResponse> = {\n\t\toperationId: \"serverHealth\",\n\t\tsummary: \"Get the health for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/health`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverHealth(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerHealthResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the health request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseWarning\",\n\t\t\t\t\t\tdescription: \"The response for the health request with warnings.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: \"warning\",\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"warning\",\n\t\t\t\t\t\t\t\t\t\tdetails: \"The database is running slow.\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseError\",\n\t\t\t\t\t\tdescription: \"The response for the health request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tname: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\t\tdetails: \"The storage is full.\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst specRoute: IRestRoute<INoContentRequest, IServerSpecResponse> = {\n\t\toperationId: \"serverSpec\",\n\t\tsummary: \"Get the OpenAPI specification for the endpoints\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/spec`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverSpec(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerSpecResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"specResponse\",\n\t\t\t\t\t\tdescription: \"The response for the spec request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\topenapi: \"3.1.0\",\n\t\t\t\t\t\t\t\tinfo: {},\n\t\t\t\t\t\t\t\tpaths: {}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\treturn [rootRoute, favIconRoute, informationRoute, livezRoute, healthRoute, specRoute];\n}\n\n/**\n * Get the root for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverRoot(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerRootResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: await component.root()\n\t};\n}\n\n/**\n * Get the information for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverInfo(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerInfoResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\tbody: await component.info()\n\t};\n}\n\n/**\n * Get the livez for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverLivez(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerLivezResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: (await component.livez()) ? \"ok\" : \"failed\"\n\t};\n}\n\n/**\n * Get the health for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverHealth(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerHealthResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\tbody: await component.health()\n\t};\n}\n\n/**\n * Get the favicon for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverFavIcon(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerFavIconResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst favIcon = await component.favicon();\n\n\tif (Is.uint8Array(favIcon)) {\n\t\treturn {\n\t\t\theaders: {\n\t\t\t\t[HeaderTypes.ContentType]: \"image/x-icon\"\n\t\t\t},\n\t\t\tbody: favIcon\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n\n/**\n * Get the spec for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverSpec(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerSpecResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst spec = await component.spec();\n\n\tif (Is.objectValue(spec)) {\n\t\treturn {\n\t\t\tbody: spec\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n"]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
5
|
+
import { Guards, Is } from "@twin.org/core";
|
|
6
|
+
/**
|
|
7
|
+
* The information service for the server.
|
|
8
|
+
*/
|
|
9
|
+
export class InformationService {
|
|
10
|
+
/**
|
|
11
|
+
* Runtime name for the class.
|
|
12
|
+
*/
|
|
13
|
+
static CLASS_NAME = "InformationService";
|
|
14
|
+
/**
|
|
15
|
+
* The server information.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
_serverInfo;
|
|
19
|
+
/**
|
|
20
|
+
* The server health.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
_healthInfo;
|
|
24
|
+
/**
|
|
25
|
+
* The path to the favicon Spec.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
_faviconPath;
|
|
29
|
+
/**
|
|
30
|
+
* The favicon.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
_favicon;
|
|
34
|
+
/**
|
|
35
|
+
* The path to the OpenAPI Spec.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
_openApiSpecPath;
|
|
39
|
+
/**
|
|
40
|
+
* The OpenAPI spec.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
_openApiSpec;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new instance of InformationService.
|
|
46
|
+
* @param options The options to create the service.
|
|
47
|
+
*/
|
|
48
|
+
constructor(options) {
|
|
49
|
+
Guards.object(InformationService.CLASS_NAME, "options", options);
|
|
50
|
+
Guards.object(InformationService.CLASS_NAME, "options.config", options.config);
|
|
51
|
+
Guards.object(InformationService.CLASS_NAME, "options.config.serverInfo", options.config.serverInfo);
|
|
52
|
+
this._serverInfo = options.config.serverInfo;
|
|
53
|
+
this._healthInfo = {
|
|
54
|
+
status: "ok"
|
|
55
|
+
};
|
|
56
|
+
this._faviconPath = options.config.favIconPath;
|
|
57
|
+
this._openApiSpecPath = options.config.openApiSpecPath;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns the class name of the component.
|
|
61
|
+
* @returns The class name of the component.
|
|
62
|
+
*/
|
|
63
|
+
className() {
|
|
64
|
+
return InformationService.CLASS_NAME;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The service needs to be started when the application is initialized.
|
|
68
|
+
* @returns Nothing.
|
|
69
|
+
*/
|
|
70
|
+
async start() {
|
|
71
|
+
const openApiPath = this._openApiSpecPath;
|
|
72
|
+
if (Is.stringValue(openApiPath)) {
|
|
73
|
+
const contentBuffer = await readFile(openApiPath, "utf8");
|
|
74
|
+
this._openApiSpec = JSON.parse(contentBuffer);
|
|
75
|
+
}
|
|
76
|
+
const favIconPath = this._faviconPath;
|
|
77
|
+
if (Is.stringValue(favIconPath)) {
|
|
78
|
+
this._favicon = await readFile(favIconPath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the root information.
|
|
83
|
+
* @returns The root information.
|
|
84
|
+
*/
|
|
85
|
+
async root() {
|
|
86
|
+
return `${this._serverInfo.name} - ${this._serverInfo.version}`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the server information.
|
|
90
|
+
* @returns The service information.
|
|
91
|
+
*/
|
|
92
|
+
async info() {
|
|
93
|
+
return this._serverInfo;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the favicon.
|
|
97
|
+
* @returns The favicon.
|
|
98
|
+
*/
|
|
99
|
+
async favicon() {
|
|
100
|
+
return this._favicon;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the OpenAPI spec.
|
|
104
|
+
* @returns The OpenAPI spec.
|
|
105
|
+
*/
|
|
106
|
+
async spec() {
|
|
107
|
+
return this._openApiSpec;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Is the server live.
|
|
111
|
+
* @returns True if the server is live.
|
|
112
|
+
*/
|
|
113
|
+
async livez() {
|
|
114
|
+
let errorCount = 0;
|
|
115
|
+
if (Is.arrayValue(this._healthInfo.components)) {
|
|
116
|
+
errorCount = this._healthInfo.components.filter(c => c.status === "error").length;
|
|
117
|
+
}
|
|
118
|
+
return errorCount === 0;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the server health.
|
|
122
|
+
* @returns The service health.
|
|
123
|
+
*/
|
|
124
|
+
async health() {
|
|
125
|
+
let errorCount = 0;
|
|
126
|
+
let warningCount = 0;
|
|
127
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
128
|
+
const tenantId = contextIds?.[ContextIdKeys.Tenant];
|
|
129
|
+
// Filter so we only get components that are not tenant specific or match the tenant id
|
|
130
|
+
const components = this._healthInfo.components?.filter(c => Is.empty(c.tenantId) || c.tenantId === tenantId);
|
|
131
|
+
if (Is.arrayValue(components)) {
|
|
132
|
+
errorCount = components.filter(c => c.status === "error").length;
|
|
133
|
+
warningCount = components.filter(c => c.status === "warning").length;
|
|
134
|
+
}
|
|
135
|
+
if (errorCount > 0) {
|
|
136
|
+
this._healthInfo.status = "error";
|
|
137
|
+
}
|
|
138
|
+
else if (warningCount > 0) {
|
|
139
|
+
this._healthInfo.status = "warning";
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this._healthInfo.status = "ok";
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
status: this._healthInfo.status,
|
|
146
|
+
components: components?.map(c => ({
|
|
147
|
+
name: c.name,
|
|
148
|
+
status: c.status,
|
|
149
|
+
details: c.details
|
|
150
|
+
}))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Set the status of a component.
|
|
155
|
+
* @param name The component name.
|
|
156
|
+
* @param status The status of the component.
|
|
157
|
+
* @param details The details for the status.
|
|
158
|
+
* @param tenantId The tenant id, optional if the health status is not tenant specific.
|
|
159
|
+
* @returns Nothing.
|
|
160
|
+
*/
|
|
161
|
+
async setComponentHealth(name, status, details, tenantId) {
|
|
162
|
+
const component = Is.empty(tenantId)
|
|
163
|
+
? this._healthInfo.components?.find(c => c.name === name && Is.empty(c.tenantId))
|
|
164
|
+
: this._healthInfo.components?.find(c => c.name === name && c.tenantId === tenantId);
|
|
165
|
+
if (Is.undefined(component)) {
|
|
166
|
+
this._healthInfo.components ??= [];
|
|
167
|
+
this._healthInfo.components.push({
|
|
168
|
+
name,
|
|
169
|
+
status,
|
|
170
|
+
details,
|
|
171
|
+
tenantId
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
component.status = status;
|
|
176
|
+
component.details = details;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Remove the status of a component.
|
|
181
|
+
* @param name The component name.
|
|
182
|
+
* @param tenantId The tenant id, optional if the health status is not tenant specific.
|
|
183
|
+
* @returns Nothing.
|
|
184
|
+
*/
|
|
185
|
+
async removeComponentHealth(name, tenantId) {
|
|
186
|
+
if (Is.arrayValue(this._healthInfo.components)) {
|
|
187
|
+
const componentIndex = Is.empty(tenantId)
|
|
188
|
+
? this._healthInfo.components?.findIndex(c => c.name === name && Is.empty(c.tenantId))
|
|
189
|
+
: this._healthInfo.components?.findIndex(c => c.name === name && c.tenantId === tenantId);
|
|
190
|
+
if (componentIndex !== -1) {
|
|
191
|
+
this._healthInfo.components.splice(componentIndex, 1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=informationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"informationService.js","sourceRoot":"","sources":["../../src/informationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { readFile } from \"node:fs/promises\";\nimport type {\n\tHealthStatus,\n\tIHealthComponentInfo,\n\tIHealthInfo,\n\tIInformationComponent,\n\tIServerInfo\n} from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { Guards, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IInformationServiceConstructorOptions } from \"./models/IInformationServiceConstructorOptions.js\";\n\n/**\n * The information service for the server.\n */\nexport class InformationService implements IInformationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<InformationService>();\n\n\t/**\n\t * The server information.\n\t * @internal\n\t */\n\tprivate readonly _serverInfo: IServerInfo;\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate readonly _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents?: (IHealthComponentInfo & { tenantId?: string })[];\n\t};\n\n\t/**\n\t * The path to the favicon Spec.\n\t * @internal\n\t */\n\tprivate readonly _faviconPath?: string;\n\n\t/**\n\t * The favicon.\n\t * @internal\n\t */\n\tprivate _favicon?: Uint8Array;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t * @internal\n\t */\n\tprivate readonly _openApiSpecPath?: string;\n\n\t/**\n\t * The OpenAPI spec.\n\t * @internal\n\t */\n\tprivate _openApiSpec?: string;\n\n\t/**\n\t * Create a new instance of InformationService.\n\t * @param options The options to create the service.\n\t */\n\tconstructor(options: IInformationServiceConstructorOptions) {\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options), options);\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.object(\n\t\t\tInformationService.CLASS_NAME,\n\t\t\tnameof(options.config.serverInfo),\n\t\t\toptions.config.serverInfo\n\t\t);\n\n\t\tthis._serverInfo = options.config.serverInfo;\n\t\tthis._healthInfo = {\n\t\t\tstatus: \"ok\"\n\t\t};\n\t\tthis._faviconPath = options.config.favIconPath;\n\t\tthis._openApiSpecPath = options.config.openApiSpecPath;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn InformationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @returns Nothing.\n\t */\n\tpublic async start(): Promise<void> {\n\t\tconst openApiPath = this._openApiSpecPath;\n\t\tif (Is.stringValue(openApiPath)) {\n\t\t\tconst contentBuffer = await readFile(openApiPath, \"utf8\");\n\t\t\tthis._openApiSpec = JSON.parse(contentBuffer);\n\t\t}\n\n\t\tconst favIconPath = this._faviconPath;\n\t\tif (Is.stringValue(favIconPath)) {\n\t\t\tthis._favicon = await readFile(favIconPath);\n\t\t}\n\t}\n\n\t/**\n\t * Get the root information.\n\t * @returns The root information.\n\t */\n\tpublic async root(): Promise<string> {\n\t\treturn `${this._serverInfo.name} - ${this._serverInfo.version}`;\n\t}\n\n\t/**\n\t * Get the server information.\n\t * @returns The service information.\n\t */\n\tpublic async info(): Promise<IServerInfo> {\n\t\treturn this._serverInfo;\n\t}\n\n\t/**\n\t * Get the favicon.\n\t * @returns The favicon.\n\t */\n\tpublic async favicon(): Promise<Uint8Array | undefined> {\n\t\treturn this._favicon;\n\t}\n\n\t/**\n\t * Get the OpenAPI spec.\n\t * @returns The OpenAPI spec.\n\t */\n\tpublic async spec(): Promise<unknown> {\n\t\treturn this._openApiSpec;\n\t}\n\n\t/**\n\t * Is the server live.\n\t * @returns True if the server is live.\n\t */\n\tpublic async livez(): Promise<boolean> {\n\t\tlet errorCount = 0;\n\n\t\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\terrorCount = this._healthInfo.components.filter(c => c.status === \"error\").length;\n\t\t}\n\n\t\treturn errorCount === 0;\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async health(): Promise<IHealthInfo> {\n\t\tlet errorCount = 0;\n\t\tlet warningCount = 0;\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst tenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t// Filter so we only get components that are not tenant specific or match the tenant id\n\t\tconst components = this._healthInfo.components?.filter(\n\t\t\tc => Is.empty(c.tenantId) || c.tenantId === tenantId\n\t\t);\n\n\t\tif (Is.arrayValue(components)) {\n\t\t\terrorCount = components.filter(c => c.status === \"error\").length;\n\t\t\twarningCount = components.filter(c => c.status === \"warning\").length;\n\t\t}\n\n\t\tif (errorCount > 0) {\n\t\t\tthis._healthInfo.status = \"error\";\n\t\t} else if (warningCount > 0) {\n\t\t\tthis._healthInfo.status = \"warning\";\n\t\t} else {\n\t\t\tthis._healthInfo.status = \"ok\";\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: this._healthInfo.status,\n\t\t\tcomponents: components?.map(c => ({\n\t\t\t\tname: c.name,\n\t\t\t\tstatus: c.status,\n\t\t\t\tdetails: c.details\n\t\t\t}))\n\t\t};\n\t}\n\n\t/**\n\t * Set the status of a component.\n\t * @param name The component name.\n\t * @param status The status of the component.\n\t * @param details The details for the status.\n\t * @param tenantId The tenant id, optional if the health status is not tenant specific.\n\t * @returns Nothing.\n\t */\n\tpublic async setComponentHealth(\n\t\tname: string,\n\t\tstatus: HealthStatus,\n\t\tdetails?: string,\n\t\ttenantId?: string\n\t): Promise<void> {\n\t\tconst component = Is.empty(tenantId)\n\t\t\t? this._healthInfo.components?.find(c => c.name === name && Is.empty(c.tenantId))\n\t\t\t: this._healthInfo.components?.find(c => c.name === name && c.tenantId === tenantId);\n\n\t\tif (Is.undefined(component)) {\n\t\t\tthis._healthInfo.components ??= [];\n\t\t\tthis._healthInfo.components.push({\n\t\t\t\tname,\n\t\t\t\tstatus,\n\t\t\t\tdetails,\n\t\t\t\ttenantId\n\t\t\t});\n\t\t} else {\n\t\t\tcomponent.status = status;\n\t\t\tcomponent.details = details;\n\t\t}\n\t}\n\n\t/**\n\t * Remove the status of a component.\n\t * @param name The component name.\n\t * @param tenantId The tenant id, optional if the health status is not tenant specific.\n\t * @returns Nothing.\n\t */\n\tpublic async removeComponentHealth(name: string, tenantId?: string): Promise<void> {\n\t\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\tconst componentIndex = Is.empty(tenantId)\n\t\t\t\t? this._healthInfo.components?.findIndex(c => c.name === name && Is.empty(c.tenantId))\n\t\t\t\t: this._healthInfo.components?.findIndex(c => c.name === name && c.tenantId === tenantId);\n\n\t\t\tif (componentIndex !== -1) {\n\t\t\t\tthis._healthInfo.components.splice(componentIndex, 1);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IInformationServiceConfig.js","sourceRoot":"","sources":["../../../src/models/IInformationServiceConfig.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IServerInfo } from \"@twin.org/api-models\";\n\n/**\n * Configuration for the information service.\n */\nexport interface IInformationServiceConfig {\n\t/**\n\t * The server information.\n\t */\n\tserverInfo: IServerInfo;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t */\n\topenApiSpecPath?: string;\n\n\t/**\n\t * The path to the favicon.\n\t */\n\tfavIconPath?: string;\n}\n"]}
|