@twin.org/api-service 0.0.3-next.31 → 0.0.3-next.33

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.
@@ -23,14 +23,14 @@ export class HealthService {
23
23
  * Interval for checking the health of the components and setting it in the health service.
24
24
  * @internal
25
25
  */
26
- _healthInterval;
26
+ _healthTimer;
27
27
  /**
28
28
  * Create a new instance of HealthService.
29
29
  * @param options The constructor options.
30
30
  */
31
31
  constructor(options) {
32
32
  this._healthInfo = { status: HealthStatus.Ok, components: [] };
33
- this._healthCheckInterval = options?.config?.healthCheckInterval ?? 30000;
33
+ this._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;
34
34
  }
35
35
  /**
36
36
  * Returns the class name of the component.
@@ -46,34 +46,11 @@ export class HealthService {
46
46
  */
47
47
  async start(nodeLoggingComponentType) {
48
48
  const engineCore = EngineCoreFactory.getIfExists("engine");
49
- if (!Is.empty(engineCore) && Is.empty(this._healthInterval)) {
50
- this._healthInterval = globalThis.setInterval(async () => {
51
- await ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {
52
- const allHealth = [];
53
- const registeredInstances = await engineCore.getRegisteredComponents();
54
- for (const registeredInstance of registeredInstances) {
55
- const healthMethod = registeredInstance.component.health?.bind(registeredInstance.component);
56
- if (Is.function(healthMethod)) {
57
- try {
58
- allHealth.push(...(await healthMethod()));
59
- }
60
- catch (error) {
61
- const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
62
- await nodeLogging?.log({
63
- level: "error",
64
- source: HealthService.CLASS_NAME,
65
- message: "componentHealthCheckFailed",
66
- data: {
67
- className: registeredInstance.component.className()
68
- },
69
- error: BaseError.fromError(error)
70
- });
71
- }
72
- }
73
- }
74
- this.groupHealthByName(allHealth);
75
- });
76
- }, this._healthCheckInterval);
49
+ if (!Is.empty(engineCore) && Is.empty(this._healthTimer)) {
50
+ // Immediately check health after a startup settling period
51
+ // the interval for the next checks are trigger on success of the current
52
+ // check to prevent overlapping checks in case of long running health checks
53
+ this._healthTimer = globalThis.setTimeout(async () => this.checkHealth(engineCore, nodeLoggingComponentType), 5000);
77
54
  }
78
55
  }
79
56
  /**
@@ -82,9 +59,9 @@ export class HealthService {
82
59
  * @returns Nothing.
83
60
  */
84
61
  async stop(nodeLoggingComponentType) {
85
- if (this._healthInterval) {
86
- clearInterval(this._healthInterval);
87
- this._healthInterval = undefined;
62
+ if (this._healthTimer) {
63
+ clearTimeout(this._healthTimer);
64
+ this._healthTimer = undefined;
88
65
  }
89
66
  }
90
67
  /**
@@ -94,6 +71,42 @@ export class HealthService {
94
71
  async healthStatus() {
95
72
  return this._healthInfo;
96
73
  }
74
+ /**
75
+ * Check the health of all registered components and set the health info in the service.
76
+ * @param engineCore The engine core to get the registered components from.
77
+ * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.
78
+ * @returns Nothing.
79
+ * @internal
80
+ */
81
+ async checkHealth(engineCore, nodeLoggingComponentType) {
82
+ await ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {
83
+ const allHealth = [];
84
+ const registeredInstances = await engineCore.getRegisteredComponents();
85
+ for (const registeredInstance of registeredInstances) {
86
+ const healthMethod = registeredInstance.component.health?.bind(registeredInstance.component);
87
+ if (Is.function(healthMethod)) {
88
+ try {
89
+ allHealth.push(...(await healthMethod()));
90
+ }
91
+ catch (error) {
92
+ const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
93
+ await nodeLogging?.log({
94
+ level: "error",
95
+ source: HealthService.CLASS_NAME,
96
+ message: "componentHealthCheckFailed",
97
+ data: {
98
+ className: registeredInstance.component.className()
99
+ },
100
+ error: BaseError.fromError(error)
101
+ });
102
+ }
103
+ }
104
+ }
105
+ this.groupHealthByName(allHealth);
106
+ });
107
+ // Queue the next health check.
108
+ this._healthTimer = globalThis.setTimeout(async () => this.checkHealth(engineCore, nodeLoggingComponentType), this._healthCheckInterval);
109
+ }
97
110
  /**
98
111
  * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.
99
112
  * @param entries The flat list of health entries from all components.
@@ -1 +1 @@
1
- {"version":3,"file":"healthService.js","sourceRoot":"","sources":["../../src/healthService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHealthComponent } from \"@twin.org/api-models\";\nimport { ContextIdStore } from \"@twin.org/context\";\nimport { BaseError, ComponentFactory, HealthStatus, type IHealth, Is } from \"@twin.org/core\";\nimport { EngineCoreFactory } from \"@twin.org/engine-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IHealthServiceConstructorOptions } from \"./models/IHealthServiceConstructorOptions.js\";\n\n/**\n * The health service for the server.\n */\nexport class HealthService implements IHealthComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<HealthService>();\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents: IHealth[];\n\t};\n\n\t/**\n\t * The interval for checking the health of the components and setting it in the health service.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * Interval for checking the health of the components and setting it in the health service.\n\t * @internal\n\t */\n\tprivate _healthInterval: NodeJS.Timeout | undefined;\n\n\t/**\n\t * Create a new instance of HealthService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IHealthServiceConstructorOptions) {\n\t\tthis._healthInfo = { status: HealthStatus.Ok, components: [] };\n\t\tthis._healthCheckInterval = options?.config?.healthCheckInterval ?? 30000;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn HealthService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst engineCore = EngineCoreFactory.getIfExists(\"engine\");\n\n\t\tif (!Is.empty(engineCore) && Is.empty(this._healthInterval)) {\n\t\t\tthis._healthInterval = globalThis.setInterval(async () => {\n\t\t\t\tawait ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {\n\t\t\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\t\t\tregisteredInstance.component\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\tclassName: registeredInstance.component.className()\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.groupHealthByName(allHealth);\n\t\t\t\t});\n\t\t\t}, this._healthCheckInterval);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._healthInterval) {\n\t\t\tclearInterval(this._healthInterval);\n\t\t\tthis._healthInterval = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async healthStatus(): Promise<{ status: HealthStatus; components: IHealth[] }> {\n\t\treturn this._healthInfo;\n\t}\n\n\t/**\n\t * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.\n\t * @param entries The flat list of health entries from all components.\n\t * @returns The grouped list.\n\t * @internal\n\t */\n\tprivate groupHealthByName(entries: IHealth[]): void {\n\t\tconst bySource = new Map<string, IHealth[]>();\n\t\tfor (const entry of entries) {\n\t\t\tconst existing = bySource.get(entry.source) ?? [];\n\t\t\texisting.push(entry);\n\t\t\tbySource.set(entry.source, existing);\n\t\t}\n\n\t\tconst result: IHealth[] = [];\n\t\tfor (const [source, group] of bySource) {\n\t\t\tif (group.length > 1) {\n\t\t\t\tlet parentStatus: HealthStatus = HealthStatus.Ok;\n\n\t\t\t\tif (group.some(e => e.status === HealthStatus.Error)) {\n\t\t\t\t\tparentStatus = HealthStatus.Error;\n\t\t\t\t} else if (group.some(e => e.status === HealthStatus.Warning)) {\n\t\t\t\t\tparentStatus = HealthStatus.Warning;\n\t\t\t\t}\n\n\t\t\t\tresult.push({ source, status: parentStatus, grouped: group });\n\t\t\t} else {\n\t\t\t\tresult.push(group[0]);\n\t\t\t}\n\t\t}\n\n\t\tlet finalStatus: HealthStatus = HealthStatus.Ok;\n\t\tif (result.some(e => e.status === HealthStatus.Error)) {\n\t\t\tfinalStatus = HealthStatus.Error;\n\t\t} else if (result.some(e => e.status === HealthStatus.Warning)) {\n\t\t\tfinalStatus = HealthStatus.Warning;\n\t\t}\n\n\t\tthis._healthInfo = { status: finalStatus, components: result };\n\t}\n}\n"]}
1
+ {"version":3,"file":"healthService.js","sourceRoot":"","sources":["../../src/healthService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IHealthComponent } from \"@twin.org/api-models\";\nimport { ContextIdStore } from \"@twin.org/context\";\nimport { BaseError, ComponentFactory, HealthStatus, type IHealth, Is } from \"@twin.org/core\";\nimport { EngineCoreFactory, type IEngineCore } from \"@twin.org/engine-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IHealthServiceConstructorOptions } from \"./models/IHealthServiceConstructorOptions.js\";\n\n/**\n * The health service for the server.\n */\nexport class HealthService implements IHealthComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<HealthService>();\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents: IHealth[];\n\t};\n\n\t/**\n\t * The interval for checking the health of the components and setting it in the health service.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * Interval for checking the health of the components and setting it in the health service.\n\t * @internal\n\t */\n\tprivate _healthTimer: NodeJS.Timeout | undefined;\n\n\t/**\n\t * Create a new instance of HealthService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IHealthServiceConstructorOptions) {\n\t\tthis._healthInfo = { status: HealthStatus.Ok, components: [] };\n\t\tthis._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn HealthService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst engineCore = EngineCoreFactory.getIfExists(\"engine\");\n\n\t\tif (!Is.empty(engineCore) && Is.empty(this._healthTimer)) {\n\t\t\t// Immediately check health after a startup settling period\n\t\t\t// the interval for the next checks are trigger on success of the current\n\t\t\t// check to prevent overlapping checks in case of long running health checks\n\t\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\t\tasync () => this.checkHealth(engineCore, nodeLoggingComponentType),\n\t\t\t\t5000\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._healthTimer) {\n\t\t\tclearTimeout(this._healthTimer);\n\t\t\tthis._healthTimer = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async healthStatus(): Promise<{ status: HealthStatus; components: IHealth[] }> {\n\t\treturn this._healthInfo;\n\t}\n\n\t/**\n\t * Check the health of all registered components and set the health info in the service.\n\t * @param engineCore The engine core to get the registered components from.\n\t * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async checkHealth(\n\t\tengineCore: IEngineCore,\n\t\tnodeLoggingComponentType?: string\n\t): Promise<void> {\n\t\tawait ContextIdStore.run(engineCore.getContextIds() ?? {}, async () => {\n\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\tregisteredInstance.component\n\t\t\t\t);\n\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tclassName: registeredInstance.component.className()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.groupHealthByName(allHealth);\n\t\t});\n\n\t\t// Queue the next health check.\n\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\tasync () => this.checkHealth(engineCore, nodeLoggingComponentType),\n\t\t\tthis._healthCheckInterval\n\t\t);\n\t}\n\n\t/**\n\t * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.\n\t * @param entries The flat list of health entries from all components.\n\t * @returns The grouped list.\n\t * @internal\n\t */\n\tprivate groupHealthByName(entries: IHealth[]): void {\n\t\tconst bySource = new Map<string, IHealth[]>();\n\t\tfor (const entry of entries) {\n\t\t\tconst existing = bySource.get(entry.source) ?? [];\n\t\t\texisting.push(entry);\n\t\t\tbySource.set(entry.source, existing);\n\t\t}\n\n\t\tconst result: IHealth[] = [];\n\t\tfor (const [source, group] of bySource) {\n\t\t\tif (group.length > 1) {\n\t\t\t\tlet parentStatus: HealthStatus = HealthStatus.Ok;\n\n\t\t\t\tif (group.some(e => e.status === HealthStatus.Error)) {\n\t\t\t\t\tparentStatus = HealthStatus.Error;\n\t\t\t\t} else if (group.some(e => e.status === HealthStatus.Warning)) {\n\t\t\t\t\tparentStatus = HealthStatus.Warning;\n\t\t\t\t}\n\n\t\t\t\tresult.push({ source, status: parentStatus, grouped: group });\n\t\t\t} else {\n\t\t\t\tresult.push(group[0]);\n\t\t\t}\n\t\t}\n\n\t\tlet finalStatus: HealthStatus = HealthStatus.Ok;\n\t\tif (result.some(e => e.status === HealthStatus.Error)) {\n\t\t\tfinalStatus = HealthStatus.Error;\n\t\t} else if (result.some(e => e.status === HealthStatus.Warning)) {\n\t\t\tfinalStatus = HealthStatus.Warning;\n\t\t}\n\n\t\tthis._healthInfo = { status: finalStatus, components: result };\n\t}\n}\n"]}
@@ -1 +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"]}
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"]}
@@ -1 +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"]}
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"]}
@@ -4,7 +4,7 @@
4
4
  export interface IHealthServiceConfig {
5
5
  /**
6
6
  * The interval for checking the health of the components and setting it in the health service.
7
- * @default 30000
7
+ * @default 60000
8
8
  */
9
9
  healthCheckInterval?: number;
10
10
  }
@@ -9,7 +9,7 @@ export interface IUrlTransformerServiceConfig {
9
9
  paramEncryptionKeyName?: string;
10
10
  /**
11
11
  * A dictionary mapping logical token identifiers to their URL query parameter names.
12
- * For example: { "tenant": "tenant-token" } maps the logical id "tenant" to the
12
+ * For example: tenant => tenant-token maps the logical id "tenant" to the
13
13
  * query param "tenant-token". When an id is not present the id itself is used as
14
14
  * the param name.
15
15
  */
package/docs/changelog.md CHANGED
@@ -1,11 +1,45 @@
1
1
  # Changelog
2
2
 
3
- ## [0.0.3-next.31](https://github.com/twinfoundation/twin-api/compare/api-service-v0.0.3-next.30...api-service-v0.0.3-next.31) (2026-05-06)
3
+ ## [0.0.3-next.33](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.32...api-service-v0.0.3-next.33) (2026-05-11)
4
4
 
5
5
 
6
6
  ### Features
7
7
 
8
- * update health format ([cfbfbbb](https://github.com/twinfoundation/twin-api/commit/cfbfbbb2e9afbd2574ffd2446ad51e4217437951))
8
+ * typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * docs ([4d7e1d2](https://github.com/iotaledger/twin-api/commit/4d7e1d265a2b2225ea02409a31989727a0534265))
14
+
15
+
16
+ ### Dependencies
17
+
18
+ * The following workspace dependencies were updated
19
+ * dependencies
20
+ * @twin.org/api-models bumped from 0.0.3-next.32 to 0.0.3-next.33
21
+
22
+ ## [0.0.3-next.32](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.31...api-service-v0.0.3-next.32) (2026-05-07)
23
+
24
+
25
+ ### Features
26
+
27
+ * avoid overlapping health checks ([35150b8](https://github.com/iotaledger/twin-api/commit/35150b81861cd2911641d0c36cd7dc3dfa5fcb68))
28
+ * update health service interval defaults ([2c66ae1](https://github.com/iotaledger/twin-api/commit/2c66ae1320acb0c80a0d7cba4e11a00358707dbb))
29
+
30
+
31
+ ### Dependencies
32
+
33
+ * The following workspace dependencies were updated
34
+ * dependencies
35
+ * @twin.org/api-models bumped from 0.0.3-next.31 to 0.0.3-next.32
36
+
37
+ ## [0.0.3-next.31](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.30...api-service-v0.0.3-next.31) (2026-05-06)
38
+
39
+
40
+ ### Features
41
+
42
+ * update health format ([cfbfbbb](https://github.com/iotaledger/twin-api/commit/cfbfbbb2e9afbd2574ffd2446ad51e4217437951))
9
43
 
10
44
 
11
45
  ### Dependencies
@@ -14,12 +48,12 @@
14
48
  * dependencies
15
49
  * @twin.org/api-models bumped from 0.0.3-next.30 to 0.0.3-next.31
16
50
 
17
- ## [0.0.3-next.30](https://github.com/twinfoundation/twin-api/compare/api-service-v0.0.3-next.29...api-service-v0.0.3-next.30) (2026-05-05)
51
+ ## [0.0.3-next.30](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.29...api-service-v0.0.3-next.30) (2026-05-05)
18
52
 
19
53
 
20
54
  ### Features
21
55
 
22
- * separate service responsibilities ([#116](https://github.com/twinfoundation/twin-api/issues/116)) ([2234648](https://github.com/twinfoundation/twin-api/commit/2234648de4a2de5b7356aadde328f40470bc12e3))
56
+ * separate service responsibilities ([#116](https://github.com/iotaledger/twin-api/issues/116)) ([2234648](https://github.com/iotaledger/twin-api/commit/2234648de4a2de5b7356aadde328f40470bc12e3))
23
57
 
24
58
 
25
59
  ### Dependencies
@@ -28,12 +62,12 @@
28
62
  * dependencies
29
63
  * @twin.org/api-models bumped from 0.0.3-next.29 to 0.0.3-next.30
30
64
 
31
- ## [0.0.3-next.29](https://github.com/twinfoundation/twin-api/compare/api-service-v0.0.3-next.28...api-service-v0.0.3-next.29) (2026-05-01)
65
+ ## [0.0.3-next.29](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.28...api-service-v0.0.3-next.29) (2026-05-01)
32
66
 
33
67
 
34
68
  ### Features
35
69
 
36
- * hosting service ([#109](https://github.com/twinfoundation/twin-api/issues/109)) ([985bf1f](https://github.com/twinfoundation/twin-api/commit/985bf1f5c07b09ecb800df7120bc2422ac7a6d25))
70
+ * hosting service ([#109](https://github.com/iotaledger/twin-api/issues/109)) ([985bf1f](https://github.com/iotaledger/twin-api/commit/985bf1f5c07b09ecb800df7120bc2422ac7a6d25))
37
71
 
38
72
 
39
73
  ### Dependencies
@@ -42,7 +76,7 @@
42
76
  * dependencies
43
77
  * @twin.org/api-models bumped from 0.0.3-next.28 to 0.0.3-next.29
44
78
 
45
- ## [0.0.3-next.28](https://github.com/twinfoundation/twin-api/compare/api-service-v0.0.3-next.27...api-service-v0.0.3-next.28) (2026-04-30)
79
+ ## [0.0.3-next.28](https://github.com/iotaledger/twin-api/compare/api-service-v0.0.3-next.27...api-service-v0.0.3-next.28) (2026-04-30)
46
80
 
47
81
 
48
82
  ### Miscellaneous Chores
@@ -13,5 +13,5 @@ The interval for checking the health of the components and setting it in the hea
13
13
  #### Default
14
14
 
15
15
  ```ts
16
- 30000
16
+ 60000
17
17
  ```
@@ -23,7 +23,7 @@ param-encryption
23
23
  > `optional` **queryParamNames?**: `object`
24
24
 
25
25
  A dictionary mapping logical token identifiers to their URL query parameter names.
26
- For example: { "tenant": "tenant-token" } maps the logical id "tenant" to the
26
+ For example: tenant => tenant-token maps the logical id "tenant" to the
27
27
  query param "tenant-token". When an id is not present the id itself is used as
28
28
  the param name.
29
29
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@twin.org/api-service",
3
- "version": "0.0.3-next.31",
3
+ "version": "0.0.3-next.33",
4
4
  "description": "Information and hosting service implementations with generated REST route handlers.",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/twinfoundation/api.git",
7
+ "url": "git+https://github.com/iotaledger/api.git",
8
8
  "directory": "packages/api-service"
9
9
  },
10
10
  "author": "martyn.janes@iota.org",
@@ -14,7 +14,7 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/api-models": "0.0.3-next.31",
17
+ "@twin.org/api-models": "0.0.3-next.33",
18
18
  "@twin.org/context": "next",
19
19
  "@twin.org/core": "next",
20
20
  "@twin.org/engine-models": "next",
@@ -50,7 +50,7 @@
50
50
  "business-logic"
51
51
  ],
52
52
  "bugs": {
53
- "url": "git+https://github.com/twinfoundation/api/issues"
53
+ "url": "git+https://github.com/iotaledger/api/issues"
54
54
  },
55
55
  "homepage": "https://twindev.org"
56
56
  }