@twin.org/api-service 0.0.3-next.8 → 0.9.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) 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 +155 -0
  5. package/dist/es/healthService.js.map +1 -0
  6. package/dist/es/index.js +7 -0
  7. package/dist/es/index.js.map +1 -1
  8. package/dist/es/informationRoutes.js +92 -62
  9. package/dist/es/informationRoutes.js.map +1 -1
  10. package/dist/es/informationService.js +13 -59
  11. package/dist/es/informationService.js.map +1 -1
  12. package/dist/es/models/IHealthServiceConfig.js +4 -0
  13. package/dist/es/models/IHealthServiceConfig.js.map +1 -0
  14. package/dist/es/models/IHealthServiceConstructorOptions.js +2 -0
  15. package/dist/es/models/IHealthServiceConstructorOptions.js.map +1 -0
  16. package/dist/es/models/IPlatformServiceConfig.js +4 -0
  17. package/dist/es/models/IPlatformServiceConfig.js.map +1 -0
  18. package/dist/es/models/IPlatformServiceConstructorOptions.js +2 -0
  19. package/dist/es/models/IPlatformServiceConstructorOptions.js.map +1 -0
  20. package/dist/es/platformService.js +155 -0
  21. package/dist/es/platformService.js.map +1 -0
  22. package/dist/es/restEntryPoints.js +10 -0
  23. package/dist/es/restEntryPoints.js.map +1 -1
  24. package/dist/types/healthRoutes.d.ts +20 -0
  25. package/dist/types/healthService.d.ts +42 -0
  26. package/dist/types/index.d.ts +7 -0
  27. package/dist/types/informationRoutes.d.ts +11 -3
  28. package/dist/types/informationService.d.ts +12 -17
  29. package/dist/types/models/IHealthServiceConfig.d.ts +16 -0
  30. package/dist/types/models/IHealthServiceConstructorOptions.d.ts +10 -0
  31. package/dist/types/models/IPlatformServiceConfig.d.ts +10 -0
  32. package/dist/types/models/IPlatformServiceConstructorOptions.d.ts +15 -0
  33. package/dist/types/platformService.d.ts +39 -0
  34. package/dist/types/restEntryPoints.d.ts +3 -0
  35. package/docs/changelog.md +752 -59
  36. package/docs/examples.md +74 -1
  37. package/docs/reference/classes/HealthService.md +123 -0
  38. package/docs/reference/classes/InformationService.md +20 -66
  39. package/docs/reference/classes/PlatformService.md +123 -0
  40. package/docs/reference/functions/generateRestRoutesHealth.md +25 -0
  41. package/docs/reference/functions/serverLivez.md +31 -0
  42. package/docs/reference/functions/serverReadyz.md +31 -0
  43. package/docs/reference/index.md +11 -1
  44. package/docs/reference/interfaces/IHealthServiceConfig.md +32 -0
  45. package/docs/reference/interfaces/IHealthServiceConstructorOptions.md +11 -0
  46. package/docs/reference/interfaces/IInformationServiceConfig.md +5 -5
  47. package/docs/reference/interfaces/IInformationServiceConstructorOptions.md +1 -1
  48. package/docs/reference/interfaces/IPlatformServiceConfig.md +17 -0
  49. package/docs/reference/interfaces/IPlatformServiceConstructorOptions.md +25 -0
  50. package/docs/reference/variables/restEntryPoints.md +2 -0
  51. package/docs/reference/variables/tagsHealth.md +5 -0
  52. package/locales/en.json +7 -1
  53. package/package.json +13 -8
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { Guards, Is } from "@twin.org/core";
5
+ import { EngineCoreFactory } from "@twin.org/engine-models";
5
6
  /**
6
7
  * The information service for the server.
7
8
  */
@@ -15,11 +16,6 @@ export class InformationService {
15
16
  * @internal
16
17
  */
17
18
  _serverInfo;
18
- /**
19
- * The server health.
20
- * @internal
21
- */
22
- _healthInfo;
23
19
  /**
24
20
  * The path to the favicon Spec.
25
21
  * @internal
@@ -49,9 +45,6 @@ export class InformationService {
49
45
  Guards.object(InformationService.CLASS_NAME, "options.config", options.config);
50
46
  Guards.object(InformationService.CLASS_NAME, "options.config.serverInfo", options.config.serverInfo);
51
47
  this._serverInfo = options.config.serverInfo;
52
- this._healthInfo = {
53
- status: "ok"
54
- };
55
48
  this._faviconPath = options.config.favIconPath;
56
49
  this._openApiSpecPath = options.config.openApiSpecPath;
57
50
  }
@@ -64,7 +57,7 @@ export class InformationService {
64
57
  }
65
58
  /**
66
59
  * The service needs to be started when the application is initialized.
67
- * @returns Nothing.
60
+ * @returns A promise that resolves when the OpenAPI spec and favicon have been loaded from disk.
68
61
  */
69
62
  async start() {
70
63
  const openApiPath = this._openApiSpecPath;
@@ -106,61 +99,22 @@ export class InformationService {
106
99
  return this._openApiSpec;
107
100
  }
108
101
  /**
109
- * Get the server health.
110
- * @returns The service health.
111
- */
112
- async health() {
113
- let errorCount = 0;
114
- let warningCount = 0;
115
- if (Is.arrayValue(this._healthInfo.components)) {
116
- errorCount = this._healthInfo.components.filter(c => c.status === "error").length;
117
- warningCount = this._healthInfo.components.filter(c => c.status === "warning").length;
118
- }
119
- if (errorCount > 0) {
120
- this._healthInfo.status = "error";
121
- }
122
- else if (warningCount > 0) {
123
- this._healthInfo.status = "warning";
124
- }
125
- else {
126
- this._healthInfo.status = "ok";
127
- }
128
- return this._healthInfo;
129
- }
130
- /**
131
- * Set the status of a component.
132
- * @param name The component name.
133
- * @param status The status of the component.
134
- * @param details The details for the status.
135
- * @returns Nothing.
102
+ * Is the server live.
103
+ * @returns The liveness status of the server.
136
104
  */
137
- async setComponentHealth(name, status, details) {
138
- const component = this._healthInfo.components?.find(c => c.name === name);
139
- if (Is.undefined(component)) {
140
- this._healthInfo.components ??= [];
141
- this._healthInfo.components.push({
142
- name,
143
- status,
144
- details
145
- });
146
- }
147
- else {
148
- component.status = status;
149
- component.details = details;
150
- }
105
+ async livez() {
106
+ return { status: "alive" };
151
107
  }
152
108
  /**
153
- * Remove the status of a component.
154
- * @param name The component name.
155
- * @returns Nothing.
109
+ * Is the server ready.
110
+ * @returns The readyz status of the server.
156
111
  */
157
- async removeComponentHealth(name) {
158
- if (Is.arrayValue(this._healthInfo.components)) {
159
- const componentIndex = this._healthInfo.components.findIndex(c => c.name === name);
160
- if (componentIndex !== -1) {
161
- this._healthInfo.components.splice(componentIndex, 1);
162
- }
112
+ async readyz() {
113
+ const engine = EngineCoreFactory.getIfExists("engine");
114
+ if (engine?.isStarted()) {
115
+ return { status: "ready" };
163
116
  }
117
+ return { status: "not ready" };
164
118
  }
165
119
  }
166
120
  //# sourceMappingURL=informationService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"informationService.js","sourceRoot":"","sources":["../../src/informationService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { readFile } from \"node:fs/promises\";\nimport type {\n\tHealthStatus,\n\tIHealthInfo,\n\tIInformationComponent,\n\tIServerInfo\n} from \"@twin.org/api-models\";\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: IHealthInfo;\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 * 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\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\terrorCount = this._healthInfo.components.filter(c => c.status === \"error\").length;\n\t\t\twarningCount = this._healthInfo.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 this._healthInfo;\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 * @returns Nothing.\n\t */\n\tpublic async setComponentHealth(\n\t\tname: string,\n\t\tstatus: HealthStatus,\n\t\tdetails?: string\n\t): Promise<void> {\n\t\tconst component = this._healthInfo.components?.find(c => c.name === name);\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});\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 * @returns Nothing.\n\t */\n\tpublic async removeComponentHealth(name: string): Promise<void> {\n\t\tif (Is.arrayValue(this._healthInfo.components)) {\n\t\t\tconst componentIndex = this._healthInfo.components.findIndex(c => c.name === name);\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 A promise that resolves when the OpenAPI spec and favicon have been loaded from disk.\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 The liveness status of the server.\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 60000\n\t */\n\thealthCheckInterval?: 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 * @default 2000\n\t */\n\tinitialInterval?: 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"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=IPlatformServiceConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IPlatformServiceConfig.js","sourceRoot":"","sources":["../../../src/models/IPlatformServiceConfig.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 platform service\n */\nexport interface IPlatformServiceConfig {\n\t/**\n\t * Indicates whether the service is running in a multi-tenant environment.\n\t * @default false\n\t */\n\tisMultiTenant?: boolean;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IPlatformServiceConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IPlatformServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IPlatformServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IPlatformServiceConfig } from \"./IPlatformServiceConfig.js\";\n\n/**\n * Options for the Platform Service constructor.\n */\nexport interface IPlatformServiceConstructorOptions {\n\t/**\n\t * The entity storage for the tenants.\n\t * @default tenant\n\t */\n\ttenantEntityStorageType?: string;\n\n\t/**\n\t * Configuration for the service.\n\t */\n\tconfig?: IPlatformServiceConfig;\n}\n"]}
@@ -0,0 +1,155 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { HttpContextIdKeys, HttpUrlHelper } from "@twin.org/api-models";
4
+ import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
5
+ import { Guards, Is } from "@twin.org/core";
6
+ import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
7
+ /**
8
+ * Service for performing platform operations.
9
+ */
10
+ export class PlatformService {
11
+ /**
12
+ * Runtime name for the class.
13
+ */
14
+ static CLASS_NAME = "PlatformService";
15
+ /**
16
+ * The type of entity storage connector to use for tenant lookups, if multi-tenant.
17
+ * @internal
18
+ */
19
+ _tenantEntityStorageConnectorType;
20
+ /**
21
+ * Entity storage connector used by the service.
22
+ * @internal
23
+ */
24
+ _tenantEntityStorageConnector;
25
+ /**
26
+ * Indicates whether the service is running in a multi-tenant environment.
27
+ * @internal
28
+ */
29
+ _isMultiTenant;
30
+ /**
31
+ * Create a new instance of PlatformService.
32
+ * @param options The options for the connector.
33
+ */
34
+ constructor(options) {
35
+ this._tenantEntityStorageConnectorType = options?.tenantEntityStorageType ?? "tenant";
36
+ this._isMultiTenant = options?.config?.isMultiTenant ?? false;
37
+ }
38
+ /**
39
+ * Returns the class name of the component.
40
+ * @returns The class name of the component.
41
+ */
42
+ className() {
43
+ return PlatformService.CLASS_NAME;
44
+ }
45
+ /**
46
+ * Indicates whether the component is running in a multi-tenant environment.
47
+ * @returns True if the component is running in a multi-tenant environment, false otherwise.
48
+ */
49
+ isMultiTenant() {
50
+ return this._isMultiTenant;
51
+ }
52
+ /**
53
+ * Execute a method, if single tenant will run once, if multi-tenant will run for each tenant.
54
+ * @param method The method to run for each tenant.
55
+ * @returns A promise that resolves when the method has been executed for all applicable tenants.
56
+ */
57
+ async execute(method) {
58
+ if (this._isMultiTenant) {
59
+ const tenantEntityStorageConnector = this.ensureEntityStorageConnector();
60
+ if (!Is.empty(tenantEntityStorageConnector)) {
61
+ let cursor;
62
+ const baseContextIds = (await ContextIdStore.getContextIds()) ?? {};
63
+ do {
64
+ const result = await tenantEntityStorageConnector.query(undefined, undefined, ["id"], cursor);
65
+ for (const tenant of result.entities) {
66
+ await ContextIdStore.run({ ...baseContextIds, [ContextIdKeys.Tenant]: tenant.id }, async () => {
67
+ await method();
68
+ });
69
+ }
70
+ cursor = result.cursor;
71
+ } while (Is.stringValue(cursor));
72
+ }
73
+ }
74
+ else {
75
+ await method();
76
+ }
77
+ }
78
+ /**
79
+ * Get the local origin context IDs for the given URL.
80
+ * @param url The URL to check.
81
+ * @returns A promise that resolves to the context IDs if the URL is a local origin, undefined otherwise.
82
+ */
83
+ async getLocalOriginContext(url) {
84
+ Guards.stringValue(PlatformService.CLASS_NAME, "url", url);
85
+ const origin = HttpUrlHelper.extractOrigin(url);
86
+ if (!Is.stringValue(origin)) {
87
+ return undefined;
88
+ }
89
+ const contextIds = await ContextIdStore.getContextIds();
90
+ if (this._isMultiTenant) {
91
+ const tenantEntityStorageConnector = this.ensureEntityStorageConnector();
92
+ if (!Is.empty(tenantEntityStorageConnector)) {
93
+ // Post-#203 organization routing: when the URL carries an ?organization=<org-did>
94
+ // query param, that param — not the origin — identifies the target tenant. A single
95
+ // node hosts many tenants behind one shared origin, so an origin match alone cannot
96
+ // distinguish them and would incorrectly return the caller's own context. Resolve the
97
+ // tenant by its organization id (mirrors TenantProcessor inbound routing); when the
98
+ // organization is not a local tenant, treat the URL as remote (undefined).
99
+ const organizationId = HttpUrlHelper.getQueryStringParam(url, ContextIdKeys.Organization);
100
+ if (Is.stringValue(organizationId)) {
101
+ const orgTenant = await tenantEntityStorageConnector.get(organizationId, "organizationId");
102
+ if (!Is.empty(orgTenant)) {
103
+ return {
104
+ ...contextIds,
105
+ [ContextIdKeys.Tenant]: orgTenant.id,
106
+ [ContextIdKeys.Organization]: orgTenant.organizationId,
107
+ ...(Is.stringValue(orgTenant.publicOrigin)
108
+ ? { [HttpContextIdKeys.PublicOrigin]: orgTenant.publicOrigin }
109
+ : {})
110
+ };
111
+ }
112
+ return undefined;
113
+ }
114
+ }
115
+ }
116
+ const publicOrigin = contextIds?.[HttpContextIdKeys.PublicOrigin];
117
+ if (publicOrigin === origin) {
118
+ return contextIds;
119
+ }
120
+ if (this._isMultiTenant) {
121
+ const tenantEntityStorageConnector = this.ensureEntityStorageConnector();
122
+ if (!Is.empty(tenantEntityStorageConnector)) {
123
+ const tenant = await tenantEntityStorageConnector.get(origin, "publicOrigin");
124
+ if (!Is.empty(tenant)) {
125
+ return {
126
+ ...contextIds,
127
+ [ContextIdKeys.Tenant]: tenant.id,
128
+ [ContextIdKeys.Organization]: tenant.organizationId,
129
+ ...(Is.stringValue(tenant.publicOrigin)
130
+ ? { [HttpContextIdKeys.PublicOrigin]: tenant.publicOrigin }
131
+ : {})
132
+ };
133
+ }
134
+ }
135
+ }
136
+ else {
137
+ const localOrigin = contextIds?.[HttpContextIdKeys.LocalOrigin];
138
+ if (localOrigin === origin) {
139
+ return contextIds;
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Ensure that the entity storage connector is initialized and return it.
145
+ * @returns The entity storage connector.
146
+ * @internal
147
+ */
148
+ ensureEntityStorageConnector() {
149
+ if (Is.empty(this._tenantEntityStorageConnector)) {
150
+ this._tenantEntityStorageConnector = EntityStorageConnectorFactory.getIfExists(this._tenantEntityStorageConnectorType);
151
+ }
152
+ return this._tenantEntityStorageConnector;
153
+ }
154
+ }
155
+ //# sourceMappingURL=platformService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platformService.js","sourceRoot":"","sources":["../../src/platformService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpContextIdKeys,\n\tHttpUrlHelper,\n\ttype IPlatformComponent,\n\ttype ITenant\n} from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport { Guards, Is } from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IPlatformServiceConstructorOptions } from \"./models/IPlatformServiceConstructorOptions.js\";\n\n/**\n * Service for performing platform operations.\n */\nexport class PlatformService implements IPlatformComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<PlatformService>();\n\n\t/**\n\t * The type of entity storage connector to use for tenant lookups, if multi-tenant.\n\t * @internal\n\t */\n\tprivate readonly _tenantEntityStorageConnectorType: string;\n\n\t/**\n\t * Entity storage connector used by the service.\n\t * @internal\n\t */\n\tprivate _tenantEntityStorageConnector?: IEntityStorageConnector<ITenant>;\n\n\t/**\n\t * Indicates whether the service is running in a multi-tenant environment.\n\t * @internal\n\t */\n\tprivate readonly _isMultiTenant: boolean;\n\n\t/**\n\t * Create a new instance of PlatformService.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options?: IPlatformServiceConstructorOptions) {\n\t\tthis._tenantEntityStorageConnectorType = options?.tenantEntityStorageType ?? \"tenant\";\n\t\tthis._isMultiTenant = options?.config?.isMultiTenant ?? false;\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 PlatformService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Indicates whether the component is running in a multi-tenant environment.\n\t * @returns True if the component is running in a multi-tenant environment, false otherwise.\n\t */\n\tpublic isMultiTenant(): boolean {\n\t\treturn this._isMultiTenant;\n\t}\n\n\t/**\n\t * Execute a method, if single tenant will run once, if multi-tenant will run for each tenant.\n\t * @param method The method to run for each tenant.\n\t * @returns A promise that resolves when the method has been executed for all applicable tenants.\n\t */\n\tpublic async execute(method: () => Promise<void>): Promise<void> {\n\t\tif (this._isMultiTenant) {\n\t\t\tconst tenantEntityStorageConnector = this.ensureEntityStorageConnector();\n\n\t\t\tif (!Is.empty(tenantEntityStorageConnector)) {\n\t\t\t\tlet cursor: string | undefined;\n\n\t\t\t\tconst baseContextIds = (await ContextIdStore.getContextIds()) ?? {};\n\n\t\t\t\tdo {\n\t\t\t\t\tconst result = await tenantEntityStorageConnector.query(\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t[\"id\"],\n\t\t\t\t\t\tcursor\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (const tenant of result.entities) {\n\t\t\t\t\t\tawait ContextIdStore.run(\n\t\t\t\t\t\t\t{ ...baseContextIds, [ContextIdKeys.Tenant]: tenant.id },\n\t\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\t\tawait method();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tcursor = result.cursor;\n\t\t\t\t} while (Is.stringValue(cursor));\n\t\t\t}\n\t\t} else {\n\t\t\tawait method();\n\t\t}\n\t}\n\n\t/**\n\t * Get the local origin context IDs for the given URL.\n\t * @param url The URL to check.\n\t * @returns A promise that resolves to the context IDs if the URL is a local origin, undefined otherwise.\n\t */\n\tpublic async getLocalOriginContext(url: string): Promise<IContextIds | undefined> {\n\t\tGuards.stringValue(PlatformService.CLASS_NAME, nameof(url), url);\n\n\t\tconst origin = HttpUrlHelper.extractOrigin(url);\n\t\tif (!Is.stringValue(origin)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\n\t\tif (this._isMultiTenant) {\n\t\t\tconst tenantEntityStorageConnector = this.ensureEntityStorageConnector();\n\t\t\tif (!Is.empty(tenantEntityStorageConnector)) {\n\t\t\t\t// Post-#203 organization routing: when the URL carries an ?organization=<org-did>\n\t\t\t\t// query param, that param — not the origin — identifies the target tenant. A single\n\t\t\t\t// node hosts many tenants behind one shared origin, so an origin match alone cannot\n\t\t\t\t// distinguish them and would incorrectly return the caller's own context. Resolve the\n\t\t\t\t// tenant by its organization id (mirrors TenantProcessor inbound routing); when the\n\t\t\t\t// organization is not a local tenant, treat the URL as remote (undefined).\n\t\t\t\tconst organizationId = HttpUrlHelper.getQueryStringParam(url, ContextIdKeys.Organization);\n\t\t\t\tif (Is.stringValue(organizationId)) {\n\t\t\t\t\tconst orgTenant = await tenantEntityStorageConnector.get(\n\t\t\t\t\t\torganizationId,\n\t\t\t\t\t\t\"organizationId\"\n\t\t\t\t\t);\n\t\t\t\t\tif (!Is.empty(orgTenant)) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t...contextIds,\n\t\t\t\t\t\t\t[ContextIdKeys.Tenant]: orgTenant.id,\n\t\t\t\t\t\t\t[ContextIdKeys.Organization]: orgTenant.organizationId,\n\t\t\t\t\t\t\t...(Is.stringValue(orgTenant.publicOrigin)\n\t\t\t\t\t\t\t\t? { [HttpContextIdKeys.PublicOrigin]: orgTenant.publicOrigin }\n\t\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\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst publicOrigin = contextIds?.[HttpContextIdKeys.PublicOrigin];\n\t\tif (publicOrigin === origin) {\n\t\t\treturn contextIds;\n\t\t}\n\n\t\tif (this._isMultiTenant) {\n\t\t\tconst tenantEntityStorageConnector = this.ensureEntityStorageConnector();\n\t\t\tif (!Is.empty(tenantEntityStorageConnector)) {\n\t\t\t\tconst tenant = await tenantEntityStorageConnector.get(origin, \"publicOrigin\");\n\t\t\t\tif (!Is.empty(tenant)) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...contextIds,\n\t\t\t\t\t\t[ContextIdKeys.Tenant]: tenant.id,\n\t\t\t\t\t\t[ContextIdKeys.Organization]: tenant.organizationId,\n\t\t\t\t\t\t...(Is.stringValue(tenant.publicOrigin)\n\t\t\t\t\t\t\t? { [HttpContextIdKeys.PublicOrigin]: tenant.publicOrigin }\n\t\t\t\t\t\t\t: {})\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst localOrigin = contextIds?.[HttpContextIdKeys.LocalOrigin];\n\t\t\tif (localOrigin === origin) {\n\t\t\t\treturn contextIds;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Ensure that the entity storage connector is initialized and return it.\n\t * @returns The entity storage connector.\n\t * @internal\n\t */\n\tprivate ensureEntityStorageConnector(): IEntityStorageConnector<ITenant> | undefined {\n\t\tif (Is.empty(this._tenantEntityStorageConnector)) {\n\t\t\tthis._tenantEntityStorageConnector = EntityStorageConnectorFactory.getIfExists(\n\t\t\t\tthis._tenantEntityStorageConnectorType\n\t\t\t);\n\t\t}\n\n\t\treturn this._tenantEntityStorageConnector;\n\t}\n}\n"]}
@@ -1,10 +1,20 @@
1
+ import { generateRestRoutesHealth, tagsHealth } from "./healthRoutes.js";
1
2
  import { generateRestRoutesInformation, tagsInformation } from "./informationRoutes.js";
3
+ /**
4
+ * REST entry points for the information and health services.
5
+ */
2
6
  export const restEntryPoints = [
3
7
  {
4
8
  name: "information",
5
9
  defaultBaseRoute: "",
6
10
  tags: tagsInformation,
7
11
  generateRoutes: generateRestRoutesInformation
12
+ },
13
+ {
14
+ name: "health",
15
+ defaultBaseRoute: "",
16
+ tags: tagsHealth,
17
+ generateRoutes: generateRestRoutesHealth
8
18
  }
9
19
  ];
10
20
  //# 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;;GAEG;AACH,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\n/**\n * REST entry points for the information and health services.\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,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 { 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 A promise that resolves when the initial health check timer has been scheduled.
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 A promise that resolves when the health check timer has been cancelled.
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,5 +1,12 @@
1
+ export * from "./healthRoutes.js";
2
+ export * from "./healthService.js";
1
3
  export * from "./informationRoutes.js";
2
4
  export * from "./informationService.js";
5
+ export * from "./models/IHealthServiceConfig.js";
6
+ export * from "./models/IHealthServiceConstructorOptions.js";
3
7
  export * from "./models/IInformationServiceConfig.js";
4
8
  export * from "./models/IInformationServiceConstructorOptions.js";
9
+ export * from "./models/IPlatformServiceConfig.js";
10
+ export * from "./models/IPlatformServiceConstructorOptions.js";
11
+ export * from "./platformService.js";
5
12
  export * from "./restEntryPoints.js";
@@ -1,4 +1,4 @@
1
- import type { IHttpRequestContext, INoContentRequest, IRestRoute, IServerFavIconResponse, IServerHealthResponse, IServerInfoResponse, 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
  */
@@ -27,13 +27,21 @@ export declare function serverRoot(httpRequestContext: IHttpRequestContext, comp
27
27
  */
28
28
  export declare function serverInfo(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerInfoResponse>;
29
29
  /**
30
- * Get the health for the server.
30
+ * Get the livez for the server.
31
31
  * @param httpRequestContext The request context for the API.
32
32
  * @param componentName The name of the component to use in the routes.
33
33
  * @param request The request.
34
34
  * @returns The response object with additional http response properties.
35
35
  */
36
- export declare function serverHealth(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerHealthResponse>;
36
+ export declare function serverLivez(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerLivezResponse>;
37
+ /**
38
+ * Get the readyz for the server.
39
+ * @param httpRequestContext The request context for the API.
40
+ * @param componentName The name of the component to use in the routes.
41
+ * @param request The request.
42
+ * @returns The response object with additional http response properties.
43
+ */
44
+ export declare function serverReadyz(httpRequestContext: IHttpRequestContext, componentName: string, request: INoContentRequest): Promise<IServerReadyzResponse>;
37
45
  /**
38
46
  * Get the favicon for the server.
39
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.
@@ -20,7 +20,7 @@ export declare class InformationService implements IInformationComponent {
20
20
  className(): string;
21
21
  /**
22
22
  * The service needs to be started when the application is initialized.
23
- * @returns Nothing.
23
+ * @returns A promise that resolves when the OpenAPI spec and favicon have been loaded from disk.
24
24
  */
25
25
  start(): Promise<void>;
26
26
  /**
@@ -44,22 +44,17 @@ export declare class InformationService implements IInformationComponent {
44
44
  */
45
45
  spec(): Promise<unknown>;
46
46
  /**
47
- * Get the server health.
48
- * @returns The service health.
47
+ * Is the server live.
48
+ * @returns The liveness status of the server.
49
49
  */
50
- health(): Promise<IHealthInfo>;
50
+ livez(): Promise<{
51
+ status: "alive" | "dead";
52
+ }>;
51
53
  /**
52
- * Set the status of a component.
53
- * @param name The component name.
54
- * @param status The status of the component.
55
- * @param details The details for the status.
56
- * @returns Nothing.
54
+ * Is the server ready.
55
+ * @returns The readyz status of the server.
57
56
  */
58
- setComponentHealth(name: string, status: HealthStatus, details?: string): Promise<void>;
59
- /**
60
- * Remove the status of a component.
61
- * @param name The component name.
62
- * @returns Nothing.
63
- */
64
- removeComponentHealth(name: string): Promise<void>;
57
+ readyz(): Promise<{
58
+ status: "ready" | "not ready";
59
+ }>;
65
60
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Configuration for the health service.
3
+ */
4
+ export interface IHealthServiceConfig {
5
+ /**
6
+ * The interval for checking the health of the components and setting it in the health service.
7
+ * @default 60000
8
+ */
9
+ healthCheckInterval?: number;
10
+ /**
11
+ * The initial interval for checking the health of the components and setting it in the health service.
12
+ * This is used to check the health of the components immediately after the service is started.
13
+ * @default 2000
14
+ */
15
+ initialInterval?: number;
16
+ }
@@ -0,0 +1,10 @@
1
+ import type { IHealthServiceConfig } from "./IHealthServiceConfig.js";
2
+ /**
3
+ * Options for the HealthService constructor.
4
+ */
5
+ export interface IHealthServiceConstructorOptions {
6
+ /**
7
+ * The configuration for the service.
8
+ */
9
+ config?: IHealthServiceConfig;
10
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Configuration for the platform service
3
+ */
4
+ export interface IPlatformServiceConfig {
5
+ /**
6
+ * Indicates whether the service is running in a multi-tenant environment.
7
+ * @default false
8
+ */
9
+ isMultiTenant?: boolean;
10
+ }
@@ -0,0 +1,15 @@
1
+ import type { IPlatformServiceConfig } from "./IPlatformServiceConfig.js";
2
+ /**
3
+ * Options for the Platform Service constructor.
4
+ */
5
+ export interface IPlatformServiceConstructorOptions {
6
+ /**
7
+ * The entity storage for the tenants.
8
+ * @default tenant
9
+ */
10
+ tenantEntityStorageType?: string;
11
+ /**
12
+ * Configuration for the service.
13
+ */
14
+ config?: IPlatformServiceConfig;
15
+ }