@twin.org/api-service 0.0.3-next.28 → 0.0.3-next.30

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 (51) hide show
  1. package/dist/es/healthRoutes.js +108 -0
  2. package/dist/es/healthRoutes.js.map +1 -0
  3. package/dist/es/healthService.js +136 -0
  4. package/dist/es/healthService.js.map +1 -0
  5. package/dist/es/hostingService.js +5 -10
  6. package/dist/es/hostingService.js.map +1 -1
  7. package/dist/es/index.js +7 -0
  8. package/dist/es/index.js.map +1 -1
  9. package/dist/es/informationRoutes.js +31 -65
  10. package/dist/es/informationRoutes.js.map +1 -1
  11. package/dist/es/informationService.js +9 -85
  12. package/dist/es/informationService.js.map +1 -1
  13. package/dist/es/models/IHealthServiceConfig.js +4 -0
  14. package/dist/es/models/IHealthServiceConfig.js.map +1 -0
  15. package/dist/es/models/IHealthServiceConstructorOptions.js +2 -0
  16. package/dist/es/models/IHealthServiceConstructorOptions.js.map +1 -0
  17. package/dist/es/models/IHostingServiceConstructorOptions.js.map +1 -1
  18. package/dist/es/models/IUrlTransformerServiceConfig.js +4 -0
  19. package/dist/es/models/IUrlTransformerServiceConfig.js.map +1 -0
  20. package/dist/es/models/IUrlTransformerServiceConstructorOptions.js +2 -0
  21. package/dist/es/models/IUrlTransformerServiceConstructorOptions.js.map +1 -0
  22. package/dist/es/restEntryPoints.js +7 -0
  23. package/dist/es/restEntryPoints.js.map +1 -1
  24. package/dist/es/urlTransformerService.js +214 -0
  25. package/dist/es/urlTransformerService.js.map +1 -0
  26. package/dist/types/healthRoutes.d.ts +20 -0
  27. package/dist/types/healthService.d.ts +42 -0
  28. package/dist/types/index.d.ts +7 -0
  29. package/dist/types/informationRoutes.d.ts +3 -3
  30. package/dist/types/informationService.d.ts +9 -21
  31. package/dist/types/models/IHealthServiceConfig.d.ts +10 -0
  32. package/dist/types/models/IHealthServiceConstructorOptions.d.ts +10 -0
  33. package/dist/types/models/IHostingServiceConstructorOptions.d.ts +1 -1
  34. package/dist/types/models/IUrlTransformerServiceConfig.d.ts +19 -0
  35. package/dist/types/models/IUrlTransformerServiceConstructorOptions.d.ts +15 -0
  36. package/dist/types/urlTransformerService.d.ts +81 -0
  37. package/docs/changelog.md +28 -0
  38. package/docs/reference/classes/HealthService.md +123 -0
  39. package/docs/reference/classes/InformationService.md +8 -84
  40. package/docs/reference/classes/UrlTransformerService.md +321 -0
  41. package/docs/reference/functions/generateRestRoutesHealth.md +25 -0
  42. package/docs/reference/functions/serverReadyz.md +31 -0
  43. package/docs/reference/index.md +10 -1
  44. package/docs/reference/interfaces/IHealthServiceConfig.md +17 -0
  45. package/docs/reference/interfaces/IHealthServiceConstructorOptions.md +11 -0
  46. package/docs/reference/interfaces/IHostingServiceConstructorOptions.md +1 -1
  47. package/docs/reference/interfaces/IUrlTransformerServiceConfig.md +32 -0
  48. package/docs/reference/interfaces/IUrlTransformerServiceConstructorOptions.md +25 -0
  49. package/docs/reference/variables/tagsHealth.md +5 -0
  50. package/locales/en.json +13 -1
  51. package/package.json +5 -2
@@ -1,8 +1,8 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { readFile } from "node:fs/promises";
4
- import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
5
4
  import { Guards, Is } from "@twin.org/core";
5
+ import { EngineCoreFactory } from "@twin.org/engine-models";
6
6
  /**
7
7
  * The information service for the server.
8
8
  */
@@ -16,11 +16,6 @@ export class InformationService {
16
16
  * @internal
17
17
  */
18
18
  _serverInfo;
19
- /**
20
- * The server health.
21
- * @internal
22
- */
23
- _healthInfo;
24
19
  /**
25
20
  * The path to the favicon Spec.
26
21
  * @internal
@@ -50,9 +45,6 @@ export class InformationService {
50
45
  Guards.object(InformationService.CLASS_NAME, "options.config", options.config);
51
46
  Guards.object(InformationService.CLASS_NAME, "options.config.serverInfo", options.config.serverInfo);
52
47
  this._serverInfo = options.config.serverInfo;
53
- this._healthInfo = {
54
- status: "ok"
55
- };
56
48
  this._faviconPath = options.config.favIconPath;
57
49
  this._openApiSpecPath = options.config.openApiSpecPath;
58
50
  }
@@ -111,86 +103,18 @@ export class InformationService {
111
103
  * @returns True if the server is live.
112
104
  */
113
105
  async livez() {
114
- let errorCount = 0;
115
- if (Is.arrayValue(this._healthInfo.components)) {
116
- errorCount = this._healthInfo.components.filter(c => c.status === "error").length;
117
- }
118
- return errorCount === 0;
119
- }
120
- /**
121
- * Get the server health.
122
- * @returns The service health.
123
- */
124
- async health() {
125
- let errorCount = 0;
126
- let warningCount = 0;
127
- const contextIds = await ContextIdStore.getContextIds();
128
- const tenantId = contextIds?.[ContextIdKeys.Tenant];
129
- // Filter so we only get components that are not tenant specific or match the tenant id
130
- const components = this._healthInfo.components?.filter(c => Is.empty(c.tenantId) || c.tenantId === tenantId);
131
- if (Is.arrayValue(components)) {
132
- errorCount = components.filter(c => c.status === "error").length;
133
- warningCount = components.filter(c => c.status === "warning").length;
134
- }
135
- if (errorCount > 0) {
136
- this._healthInfo.status = "error";
137
- }
138
- else if (warningCount > 0) {
139
- this._healthInfo.status = "warning";
140
- }
141
- else {
142
- this._healthInfo.status = "ok";
143
- }
144
- return {
145
- status: this._healthInfo.status,
146
- components: components?.map(c => ({
147
- name: c.name,
148
- status: c.status,
149
- details: c.details
150
- }))
151
- };
106
+ return { status: "alive" };
152
107
  }
153
108
  /**
154
- * Set the status of a component.
155
- * @param name The component name.
156
- * @param status The status of the component.
157
- * @param details The details for the status.
158
- * @param tenantId The tenant id, optional if the health status is not tenant specific.
159
- * @returns Nothing.
160
- */
161
- async setComponentHealth(name, status, details, tenantId) {
162
- const component = Is.empty(tenantId)
163
- ? this._healthInfo.components?.find(c => c.name === name && Is.empty(c.tenantId))
164
- : this._healthInfo.components?.find(c => c.name === name && c.tenantId === tenantId);
165
- if (Is.undefined(component)) {
166
- this._healthInfo.components ??= [];
167
- this._healthInfo.components.push({
168
- name,
169
- status,
170
- details,
171
- tenantId
172
- });
173
- }
174
- else {
175
- component.status = status;
176
- component.details = details;
177
- }
178
- }
179
- /**
180
- * Remove the status of a component.
181
- * @param name The component name.
182
- * @param tenantId The tenant id, optional if the health status is not tenant specific.
183
- * @returns Nothing.
109
+ * Is the server ready.
110
+ * @returns The readyz status of the server.
184
111
  */
185
- async removeComponentHealth(name, tenantId) {
186
- if (Is.arrayValue(this._healthInfo.components)) {
187
- const componentIndex = Is.empty(tenantId)
188
- ? this._healthInfo.components?.findIndex(c => c.name === name && Is.empty(c.tenantId))
189
- : this._healthInfo.components?.findIndex(c => c.name === name && c.tenantId === tenantId);
190
- if (componentIndex !== -1) {
191
- this._healthInfo.components.splice(componentIndex, 1);
192
- }
112
+ async readyz() {
113
+ const engine = EngineCoreFactory.getIfExists("engine");
114
+ if (engine?.isStarted()) {
115
+ return { status: "ready" };
193
116
  }
117
+ return { status: "not ready" };
194
118
  }
195
119
  }
196
120
  //# sourceMappingURL=informationService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"informationService.js","sourceRoot":"","sources":["../../src/informationService.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQ5C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAI5C;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAC9B;;OAEG;IACI,MAAM,CAAU,UAAU,wBAAwC;IAEzE;;;OAGG;IACc,WAAW,CAAc;IAE1C;;;OAGG;IACc,WAAW,CAG1B;IAEF;;;OAGG;IACc,YAAY,CAAU;IAEvC;;;OAGG;IACK,QAAQ,CAAc;IAE9B;;;OAGG;IACc,gBAAgB,CAAU;IAE3C;;;OAGG;IACK,YAAY,CAAU;IAE9B;;;OAGG;IACH,YAAY,OAA8C;QACzD,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QACvE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,oBAA0B,OAAO,CAAC,MAAM,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,+BAE7B,OAAO,CAAC,MAAM,CAAC,UAAU,CACzB,CAAC;QAEF,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;QAC7C,IAAI,CAAC,WAAW,GAAG;YAClB,MAAM,EAAE,IAAI;SACZ,CAAC;QACF,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;QAC/C,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;IACxD,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,kBAAkB,CAAC,UAAU,CAAC;IACtC,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC1C,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAC1D,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;IACjE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAChD,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;QACnF,CAAC;QAED,OAAO,UAAU,KAAK,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAEpD,uFAAuF;QACvF,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CACrD,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CACpD,CAAC;QAEF,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;YACjE,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QACtE,CAAC;QAED,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,OAAO,CAAC;QACnC,CAAC;aAAM,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,SAAS,CAAC;QACrC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC;QAChC,CAAC;QAED,OAAO;YACN,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM;YAC/B,UAAU,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACjC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;aAClB,CAAC,CAAC;SACH,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,kBAAkB,CAC9B,IAAY,EACZ,MAAoB,EACpB,OAAgB,EAChB,QAAiB;QAEjB,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;YACnC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACjF,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QAEtF,IAAI,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,CAAC,UAAU,KAAK,EAAE,CAAC;YACnC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC;gBAChC,IAAI;gBACJ,MAAM;gBACN,OAAO;gBACP,QAAQ;aACR,CAAC,CAAC;QACJ,CAAC;aAAM,CAAC;YACP,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC;YAC1B,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC;QAC7B,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,qBAAqB,CAAC,IAAY,EAAE,QAAiB;QACjE,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAChD,MAAM,cAAc,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;gBACxC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;gBACtF,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YAE3F,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;YACvD,CAAC;QACF,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { readFile } from \"node:fs/promises\";\nimport type {\n\tHealthStatus,\n\tIHealthComponentInfo,\n\tIHealthInfo,\n\tIInformationComponent,\n\tIServerInfo\n} from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { Guards, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IInformationServiceConstructorOptions } from \"./models/IInformationServiceConstructorOptions.js\";\n\n/**\n * The information service for the server.\n */\nexport class InformationService implements IInformationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<InformationService>();\n\n\t/**\n\t * The server information.\n\t * @internal\n\t */\n\tprivate readonly _serverInfo: IServerInfo;\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate readonly _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents?: (IHealthComponentInfo & { tenantId?: string })[];\n\t};\n\n\t/**\n\t * The path to the favicon Spec.\n\t * @internal\n\t */\n\tprivate readonly _faviconPath?: string;\n\n\t/**\n\t * The favicon.\n\t * @internal\n\t */\n\tprivate _favicon?: Uint8Array;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t * @internal\n\t */\n\tprivate readonly _openApiSpecPath?: string;\n\n\t/**\n\t * The OpenAPI spec.\n\t * @internal\n\t */\n\tprivate _openApiSpec?: string;\n\n\t/**\n\t * Create a new instance of InformationService.\n\t * @param options The options to create the service.\n\t */\n\tconstructor(options: IInformationServiceConstructorOptions) {\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options), options);\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.object(\n\t\t\tInformationService.CLASS_NAME,\n\t\t\tnameof(options.config.serverInfo),\n\t\t\toptions.config.serverInfo\n\t\t);\n\n\t\tthis._serverInfo = options.config.serverInfo;\n\t\tthis._healthInfo = {\n\t\t\tstatus: \"ok\"\n\t\t};\n\t\tthis._faviconPath = options.config.favIconPath;\n\t\tthis._openApiSpecPath = options.config.openApiSpecPath;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn InformationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @returns Nothing.\n\t */\n\tpublic async start(): Promise<void> {\n\t\tconst openApiPath = this._openApiSpecPath;\n\t\tif (Is.stringValue(openApiPath)) {\n\t\t\tconst contentBuffer = await readFile(openApiPath, \"utf8\");\n\t\t\tthis._openApiSpec = JSON.parse(contentBuffer);\n\t\t}\n\n\t\tconst favIconPath = this._faviconPath;\n\t\tif (Is.stringValue(favIconPath)) {\n\t\t\tthis._favicon = await readFile(favIconPath);\n\t\t}\n\t}\n\n\t/**\n\t * Get the root information.\n\t * @returns The root information.\n\t */\n\tpublic async root(): Promise<string> {\n\t\treturn `${this._serverInfo.name} - ${this._serverInfo.version}`;\n\t}\n\n\t/**\n\t * Get the server information.\n\t * @returns The service information.\n\t */\n\tpublic async info(): Promise<IServerInfo> {\n\t\treturn this._serverInfo;\n\t}\n\n\t/**\n\t * Get the favicon.\n\t * @returns The favicon.\n\t */\n\tpublic async favicon(): Promise<Uint8Array | undefined> {\n\t\treturn this._favicon;\n\t}\n\n\t/**\n\t * Get the OpenAPI spec.\n\t * @returns The OpenAPI spec.\n\t */\n\tpublic async spec(): Promise<unknown> {\n\t\treturn this._openApiSpec;\n\t}\n\n\t/**\n\t * Is the server live.\n\t * @returns True if the server is live.\n\t */\n\tpublic async livez(): Promise<boolean> {\n\t\tlet errorCount = 0;\n\n\t\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\terrorCount = this._healthInfo.components.filter(c => c.status === \"error\").length;\n\t\t}\n\n\t\treturn errorCount === 0;\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async health(): Promise<IHealthInfo> {\n\t\tlet errorCount = 0;\n\t\tlet warningCount = 0;\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst tenantId = contextIds?.[ContextIdKeys.Tenant];\n\n\t\t// Filter so we only get components that are not tenant specific or match the tenant id\n\t\tconst components = this._healthInfo.components?.filter(\n\t\t\tc => Is.empty(c.tenantId) || c.tenantId === tenantId\n\t\t);\n\n\t\tif (Is.arrayValue(components)) {\n\t\t\terrorCount = components.filter(c => c.status === \"error\").length;\n\t\t\twarningCount = components.filter(c => c.status === \"warning\").length;\n\t\t}\n\n\t\tif (errorCount > 0) {\n\t\t\tthis._healthInfo.status = \"error\";\n\t\t} else if (warningCount > 0) {\n\t\t\tthis._healthInfo.status = \"warning\";\n\t\t} else {\n\t\t\tthis._healthInfo.status = \"ok\";\n\t\t}\n\n\t\treturn {\n\t\t\tstatus: this._healthInfo.status,\n\t\t\tcomponents: components?.map(c => ({\n\t\t\t\tname: c.name,\n\t\t\t\tstatus: c.status,\n\t\t\t\tdetails: c.details\n\t\t\t}))\n\t\t};\n\t}\n\n\t/**\n\t * Set the status of a component.\n\t * @param name The component name.\n\t * @param status The status of the component.\n\t * @param details The details for the status.\n\t * @param tenantId The tenant id, optional if the health status is not tenant specific.\n\t * @returns Nothing.\n\t */\n\tpublic async setComponentHealth(\n\t\tname: string,\n\t\tstatus: HealthStatus,\n\t\tdetails?: string,\n\t\ttenantId?: string\n\t): Promise<void> {\n\t\tconst component = Is.empty(tenantId)\n\t\t\t? this._healthInfo.components?.find(c => c.name === name && Is.empty(c.tenantId))\n\t\t\t: this._healthInfo.components?.find(c => c.name === name && c.tenantId === tenantId);\n\n\t\tif (Is.undefined(component)) {\n\t\t\tthis._healthInfo.components ??= [];\n\t\t\tthis._healthInfo.components.push({\n\t\t\t\tname,\n\t\t\t\tstatus,\n\t\t\t\tdetails,\n\t\t\t\ttenantId\n\t\t\t});\n\t\t} else {\n\t\t\tcomponent.status = status;\n\t\t\tcomponent.details = details;\n\t\t}\n\t}\n\n\t/**\n\t * Remove the status of a component.\n\t * @param name The component name.\n\t * @param tenantId The tenant id, optional if the health status is not tenant specific.\n\t * @returns Nothing.\n\t */\n\tpublic async removeComponentHealth(name: string, tenantId?: string): Promise<void> {\n\t\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\tconst componentIndex = Is.empty(tenantId)\n\t\t\t\t? this._healthInfo.components?.findIndex(c => c.name === name && Is.empty(c.tenantId))\n\t\t\t\t: this._healthInfo.components?.findIndex(c => c.name === name && c.tenantId === tenantId);\n\n\t\t\tif (componentIndex !== -1) {\n\t\t\t\tthis._healthInfo.components.splice(componentIndex, 1);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"informationService.js","sourceRoot":"","sources":["../../src/informationService.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5D;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAC9B;;OAEG;IACI,MAAM,CAAU,UAAU,wBAAwC;IAEzE;;;OAGG;IACc,WAAW,CAAc;IAE1C;;;OAGG;IACc,YAAY,CAAU;IAEvC;;;OAGG;IACK,QAAQ,CAAc;IAE9B;;;OAGG;IACc,gBAAgB,CAAU;IAE3C;;;OAGG;IACK,YAAY,CAAU;IAE9B;;;OAGG;IACH,YAAY,OAA8C;QACzD,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QACvE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,oBAA0B,OAAO,CAAC,MAAM,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,+BAE7B,OAAO,CAAC,MAAM,CAAC,UAAU,CACzB,CAAC;QAEF,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;QAC7C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;QAC/C,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;IACxD,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,kBAAkB,CAAC,UAAU,CAAC;IACtC,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC1C,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAC1D,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;IACjE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QAGjB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM;QAGlB,MAAM,MAAM,GAAG,iBAAiB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAEvD,IAAI,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC5B,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAChC,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { readFile } from \"node:fs/promises\";\nimport type { IInformationComponent, IServerInfo } from \"@twin.org/api-models\";\nimport { Guards, Is } from \"@twin.org/core\";\nimport { EngineCoreFactory } from \"@twin.org/engine-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IInformationServiceConstructorOptions } from \"./models/IInformationServiceConstructorOptions.js\";\n\n/**\n * The information service for the server.\n */\nexport class InformationService implements IInformationComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<InformationService>();\n\n\t/**\n\t * The server information.\n\t * @internal\n\t */\n\tprivate readonly _serverInfo: IServerInfo;\n\n\t/**\n\t * The path to the favicon Spec.\n\t * @internal\n\t */\n\tprivate readonly _faviconPath?: string;\n\n\t/**\n\t * The favicon.\n\t * @internal\n\t */\n\tprivate _favicon?: Uint8Array;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t * @internal\n\t */\n\tprivate readonly _openApiSpecPath?: string;\n\n\t/**\n\t * The OpenAPI spec.\n\t * @internal\n\t */\n\tprivate _openApiSpec?: string;\n\n\t/**\n\t * Create a new instance of InformationService.\n\t * @param options The options to create the service.\n\t */\n\tconstructor(options: IInformationServiceConstructorOptions) {\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options), options);\n\t\tGuards.object(InformationService.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.object(\n\t\t\tInformationService.CLASS_NAME,\n\t\t\tnameof(options.config.serverInfo),\n\t\t\toptions.config.serverInfo\n\t\t);\n\n\t\tthis._serverInfo = options.config.serverInfo;\n\t\tthis._faviconPath = options.config.favIconPath;\n\t\tthis._openApiSpecPath = options.config.openApiSpecPath;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn InformationService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The service needs to be started when the application is initialized.\n\t * @returns Nothing.\n\t */\n\tpublic async start(): Promise<void> {\n\t\tconst openApiPath = this._openApiSpecPath;\n\t\tif (Is.stringValue(openApiPath)) {\n\t\t\tconst contentBuffer = await readFile(openApiPath, \"utf8\");\n\t\t\tthis._openApiSpec = JSON.parse(contentBuffer);\n\t\t}\n\n\t\tconst favIconPath = this._faviconPath;\n\t\tif (Is.stringValue(favIconPath)) {\n\t\t\tthis._favicon = await readFile(favIconPath);\n\t\t}\n\t}\n\n\t/**\n\t * Get the root information.\n\t * @returns The root information.\n\t */\n\tpublic async root(): Promise<string> {\n\t\treturn `${this._serverInfo.name} - ${this._serverInfo.version}`;\n\t}\n\n\t/**\n\t * Get the server information.\n\t * @returns The service information.\n\t */\n\tpublic async info(): Promise<IServerInfo> {\n\t\treturn this._serverInfo;\n\t}\n\n\t/**\n\t * Get the favicon.\n\t * @returns The favicon.\n\t */\n\tpublic async favicon(): Promise<Uint8Array | undefined> {\n\t\treturn this._favicon;\n\t}\n\n\t/**\n\t * Get the OpenAPI spec.\n\t * @returns The OpenAPI spec.\n\t */\n\tpublic async spec(): Promise<unknown> {\n\t\treturn this._openApiSpec;\n\t}\n\n\t/**\n\t * Is the server live.\n\t * @returns True if the server is live.\n\t */\n\tpublic async livez(): Promise<{\n\t\tstatus: \"alive\" | \"dead\";\n\t}> {\n\t\treturn { status: \"alive\" };\n\t}\n\n\t/**\n\t * Is the server ready.\n\t * @returns The readyz status of the server.\n\t */\n\tpublic async readyz(): Promise<{\n\t\tstatus: \"ready\" | \"not ready\";\n\t}> {\n\t\tconst engine = EngineCoreFactory.getIfExists(\"engine\");\n\n\t\tif (engine?.isStarted()) {\n\t\t\treturn { status: \"ready\" };\n\t\t}\n\n\t\treturn { status: \"not ready\" };\n\t}\n}\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IHealthServiceConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IHealthServiceConfig.js","sourceRoot":"","sources":["../../../src/models/IHealthServiceConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the health service.\n */\nexport interface IHealthServiceConfig {\n\t/**\n\t * The interval for checking the health of the components and setting it in the health service.\n\t * @default 30000\n\t */\n\thealthCheckInterval?: number;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IHealthServiceConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IHealthServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IHealthServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHealthServiceConfig } from \"./IHealthServiceConfig.js\";\n\n/**\n * Options for the HealthService constructor.\n */\nexport interface IHealthServiceConstructorOptions {\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig?: IHealthServiceConfig;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IHostingServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IHostingServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHostingServiceConfig } from \"./IHostingServiceConfig.js\";\n\n/**\n * Options for the IHostingService constructor.\n */\nexport interface IHostingServiceConstructorOptions {\n\t/**\n\t * The tenant admin component type.\n\t * @default tenant-admin\n\t */\n\ttenantAdminComponentType?: string;\n\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig: IHostingServiceConfig;\n}\n"]}
1
+ {"version":3,"file":"IHostingServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IHostingServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHostingServiceConfig } from \"./IHostingServiceConfig.js\";\n\n/**\n * Options for the HostingService constructor.\n */\nexport interface IHostingServiceConstructorOptions {\n\t/**\n\t * The tenant admin component type.\n\t * @default tenant-admin\n\t */\n\ttenantAdminComponentType?: string;\n\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig: IHostingServiceConfig;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IUrlTransformerServiceConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IUrlTransformerServiceConfig.js","sourceRoot":"","sources":["../../../src/models/IUrlTransformerServiceConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the URL transformer service.\n */\nexport interface IUrlTransformerServiceConfig {\n\t/**\n\t * The name of the key to retrieve from the vault for encryption/decryption of parameters.\n\t * @default param-encryption\n\t */\n\tparamEncryptionKeyName?: string;\n\n\t/**\n\t * A dictionary mapping logical token identifiers to their URL query parameter names.\n\t * For example: { \"tenant\": \"tenant-token\" } maps the logical id \"tenant\" to the\n\t * query param \"tenant-token\". When an id is not present the id itself is used as\n\t * the param name.\n\t */\n\tqueryParamNames?: { [id: string]: string };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IUrlTransformerServiceConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IUrlTransformerServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IUrlTransformerServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IUrlTransformerServiceConfig } from \"./IUrlTransformerServiceConfig.js\";\n\n/**\n * Options for the UrlTransformerService constructor.\n */\nexport interface IUrlTransformerServiceConstructorOptions {\n\t/**\n\t * The vault connector type.\n\t * @default vault\n\t */\n\tvaultConnectorType?: string;\n\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig?: IUrlTransformerServiceConfig;\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { generateRestRoutesHealth, tagsHealth } from "./healthRoutes.js";
1
2
  import { generateRestRoutesInformation, tagsInformation } from "./informationRoutes.js";
2
3
  export const restEntryPoints = [
3
4
  {
@@ -5,6 +6,12 @@ export const restEntryPoints = [
5
6
  defaultBaseRoute: "",
6
7
  tags: tagsInformation,
7
8
  generateRoutes: generateRestRoutesInformation
9
+ },
10
+ {
11
+ name: "health",
12
+ defaultBaseRoute: "",
13
+ tags: tagsHealth,
14
+ generateRoutes: generateRestRoutesHealth
8
15
  }
9
16
  ];
10
17
  //# sourceMappingURL=restEntryPoints.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"restEntryPoints.js","sourceRoot":"","sources":["../../src/restEntryPoints.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAExF,MAAM,CAAC,MAAM,eAAe,GAA2B;IACtD;QACC,IAAI,EAAE,aAAa;QACnB,gBAAgB,EAAE,EAAE;QACpB,IAAI,EAAE,eAAe;QACrB,cAAc,EAAE,6BAA6B;KAC7C;CACD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IRestRouteEntryPoint } from \"@twin.org/api-models\";\nimport { generateRestRoutesInformation, tagsInformation } from \"./informationRoutes.js\";\n\nexport const restEntryPoints: IRestRouteEntryPoint[] = [\n\t{\n\t\tname: \"information\",\n\t\tdefaultBaseRoute: \"\",\n\t\ttags: tagsInformation,\n\t\tgenerateRoutes: generateRestRoutesInformation\n\t}\n];\n"]}
1
+ {"version":3,"file":"restEntryPoints.js","sourceRoot":"","sources":["../../src/restEntryPoints.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAExF,MAAM,CAAC,MAAM,eAAe,GAA2B;IACtD;QACC,IAAI,EAAE,aAAa;QACnB,gBAAgB,EAAE,EAAE;QACpB,IAAI,EAAE,eAAe;QACrB,cAAc,EAAE,6BAA6B;KAC7C;IACD;QACC,IAAI,EAAE,QAAQ;QACd,gBAAgB,EAAE,EAAE;QACpB,IAAI,EAAE,UAAU;QAChB,cAAc,EAAE,wBAAwB;KACxC;CACD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IRestRouteEntryPoint } from \"@twin.org/api-models\";\nimport { generateRestRoutesHealth, tagsHealth } from \"./healthRoutes.js\";\nimport { generateRestRoutesInformation, tagsInformation } from \"./informationRoutes.js\";\n\nexport const restEntryPoints: IRestRouteEntryPoint[] = [\n\t{\n\t\tname: \"information\",\n\t\tdefaultBaseRoute: \"\",\n\t\ttags: tagsInformation,\n\t\tgenerateRoutes: generateRestRoutesInformation\n\t},\n\t{\n\t\tname: \"health\",\n\t\tdefaultBaseRoute: \"\",\n\t\ttags: tagsHealth,\n\t\tgenerateRoutes: generateRestRoutesHealth\n\t}\n];\n"]}
@@ -0,0 +1,214 @@
1
+ import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
2
+ import { BaseError, Converter, GeneralError, Guards, Is, ObjectHelper, RandomHelper, Uint8ArrayHelper } from "@twin.org/core";
3
+ import { VaultConnectorFactory, VaultEncryptionType } from "@twin.org/vault-models";
4
+ /**
5
+ * The URL transformer service for encrypting and decrypting URL parameters.
6
+ */
7
+ export class UrlTransformerService {
8
+ /**
9
+ * Runtime name for the class.
10
+ */
11
+ static CLASS_NAME = "UrlTransformerService";
12
+ /**
13
+ * The prefix to use for encrypted query parameters.
14
+ * @internal
15
+ */
16
+ static _KEY_PREFIX = "x-enc-";
17
+ /**
18
+ * The default name for the parameter encryption key query parameter.
19
+ * @internal
20
+ */
21
+ static _DEFAULT_PARAM_ENCRYPTION_KEY_NAME = "param-encryption";
22
+ /**
23
+ * The vault connector.
24
+ * @internal
25
+ */
26
+ _vaultConnector;
27
+ /**
28
+ * The name of the key to retrieve from the vault for encryption/decryption of parameters.
29
+ * @internal
30
+ */
31
+ _paramEncryptionKeyName;
32
+ /**
33
+ * Maps logical token ids to their URL query parameter names.
34
+ * @internal
35
+ */
36
+ _queryParamNames;
37
+ /**
38
+ * The node identity, captured at start.
39
+ * @internal
40
+ */
41
+ _nodeId;
42
+ /**
43
+ * Create a new instance of UrlTransformerService.
44
+ * @param options The options to create the service.
45
+ */
46
+ constructor(options) {
47
+ this._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? "vault");
48
+ this._paramEncryptionKeyName =
49
+ options?.config?.paramEncryptionKeyName ??
50
+ UrlTransformerService._DEFAULT_PARAM_ENCRYPTION_KEY_NAME;
51
+ this._queryParamNames = options?.config?.queryParamNames ?? {};
52
+ }
53
+ /**
54
+ * Returns the class name of the component.
55
+ * @returns The class name of the component.
56
+ */
57
+ className() {
58
+ return UrlTransformerService.CLASS_NAME;
59
+ }
60
+ /**
61
+ * The component needs to be started when the node is initialized.
62
+ * @returns Nothing.
63
+ */
64
+ async start() {
65
+ const contextIds = await ContextIdStore.getContextIds();
66
+ this._nodeId = contextIds?.[ContextIdKeys.Node];
67
+ }
68
+ /**
69
+ * Encrypt a named token value and append it as a query parameter to the given URL.
70
+ * @param url The URL to append the encrypted token to.
71
+ * @param id The logical token identifier (e.g. "tenant").
72
+ * @param value The value to encrypt and add.
73
+ * @returns The URL with the encrypted token added as a query parameter.
74
+ */
75
+ async addEncryptedQueryParamToUrl(url, id, value) {
76
+ const paramName = this._queryParamNames[id] ?? id;
77
+ return this.addEncryptedParamsToUrl(url, { [paramName]: value });
78
+ }
79
+ /**
80
+ * Get a named token value from the query parameters.
81
+ * @param queryParams The HTTP request query containing the parameters.
82
+ * @param id The logical token identifier (e.g. "tenant").
83
+ * @returns The decrypted token value if it exists.
84
+ */
85
+ async getEncryptedQueryParam(queryParams, id) {
86
+ const paramName = this._queryParamNames[id] ?? id;
87
+ const decrypted = await this.getDecryptedParamsFromQueryParams(queryParams, [paramName]);
88
+ return decrypted[paramName];
89
+ }
90
+ /**
91
+ * Add encrypted key/value pairs to a URL's query string.
92
+ * @param url The base URL to add parameters to.
93
+ * @param params The key/value pairs to encrypt and append.
94
+ * @returns The URL with the encrypted parameters added.
95
+ */
96
+ async addEncryptedParamsToUrl(url, params) {
97
+ const urlObj = new URL(url);
98
+ const query = {};
99
+ for (const [key, value] of urlObj.searchParams.entries()) {
100
+ query[key] = value;
101
+ }
102
+ const keysToEncrypt = Object.keys(params);
103
+ for (const [key, value] of Object.entries(params)) {
104
+ query[key] = value;
105
+ }
106
+ await this.encryptQueryParams(query, keysToEncrypt);
107
+ urlObj.search = "";
108
+ for (const [key, value] of Object.entries(query)) {
109
+ urlObj.searchParams.set(key, value);
110
+ }
111
+ return urlObj.toString();
112
+ }
113
+ /**
114
+ * Decrypt specified keys from a query parameter object and return their plain-text values.
115
+ * @param queryParams The HTTP request query containing the encrypted parameters.
116
+ * @param keys The keys to decrypt.
117
+ * @returns A map of the decrypted key/value pairs that were present.
118
+ */
119
+ async getDecryptedParamsFromQueryParams(queryParams, keys) {
120
+ const queryParamsClone = ObjectHelper.clone(queryParams) ?? {};
121
+ await this.decryptQueryParams(queryParamsClone, keys);
122
+ const result = {};
123
+ for (const key of keys) {
124
+ if (Is.stringValue(queryParamsClone[key])) {
125
+ result[key] = queryParamsClone[key];
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ /**
131
+ * Encrypt query parameters.
132
+ * @param httpRequestQuery The HTTP request query containing the parameters to encrypt.
133
+ * @param keys The keys of the parameters to encrypt.
134
+ * @returns A promise that resolves when the query parameters have been encrypted.
135
+ */
136
+ async encryptQueryParams(httpRequestQuery, keys) {
137
+ if (Is.empty(httpRequestQuery)) {
138
+ return;
139
+ }
140
+ for (const key of keys) {
141
+ if (Is.stringValue(httpRequestQuery[key])) {
142
+ const encryptedValue = await this.encryptParam(httpRequestQuery[key]);
143
+ httpRequestQuery[`${UrlTransformerService._KEY_PREFIX}${key}`] = encryptedValue;
144
+ delete httpRequestQuery[key];
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Decrypt query parameters.
150
+ * @param httpRequestQuery The HTTP request query containing the encrypted values.
151
+ * @param keys The keys of the parameters to decrypt.
152
+ * @returns A promise that resolves when the query parameters have been decrypted.
153
+ */
154
+ async decryptQueryParams(httpRequestQuery, keys) {
155
+ if (Is.empty(httpRequestQuery)) {
156
+ return;
157
+ }
158
+ for (const key of Object.keys(httpRequestQuery)) {
159
+ if (key.startsWith(UrlTransformerService._KEY_PREFIX)) {
160
+ const originalKey = key.slice(UrlTransformerService._KEY_PREFIX.length);
161
+ if (keys.includes(originalKey)) {
162
+ const decryptedValue = await this.decryptParam(httpRequestQuery[key]);
163
+ httpRequestQuery[originalKey] = decryptedValue;
164
+ delete httpRequestQuery[key];
165
+ }
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * Encrypt a parameter value.
171
+ * @param paramValue The value of the parameter to encrypt.
172
+ * @returns A promise that resolves to the encrypted value of the parameter.
173
+ */
174
+ async encryptParam(paramValue) {
175
+ Guards.stringValue(UrlTransformerService.CLASS_NAME, "paramValue", paramValue);
176
+ if (Is.empty(this._nodeId)) {
177
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "encryptionUnavailable");
178
+ }
179
+ try {
180
+ const salt = RandomHelper.generate(8);
181
+ const encryptedParamValue = await this._vaultConnector.encrypt(`${this._nodeId}/${this._paramEncryptionKeyName}`, VaultEncryptionType.ChaCha20Poly1305, Uint8ArrayHelper.concat([salt, Converter.utf8ToBytes(paramValue)]));
182
+ if (!Is.uint8Array(encryptedParamValue)) {
183
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "encryptionFailed");
184
+ }
185
+ return Converter.bytesToBase64Url(encryptedParamValue);
186
+ }
187
+ catch (err) {
188
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "encryptionFailed", undefined, BaseError.fromError(err));
189
+ }
190
+ }
191
+ /**
192
+ * Decrypt a parameter value.
193
+ * @param encryptedValue The encrypted value of the parameter.
194
+ * @returns A promise that resolves to the decrypted value of the parameter.
195
+ */
196
+ async decryptParam(encryptedValue) {
197
+ Guards.stringValue(UrlTransformerService.CLASS_NAME, "encryptedValue", encryptedValue);
198
+ if (Is.empty(this._nodeId)) {
199
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "decryptionUnavailable");
200
+ }
201
+ try {
202
+ const encryptedBytes = Converter.base64UrlToBytes(encryptedValue);
203
+ const decryptedBytes = await this._vaultConnector.decrypt(`${this._nodeId}/${this._paramEncryptionKeyName}`, VaultEncryptionType.ChaCha20Poly1305, encryptedBytes);
204
+ if (!Is.uint8Array(decryptedBytes)) {
205
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "decryptionFailed");
206
+ }
207
+ return Converter.bytesToUtf8(decryptedBytes.slice(8));
208
+ }
209
+ catch (err) {
210
+ throw new GeneralError(UrlTransformerService.CLASS_NAME, "decryptionFailed", undefined, BaseError.fromError(err));
211
+ }
212
+ }
213
+ }
214
+ //# sourceMappingURL=urlTransformerService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlTransformerService.js","sourceRoot":"","sources":["../../src/urlTransformerService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHttpRequestQuery, IUrlTransformerComponent } from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tObjectHelper,\n\tRandomHelper,\n\tUint8ArrayHelper\n} from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\ttype IVaultConnector,\n\tVaultConnectorFactory,\n\tVaultEncryptionType\n} from \"@twin.org/vault-models\";\nimport type { IUrlTransformerServiceConstructorOptions } from \"./models/IUrlTransformerServiceConstructorOptions.js\";\n\n/**\n * The URL transformer service for encrypting and decrypting URL parameters.\n */\nexport class UrlTransformerService implements IUrlTransformerComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<UrlTransformerService>();\n\n\t/**\n\t * The prefix to use for encrypted query parameters.\n\t * @internal\n\t */\n\tprivate static readonly _KEY_PREFIX = \"x-enc-\";\n\n\t/**\n\t * The default name for the parameter encryption key query parameter.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_PARAM_ENCRYPTION_KEY_NAME: string = \"param-encryption\";\n\n\t/**\n\t * The vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The name of the key to retrieve from the vault for encryption/decryption of parameters.\n\t * @internal\n\t */\n\tprivate readonly _paramEncryptionKeyName: string;\n\n\t/**\n\t * Maps logical token ids to their URL query parameter names.\n\t * @internal\n\t */\n\tprivate readonly _queryParamNames: { [id: string]: string };\n\n\t/**\n\t * The node identity, captured at start.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of UrlTransformerService.\n\t * @param options The options to create the service.\n\t */\n\tconstructor(options?: IUrlTransformerServiceConstructorOptions) {\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options?.vaultConnectorType ?? \"vault\");\n\t\tthis._paramEncryptionKeyName =\n\t\t\toptions?.config?.paramEncryptionKeyName ??\n\t\t\tUrlTransformerService._DEFAULT_PARAM_ENCRYPTION_KEY_NAME;\n\t\tthis._queryParamNames = options?.config?.queryParamNames ?? {};\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 UrlTransformerService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @returns Nothing.\n\t */\n\tpublic async start(): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tthis._nodeId = contextIds?.[ContextIdKeys.Node];\n\t}\n\n\t/**\n\t * Encrypt a named token value and append it as a query parameter to the given URL.\n\t * @param url The URL to append the encrypted token to.\n\t * @param id The logical token identifier (e.g. \"tenant\").\n\t * @param value The value to encrypt and add.\n\t * @returns The URL with the encrypted token added as a query parameter.\n\t */\n\tpublic async addEncryptedQueryParamToUrl(\n\t\turl: string,\n\t\tid: string,\n\t\tvalue: string\n\t): Promise<string> {\n\t\tconst paramName = this._queryParamNames[id] ?? id;\n\t\treturn this.addEncryptedParamsToUrl(url, { [paramName]: value });\n\t}\n\n\t/**\n\t * Get a named token value from the query parameters.\n\t * @param queryParams The HTTP request query containing the parameters.\n\t * @param id The logical token identifier (e.g. \"tenant\").\n\t * @returns The decrypted token value if it exists.\n\t */\n\tpublic async getEncryptedQueryParam(\n\t\tqueryParams: IHttpRequestQuery | undefined,\n\t\tid: string\n\t): Promise<string | undefined> {\n\t\tconst paramName = this._queryParamNames[id] ?? id;\n\t\tconst decrypted = await this.getDecryptedParamsFromQueryParams(queryParams, [paramName]);\n\t\treturn decrypted[paramName];\n\t}\n\n\t/**\n\t * Add encrypted key/value pairs to a URL's query string.\n\t * @param url The base URL to add parameters to.\n\t * @param params The key/value pairs to encrypt and append.\n\t * @returns The URL with the encrypted parameters added.\n\t */\n\tpublic async addEncryptedParamsToUrl(url: string, params: IHttpRequestQuery): Promise<string> {\n\t\tconst urlObj = new URL(url);\n\t\tconst query: IHttpRequestQuery = {};\n\t\tfor (const [key, value] of urlObj.searchParams.entries()) {\n\t\t\tquery[key] = value;\n\t\t}\n\t\tconst keysToEncrypt = Object.keys(params);\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tquery[key] = value;\n\t\t}\n\t\tawait this.encryptQueryParams(query, keysToEncrypt);\n\t\turlObj.search = \"\";\n\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\turlObj.searchParams.set(key, value);\n\t\t}\n\t\treturn urlObj.toString();\n\t}\n\n\t/**\n\t * Decrypt specified keys from a query parameter object and return their plain-text values.\n\t * @param queryParams The HTTP request query containing the encrypted parameters.\n\t * @param keys The keys to decrypt.\n\t * @returns A map of the decrypted key/value pairs that were present.\n\t */\n\tpublic async getDecryptedParamsFromQueryParams(\n\t\tqueryParams: IHttpRequestQuery | undefined,\n\t\tkeys: string[]\n\t): Promise<IHttpRequestQuery> {\n\t\tconst queryParamsClone = ObjectHelper.clone(queryParams) ?? {};\n\t\tawait this.decryptQueryParams(queryParamsClone, keys);\n\t\tconst result: IHttpRequestQuery = {};\n\t\tfor (const key of keys) {\n\t\t\tif (Is.stringValue(queryParamsClone[key])) {\n\t\t\t\tresult[key] = queryParamsClone[key];\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Encrypt query parameters.\n\t * @param httpRequestQuery The HTTP request query containing the parameters to encrypt.\n\t * @param keys The keys of the parameters to encrypt.\n\t * @returns A promise that resolves when the query parameters have been encrypted.\n\t */\n\tpublic async encryptQueryParams(\n\t\thttpRequestQuery: IHttpRequestQuery | undefined,\n\t\tkeys: string[]\n\t): Promise<void> {\n\t\tif (Is.empty(httpRequestQuery)) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const key of keys) {\n\t\t\tif (Is.stringValue(httpRequestQuery[key])) {\n\t\t\t\tconst encryptedValue = await this.encryptParam(httpRequestQuery[key]);\n\t\t\t\thttpRequestQuery[`${UrlTransformerService._KEY_PREFIX}${key}`] = encryptedValue;\n\t\t\t\tdelete httpRequestQuery[key];\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Decrypt query parameters.\n\t * @param httpRequestQuery The HTTP request query containing the encrypted values.\n\t * @param keys The keys of the parameters to decrypt.\n\t * @returns A promise that resolves when the query parameters have been decrypted.\n\t */\n\tpublic async decryptQueryParams(\n\t\thttpRequestQuery: IHttpRequestQuery | undefined,\n\t\tkeys: string[]\n\t): Promise<void> {\n\t\tif (Is.empty(httpRequestQuery)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const key of Object.keys(httpRequestQuery)) {\n\t\t\tif (key.startsWith(UrlTransformerService._KEY_PREFIX)) {\n\t\t\t\tconst originalKey = key.slice(UrlTransformerService._KEY_PREFIX.length);\n\n\t\t\t\tif (keys.includes(originalKey)) {\n\t\t\t\t\tconst decryptedValue = await this.decryptParam(httpRequestQuery[key]);\n\t\t\t\t\thttpRequestQuery[originalKey] = decryptedValue;\n\t\t\t\t\tdelete httpRequestQuery[key];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Encrypt a parameter value.\n\t * @param paramValue The value of the parameter to encrypt.\n\t * @returns A promise that resolves to the encrypted value of the parameter.\n\t */\n\tpublic async encryptParam(paramValue: string): Promise<string> {\n\t\tGuards.stringValue(UrlTransformerService.CLASS_NAME, nameof(paramValue), paramValue);\n\n\t\tif (Is.empty(this._nodeId)) {\n\t\t\tthrow new GeneralError(UrlTransformerService.CLASS_NAME, \"encryptionUnavailable\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst salt = RandomHelper.generate(8);\n\n\t\t\tconst encryptedParamValue = await this._vaultConnector.encrypt(\n\t\t\t\t`${this._nodeId}/${this._paramEncryptionKeyName}`,\n\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\tUint8ArrayHelper.concat([salt, Converter.utf8ToBytes(paramValue)])\n\t\t\t);\n\n\t\t\tif (!Is.uint8Array(encryptedParamValue)) {\n\t\t\t\tthrow new GeneralError(UrlTransformerService.CLASS_NAME, \"encryptionFailed\");\n\t\t\t}\n\n\t\t\treturn Converter.bytesToBase64Url(encryptedParamValue);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tUrlTransformerService.CLASS_NAME,\n\t\t\t\t\"encryptionFailed\",\n\t\t\t\tundefined,\n\t\t\t\tBaseError.fromError(err)\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Decrypt a parameter value.\n\t * @param encryptedValue The encrypted value of the parameter.\n\t * @returns A promise that resolves to the decrypted value of the parameter.\n\t */\n\tpublic async decryptParam(encryptedValue: string): Promise<string> {\n\t\tGuards.stringValue(UrlTransformerService.CLASS_NAME, nameof(encryptedValue), encryptedValue);\n\n\t\tif (Is.empty(this._nodeId)) {\n\t\t\tthrow new GeneralError(UrlTransformerService.CLASS_NAME, \"decryptionUnavailable\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst encryptedBytes = Converter.base64UrlToBytes(encryptedValue);\n\t\t\tconst decryptedBytes = await this._vaultConnector.decrypt(\n\t\t\t\t`${this._nodeId}/${this._paramEncryptionKeyName}`,\n\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\tencryptedBytes\n\t\t\t);\n\n\t\t\tif (!Is.uint8Array(decryptedBytes)) {\n\t\t\t\tthrow new GeneralError(UrlTransformerService.CLASS_NAME, \"decryptionFailed\");\n\t\t\t}\n\n\t\t\treturn Converter.bytesToUtf8(decryptedBytes.slice(8));\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tUrlTransformerService.CLASS_NAME,\n\t\t\t\t\"decryptionFailed\",\n\t\t\t\tundefined,\n\t\t\t\tBaseError.fromError(err)\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { IHttpRequestContext, INoContentRequest, IRestRoute, IServerHealthResponse, ITag } from "@twin.org/api-models";
2
+ /**
3
+ * The tag to associate with the routes.
4
+ */
5
+ export declare const tagsHealth: ITag[];
6
+ /**
7
+ * The REST routes for server health.
8
+ * @param baseRouteName Prefix to prepend to the paths.
9
+ * @param componentName The name of the component to use in the routes stored in the ComponentFactory.
10
+ * @returns The generated routes.
11
+ */
12
+ export declare function generateRestRoutesHealth(baseRouteName: string, componentName: string): IRestRoute[];
13
+ /**
14
+ * Get the health for the server.
15
+ * @param httpRequestContext The request context for the API.
16
+ * @param componentName The name of the component to use in the routes.
17
+ * @param request The request.
18
+ * @returns The response object with additional http response properties.
19
+ */
20
+ export declare function serverHealth(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerHealthResponse>;
@@ -0,0 +1,42 @@
1
+ import type { IHealthComponent } from "@twin.org/api-models";
2
+ import { type HealthStatus, type IHealth } from "@twin.org/core";
3
+ import type { IHealthServiceConstructorOptions } from "./models/IHealthServiceConstructorOptions.js";
4
+ /**
5
+ * The health service for the server.
6
+ */
7
+ export declare class HealthService implements IHealthComponent {
8
+ /**
9
+ * Runtime name for the class.
10
+ */
11
+ static readonly CLASS_NAME: string;
12
+ /**
13
+ * Create a new instance of HealthService.
14
+ * @param options The constructor options.
15
+ */
16
+ constructor(options?: IHealthServiceConstructorOptions);
17
+ /**
18
+ * Returns the class name of the component.
19
+ * @returns The class name of the component.
20
+ */
21
+ className(): string;
22
+ /**
23
+ * The component needs to be started when the node is initialized.
24
+ * @param nodeLoggingComponentType The node logging component type.
25
+ * @returns Nothing.
26
+ */
27
+ start(nodeLoggingComponentType?: string): Promise<void>;
28
+ /**
29
+ * The component needs to be stopped when the node is closed.
30
+ * @param nodeLoggingComponentType The node logging component type.
31
+ * @returns Nothing.
32
+ */
33
+ stop(nodeLoggingComponentType?: string): Promise<void>;
34
+ /**
35
+ * Get the server health.
36
+ * @returns The service health.
37
+ */
38
+ healthStatus(): Promise<{
39
+ status: HealthStatus;
40
+ components: IHealth[];
41
+ }>;
42
+ }
@@ -1,8 +1,15 @@
1
+ export * from "./healthRoutes.js";
2
+ export * from "./healthService.js";
1
3
  export * from "./hostingService.js";
2
4
  export * from "./informationRoutes.js";
3
5
  export * from "./informationService.js";
6
+ export * from "./urlTransformerService.js";
7
+ export * from "./models/IHealthServiceConfig.js";
8
+ export * from "./models/IHealthServiceConstructorOptions.js";
4
9
  export * from "./models/IHostingServiceConfig.js";
5
10
  export * from "./models/IHostingServiceConstructorOptions.js";
6
11
  export * from "./models/IInformationServiceConfig.js";
7
12
  export * from "./models/IInformationServiceConstructorOptions.js";
13
+ export * from "./models/IUrlTransformerServiceConfig.js";
14
+ export * from "./models/IUrlTransformerServiceConstructorOptions.js";
8
15
  export * from "./restEntryPoints.js";
@@ -1,4 +1,4 @@
1
- import type { IHttpRequestContext, INoContentRequest, IRestRoute, IServerFavIconResponse, IServerHealthResponse, IServerInfoResponse, IServerLivezResponse, IServerRootResponse, IServerSpecResponse, ITag } from "@twin.org/api-models";
1
+ import type { IHttpRequestContext, INoContentRequest, IRestRoute, IServerFavIconResponse, IServerInfoResponse, IServerLivezResponse, IServerReadyzResponse, IServerRootResponse, IServerSpecResponse, ITag } from "@twin.org/api-models";
2
2
  /**
3
3
  * The tag to associate with the routes.
4
4
  */
@@ -35,13 +35,13 @@ export declare function serverInfo(httpRequestContext: IHttpRequestContext, comp
35
35
  */
36
36
  export declare function serverLivez(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerLivezResponse>;
37
37
  /**
38
- * Get the health for the server.
38
+ * Get the readyz for the server.
39
39
  * @param httpRequestContext The request context for the API.
40
40
  * @param componentName The name of the component to use in the routes.
41
41
  * @param request The request.
42
42
  * @returns The response object with additional http response properties.
43
43
  */
44
- export declare function serverHealth(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerHealthResponse>;
44
+ export declare function serverReadyz(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerReadyzResponse>;
45
45
  /**
46
46
  * Get the favicon for the server.
47
47
  * @param httpRequestContext The request context for the API.
@@ -1,4 +1,4 @@
1
- import type { HealthStatus, IHealthInfo, IInformationComponent, IServerInfo } from "@twin.org/api-models";
1
+ import type { IInformationComponent, IServerInfo } from "@twin.org/api-models";
2
2
  import type { IInformationServiceConstructorOptions } from "./models/IInformationServiceConstructorOptions.js";
3
3
  /**
4
4
  * The information service for the server.
@@ -47,26 +47,14 @@ export declare class InformationService implements IInformationComponent {
47
47
  * Is the server live.
48
48
  * @returns True if the server is live.
49
49
  */
50
- livez(): Promise<boolean>;
50
+ livez(): Promise<{
51
+ status: "alive" | "dead";
52
+ }>;
51
53
  /**
52
- * Get the server health.
53
- * @returns The service health.
54
+ * Is the server ready.
55
+ * @returns The readyz status of the server.
54
56
  */
55
- health(): Promise<IHealthInfo>;
56
- /**
57
- * Set the status of a component.
58
- * @param name The component name.
59
- * @param status The status of the component.
60
- * @param details The details for the status.
61
- * @param tenantId The tenant id, optional if the health status is not tenant specific.
62
- * @returns Nothing.
63
- */
64
- setComponentHealth(name: string, status: HealthStatus, details?: string, tenantId?: string): Promise<void>;
65
- /**
66
- * Remove the status of a component.
67
- * @param name The component name.
68
- * @param tenantId The tenant id, optional if the health status is not tenant specific.
69
- * @returns Nothing.
70
- */
71
- removeComponentHealth(name: string, tenantId?: string): Promise<void>;
57
+ readyz(): Promise<{
58
+ status: "ready" | "not ready";
59
+ }>;
72
60
  }