@twin.org/api-service 0.0.3-next.4 → 0.0.3-next.40

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/es/healthRoutes.js +107 -0
  3. package/dist/es/healthRoutes.js.map +1 -0
  4. package/dist/es/healthService.js +156 -0
  5. package/dist/es/healthService.js.map +1 -0
  6. package/dist/es/hostingService.js +89 -0
  7. package/dist/es/hostingService.js.map +1 -0
  8. package/dist/es/index.js +10 -0
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/informationRoutes.js +92 -62
  11. package/dist/es/informationRoutes.js.map +1 -1
  12. package/dist/es/informationService.js +12 -58
  13. package/dist/es/informationService.js.map +1 -1
  14. package/dist/es/models/IHealthServiceConfig.js +4 -0
  15. package/dist/es/models/IHealthServiceConfig.js.map +1 -0
  16. package/dist/es/models/IHealthServiceConstructorOptions.js +2 -0
  17. package/dist/es/models/IHealthServiceConstructorOptions.js.map +1 -0
  18. package/dist/es/models/IHostingServiceConfig.js +4 -0
  19. package/dist/es/models/IHostingServiceConfig.js.map +1 -0
  20. package/dist/es/models/IHostingServiceConstructorOptions.js +2 -0
  21. package/dist/es/models/IHostingServiceConstructorOptions.js.map +1 -0
  22. package/dist/es/models/IUrlTransformerServiceConfig.js +4 -0
  23. package/dist/es/models/IUrlTransformerServiceConfig.js.map +1 -0
  24. package/dist/es/models/IUrlTransformerServiceConstructorOptions.js +2 -0
  25. package/dist/es/models/IUrlTransformerServiceConstructorOptions.js.map +1 -0
  26. package/dist/es/restEntryPoints.js +7 -0
  27. package/dist/es/restEntryPoints.js.map +1 -1
  28. package/dist/es/urlTransformerService.js +256 -0
  29. package/dist/es/urlTransformerService.js.map +1 -0
  30. package/dist/types/healthRoutes.d.ts +20 -0
  31. package/dist/types/healthService.d.ts +42 -0
  32. package/dist/types/hostingService.d.ts +39 -0
  33. package/dist/types/index.d.ts +10 -0
  34. package/dist/types/informationRoutes.d.ts +11 -3
  35. package/dist/types/informationService.d.ts +11 -16
  36. package/dist/types/models/IHealthServiceConfig.d.ts +16 -0
  37. package/dist/types/models/IHealthServiceConstructorOptions.d.ts +10 -0
  38. package/dist/types/models/IHostingServiceConfig.d.ts +13 -0
  39. package/dist/types/models/IHostingServiceConstructorOptions.d.ts +15 -0
  40. package/dist/types/models/IUrlTransformerServiceConfig.d.ts +19 -0
  41. package/dist/types/models/IUrlTransformerServiceConstructorOptions.d.ts +15 -0
  42. package/dist/types/urlTransformerService.d.ts +94 -0
  43. package/docs/changelog.md +574 -55
  44. package/docs/examples.md +74 -1
  45. package/docs/reference/classes/HealthService.md +123 -0
  46. package/docs/reference/classes/HostingService.md +131 -0
  47. package/docs/reference/classes/InformationService.md +19 -65
  48. package/docs/reference/classes/UrlTransformerService.md +379 -0
  49. package/docs/reference/functions/generateRestRoutesHealth.md +25 -0
  50. package/docs/reference/functions/serverLivez.md +31 -0
  51. package/docs/reference/functions/serverReadyz.md +31 -0
  52. package/docs/reference/index.md +14 -1
  53. package/docs/reference/interfaces/IHealthServiceConfig.md +32 -0
  54. package/docs/reference/interfaces/IHealthServiceConstructorOptions.md +11 -0
  55. package/docs/reference/interfaces/IHostingServiceConfig.md +19 -0
  56. package/docs/reference/interfaces/IHostingServiceConstructorOptions.md +25 -0
  57. package/docs/reference/interfaces/IInformationServiceConfig.md +5 -5
  58. package/docs/reference/interfaces/IInformationServiceConstructorOptions.md +1 -1
  59. package/docs/reference/interfaces/IUrlTransformerServiceConfig.md +32 -0
  60. package/docs/reference/interfaces/IUrlTransformerServiceConstructorOptions.md +25 -0
  61. package/docs/reference/variables/tagsHealth.md +5 -0
  62. package/locales/en.json +13 -1
  63. package/package.json +9 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TWIN API Service
2
2
 
3
- Implementation of the information service.
3
+ This package provides information and hosting service implementations with generated REST route handlers.
4
4
 
5
5
  ## Installation
6
6
 
@@ -0,0 +1,107 @@
1
+ import { ComponentFactory, HealthStatus } from "@twin.org/core";
2
+ /**
3
+ * The tag to associate with the routes.
4
+ */
5
+ export const tagsHealth = [
6
+ {
7
+ name: "Health",
8
+ description: "Health endpoints for the REST server."
9
+ }
10
+ ];
11
+ /**
12
+ * The REST routes for server health.
13
+ * @param baseRouteName Prefix to prepend to the paths.
14
+ * @param componentName The name of the component to use in the routes stored in the ComponentFactory.
15
+ * @returns The generated routes.
16
+ */
17
+ export function generateRestRoutesHealth(baseRouteName, componentName) {
18
+ const healthRoute = {
19
+ operationId: "serverHealth",
20
+ summary: "Get the health for the server",
21
+ tag: tagsHealth[0].name,
22
+ method: "GET",
23
+ path: `${baseRouteName}/`,
24
+ handler: async (httpRequestContext, request) => serverHealth(httpRequestContext, componentName, request),
25
+ responseType: [
26
+ {
27
+ type: "IServerHealthResponse",
28
+ examples: [
29
+ {
30
+ id: "healthResponseOK",
31
+ description: "The response for the health request.",
32
+ response: {
33
+ body: {
34
+ status: HealthStatus.Ok,
35
+ components: [
36
+ {
37
+ source: "Database",
38
+ status: HealthStatus.Ok
39
+ },
40
+ {
41
+ source: "Storage",
42
+ status: HealthStatus.Ok
43
+ }
44
+ ]
45
+ }
46
+ }
47
+ },
48
+ {
49
+ id: "healthResponseWarning",
50
+ description: "The response for the health request with warnings.",
51
+ response: {
52
+ body: {
53
+ status: HealthStatus.Warning,
54
+ components: [
55
+ {
56
+ source: "Database",
57
+ status: HealthStatus.Warning,
58
+ description: "slowRunning"
59
+ },
60
+ {
61
+ source: "Storage",
62
+ status: HealthStatus.Ok
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ },
68
+ {
69
+ id: "healthResponseError",
70
+ description: "The response for the health request with errors.",
71
+ response: {
72
+ body: {
73
+ status: "error",
74
+ components: [
75
+ {
76
+ source: "Database",
77
+ status: "ok"
78
+ },
79
+ {
80
+ source: "Storage",
81
+ status: "error",
82
+ description: "storageFull"
83
+ }
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ ]
89
+ }
90
+ ]
91
+ };
92
+ return [healthRoute];
93
+ }
94
+ /**
95
+ * Get the health for the server.
96
+ * @param httpRequestContext The request context for the API.
97
+ * @param componentName The name of the component to use in the routes.
98
+ * @param request The request.
99
+ * @returns The response object with additional http response properties.
100
+ */
101
+ export async function serverHealth(httpRequestContext, componentName, request) {
102
+ const component = ComponentFactory.get(componentName);
103
+ return {
104
+ body: await component.healthStatus()
105
+ };
106
+ }
107
+ //# sourceMappingURL=healthRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthRoutes.js","sourceRoot":"","sources":["../../src/healthRoutes.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGhE;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAW;IACjC;QACC,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uCAAuC;KACpD;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACvC,aAAqB,EACrB,aAAqB;IAErB,MAAM,WAAW,GAAyD;QACzE,WAAW,EAAE,cAAc;QAC3B,OAAO,EAAE,+BAA+B;QACxC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QACvB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,GAAG,aAAa,GAAG;QACzB,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,YAAY,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACzD,YAAY,EAAE;YACb;gBACC,IAAI,yBAAiC;gBACrC,QAAQ,EAAE;oBACT;wBACC,EAAE,EAAE,kBAAkB;wBACtB,WAAW,EAAE,sCAAsC;wBACnD,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,YAAY,CAAC,EAAE;gCACvB,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,YAAY,CAAC,EAAE;qCACvB;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,YAAY,CAAC,EAAE;qCACvB;iCACD;6BACD;yBACD;qBACD;oBACD;wBACC,EAAE,EAAE,uBAAuB;wBAC3B,WAAW,EAAE,oDAAoD;wBACjE,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,YAAY,CAAC,OAAO;gCAC5B,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,YAAY,CAAC,OAAO;wCAC5B,WAAW,EAAE,aAAa;qCAC1B;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,YAAY,CAAC,EAAE;qCACvB;iCACD;6BACD;yBACD;qBACD;oBACD;wBACC,EAAE,EAAE,qBAAqB;wBACzB,WAAW,EAAE,kDAAkD;wBAC/D,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,OAAO;gCACf,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,IAAI;qCACZ;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,OAAO;wCACf,WAAW,EAAE,aAAa;qCAC1B;iCACD;6BACD;yBACD;qBACD;iBACD;aACD;SACD;KACD,CAAC;IAEF,OAAO,CAAC,WAAW,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,kBAAuC,EACvC,aAAqB,EACrB,OAA0B;IAE1B,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAmB,aAAa,CAAC,CAAC;IACxE,OAAO;QACN,IAAI,EAAE,MAAM,SAAS,CAAC,YAAY,EAAE;KACpC,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHealthComponent,\n\tIHttpRequestContext,\n\tINoContentRequest,\n\tIRestRoute,\n\tIServerHealthResponse,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, HealthStatus } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsHealth: ITag[] = [\n\t{\n\t\tname: \"Health\",\n\t\tdescription: \"Health endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for server health.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesHealth(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst healthRoute: IRestRoute<INoContentRequest, IServerHealthResponse> = {\n\t\toperationId: \"serverHealth\",\n\t\tsummary: \"Get the health for the server\",\n\t\ttag: tagsHealth[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverHealth(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerHealthResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the health request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseWarning\",\n\t\t\t\t\t\tdescription: \"The response for the health request with warnings.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\t\t\tdescription: \"slowRunning\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseError\",\n\t\t\t\t\t\tdescription: \"The response for the health request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"storageFull\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t};\n\n\treturn [healthRoute];\n}\n\n/**\n * Get the health for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverHealth(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerHealthResponse> {\n\tconst component = ComponentFactory.get<IHealthComponent>(componentName);\n\treturn {\n\t\tbody: await component.healthStatus()\n\t};\n}\n"]}
@@ -0,0 +1,156 @@
1
+ import { ContextIdStore } from "@twin.org/context";
2
+ import { BaseError, ComponentFactory, HealthStatus, Is } from "@twin.org/core";
3
+ import { EngineCoreFactory } from "@twin.org/engine-models";
4
+ /**
5
+ * The health service for the server.
6
+ */
7
+ export class HealthService {
8
+ /**
9
+ * Runtime name for the class.
10
+ */
11
+ static CLASS_NAME = "HealthService";
12
+ /**
13
+ * The server health.
14
+ * @internal
15
+ */
16
+ _healthInfo;
17
+ /**
18
+ * The interval for checking the health of the components and setting it in the health service.
19
+ * @internal
20
+ */
21
+ _healthCheckInterval;
22
+ /**
23
+ * The initial interval for checking the health of the components and setting it in the health service.
24
+ * This is used to check the health of the components immediately after the service is started.
25
+ * @internal
26
+ */
27
+ _initialInterval;
28
+ /**
29
+ * Interval for checking the health of the components and setting it in the health service.
30
+ * @internal
31
+ */
32
+ _healthTimer;
33
+ /**
34
+ * Create a new instance of HealthService.
35
+ * @param options The constructor options.
36
+ */
37
+ constructor(options) {
38
+ this._healthInfo = { status: HealthStatus.Ok, components: [] };
39
+ this._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;
40
+ this._initialInterval = options?.config?.initialInterval ?? 2000;
41
+ }
42
+ /**
43
+ * Returns the class name of the component.
44
+ * @returns The class name of the component.
45
+ */
46
+ className() {
47
+ return HealthService.CLASS_NAME;
48
+ }
49
+ /**
50
+ * The component needs to be started when the node is initialized.
51
+ * @param nodeLoggingComponentType The node logging component type.
52
+ * @returns Nothing.
53
+ */
54
+ async start(nodeLoggingComponentType) {
55
+ const engineCore = EngineCoreFactory.getIfExists("engine");
56
+ if (!Is.empty(engineCore) && Is.empty(this._healthTimer)) {
57
+ // Immediately check health after a startup settling period
58
+ // the interval for the next checks are trigger on success of the current
59
+ // check to prevent overlapping checks in case of long running health checks
60
+ this._healthTimer = globalThis.setTimeout(async () => this.checkHealth(engineCore, nodeLoggingComponentType), this._initialInterval);
61
+ }
62
+ }
63
+ /**
64
+ * The component needs to be stopped when the node is closed.
65
+ * @param nodeLoggingComponentType The node logging component type.
66
+ * @returns Nothing.
67
+ */
68
+ async stop(nodeLoggingComponentType) {
69
+ if (this._healthTimer) {
70
+ clearTimeout(this._healthTimer);
71
+ this._healthTimer = undefined;
72
+ }
73
+ }
74
+ /**
75
+ * Get the server health.
76
+ * @returns The service health.
77
+ */
78
+ async healthStatus() {
79
+ return this._healthInfo;
80
+ }
81
+ /**
82
+ * Check the health of all registered components and set the health info in the service.
83
+ * @param engineCore The engine core to get the registered components from.
84
+ * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.
85
+ * @returns Nothing.
86
+ * @internal
87
+ */
88
+ async checkHealth(engineCore, nodeLoggingComponentType) {
89
+ await ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {
90
+ const allHealth = [];
91
+ const registeredInstances = await engineCore.getRegisteredComponents();
92
+ for (const registeredInstance of registeredInstances) {
93
+ const healthMethod = registeredInstance.component.health?.bind(registeredInstance.component);
94
+ if (Is.function(healthMethod)) {
95
+ try {
96
+ allHealth.push(...(await healthMethod()));
97
+ }
98
+ catch (error) {
99
+ const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
100
+ await nodeLogging?.log({
101
+ level: "error",
102
+ source: HealthService.CLASS_NAME,
103
+ message: "componentHealthCheckFailed",
104
+ data: {
105
+ className: registeredInstance.component.className()
106
+ },
107
+ error: BaseError.fromError(error)
108
+ });
109
+ }
110
+ }
111
+ }
112
+ this.groupHealthByName(allHealth);
113
+ });
114
+ // Queue the next health check.
115
+ this._healthTimer = globalThis.setTimeout(async () => this.checkHealth(engineCore, nodeLoggingComponentType), this._healthCheckInterval);
116
+ }
117
+ /**
118
+ * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.
119
+ * @param entries The flat list of health entries from all components.
120
+ * @returns The grouped list.
121
+ * @internal
122
+ */
123
+ groupHealthByName(entries) {
124
+ const bySource = new Map();
125
+ for (const entry of entries) {
126
+ const existing = bySource.get(entry.source) ?? [];
127
+ existing.push(entry);
128
+ bySource.set(entry.source, existing);
129
+ }
130
+ const result = [];
131
+ for (const [source, group] of bySource) {
132
+ if (group.length > 1) {
133
+ let parentStatus = HealthStatus.Ok;
134
+ if (group.some(e => e.status === HealthStatus.Error)) {
135
+ parentStatus = HealthStatus.Error;
136
+ }
137
+ else if (group.some(e => e.status === HealthStatus.Warning)) {
138
+ parentStatus = HealthStatus.Warning;
139
+ }
140
+ result.push({ source, status: parentStatus, grouped: group });
141
+ }
142
+ else {
143
+ result.push(group[0]);
144
+ }
145
+ }
146
+ let finalStatus = HealthStatus.Ok;
147
+ if (result.some(e => e.status === HealthStatus.Error)) {
148
+ finalStatus = HealthStatus.Error;
149
+ }
150
+ else if (result.some(e => e.status === HealthStatus.Warning)) {
151
+ finalStatus = HealthStatus.Warning;
152
+ }
153
+ this._healthInfo = { status: finalStatus, components: result };
154
+ }
155
+ }
156
+ //# sourceMappingURL=healthService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healthService.js","sourceRoot":"","sources":["../../src/healthService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHealthComponent } from \"@twin.org/api-models\";\nimport { ContextIdStore } from \"@twin.org/context\";\nimport { BaseError, ComponentFactory, HealthStatus, type IHealth, Is } from \"@twin.org/core\";\nimport { EngineCoreFactory, type IEngineCore } from \"@twin.org/engine-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/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 * 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 and setting it in the health service.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * The initial interval for checking the health of the components and setting it in the health service.\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 * Interval for checking the health of the components and setting it in the health service.\n\t * @internal\n\t */\n\tprivate _healthTimer: NodeJS.Timeout | 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._initialInterval = options?.config?.initialInterval ?? 2000;\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 Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst engineCore = EngineCoreFactory.getIfExists(\"engine\");\n\n\t\tif (!Is.empty(engineCore) && Is.empty(this._healthTimer)) {\n\t\t\t// Immediately check health after a startup settling period\n\t\t\t// the interval for the next checks are trigger 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._healthTimer = globalThis.setTimeout(\n\t\t\t\tasync () => this.checkHealth(engineCore, nodeLoggingComponentType),\n\t\t\t\tthis._initialInterval\n\t\t\t);\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 Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._healthTimer) {\n\t\t\tclearTimeout(this._healthTimer);\n\t\t\tthis._healthTimer = undefined;\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 and set the health info in the service.\n\t * @param engineCore The engine core to get the registered components from.\n\t * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async checkHealth(\n\t\tengineCore: IEngineCore,\n\t\tnodeLoggingComponentType?: string\n\t): Promise<void> {\n\t\tawait ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {\n\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\tregisteredInstance.component\n\t\t\t\t);\n\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tclassName: registeredInstance.component.className()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.groupHealthByName(allHealth);\n\t\t});\n\n\t\t// Queue the next health check.\n\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\tasync () => this.checkHealth(engineCore, nodeLoggingComponentType),\n\t\t\tthis._healthCheckInterval\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 * @returns The grouped list.\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(entry);\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"]}
@@ -0,0 +1,89 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { HttpUrlHelper } from "@twin.org/api-models";
4
+ import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
5
+ import { ComponentFactory, Guards, Is } from "@twin.org/core";
6
+ /**
7
+ * The hosting service for the server.
8
+ */
9
+ export class HostingService {
10
+ /**
11
+ * Runtime name for the class.
12
+ */
13
+ static CLASS_NAME = "HostingService";
14
+ /**
15
+ * The tenant admin component.
16
+ * @internal
17
+ */
18
+ _tenantAdminComponentType;
19
+ /**
20
+ * The local origin URL e.g. "http://localhost:3000".
21
+ * @internal
22
+ */
23
+ _localOrigin;
24
+ /**
25
+ * The APIs public base URL e.g. "https://api.example.com:1234".
26
+ * @internal
27
+ */
28
+ _publicOrigin;
29
+ /**
30
+ * Create a new instance of HostingService.
31
+ * @param options The options to create the service.
32
+ */
33
+ constructor(options) {
34
+ Guards.object(HostingService.CLASS_NAME, "options", options);
35
+ Guards.object(HostingService.CLASS_NAME, "options.config", options.config);
36
+ Guards.stringValue(HostingService.CLASS_NAME, "options.config.localOrigin", options.config.localOrigin);
37
+ this._tenantAdminComponentType = options?.tenantAdminComponentType ?? "tenant-admin";
38
+ this._localOrigin = options.config.localOrigin;
39
+ this._publicOrigin = options.config.publicOrigin;
40
+ }
41
+ /**
42
+ * Returns the class name of the component.
43
+ * @returns The class name of the component.
44
+ */
45
+ className() {
46
+ return HostingService.CLASS_NAME;
47
+ }
48
+ /**
49
+ * Get the public origin for the hosting.
50
+ * @param serverRequestUrl The url of the current server request if there is one.
51
+ * @returns The public origin.
52
+ */
53
+ async getPublicOrigin(serverRequestUrl) {
54
+ const contextIds = await ContextIdStore.getContextIds();
55
+ const tenantId = contextIds?.[ContextIdKeys.Tenant];
56
+ let tenantPublicOrigin;
57
+ if (Is.stringValue(tenantId)) {
58
+ tenantPublicOrigin = await this.getTenantOrigin(tenantId);
59
+ }
60
+ const serverRequestOrigin = Is.stringValue(serverRequestUrl)
61
+ ? HttpUrlHelper.extractOrigin(serverRequestUrl)
62
+ : undefined;
63
+ return tenantPublicOrigin ?? this._publicOrigin ?? serverRequestOrigin ?? this._localOrigin;
64
+ }
65
+ /**
66
+ * Get the public origin for the tenant if one exists.
67
+ * @param tenantId The tenant identifier.
68
+ * @returns The public origin for the tenant.
69
+ */
70
+ async getTenantOrigin(tenantId) {
71
+ Guards.stringHexLength(HostingService.CLASS_NAME, "tenantId", tenantId, 32);
72
+ const tenantAdminComponent = ComponentFactory.getIfExists(this._tenantAdminComponentType);
73
+ if (Is.empty(tenantAdminComponent)) {
74
+ return undefined;
75
+ }
76
+ const tenant = await tenantAdminComponent.get(tenantId);
77
+ return tenant?.publicOrigin;
78
+ }
79
+ /**
80
+ * Build a public url based on the public origin and the url provided.
81
+ * @param url The url to build upon the public origin.
82
+ * @returns The full url based on the public origin.
83
+ */
84
+ async buildPublicUrl(url) {
85
+ const publicOrigin = await this.getPublicOrigin(url);
86
+ return HttpUrlHelper.replaceOrigin(url, publicOrigin);
87
+ }
88
+ }
89
+ //# sourceMappingURL=hostingService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostingService.js","sourceRoot":"","sources":["../../src/hostingService.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EACN,aAAa,EAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAI9D;;GAEG;AACH,MAAM,OAAO,cAAc;IAC1B;;OAEG;IACI,MAAM,CAAU,UAAU,oBAAoC;IAErE;;;OAGG;IACc,yBAAyB,CAAU;IAEpD;;;OAGG;IACc,YAAY,CAAS;IAEtC;;;OAGG;IACc,aAAa,CAAU;IAExC;;;OAGG;IACH,YAAY,OAA0C;QACrD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,oBAA0B,OAAO,CAAC,MAAM,CAAC,CAAC;QACjF,MAAM,CAAC,WAAW,CACjB,cAAc,CAAC,UAAU,gCAEzB,OAAO,CAAC,MAAM,CAAC,WAAW,CAC1B,CAAC;QACF,IAAI,CAAC,yBAAyB,GAAG,OAAO,EAAE,wBAAwB,IAAI,cAAc,CAAC;QACrF,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;QAC/C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;IAClD,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,cAAc,CAAC,UAAU,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,eAAe,CAAC,gBAAyB;QACrD,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,kBAAkB,CAAC;QACvB,IAAI,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,kBAAkB,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,mBAAmB,GAAG,EAAE,CAAC,WAAW,CAAC,gBAAgB,CAAC;YAC3D,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,gBAAgB,CAAC;YAC/C,CAAC,CAAC,SAAS,CAAC;QAEb,OAAO,kBAAkB,IAAI,IAAI,CAAC,aAAa,IAAI,mBAAmB,IAAI,IAAI,CAAC,YAAY,CAAC;IAC7F,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,eAAe,CAAC,QAAgB;QAC5C,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,UAAU,cAAoB,QAAQ,EAAE,EAAE,CAAC,CAAC;QAElF,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,WAAW,CACxD,IAAI,CAAC,yBAAyB,CAC9B,CAAC;QAEF,IAAI,EAAE,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACpC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxD,OAAO,MAAM,EAAE,YAAY,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,cAAc,CAAC,GAAW;QACtC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO,aAAa,CAAC,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACvD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpUrlHelper,\n\ttype IHostingComponent,\n\ttype ITenantAdminComponent\n} from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { ComponentFactory, Guards, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IHostingServiceConstructorOptions } from \"./models/IHostingServiceConstructorOptions.js\";\n\n/**\n * The hosting service for the server.\n */\nexport class HostingService implements IHostingComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<HostingService>();\n\n\t/**\n\t * The tenant admin component.\n\t * @internal\n\t */\n\tprivate readonly _tenantAdminComponentType?: string;\n\n\t/**\n\t * The local origin URL e.g. \"http://localhost:3000\".\n\t * @internal\n\t */\n\tprivate readonly _localOrigin: string;\n\n\t/**\n\t * The APIs public base URL e.g. \"https://api.example.com:1234\".\n\t * @internal\n\t */\n\tprivate readonly _publicOrigin?: string;\n\n\t/**\n\t * Create a new instance of HostingService.\n\t * @param options The options to create the service.\n\t */\n\tconstructor(options: IHostingServiceConstructorOptions) {\n\t\tGuards.object(HostingService.CLASS_NAME, nameof(options), options);\n\t\tGuards.object(HostingService.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.stringValue(\n\t\t\tHostingService.CLASS_NAME,\n\t\t\tnameof(options.config.localOrigin),\n\t\t\toptions.config.localOrigin\n\t\t);\n\t\tthis._tenantAdminComponentType = options?.tenantAdminComponentType ?? \"tenant-admin\";\n\t\tthis._localOrigin = options.config.localOrigin;\n\t\tthis._publicOrigin = options.config.publicOrigin;\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 HostingService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Get the public origin for the hosting.\n\t * @param serverRequestUrl The url of the current server request if there is one.\n\t * @returns The public origin.\n\t */\n\tpublic async getPublicOrigin(serverRequestUrl?: string): Promise<string> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst tenantId = contextIds?.[ContextIdKeys.Tenant];\n\t\tlet tenantPublicOrigin;\n\t\tif (Is.stringValue(tenantId)) {\n\t\t\ttenantPublicOrigin = await this.getTenantOrigin(tenantId);\n\t\t}\n\n\t\tconst serverRequestOrigin = Is.stringValue(serverRequestUrl)\n\t\t\t? HttpUrlHelper.extractOrigin(serverRequestUrl)\n\t\t\t: undefined;\n\n\t\treturn tenantPublicOrigin ?? this._publicOrigin ?? serverRequestOrigin ?? this._localOrigin;\n\t}\n\n\t/**\n\t * Get the public origin for the tenant if one exists.\n\t * @param tenantId The tenant identifier.\n\t * @returns The public origin for the tenant.\n\t */\n\tpublic async getTenantOrigin(tenantId: string): Promise<string | undefined> {\n\t\tGuards.stringHexLength(HostingService.CLASS_NAME, nameof(tenantId), tenantId, 32);\n\n\t\tconst tenantAdminComponent = ComponentFactory.getIfExists<ITenantAdminComponent>(\n\t\t\tthis._tenantAdminComponentType\n\t\t);\n\n\t\tif (Is.empty(tenantAdminComponent)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst tenant = await tenantAdminComponent.get(tenantId);\n\t\treturn tenant?.publicOrigin;\n\t}\n\n\t/**\n\t * Build a public url based on the public origin and the url provided.\n\t * @param url The url to build upon the public origin.\n\t * @returns The full url based on the public origin.\n\t */\n\tpublic async buildPublicUrl(url: string): Promise<string> {\n\t\tconst publicOrigin = await this.getPublicOrigin(url);\n\t\treturn HttpUrlHelper.replaceOrigin(url, publicOrigin);\n\t}\n}\n"]}
package/dist/es/index.js CHANGED
@@ -1,8 +1,18 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./healthRoutes.js";
4
+ export * from "./healthService.js";
5
+ export * from "./hostingService.js";
3
6
  export * from "./informationRoutes.js";
4
7
  export * from "./informationService.js";
8
+ export * from "./urlTransformerService.js";
9
+ export * from "./models/IHealthServiceConfig.js";
10
+ export * from "./models/IHealthServiceConstructorOptions.js";
11
+ export * from "./models/IHostingServiceConfig.js";
12
+ export * from "./models/IHostingServiceConstructorOptions.js";
5
13
  export * from "./models/IInformationServiceConfig.js";
6
14
  export * from "./models/IInformationServiceConstructorOptions.js";
15
+ export * from "./models/IUrlTransformerServiceConfig.js";
16
+ export * from "./models/IUrlTransformerServiceConstructorOptions.js";
7
17
  export * from "./restEntryPoints.js";
8
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,sBAAsB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./restEntryPoints.js\";\n"]}
1
+ {"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,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mCAAmC,CAAC;AAClD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,0CAA0C,CAAC;AACzD,cAAc,sDAAsD,CAAC;AACrE,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 \"./hostingService.js\";\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./urlTransformerService.js\";\nexport * from \"./models/IHealthServiceConfig.js\";\nexport * from \"./models/IHealthServiceConstructorOptions.js\";\nexport * from \"./models/IHostingServiceConfig.js\";\nexport * from \"./models/IHostingServiceConstructorOptions.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./models/IUrlTransformerServiceConfig.js\";\nexport * from \"./models/IUrlTransformerServiceConstructorOptions.js\";\nexport * from \"./restEntryPoints.js\";\n"]}