@3sixty/api-service 0.0.0-stage → 0.20.0-next.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/LICENSE +201 -0
- package/README.md +24 -2
- package/dist/es/healthApplicationTask.js +151 -0
- package/dist/es/healthApplicationTask.js.map +1 -0
- package/dist/es/healthRoutes.js +125 -0
- package/dist/es/healthRoutes.js.map +1 -0
- package/dist/es/healthService.js +308 -0
- package/dist/es/healthService.js.map +1 -0
- package/dist/es/index.js +15 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/informationRoutes.js +298 -0
- package/dist/es/informationRoutes.js.map +1 -0
- package/dist/es/informationService.js +123 -0
- package/dist/es/informationService.js.map +1 -0
- package/dist/es/models/IHealthServiceConfig.js +4 -0
- package/dist/es/models/IHealthServiceConfig.js.map +1 -0
- package/dist/es/models/IHealthServiceConstructorOptions.js +2 -0
- package/dist/es/models/IHealthServiceConstructorOptions.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/models/IPlatformServiceConfig.js +4 -0
- package/dist/es/models/IPlatformServiceConfig.js.map +1 -0
- package/dist/es/models/IPlatformServiceConstructorOptions.js +2 -0
- package/dist/es/models/IPlatformServiceConstructorOptions.js.map +1 -0
- package/dist/es/platformService.js +211 -0
- package/dist/es/platformService.js.map +1 -0
- package/dist/es/restEntryPoints.js +20 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/types/healthApplicationTask.d.ts +20 -0
- package/dist/types/healthRoutes.d.ts +20 -0
- package/dist/types/healthService.d.ts +42 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/informationRoutes.d.ts +60 -0
- package/dist/types/informationService.d.ts +60 -0
- package/dist/types/models/IHealthServiceConfig.d.ts +37 -0
- package/dist/types/models/IHealthServiceConstructorOptions.d.ts +15 -0
- package/dist/types/models/IInformationServiceConfig.d.ts +18 -0
- package/dist/types/models/IInformationServiceConstructorOptions.d.ts +10 -0
- package/dist/types/models/IPlatformServiceConfig.d.ts +10 -0
- package/dist/types/models/IPlatformServiceConstructorOptions.d.ts +19 -0
- package/dist/types/platformService.d.ts +57 -0
- package/dist/types/restEntryPoints.d.ts +5 -0
- package/docs/changelog.md +17 -0
- package/docs/examples.md +74 -0
- package/docs/open-api/spec.json +712 -0
- package/docs/reference/classes/HealthService.md +127 -0
- package/docs/reference/classes/InformationService.md +179 -0
- package/docs/reference/classes/PlatformService.md +209 -0
- package/docs/reference/functions/generateRestRoutesHealth.md +25 -0
- package/docs/reference/functions/generateRestRoutesInformation.md +25 -0
- package/docs/reference/functions/serverFavIcon.md +31 -0
- package/docs/reference/functions/serverHealth.md +31 -0
- package/docs/reference/functions/serverInfo.md +31 -0
- package/docs/reference/functions/serverLivez.md +31 -0
- package/docs/reference/functions/serverReadyz.md +31 -0
- package/docs/reference/functions/serverRoot.md +31 -0
- package/docs/reference/functions/serverSpec.md +31 -0
- package/docs/reference/index.md +34 -0
- package/docs/reference/interfaces/IHealthServiceConfig.md +79 -0
- package/docs/reference/interfaces/IHealthServiceConstructorOptions.md +25 -0
- package/docs/reference/interfaces/IInformationServiceConfig.md +27 -0
- package/docs/reference/interfaces/IInformationServiceConstructorOptions.md +11 -0
- package/docs/reference/interfaces/IPlatformServiceConfig.md +17 -0
- package/docs/reference/interfaces/IPlatformServiceConstructorOptions.md +33 -0
- package/docs/reference/variables/restEntryPoints.md +5 -0
- package/docs/reference/variables/tagsHealth.md +5 -0
- package/docs/reference/variables/tagsInformation.md +5 -0
- package/locales/en.json +14 -0
- package/package.json +57 -5
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { HealthStatus } from "@3sixty/api-models";
|
|
4
|
+
import { TaskStatus } from "@3sixty/background-task-models";
|
|
5
|
+
import { BaseError, ComponentFactory, Factory, Is } from "@3sixty/core";
|
|
6
|
+
import { ModuleHelper } from "@3sixty/modules";
|
|
7
|
+
/**
|
|
8
|
+
* The health service for the server.
|
|
9
|
+
*/
|
|
10
|
+
export class HealthService {
|
|
11
|
+
/**
|
|
12
|
+
* Runtime name for the class.
|
|
13
|
+
*/
|
|
14
|
+
static CLASS_NAME = "HealthService";
|
|
15
|
+
/**
|
|
16
|
+
* Task type identifier for the application health background task.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
static _APPLICATION_HEALTH_TASK_TYPE = "health-application-check";
|
|
20
|
+
/**
|
|
21
|
+
* The server health.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
_healthInfo;
|
|
25
|
+
/**
|
|
26
|
+
* The interval for checking the health of the components.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
_healthCheckInterval;
|
|
30
|
+
/**
|
|
31
|
+
* The interval for running the application health lifecycle (init, application, teardown).
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
_healthApplicationCheckInterval;
|
|
35
|
+
/**
|
|
36
|
+
* The initial interval for checking the health of the components.
|
|
37
|
+
* This is used to check the health of the components immediately after the service is started.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
_initialInterval;
|
|
41
|
+
/**
|
|
42
|
+
* Timer for the regular health check.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
_healthTimer;
|
|
46
|
+
/**
|
|
47
|
+
* Timer for the application health lifecycle.
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
_healthApplicationTimer;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the service has been started.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
_started;
|
|
56
|
+
/**
|
|
57
|
+
* Health entries from the most recently completed regular health check.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
_regularComponents;
|
|
61
|
+
/**
|
|
62
|
+
* Health entries from the most recently completed application health cycle.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
_applicationComponents;
|
|
66
|
+
/**
|
|
67
|
+
* Whether to include stack traces in health check error details.
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
_includeErrorStack;
|
|
71
|
+
/**
|
|
72
|
+
* The background task component for running application health checks.
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
_backgroundTaskComponent;
|
|
76
|
+
/**
|
|
77
|
+
* The URL of the module to use for the application health background task.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
_applicationHealthTaskHandler;
|
|
81
|
+
/**
|
|
82
|
+
* Regular expression patterns for component types to exclude from the health engine clone,
|
|
83
|
+
* verified when the service is started.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
_excludeCloneComponents;
|
|
87
|
+
/**
|
|
88
|
+
* Create a new instance of HealthService.
|
|
89
|
+
* @param options The constructor options.
|
|
90
|
+
*/
|
|
91
|
+
constructor(options) {
|
|
92
|
+
this._healthInfo = { status: HealthStatus.Ok, components: [] };
|
|
93
|
+
this._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;
|
|
94
|
+
this._healthApplicationCheckInterval =
|
|
95
|
+
options?.config?.healthCheckApplicationInterval ?? 300000;
|
|
96
|
+
this._initialInterval = options?.config?.initialInterval ?? 2000;
|
|
97
|
+
this._includeErrorStack = options?.config?.includeErrorStack;
|
|
98
|
+
this._backgroundTaskComponent = ComponentFactory.get(options?.backgroundTaskComponentType ?? "background-task");
|
|
99
|
+
this._applicationHealthTaskHandler =
|
|
100
|
+
options?.config?.overrideApplicationHealthTaskHandler ??
|
|
101
|
+
new URL("./healthApplicationTask.js", import.meta.url).href;
|
|
102
|
+
this._excludeCloneComponents = options?.config?.excludeCloneComponents;
|
|
103
|
+
this._started = false;
|
|
104
|
+
this._regularComponents = [];
|
|
105
|
+
this._applicationComponents = [];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns the class name of the component.
|
|
109
|
+
* @returns The class name of the component.
|
|
110
|
+
*/
|
|
111
|
+
className() {
|
|
112
|
+
return HealthService.CLASS_NAME;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The component needs to be started when the node is initialized.
|
|
116
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
117
|
+
* @returns A promise that resolves when the initial health check timers have been scheduled.
|
|
118
|
+
* @throws GeneralError if an exclude clone component is not a valid regular expression.
|
|
119
|
+
*/
|
|
120
|
+
async start(nodeLoggingComponentType) {
|
|
121
|
+
if (!this._started) {
|
|
122
|
+
// Only load the engine models module when there is something to verify, so a node
|
|
123
|
+
// with no configured exclusions never has to resolve it.
|
|
124
|
+
if (Is.arrayValue(this._excludeCloneComponents)) {
|
|
125
|
+
this._excludeCloneComponents = await ModuleHelper.execModuleMethod("@3sixty/engine-models", "EngineCloneHelper.verifyExcludeCloneComponents", [this._excludeCloneComponents]);
|
|
126
|
+
}
|
|
127
|
+
this._started = true;
|
|
128
|
+
await this._backgroundTaskComponent.registerHandler(HealthService._APPLICATION_HEALTH_TASK_TYPE, this._applicationHealthTaskHandler, "healthApplicationTask", async (task) => {
|
|
129
|
+
if (task.status === TaskStatus.Success ||
|
|
130
|
+
task.status === TaskStatus.Failed ||
|
|
131
|
+
task.status === TaskStatus.Cancelled) {
|
|
132
|
+
if (task.status === TaskStatus.Success && Is.array(task.result)) {
|
|
133
|
+
this._applicationComponents = task.result;
|
|
134
|
+
this.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);
|
|
135
|
+
}
|
|
136
|
+
else if (task.status === TaskStatus.Failed) {
|
|
137
|
+
const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
138
|
+
await nodeLogging?.log({
|
|
139
|
+
level: "error",
|
|
140
|
+
source: HealthService.CLASS_NAME,
|
|
141
|
+
message: "applicationHealthCheckTaskFailed",
|
|
142
|
+
error: Is.object(task.error) ? BaseError.fromError(task.error) : undefined
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
this.startApplicationTimer(this._healthApplicationCheckInterval);
|
|
146
|
+
}
|
|
147
|
+
}, {
|
|
148
|
+
idleShutdownTimeout: -1,
|
|
149
|
+
initialiseMethod: "healthApplicationTaskStart",
|
|
150
|
+
initialiseMethodParams: async () => [this._excludeCloneComponents],
|
|
151
|
+
shutdownMethod: "healthApplicationTaskEnd"
|
|
152
|
+
});
|
|
153
|
+
// Immediately check health after a startup settling period.
|
|
154
|
+
// The interval for the next checks are triggered on success of the current
|
|
155
|
+
// check to prevent overlapping checks in case of long running health checks.
|
|
156
|
+
this.startTimer(nodeLoggingComponentType, this._initialInterval);
|
|
157
|
+
this.startApplicationTimer(this._initialInterval);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The component needs to be stopped when the node is closed.
|
|
162
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
163
|
+
* @returns A promise that resolves when the health check timers have been cancelled.
|
|
164
|
+
*/
|
|
165
|
+
async stop(nodeLoggingComponentType) {
|
|
166
|
+
if (this._started) {
|
|
167
|
+
this._started = false;
|
|
168
|
+
this.stopTimer();
|
|
169
|
+
this.stopApplicationTimer();
|
|
170
|
+
await this._backgroundTaskComponent.unregisterHandler(HealthService._APPLICATION_HEALTH_TASK_TYPE);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get the server health.
|
|
175
|
+
* @returns The service health.
|
|
176
|
+
*/
|
|
177
|
+
async healthStatus() {
|
|
178
|
+
return this._healthInfo;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check the health of all registered components by calling health() on each.
|
|
182
|
+
* @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.
|
|
183
|
+
* @returns A promise that resolves when all component health checks are complete and the next check is scheduled.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
async checkHealth(nodeLoggingComponentType) {
|
|
187
|
+
this.stopTimer();
|
|
188
|
+
const engineCoreFactory = Factory.getFactory("engine-core");
|
|
189
|
+
if (engineCoreFactory) {
|
|
190
|
+
// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.
|
|
191
|
+
const engineCore = engineCoreFactory.getIfExists("engine");
|
|
192
|
+
if (engineCore) {
|
|
193
|
+
const registeredInstances = await engineCore.getRegisteredComponents();
|
|
194
|
+
const allHealth = [];
|
|
195
|
+
for (const registeredInstance of registeredInstances) {
|
|
196
|
+
if (Is.object(registeredInstance.component)) {
|
|
197
|
+
const healthMethod = registeredInstance.component.health?.bind(registeredInstance.component);
|
|
198
|
+
if (Is.function(healthMethod)) {
|
|
199
|
+
try {
|
|
200
|
+
allHealth.push(...(await healthMethod()));
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
204
|
+
await nodeLogging?.log({
|
|
205
|
+
level: "error",
|
|
206
|
+
source: HealthService.CLASS_NAME,
|
|
207
|
+
message: "componentHealthCheckFailed",
|
|
208
|
+
data: { className: registeredInstance.component.className() },
|
|
209
|
+
error: BaseError.fromError(error)
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this._regularComponents = allHealth;
|
|
216
|
+
this.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);
|
|
217
|
+
this.startTimer(nodeLoggingComponentType, this._healthCheckInterval);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Group raw health entries by name, collapsing duplicates into a parent with a grouped array.
|
|
223
|
+
* @param entries The flat list of health entries from all components.
|
|
224
|
+
* @internal
|
|
225
|
+
*/
|
|
226
|
+
groupHealthByName(entries) {
|
|
227
|
+
const bySource = new Map();
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const existing = bySource.get(entry.source) ?? [];
|
|
230
|
+
existing.push({
|
|
231
|
+
...entry,
|
|
232
|
+
error: !Is.empty(entry.error)
|
|
233
|
+
? BaseError.fromError(entry.error).toJsonObject(this._includeErrorStack)
|
|
234
|
+
: undefined
|
|
235
|
+
});
|
|
236
|
+
bySource.set(entry.source, existing);
|
|
237
|
+
}
|
|
238
|
+
const result = [];
|
|
239
|
+
for (const [source, group] of bySource) {
|
|
240
|
+
if (group.length > 1) {
|
|
241
|
+
let parentStatus = HealthStatus.Ok;
|
|
242
|
+
if (group.some(e => e.status === HealthStatus.Error)) {
|
|
243
|
+
parentStatus = HealthStatus.Error;
|
|
244
|
+
}
|
|
245
|
+
else if (group.some(e => e.status === HealthStatus.Warning)) {
|
|
246
|
+
parentStatus = HealthStatus.Warning;
|
|
247
|
+
}
|
|
248
|
+
result.push({ source, status: parentStatus, grouped: group });
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
result.push(group[0]);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
let finalStatus = HealthStatus.Ok;
|
|
255
|
+
if (result.some(e => e.status === HealthStatus.Error)) {
|
|
256
|
+
finalStatus = HealthStatus.Error;
|
|
257
|
+
}
|
|
258
|
+
else if (result.some(e => e.status === HealthStatus.Warning)) {
|
|
259
|
+
finalStatus = HealthStatus.Warning;
|
|
260
|
+
}
|
|
261
|
+
this._healthInfo = { status: finalStatus, components: result };
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Start the regular health check timer.
|
|
265
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
266
|
+
* @param interval The interval for the timer.
|
|
267
|
+
* @internal
|
|
268
|
+
*/
|
|
269
|
+
startTimer(nodeLoggingComponentType, interval) {
|
|
270
|
+
if (this._started) {
|
|
271
|
+
this._healthTimer = globalThis.setTimeout(async () => this.checkHealth(nodeLoggingComponentType), interval);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Stop the regular health check timer.
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
stopTimer() {
|
|
279
|
+
if (this._healthTimer) {
|
|
280
|
+
globalThis.clearTimeout(this._healthTimer);
|
|
281
|
+
this._healthTimer = undefined;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Start the application health lifecycle timer.
|
|
286
|
+
* @param interval The interval for the timer.
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
startApplicationTimer(interval) {
|
|
290
|
+
if (this._started) {
|
|
291
|
+
this._healthApplicationTimer = globalThis.setTimeout(async () => {
|
|
292
|
+
this.stopApplicationTimer();
|
|
293
|
+
await this._backgroundTaskComponent.create(HealthService._APPLICATION_HEALTH_TASK_TYPE);
|
|
294
|
+
}, interval);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Stop the application health lifecycle timer.
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
stopApplicationTimer() {
|
|
302
|
+
if (this._healthApplicationTimer) {
|
|
303
|
+
globalThis.clearTimeout(this._healthApplicationTimer);
|
|
304
|
+
this._healthApplicationTimer = undefined;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=healthService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"healthService.js","sources":["../../src/healthService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\ttype IHealth,\n\ttype IHealthComponent,\n\tHealthStatus,\n\ttype IHealthProviderComponent\n} from \"@3sixty/api-models\";\nimport type { IBackgroundTask, IBackgroundTaskComponent } from \"@3sixty/background-task-models\";\nimport { TaskStatus } from \"@3sixty/background-task-models\";\nimport type { IContextIds } from \"@3sixty/context\";\nimport { BaseError, ComponentFactory, Factory, type IComponent, Is } from \"@3sixty/core\";\nimport type { ILoggingComponent } from \"@3sixty/logging-models\";\nimport { ModuleHelper } from \"@3sixty/modules\";\n\nimport type { IHealthServiceConstructorOptions } from \"./models/IHealthServiceConstructorOptions.js\";\n\n/**\n * The health service for the server.\n */\nexport class HealthService implements IHealthComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = \"HealthService\";\n\n\t/**\n\t * Task type identifier for the application health background task.\n\t * @internal\n\t */\n\tprivate static readonly _APPLICATION_HEALTH_TASK_TYPE: string = \"health-application-check\";\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents: IHealth[];\n\t};\n\n\t/**\n\t * The interval for checking the health of the components.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * The interval for running the application health lifecycle (init, application, teardown).\n\t * @internal\n\t */\n\tprivate readonly _healthApplicationCheckInterval: number;\n\n\t/**\n\t * The initial interval for checking the health of the components.\n\t * This is used to check the health of the components immediately after the service is started.\n\t * @internal\n\t */\n\tprivate readonly _initialInterval: number;\n\n\t/**\n\t * Timer for the regular health check.\n\t * @internal\n\t */\n\tprivate _healthTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Timer for the application health lifecycle.\n\t * @internal\n\t */\n\tprivate _healthApplicationTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Whether the service has been started.\n\t * @internal\n\t */\n\tprivate _started: boolean;\n\n\t/**\n\t * Health entries from the most recently completed regular health check.\n\t * @internal\n\t */\n\tprivate _regularComponents: IHealth[];\n\n\t/**\n\t * Health entries from the most recently completed application health cycle.\n\t * @internal\n\t */\n\tprivate _applicationComponents: IHealth[];\n\n\t/**\n\t * Whether to include stack traces in health check error details.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack?: boolean;\n\n\t/**\n\t * The background task component for running application health checks.\n\t * @internal\n\t */\n\tprivate readonly _backgroundTaskComponent: IBackgroundTaskComponent;\n\n\t/**\n\t * The URL of the module to use for the application health background task.\n\t * @internal\n\t */\n\tprivate readonly _applicationHealthTaskHandler: string;\n\n\t/**\n\t * Regular expression patterns for component types to exclude from the health engine clone,\n\t * verified when the service is started.\n\t * @internal\n\t */\n\tprivate _excludeCloneComponents: string[] | undefined;\n\n\t/**\n\t * Create a new instance of HealthService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IHealthServiceConstructorOptions) {\n\t\tthis._healthInfo = { status: HealthStatus.Ok, components: [] };\n\t\tthis._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;\n\t\tthis._healthApplicationCheckInterval =\n\t\t\toptions?.config?.healthCheckApplicationInterval ?? 300000;\n\t\tthis._initialInterval = options?.config?.initialInterval ?? 2000;\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack;\n\t\tthis._backgroundTaskComponent = ComponentFactory.get<IBackgroundTaskComponent>(\n\t\t\toptions?.backgroundTaskComponentType ?? \"background-task\"\n\t\t);\n\t\tthis._applicationHealthTaskHandler =\n\t\t\toptions?.config?.overrideApplicationHealthTaskHandler ??\n\t\t\tnew URL(\"./healthApplicationTask.js\", import.meta.url).href;\n\t\tthis._excludeCloneComponents = options?.config?.excludeCloneComponents;\n\t\tthis._started = false;\n\t\tthis._regularComponents = [];\n\t\tthis._applicationComponents = [];\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 HealthService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the initial health check timers have been scheduled.\n\t * @throws GeneralError if an exclude clone component is not a valid regular expression.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (!this._started) {\n\t\t\t// Only load the engine models module when there is something to verify, so a node\n\t\t\t// with no configured exclusions never has to resolve it.\n\t\t\tif (Is.arrayValue(this._excludeCloneComponents)) {\n\t\t\t\tthis._excludeCloneComponents = await ModuleHelper.execModuleMethod<string[] | undefined>(\n\t\t\t\t\t\"@3sixty/engine-models\",\n\t\t\t\t\t\"EngineCloneHelper.verifyExcludeCloneComponents\",\n\t\t\t\t\t[this._excludeCloneComponents]\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis._started = true;\n\n\t\t\tawait this._backgroundTaskComponent.registerHandler<undefined, IHealth[]>(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE,\n\t\t\t\tthis._applicationHealthTaskHandler,\n\t\t\t\t\"healthApplicationTask\",\n\t\t\t\tasync (task: IBackgroundTask<undefined, IHealth[]>) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\ttask.status === TaskStatus.Success ||\n\t\t\t\t\t\ttask.status === TaskStatus.Failed ||\n\t\t\t\t\t\ttask.status === TaskStatus.Cancelled\n\t\t\t\t\t) {\n\t\t\t\t\t\tif (task.status === TaskStatus.Success && Is.array(task.result)) {\n\t\t\t\t\t\t\tthis._applicationComponents = task.result;\n\t\t\t\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\t\t\t} else if (task.status === TaskStatus.Failed) {\n\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"applicationHealthCheckTaskFailed\",\n\t\t\t\t\t\t\t\terror: Is.object(task.error) ? BaseError.fromError(task.error) : undefined\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.startApplicationTimer(this._healthApplicationCheckInterval);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tidleShutdownTimeout: -1,\n\t\t\t\t\tinitialiseMethod: \"healthApplicationTaskStart\",\n\t\t\t\t\tinitialiseMethodParams: async () => [this._excludeCloneComponents],\n\t\t\t\t\tshutdownMethod: \"healthApplicationTaskEnd\"\n\t\t\t\t}\n\t\t\t);\n\n\t\t\t// Immediately check health after a startup settling period.\n\t\t\t// The interval for the next checks are triggered on success of the current\n\t\t\t// check to prevent overlapping checks in case of long running health checks.\n\t\t\tthis.startTimer(nodeLoggingComponentType, this._initialInterval);\n\t\t\tthis.startApplicationTimer(this._initialInterval);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the health check timers have been cancelled.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._started) {\n\t\t\tthis._started = false;\n\t\t\tthis.stopTimer();\n\t\t\tthis.stopApplicationTimer();\n\n\t\t\tawait this._backgroundTaskComponent.unregisterHandler(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async healthStatus(): Promise<{ status: HealthStatus; components: IHealth[] }> {\n\t\treturn this._healthInfo;\n\t}\n\n\t/**\n\t * Check the health of all registered components by calling health() on each.\n\t * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.\n\t * @returns A promise that resolves when all component health checks are complete and the next check is scheduled.\n\t * @internal\n\t */\n\tprivate async checkHealth(nodeLoggingComponentType?: string): Promise<void> {\n\t\tthis.stopTimer();\n\n\t\tconst engineCoreFactory = Factory.getFactory(\"engine-core\");\n\n\t\tif (engineCoreFactory) {\n\t\t\t// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.\n\t\t\tconst engineCore = engineCoreFactory.getIfExists<{\n\t\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\t\tgetRegisteredComponents: () => Promise<\n\t\t\t\t\t{\n\t\t\t\t\t\tinstanceType: string;\n\t\t\t\t\t\tcomponent: IComponent;\n\t\t\t\t\t}[]\n\t\t\t\t>;\n\t\t\t}>(\"engine\");\n\n\t\t\tif (engineCore) {\n\t\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\t\t\tregisteredInstance.component\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\t\t\tdata: { className: registeredInstance.component.className() },\n\t\t\t\t\t\t\t\t\terror: BaseError.fromError(error)\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\n\t\t\t\tthis._regularComponents = allHealth;\n\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\tthis.startTimer(nodeLoggingComponentType, this._healthCheckInterval);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.\n\t * @param entries The flat list of health entries from all components.\n\t * @internal\n\t */\n\tprivate groupHealthByName(entries: IHealth[]): void {\n\t\tconst bySource = new Map<string, IHealth[]>();\n\t\tfor (const entry of entries) {\n\t\t\tconst existing = bySource.get(entry.source) ?? [];\n\t\t\texisting.push({\n\t\t\t\t...entry,\n\t\t\t\terror: !Is.empty(entry.error)\n\t\t\t\t\t? BaseError.fromError(entry.error).toJsonObject(this._includeErrorStack)\n\t\t\t\t\t: undefined\n\t\t\t});\n\t\t\tbySource.set(entry.source, existing);\n\t\t}\n\n\t\tconst result: IHealth[] = [];\n\t\tfor (const [source, group] of bySource) {\n\t\t\tif (group.length > 1) {\n\t\t\t\tlet parentStatus: HealthStatus = HealthStatus.Ok;\n\n\t\t\t\tif (group.some(e => e.status === HealthStatus.Error)) {\n\t\t\t\t\tparentStatus = HealthStatus.Error;\n\t\t\t\t} else if (group.some(e => e.status === HealthStatus.Warning)) {\n\t\t\t\t\tparentStatus = HealthStatus.Warning;\n\t\t\t\t}\n\n\t\t\t\tresult.push({ source, status: parentStatus, grouped: group });\n\t\t\t} else {\n\t\t\t\tresult.push(group[0]);\n\t\t\t}\n\t\t}\n\n\t\tlet finalStatus: HealthStatus = HealthStatus.Ok;\n\t\tif (result.some(e => e.status === HealthStatus.Error)) {\n\t\t\tfinalStatus = HealthStatus.Error;\n\t\t} else if (result.some(e => e.status === HealthStatus.Warning)) {\n\t\t\tfinalStatus = HealthStatus.Warning;\n\t\t}\n\n\t\tthis._healthInfo = { status: finalStatus, components: result };\n\t}\n\n\t/**\n\t * Start the regular health check timer.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startTimer(nodeLoggingComponentType: string | undefined, interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\t\tasync () => this.checkHealth(nodeLoggingComponentType),\n\t\t\t\tinterval\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the regular health check timer.\n\t * @internal\n\t */\n\tprivate stopTimer(): void {\n\t\tif (this._healthTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthTimer);\n\t\t\tthis._healthTimer = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Start the application health lifecycle timer.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startApplicationTimer(interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthApplicationTimer = globalThis.setTimeout(async () => {\n\t\t\t\tthis.stopApplicationTimer();\n\n\t\t\t\tawait this._backgroundTaskComponent.create(HealthService._APPLICATION_HEALTH_TASK_TYPE);\n\t\t\t}, interval);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the application health lifecycle timer.\n\t * @internal\n\t */\n\tprivate stopApplicationTimer(): void {\n\t\tif (this._healthApplicationTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthApplicationTimer);\n\t\t\tthis._healthApplicationTimer = undefined;\n\t\t}\n\t}\n}\n"]}
|
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
export * from "./healthRoutes.js";
|
|
4
|
+
export * from "./healthService.js";
|
|
5
|
+
export * from "./informationRoutes.js";
|
|
6
|
+
export * from "./informationService.js";
|
|
7
|
+
export * from "./models/IHealthServiceConfig.js";
|
|
8
|
+
export * from "./models/IHealthServiceConstructorOptions.js";
|
|
9
|
+
export * from "./models/IInformationServiceConfig.js";
|
|
10
|
+
export * from "./models/IInformationServiceConstructorOptions.js";
|
|
11
|
+
export * from "./models/IPlatformServiceConfig.js";
|
|
12
|
+
export * from "./models/IPlatformServiceConstructorOptions.js";
|
|
13
|
+
export * from "./platformService.js";
|
|
14
|
+
export * from "./restEntryPoints.js";
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./healthRoutes.js\";\nexport * from \"./healthService.js\";\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./models/IHealthServiceConfig.js\";\nexport * from \"./models/IHealthServiceConstructorOptions.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./models/IPlatformServiceConfig.js\";\nexport * from \"./models/IPlatformServiceConstructorOptions.js\";\nexport * from \"./platformService.js\";\nexport * from \"./restEntryPoints.js\";\n"]}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { ComponentFactory, Is } from "@3sixty/core";
|
|
2
|
+
import { HeaderTypes, HttpStatusCode, MimeTypes } from "@3sixty/web";
|
|
3
|
+
/**
|
|
4
|
+
* The tag to associate with the routes.
|
|
5
|
+
*/
|
|
6
|
+
export const tagsInformation = [
|
|
7
|
+
{
|
|
8
|
+
name: "Info",
|
|
9
|
+
description: "Information endpoints for the REST server."
|
|
10
|
+
}
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* The REST routes for server information.
|
|
14
|
+
* @param baseRouteName Prefix to prepend to the paths.
|
|
15
|
+
* @param componentName The name of the component to use in the routes stored in the ComponentFactory.
|
|
16
|
+
* @returns The generated routes.
|
|
17
|
+
*/
|
|
18
|
+
export function generateRestRoutesInformation(baseRouteName, componentName) {
|
|
19
|
+
const rootRoute = {
|
|
20
|
+
operationId: "serverRoot",
|
|
21
|
+
summary: "Get the root text page",
|
|
22
|
+
tag: tagsInformation[0].name,
|
|
23
|
+
method: "GET",
|
|
24
|
+
path: `${baseRouteName}/`,
|
|
25
|
+
handler: async (httpRequestContext, request) => serverRoot(httpRequestContext, componentName, request),
|
|
26
|
+
responseType: [
|
|
27
|
+
{
|
|
28
|
+
type: "IServerRootResponse",
|
|
29
|
+
mimeType: MimeTypes.PlainText,
|
|
30
|
+
examples: [
|
|
31
|
+
{
|
|
32
|
+
id: "serverRootResponse",
|
|
33
|
+
description: "The response for the root request.",
|
|
34
|
+
response: {
|
|
35
|
+
headers: {
|
|
36
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
37
|
+
},
|
|
38
|
+
body: "API Server - 1.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
skipAuth: true,
|
|
45
|
+
skipTenant: true
|
|
46
|
+
};
|
|
47
|
+
const informationRoute = {
|
|
48
|
+
operationId: "serverInformation",
|
|
49
|
+
summary: "Get the information for the server",
|
|
50
|
+
tag: tagsInformation[0].name,
|
|
51
|
+
method: "GET",
|
|
52
|
+
path: `${baseRouteName}/info`,
|
|
53
|
+
handler: async (httpRequestContext, request) => serverInfo(httpRequestContext, componentName, request),
|
|
54
|
+
responseType: [
|
|
55
|
+
{
|
|
56
|
+
type: "IServerInfoResponse",
|
|
57
|
+
examples: [
|
|
58
|
+
{
|
|
59
|
+
id: "informationResponse",
|
|
60
|
+
description: "The response for the information request.",
|
|
61
|
+
response: {
|
|
62
|
+
body: {
|
|
63
|
+
name: "API Server",
|
|
64
|
+
version: "1.0.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
skipAuth: true,
|
|
72
|
+
skipTenant: true
|
|
73
|
+
};
|
|
74
|
+
const favIconRoute = {
|
|
75
|
+
operationId: "serverFavIcon",
|
|
76
|
+
summary: "Get the favicon for the server",
|
|
77
|
+
tag: tagsInformation[0].name,
|
|
78
|
+
method: "GET",
|
|
79
|
+
path: `${baseRouteName}/favicon.ico`,
|
|
80
|
+
handler: async (httpRequestContext, request) => serverFavIcon(httpRequestContext, componentName, request),
|
|
81
|
+
responseType: [
|
|
82
|
+
{
|
|
83
|
+
type: "IServerFavIconResponse",
|
|
84
|
+
mimeType: "image/x-icon"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
skipAuth: true,
|
|
88
|
+
skipTenant: true
|
|
89
|
+
};
|
|
90
|
+
const livezRoute = {
|
|
91
|
+
operationId: "serverLivez",
|
|
92
|
+
summary: "Get the livez status for the server",
|
|
93
|
+
tag: tagsInformation[0].name,
|
|
94
|
+
method: "GET",
|
|
95
|
+
path: `${baseRouteName}/livez`,
|
|
96
|
+
handler: async (httpRequestContext, request) => serverLivez(httpRequestContext, componentName, request),
|
|
97
|
+
responseType: [
|
|
98
|
+
{
|
|
99
|
+
type: "IServerLivezResponse",
|
|
100
|
+
mimeType: MimeTypes.PlainText,
|
|
101
|
+
examples: [
|
|
102
|
+
{
|
|
103
|
+
id: "livezResponseOK",
|
|
104
|
+
description: "The response for the liveness request.",
|
|
105
|
+
response: {
|
|
106
|
+
headers: {
|
|
107
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
108
|
+
},
|
|
109
|
+
body: "alive"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "livezResponseFailure",
|
|
114
|
+
description: "The response for the liveness request with errors.",
|
|
115
|
+
response: {
|
|
116
|
+
headers: {
|
|
117
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
118
|
+
},
|
|
119
|
+
body: "dead"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
skipAuth: true,
|
|
126
|
+
skipTenant: true
|
|
127
|
+
};
|
|
128
|
+
const readyzRoute = {
|
|
129
|
+
operationId: "serverReadyz",
|
|
130
|
+
summary: "Get the readyz status for the server",
|
|
131
|
+
tag: tagsInformation[0].name,
|
|
132
|
+
method: "GET",
|
|
133
|
+
path: `${baseRouteName}/readyz`,
|
|
134
|
+
handler: async (httpRequestContext, request) => serverReadyz(httpRequestContext, componentName, request),
|
|
135
|
+
responseType: [
|
|
136
|
+
{
|
|
137
|
+
type: "IServerReadyzResponse",
|
|
138
|
+
mimeType: MimeTypes.PlainText,
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
id: "readyzResponseOK",
|
|
142
|
+
description: "The response for the readiness request.",
|
|
143
|
+
response: {
|
|
144
|
+
headers: {
|
|
145
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
146
|
+
},
|
|
147
|
+
body: "ready"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "readyzResponseFailure",
|
|
152
|
+
description: "The response for the readiness request with errors.",
|
|
153
|
+
response: {
|
|
154
|
+
headers: {
|
|
155
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
156
|
+
},
|
|
157
|
+
body: "not ready"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
skipAuth: true,
|
|
164
|
+
skipTenant: true
|
|
165
|
+
};
|
|
166
|
+
const specRoute = {
|
|
167
|
+
operationId: "serverSpec",
|
|
168
|
+
summary: "Get the OpenAPI specification for the endpoints",
|
|
169
|
+
tag: tagsInformation[0].name,
|
|
170
|
+
method: "GET",
|
|
171
|
+
path: `${baseRouteName}/spec`,
|
|
172
|
+
handler: async (httpRequestContext, request) => serverSpec(httpRequestContext, componentName, request),
|
|
173
|
+
responseType: [
|
|
174
|
+
{
|
|
175
|
+
type: "IServerSpecResponse",
|
|
176
|
+
examples: [
|
|
177
|
+
{
|
|
178
|
+
id: "specResponse",
|
|
179
|
+
description: "The response for the spec request.",
|
|
180
|
+
response: {
|
|
181
|
+
body: {
|
|
182
|
+
openapi: "3.1.0",
|
|
183
|
+
info: {},
|
|
184
|
+
paths: {}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
skipAuth: true,
|
|
192
|
+
skipTenant: true
|
|
193
|
+
};
|
|
194
|
+
return [rootRoute, favIconRoute, informationRoute, livezRoute, readyzRoute, specRoute];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the root for the server.
|
|
198
|
+
* @param httpRequestContext The request context for the API.
|
|
199
|
+
* @param componentName The name of the component to use in the routes.
|
|
200
|
+
* @param request The request.
|
|
201
|
+
* @returns The response object with additional http response properties.
|
|
202
|
+
*/
|
|
203
|
+
export async function serverRoot(httpRequestContext, componentName, request) {
|
|
204
|
+
const component = ComponentFactory.get(componentName);
|
|
205
|
+
return {
|
|
206
|
+
headers: {
|
|
207
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
208
|
+
},
|
|
209
|
+
body: await component.root()
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get the information for the server.
|
|
214
|
+
* @param httpRequestContext The request context for the API.
|
|
215
|
+
* @param componentName The name of the component to use in the routes.
|
|
216
|
+
* @param request The request.
|
|
217
|
+
* @returns The response object with additional http response properties.
|
|
218
|
+
*/
|
|
219
|
+
export async function serverInfo(httpRequestContext, componentName, request) {
|
|
220
|
+
const component = ComponentFactory.get(componentName);
|
|
221
|
+
return {
|
|
222
|
+
body: await component.info()
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get the livez for the server.
|
|
227
|
+
* @param httpRequestContext The request context for the API.
|
|
228
|
+
* @param componentName The name of the component to use in the routes.
|
|
229
|
+
* @param request The request.
|
|
230
|
+
* @returns The response object with additional http response properties.
|
|
231
|
+
*/
|
|
232
|
+
export async function serverLivez(httpRequestContext, componentName, request) {
|
|
233
|
+
const component = ComponentFactory.get(componentName);
|
|
234
|
+
return {
|
|
235
|
+
headers: {
|
|
236
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
237
|
+
},
|
|
238
|
+
body: (await component.livez()).status
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get the readyz for the server.
|
|
243
|
+
* @param httpRequestContext The request context for the API.
|
|
244
|
+
* @param componentName The name of the component to use in the routes.
|
|
245
|
+
* @param request The request.
|
|
246
|
+
* @returns The response object with additional http response properties.
|
|
247
|
+
*/
|
|
248
|
+
export async function serverReadyz(httpRequestContext, componentName, request) {
|
|
249
|
+
const component = ComponentFactory.get(componentName);
|
|
250
|
+
return {
|
|
251
|
+
headers: {
|
|
252
|
+
[HeaderTypes.ContentType]: MimeTypes.PlainText
|
|
253
|
+
},
|
|
254
|
+
body: (await component.readyz()).status
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get the favicon for the server.
|
|
259
|
+
* @param httpRequestContext The request context for the API.
|
|
260
|
+
* @param componentName The name of the component to use in the routes.
|
|
261
|
+
* @param request The request.
|
|
262
|
+
* @returns The response object with additional http response properties.
|
|
263
|
+
*/
|
|
264
|
+
export async function serverFavIcon(httpRequestContext, componentName, request) {
|
|
265
|
+
const component = ComponentFactory.get(componentName);
|
|
266
|
+
const favIcon = await component.favicon();
|
|
267
|
+
if (Is.uint8Array(favIcon)) {
|
|
268
|
+
return {
|
|
269
|
+
headers: {
|
|
270
|
+
[HeaderTypes.ContentType]: "image/x-icon"
|
|
271
|
+
},
|
|
272
|
+
body: favIcon
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
statusCode: HttpStatusCode.notFound
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the spec for the server.
|
|
281
|
+
* @param httpRequestContext The request context for the API.
|
|
282
|
+
* @param componentName The name of the component to use in the routes.
|
|
283
|
+
* @param request The request.
|
|
284
|
+
* @returns The response object with additional http response properties.
|
|
285
|
+
*/
|
|
286
|
+
export async function serverSpec(httpRequestContext, componentName, request) {
|
|
287
|
+
const component = ComponentFactory.get(componentName);
|
|
288
|
+
const spec = await component.spec();
|
|
289
|
+
if (Is.objectValue(spec)) {
|
|
290
|
+
return {
|
|
291
|
+
body: spec
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
statusCode: HttpStatusCode.notFound
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=informationRoutes.js.map
|