@3sixty/api-service 0.0.0-stage → 0.20.0-next.0

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.
Files changed (71) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +24 -2
  3. package/dist/es/healthApplicationTask.js +151 -0
  4. package/dist/es/healthApplicationTask.js.map +1 -0
  5. package/dist/es/healthRoutes.js +116 -0
  6. package/dist/es/healthRoutes.js.map +1 -0
  7. package/dist/es/healthService.js +308 -0
  8. package/dist/es/healthService.js.map +1 -0
  9. package/dist/es/index.js +15 -0
  10. package/dist/es/index.js.map +1 -0
  11. package/dist/es/informationRoutes.js +298 -0
  12. package/dist/es/informationRoutes.js.map +1 -0
  13. package/dist/es/informationService.js +123 -0
  14. package/dist/es/informationService.js.map +1 -0
  15. package/dist/es/models/IHealthServiceConfig.js +4 -0
  16. package/dist/es/models/IHealthServiceConfig.js.map +1 -0
  17. package/dist/es/models/IHealthServiceConstructorOptions.js +2 -0
  18. package/dist/es/models/IHealthServiceConstructorOptions.js.map +1 -0
  19. package/dist/es/models/IInformationServiceConfig.js +2 -0
  20. package/dist/es/models/IInformationServiceConfig.js.map +1 -0
  21. package/dist/es/models/IInformationServiceConstructorOptions.js +2 -0
  22. package/dist/es/models/IInformationServiceConstructorOptions.js.map +1 -0
  23. package/dist/es/models/IPlatformServiceConfig.js +4 -0
  24. package/dist/es/models/IPlatformServiceConfig.js.map +1 -0
  25. package/dist/es/models/IPlatformServiceConstructorOptions.js +2 -0
  26. package/dist/es/models/IPlatformServiceConstructorOptions.js.map +1 -0
  27. package/dist/es/platformService.js +159 -0
  28. package/dist/es/platformService.js.map +1 -0
  29. package/dist/es/restEntryPoints.js +20 -0
  30. package/dist/es/restEntryPoints.js.map +1 -0
  31. package/dist/types/healthApplicationTask.d.ts +20 -0
  32. package/dist/types/healthRoutes.d.ts +20 -0
  33. package/dist/types/healthService.d.ts +42 -0
  34. package/dist/types/index.d.ts +12 -0
  35. package/dist/types/informationRoutes.d.ts +60 -0
  36. package/dist/types/informationService.d.ts +60 -0
  37. package/dist/types/models/IHealthServiceConfig.d.ts +37 -0
  38. package/dist/types/models/IHealthServiceConstructorOptions.d.ts +15 -0
  39. package/dist/types/models/IInformationServiceConfig.d.ts +18 -0
  40. package/dist/types/models/IInformationServiceConstructorOptions.d.ts +10 -0
  41. package/dist/types/models/IPlatformServiceConfig.d.ts +10 -0
  42. package/dist/types/models/IPlatformServiceConstructorOptions.d.ts +15 -0
  43. package/dist/types/platformService.d.ts +39 -0
  44. package/dist/types/restEntryPoints.d.ts +5 -0
  45. package/docs/changelog.md +0 -0
  46. package/docs/examples.md +74 -0
  47. package/docs/open-api/spec.json +712 -0
  48. package/docs/reference/classes/HealthService.md +127 -0
  49. package/docs/reference/classes/InformationService.md +179 -0
  50. package/docs/reference/classes/PlatformService.md +123 -0
  51. package/docs/reference/functions/generateRestRoutesHealth.md +25 -0
  52. package/docs/reference/functions/generateRestRoutesInformation.md +25 -0
  53. package/docs/reference/functions/serverFavIcon.md +31 -0
  54. package/docs/reference/functions/serverHealth.md +31 -0
  55. package/docs/reference/functions/serverInfo.md +31 -0
  56. package/docs/reference/functions/serverLivez.md +31 -0
  57. package/docs/reference/functions/serverReadyz.md +31 -0
  58. package/docs/reference/functions/serverRoot.md +31 -0
  59. package/docs/reference/functions/serverSpec.md +31 -0
  60. package/docs/reference/index.md +34 -0
  61. package/docs/reference/interfaces/IHealthServiceConfig.md +79 -0
  62. package/docs/reference/interfaces/IHealthServiceConstructorOptions.md +25 -0
  63. package/docs/reference/interfaces/IInformationServiceConfig.md +27 -0
  64. package/docs/reference/interfaces/IInformationServiceConstructorOptions.md +11 -0
  65. package/docs/reference/interfaces/IPlatformServiceConfig.md +17 -0
  66. package/docs/reference/interfaces/IPlatformServiceConstructorOptions.md +25 -0
  67. package/docs/reference/variables/restEntryPoints.md +5 -0
  68. package/docs/reference/variables/tagsHealth.md +5 -0
  69. package/docs/reference/variables/tagsInformation.md +5 -0
  70. package/locales/en.json +11 -0
  71. 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","sourceRoot":"","sources":["../../src/healthService.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAGN,YAAY,EAEZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAmB,EAAE,EAAE,MAAM,cAAc,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI/C;;GAEG;AACH,MAAM,OAAO,aAAa;IACzB;;OAEG;IACI,MAAM,CAAU,UAAU,mBAAmC;IAEpE;;;OAGG;IACK,MAAM,CAAU,6BAA6B,GAAW,0BAA0B,CAAC;IAE3F;;;OAGG;IACK,WAAW,CAGjB;IAEF;;;OAGG;IACc,oBAAoB,CAAS;IAE9C;;;OAGG;IACc,+BAA+B,CAAS;IAEzD;;;;OAIG;IACc,gBAAgB,CAAS;IAE1C;;;OAGG;IACK,YAAY,CAAuD;IAE3E;;;OAGG;IACK,uBAAuB,CAAuD;IAEtF;;;OAGG;IACK,QAAQ,CAAU;IAE1B;;;OAGG;IACK,kBAAkB,CAAY;IAEtC;;;OAGG;IACK,sBAAsB,CAAY;IAE1C;;;OAGG;IACc,kBAAkB,CAAW;IAE9C;;;OAGG;IACc,wBAAwB,CAA2B;IAEpE;;;OAGG;IACc,6BAA6B,CAAS;IAEvD;;;;OAIG;IACK,uBAAuB,CAAuB;IAEtD;;;OAGG;IACH,YAAY,OAA0C;QACrD,IAAI,CAAC,WAAW,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC/D,IAAI,CAAC,oBAAoB,GAAG,OAAO,EAAE,MAAM,EAAE,mBAAmB,IAAI,KAAK,CAAC;QAC1E,IAAI,CAAC,+BAA+B;YACnC,OAAO,EAAE,MAAM,EAAE,8BAA8B,IAAI,MAAM,CAAC;QAC3D,IAAI,CAAC,gBAAgB,GAAG,OAAO,EAAE,MAAM,EAAE,eAAe,IAAI,IAAI,CAAC;QACjE,IAAI,CAAC,kBAAkB,GAAG,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC;QAC7D,IAAI,CAAC,wBAAwB,GAAG,gBAAgB,CAAC,GAAG,CACnD,OAAO,EAAE,2BAA2B,IAAI,iBAAiB,CACzD,CAAC;QACF,IAAI,CAAC,6BAA6B;YACjC,OAAO,EAAE,MAAM,EAAE,oCAAoC;gBACrD,IAAI,GAAG,CAAC,4BAA4B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QAC7D,IAAI,CAAC,uBAAuB,GAAG,OAAO,EAAE,MAAM,EAAE,sBAAsB,CAAC;QACvE,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,sBAAsB,GAAG,EAAE,CAAC;IAClC,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,aAAa,CAAC,UAAU,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,KAAK,CAAC,wBAAiC;QACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpB,kFAAkF;YAClF,yDAAyD;YACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,uBAAuB,CAAC,EAAE,CAAC;gBACjD,IAAI,CAAC,uBAAuB,GAAG,MAAM,YAAY,CAAC,gBAAgB,CACjE,uBAAuB,EACvB,gDAAgD,EAChD,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAC9B,CAAC;YACH,CAAC;YAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YAErB,MAAM,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAClD,aAAa,CAAC,6BAA6B,EAC3C,IAAI,CAAC,6BAA6B,EAClC,uBAAuB,EACvB,KAAK,EAAE,IAA2C,EAAE,EAAE;gBACrD,IACC,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,OAAO;oBAClC,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM;oBACjC,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,SAAS,EACnC,CAAC;oBACF,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;wBACjE,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,MAAM,CAAC;wBAC1C,IAAI,CAAC,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;oBACtF,CAAC;yBAAM,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;wBAC9C,MAAM,WAAW,GAChB,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;wBAC3E,MAAM,WAAW,EAAE,GAAG,CAAC;4BACtB,KAAK,EAAE,OAAO;4BACd,MAAM,EAAE,aAAa,CAAC,UAAU;4BAChC,OAAO,EAAE,kCAAkC;4BAC3C,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;yBAC1E,CAAC,CAAC;oBACJ,CAAC;oBACD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;gBAClE,CAAC;YACF,CAAC,EACD;gBACC,mBAAmB,EAAE,CAAC,CAAC;gBACvB,gBAAgB,EAAE,4BAA4B;gBAC9C,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC;gBAClE,cAAc,EAAE,0BAA0B;aAC1C,CACD,CAAC;YAEF,4DAA4D;YAC5D,2EAA2E;YAC3E,6EAA6E;YAC7E,IAAI,CAAC,UAAU,CAAC,wBAAwB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACjE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACnD,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,IAAI,CAAC,wBAAiC;QAClD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAE5B,MAAM,IAAI,CAAC,wBAAwB,CAAC,iBAAiB,CACpD,aAAa,CAAC,6BAA6B,CAC3C,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,YAAY;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,WAAW,CAAC,wBAAiC;QAC1D,IAAI,CAAC,SAAS,EAAE,CAAC;QAEjB,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAE5D,IAAI,iBAAiB,EAAE,CAAC;YACvB,wGAAwG;YACxG,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,CAQ7C,QAAQ,CAAC,CAAC;YAEb,IAAI,UAAU,EAAE,CAAC;gBAChB,MAAM,mBAAmB,GAAG,MAAM,UAAU,CAAC,uBAAuB,EAAE,CAAC;gBACvE,MAAM,SAAS,GAAc,EAAE,CAAC;gBAEhC,KAAK,MAAM,kBAAkB,IAAI,mBAAmB,EAAE,CAAC;oBACtD,IAAI,EAAE,CAAC,MAAM,CAA2B,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;wBACvE,MAAM,YAAY,GAAG,kBAAkB,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAC7D,kBAAkB,CAAC,SAAS,CAC5B,CAAC;wBACF,IAAI,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;4BAC/B,IAAI,CAAC;gCACJ,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,YAAY,EAAE,CAAC,CAAC,CAAC;4BAC3C,CAAC;4BAAC,OAAO,KAAK,EAAE,CAAC;gCAChB,MAAM,WAAW,GAChB,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;gCAC3E,MAAM,WAAW,EAAE,GAAG,CAAC;oCACtB,KAAK,EAAE,OAAO;oCACd,MAAM,EAAE,aAAa,CAAC,UAAU;oCAChC,OAAO,EAAE,4BAA4B;oCACrC,IAAI,EAAE,EAAE,SAAS,EAAE,kBAAkB,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE;oCAC7D,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;iCACjC,CAAC,CAAC;4BACJ,CAAC;wBACF,CAAC;oBACF,CAAC;gBACF,CAAC;gBAED,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;gBACpC,IAAI,CAAC,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBACrF,IAAI,CAAC,UAAU,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACtE,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,iBAAiB,CAAC,OAAkB;QAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC;gBACb,GAAG,KAAK;gBACR,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;oBAC5B,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,kBAAkB,CAAC;oBACxE,CAAC,CAAC,SAAS;aACZ,CAAC,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,MAAM,GAAc,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACxC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,YAAY,GAAiB,YAAY,CAAC,EAAE,CAAC;gBAEjD,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;oBACtD,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC;gBACnC,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC/D,YAAY,GAAG,YAAY,CAAC,OAAO,CAAC;gBACrC,CAAC;gBAED,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACvB,CAAC;QACF,CAAC;QAED,IAAI,WAAW,GAAiB,YAAY,CAAC,EAAE,CAAC;QAChD,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACvD,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;QAClC,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;YAChE,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC;QACpC,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACK,UAAU,CAAC,wBAA4C,EAAE,QAAgB;QAChF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,UAAU,CACxC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,wBAAwB,CAAC,EACtD,QAAQ,CACR,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACK,SAAS;QAChB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC3C,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC/B,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,qBAAqB,CAAC,QAAgB;QAC7C,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,uBAAuB,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;gBAC/D,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAE5B,MAAM,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,aAAa,CAAC,6BAA6B,CAAC,CAAC;YACzF,CAAC,EAAE,QAAQ,CAAC,CAAC;QACd,CAAC;IACF,CAAC;IAED;;;OAGG;IACK,oBAAoB;QAC3B,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAClC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YACtD,IAAI,CAAC,uBAAuB,GAAG,SAAS,CAAC;QAC1C,CAAC;IACF,CAAC","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\";\nimport { nameof } from \"@3sixty/nameof\";\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 = nameof<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"]}
@@ -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","sourceRoot":"","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