@3sixty/api-service 0.20.0-next.0 → 0.20.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.
@@ -1 +1 @@
1
- {"version":3,"file":"healthApplicationTask.js","sourceRoot":"","sources":["../../src/healthApplicationTask.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tHealthApplicationCallback,\n\tIHealth,\n\tIHealthProviderComponent\n} from \"@3sixty/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport type { IContextIds } from \"@3sixty/context\";\nimport { GeneralError, Is } from \"@3sixty/core\";\nimport type { IComponent } from \"@3sixty/core\";\nimport { ModuleHelper } from \"@3sixty/modules\";\n\nlet engine:\n\t| {\n\t\t\tstart: () => Promise<void>;\n\t\t\tstop: () => Promise<void>;\n\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\tgetRegisteredComponents: () => Promise<{ instanceType: string; component: IComponent }[]>;\n\t }\n\t| undefined;\nlet startupPromise: Promise<void> | undefined;\nlet excludeCloneComponents: string[] | undefined;\n\n/**\n * Start the engine clone used for application health checks.\n * @param engineCloneData The engine clone data supplied automatically by the background task framework.\n * @param excludeComponents Verified regular expression patterns for component types to exclude\n * from the clone, validated by the health service before registration.\n */\nexport async function healthApplicationTaskStart(\n\tengineCloneData: unknown,\n\texcludeComponents?: string[]\n): Promise<void> {\n\texcludeCloneComponents = excludeComponents;\n\n\tstartupPromise = (async () => {\n\t\tif (!Is.empty(engineCloneData)) {\n\t\t\tconst cloneData =\n\t\t\t\tIs.object(engineCloneData) && Is.arrayValue(excludeComponents)\n\t\t\t\t\t? await ModuleHelper.execModuleMethod<unknown>(\n\t\t\t\t\t\t\t\"@3sixty/engine-models\",\n\t\t\t\t\t\t\t\"EngineCloneHelper.filterCloneComponents\",\n\t\t\t\t\t\t\t[engineCloneData, excludeComponents]\n\t\t\t\t\t\t)\n\t\t\t\t\t: engineCloneData;\n\t\t\tconst clone = await ModuleHelper.execModuleMethod<{\n\t\t\t\tstart: () => Promise<void>;\n\t\t\t\tstop: () => Promise<void>;\n\t\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\t\tgetRegisteredComponents: () => Promise<{ instanceType: string; component: IComponent }[]>;\n\t\t\t}>(\"@3sixty/engine-core\", \"EngineCoreBuilder.fromClone\", [\n\t\t\t\t\"engine\",\n\t\t\t\tcloneData,\n\t\t\t\tawait ContextIdStore.getContextIds(),\n\t\t\t\t{ logLevel: \"error\" }\n\t\t\t]);\n\t\t\tif (Is.empty(clone)) {\n\t\t\t\tthrow new GeneralError(\"applicationHealthTask\", \"engineNotStarted\");\n\t\t\t}\n\t\t\tawait clone.start();\n\t\t\tengine = clone;\n\t\t}\n\t})();\n\n\ttry {\n\t\tawait startupPromise;\n\t} catch (err) {\n\t\tstartupPromise = undefined;\n\t\tthrow err;\n\t}\n}\n\n/**\n * Stop the engine clone used for application health checks.\n */\nexport async function healthApplicationTaskEnd(): Promise<void> {\n\tif (!Is.empty(engine)) {\n\t\tawait engine.stop();\n\t\tengine = undefined;\n\t}\n\tstartupPromise = undefined;\n\texcludeCloneComponents = undefined;\n}\n\n/**\n * Execute the application health lifecycle (init, application, teardown) across all registered\n * components. Called by the background task framework in a worker thread; the engine clone data\n * is used to initialise a local clone of the engine so component factories are available.\n * @param engineCloneData The engine clone data supplied automatically by the background task framework.\n * @returns The health entries collected across all three passes.\n */\nexport async function healthApplicationTask(engineCloneData: unknown): Promise<IHealth[]> {\n\tif (Is.empty(engineCloneData)) {\n\t\treturn [];\n\t}\n\n\tif (startupPromise) {\n\t\ttry {\n\t\t\tawait startupPromise;\n\t\t} catch {\n\t\t\tstartupPromise = undefined;\n\t\t}\n\t}\n\n\tif (Is.empty(engine)) {\n\t\tawait healthApplicationTaskStart(engineCloneData, excludeCloneComponents);\n\t}\n\n\treturn executeApplicationHealthCycle();\n}\n\n/**\n * Run the application health lifecycle for each registered provider: initialise shared context,\n * run the health checks, then tear down the per-check context in reverse registration order.\n * @returns The health entries collected for the current application health pass.\n */\nasync function executeApplicationHealthCycle(): Promise<IHealth[]> {\n\tif (Is.empty(engine)) {\n\t\tthrow new GeneralError(\"applicationHealthTask\", \"engineNotStarted\");\n\t}\n\n\tconst engineContextIds = engine.getContextIds() ?? {};\n\tconst registeredInstances = await engine.getRegisteredComponents();\n\n\t// Pass 1: Init - providers populate healthContextIds with any IDs they establish\n\tconst healthContextIds: IContextIds = {\n\t\t// Only use Node from the main engine context.\n\t\t// All other keys should be provided by healthApplicationInit of each component\n\t\t// as we don't want to use real ids.\n\t\t[ContextIdKeys.Node]: engineContextIds[ContextIdKeys.Node]\n\t};\n\tfor (const registeredInstance of registeredInstances) {\n\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\tconst initMethod = registeredInstance.component.healthApplicationInit?.bind(\n\t\t\t\tregisteredInstance.component\n\t\t\t);\n\t\t\tif (Is.function(initMethod)) {\n\t\t\t\tawait initMethod(healthContextIds);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Pass 2: Application health check wrapped in combined engine + init context\n\tconst allHealth: IHealth[] = [];\n\tconst lazyPromises: Promise<void>[] = [];\n\n\tawait ContextIdStore.run(healthContextIds, async () => {\n\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\tconst healthMethod = registeredInstance.component.healthApplication?.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\tlet fired = false;\n\t\t\t\t\tlet callback: HealthApplicationCallback = async () => {};\n\t\t\t\t\tconst lazyPromise = new Promise<void>(resolve => {\n\t\t\t\t\t\tcallback = async (result: IHealth[]) => {\n\t\t\t\t\t\t\tif (!fired) {\n\t\t\t\t\t\t\t\tfired = true;\n\t\t\t\t\t\t\t\tallHealth.push(...result);\n\t\t\t\t\t\t\t\tresolve();\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\tconst result = await healthMethod(callback);\n\t\t\t\t\t// undefined result indicates that the component will provide the\n\t\t\t\t\t// result asynchronously via the callback\n\t\t\t\t\tif (Is.undefined(result)) {\n\t\t\t\t\t\tlazyPromises.push(lazyPromise);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tallHealth.push(...result);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\t// Wait for all deferred callbacks before proceeding to teardown\n\tif (lazyPromises.length > 0) {\n\t\tawait Promise.allSettled(lazyPromises);\n\t}\n\n\t// Pass 3: Teardown wrapped in combined engine + init context\n\tawait ContextIdStore.run(healthContextIds, async () => {\n\t\tfor (const registeredInstance of registeredInstances.slice().reverse()) {\n\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\tconst teardownMethod = registeredInstance.component.healthApplicationTeardown?.bind(\n\t\t\t\t\tregisteredInstance.component\n\t\t\t\t);\n\t\t\t\tif (Is.function(teardownMethod)) {\n\t\t\t\t\tawait teardownMethod();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\treturn allHealth;\n}\n"]}
1
+ {"version":3,"file":"healthApplicationTask.js","sources":["../../src/healthApplicationTask.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tHealthApplicationCallback,\n\tIHealth,\n\tIHealthProviderComponent\n} from \"@3sixty/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@3sixty/context\";\nimport type { IContextIds } from \"@3sixty/context\";\nimport { GeneralError, Is } from \"@3sixty/core\";\nimport type { IComponent } from \"@3sixty/core\";\nimport { ModuleHelper } from \"@3sixty/modules\";\n\nlet engine:\n\t| {\n\t\t\tstart: () => Promise<void>;\n\t\t\tstop: () => Promise<void>;\n\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\tgetRegisteredComponents: () => Promise<{ instanceType: string; component: IComponent }[]>;\n\t }\n\t| undefined;\nlet startupPromise: Promise<void> | undefined;\nlet excludeCloneComponents: string[] | undefined;\n\n/**\n * Start the engine clone used for application health checks.\n * @param engineCloneData The engine clone data supplied automatically by the background task framework.\n * @param excludeComponents Verified regular expression patterns for component types to exclude\n * from the clone, validated by the health service before registration.\n */\nexport async function healthApplicationTaskStart(\n\tengineCloneData: unknown,\n\texcludeComponents?: string[]\n): Promise<void> {\n\texcludeCloneComponents = excludeComponents;\n\n\tstartupPromise = (async () => {\n\t\tif (!Is.empty(engineCloneData)) {\n\t\t\tconst cloneData =\n\t\t\t\tIs.object(engineCloneData) && Is.arrayValue(excludeComponents)\n\t\t\t\t\t? await ModuleHelper.execModuleMethod<unknown>(\n\t\t\t\t\t\t\t\"@3sixty/engine-models\",\n\t\t\t\t\t\t\t\"EngineCloneHelper.filterCloneComponents\",\n\t\t\t\t\t\t\t[engineCloneData, excludeComponents]\n\t\t\t\t\t\t)\n\t\t\t\t\t: engineCloneData;\n\t\t\tconst clone = await ModuleHelper.execModuleMethod<{\n\t\t\t\tstart: () => Promise<void>;\n\t\t\t\tstop: () => Promise<void>;\n\t\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\t\tgetRegisteredComponents: () => Promise<{ instanceType: string; component: IComponent }[]>;\n\t\t\t}>(\"@3sixty/engine-core\", \"EngineCoreBuilder.fromClone\", [\n\t\t\t\t\"engine\",\n\t\t\t\tcloneData,\n\t\t\t\tawait ContextIdStore.getContextIds(),\n\t\t\t\t{ logLevel: \"error\" }\n\t\t\t]);\n\t\t\tif (Is.empty(clone)) {\n\t\t\t\tthrow new GeneralError(\"applicationHealthTask\", \"engineNotStarted\");\n\t\t\t}\n\t\t\tawait clone.start();\n\t\t\tengine = clone;\n\t\t}\n\t})();\n\n\ttry {\n\t\tawait startupPromise;\n\t} catch (err) {\n\t\tstartupPromise = undefined;\n\t\tthrow err;\n\t}\n}\n\n/**\n * Stop the engine clone used for application health checks.\n */\nexport async function healthApplicationTaskEnd(): Promise<void> {\n\tif (!Is.empty(engine)) {\n\t\tawait engine.stop();\n\t\tengine = undefined;\n\t}\n\tstartupPromise = undefined;\n\texcludeCloneComponents = undefined;\n}\n\n/**\n * Execute the application health lifecycle (init, application, teardown) across all registered\n * components. Called by the background task framework in a worker thread; the engine clone data\n * is used to initialise a local clone of the engine so component factories are available.\n * @param engineCloneData The engine clone data supplied automatically by the background task framework.\n * @returns The health entries collected across all three passes.\n */\nexport async function healthApplicationTask(engineCloneData: unknown): Promise<IHealth[]> {\n\tif (Is.empty(engineCloneData)) {\n\t\treturn [];\n\t}\n\n\tif (startupPromise) {\n\t\ttry {\n\t\t\tawait startupPromise;\n\t\t} catch {\n\t\t\tstartupPromise = undefined;\n\t\t}\n\t}\n\n\tif (Is.empty(engine)) {\n\t\tawait healthApplicationTaskStart(engineCloneData, excludeCloneComponents);\n\t}\n\n\treturn executeApplicationHealthCycle();\n}\n\n/**\n * Run the application health lifecycle for each registered provider: initialise shared context,\n * run the health checks, then tear down the per-check context in reverse registration order.\n * @returns The health entries collected for the current application health pass.\n */\nasync function executeApplicationHealthCycle(): Promise<IHealth[]> {\n\tif (Is.empty(engine)) {\n\t\tthrow new GeneralError(\"applicationHealthTask\", \"engineNotStarted\");\n\t}\n\n\tconst engineContextIds = engine.getContextIds() ?? {};\n\tconst registeredInstances = await engine.getRegisteredComponents();\n\n\t// Pass 1: Init - providers populate healthContextIds with any IDs they establish\n\tconst healthContextIds: IContextIds = {\n\t\t// Only use Node from the main engine context.\n\t\t// All other keys should be provided by healthApplicationInit of each component\n\t\t// as we don't want to use real ids.\n\t\t[ContextIdKeys.Node]: engineContextIds[ContextIdKeys.Node]\n\t};\n\tfor (const registeredInstance of registeredInstances) {\n\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\tconst initMethod = registeredInstance.component.healthApplicationInit?.bind(\n\t\t\t\tregisteredInstance.component\n\t\t\t);\n\t\t\tif (Is.function(initMethod)) {\n\t\t\t\tawait initMethod(healthContextIds);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Pass 2: Application health check wrapped in combined engine + init context\n\tconst allHealth: IHealth[] = [];\n\tconst lazyPromises: Promise<void>[] = [];\n\n\tawait ContextIdStore.run(healthContextIds, async () => {\n\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\tconst healthMethod = registeredInstance.component.healthApplication?.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\tlet fired = false;\n\t\t\t\t\tlet callback: HealthApplicationCallback = async () => {};\n\t\t\t\t\tconst lazyPromise = new Promise<void>(resolve => {\n\t\t\t\t\t\tcallback = async (result: IHealth[]) => {\n\t\t\t\t\t\t\tif (!fired) {\n\t\t\t\t\t\t\t\tfired = true;\n\t\t\t\t\t\t\t\tallHealth.push(...result);\n\t\t\t\t\t\t\t\tresolve();\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\tconst result = await healthMethod(callback);\n\t\t\t\t\t// undefined result indicates that the component will provide the\n\t\t\t\t\t// result asynchronously via the callback\n\t\t\t\t\tif (Is.undefined(result)) {\n\t\t\t\t\t\tlazyPromises.push(lazyPromise);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tallHealth.push(...result);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\t// Wait for all deferred callbacks before proceeding to teardown\n\tif (lazyPromises.length > 0) {\n\t\tawait Promise.allSettled(lazyPromises);\n\t}\n\n\t// Pass 3: Teardown wrapped in combined engine + init context\n\tawait ContextIdStore.run(healthContextIds, async () => {\n\t\tfor (const registeredInstance of registeredInstances.slice().reverse()) {\n\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\tconst teardownMethod = registeredInstance.component.healthApplicationTeardown?.bind(\n\t\t\t\t\tregisteredInstance.component\n\t\t\t\t);\n\t\t\t\tif (Is.function(teardownMethod)) {\n\t\t\t\t\tawait teardownMethod();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\treturn allHealth;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
+ // Copyright 2026 3Sixty Trade Ltd.
2
3
  // SPDX-License-Identifier: Apache-2.0.
3
4
  import { HealthCategory, HealthStatus } from "@3sixty/api-models";
4
5
  import { ComponentFactory } from "@3sixty/core";
@@ -11,6 +12,13 @@ export const tagsHealth = [
11
12
  description: "Health endpoints for the REST server."
12
13
  }
13
14
  ];
15
+ /**
16
+ * The default authorization for the routes, used to seed authorization rules.
17
+ */
18
+ const DEFAULT_AUTHORIZATION_READER = {
19
+ permission: "health:read",
20
+ role: "devops"
21
+ };
14
22
  /**
15
23
  * The REST routes for server health.
16
24
  * @param baseRouteName Prefix to prepend to the paths.
@@ -77,16 +85,16 @@ export function generateRestRoutesHealth(baseRouteName, componentName) {
77
85
  description: "The response for the health request with errors.",
78
86
  response: {
79
87
  body: {
80
- status: "error",
88
+ status: HealthStatus.Error,
81
89
  components: [
82
90
  {
83
91
  source: "Database",
84
- status: "ok",
92
+ status: HealthStatus.Ok,
85
93
  category: HealthCategory.Connectivity
86
94
  },
87
95
  {
88
96
  source: "Storage",
89
- status: "error",
97
+ status: HealthStatus.Error,
90
98
  description: "storageFull",
91
99
  category: HealthCategory.Connectivity
92
100
  }
@@ -96,7 +104,8 @@ export function generateRestRoutesHealth(baseRouteName, componentName) {
96
104
  }
97
105
  ]
98
106
  }
99
- ]
107
+ ],
108
+ defaultAuthorization: DEFAULT_AUTHORIZATION_READER
100
109
  };
101
110
  return [healthRoute];
102
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"healthRoutes.js","sourceRoot":"","sources":["../../src/healthRoutes.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EACN,cAAc,EACd,YAAY,EAOZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAW;IACjC;QACC,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uCAAuC;KACpD;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACvC,aAAqB,EACrB,aAAqB;IAErB,MAAM,WAAW,GAAyD;QACzE,WAAW,EAAE,cAAc;QAC3B,OAAO,EAAE,+BAA+B;QACxC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QACvB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,GAAG,aAAa,GAAG;QACzB,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,YAAY,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACzD,YAAY,EAAE;YACb;gBACC,IAAI,yBAAiC;gBACrC,QAAQ,EAAE;oBACT;wBACC,EAAE,EAAE,kBAAkB;wBACtB,WAAW,EAAE,sCAAsC;wBACnD,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,YAAY,CAAC,EAAE;gCACvB,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,YAAY,CAAC,EAAE;wCACvB,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,YAAY,CAAC,EAAE;wCACvB,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;iCACD;6BACD;yBACD;qBACD;oBACD;wBACC,EAAE,EAAE,uBAAuB;wBAC3B,WAAW,EAAE,oDAAoD;wBACjE,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,YAAY,CAAC,OAAO;gCAC5B,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,YAAY,CAAC,OAAO;wCAC5B,WAAW,EAAE,aAAa;wCAC1B,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,YAAY,CAAC,EAAE;wCACvB,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;iCACD;6BACD;yBACD;qBACD;oBACD;wBACC,EAAE,EAAE,qBAAqB;wBACzB,WAAW,EAAE,kDAAkD;wBAC/D,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,OAAO;gCACf,UAAU,EAAE;oCACX;wCACC,MAAM,EAAE,UAAU;wCAClB,MAAM,EAAE,IAAI;wCACZ,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;oCACD;wCACC,MAAM,EAAE,SAAS;wCACjB,MAAM,EAAE,OAAO;wCACf,WAAW,EAAE,aAAa;wCAC1B,QAAQ,EAAE,cAAc,CAAC,YAAY;qCACrC;iCACD;6BACD;yBACD;qBACD;iBACD;aACD;SACD;KACD,CAAC;IAEF,OAAO,CAAC,WAAW,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,kBAAuC,EACvC,aAAqB,EACrB,OAA0B;IAE1B,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAmB,aAAa,CAAC,CAAC;IACxE,OAAO;QACN,IAAI,EAAE,MAAM,SAAS,CAAC,YAAY,EAAE;KACpC,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHealthCategory,\n\tHealthStatus,\n\ttype IHealthComponent,\n\ttype IHttpRequestContext,\n\ttype INoContentRequest,\n\ttype IRestRoute,\n\ttype IServerHealthResponse,\n\ttype ITag\n} from \"@3sixty/api-models\";\nimport { ComponentFactory } from \"@3sixty/core\";\nimport { nameof } from \"@3sixty/nameof\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsHealth: ITag[] = [\n\t{\n\t\tname: \"Health\",\n\t\tdescription: \"Health endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for server health.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesHealth(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst healthRoute: IRestRoute<INoContentRequest, IServerHealthResponse> = {\n\t\toperationId: \"serverHealth\",\n\t\tsummary: \"Get the health for the server\",\n\t\ttag: tagsHealth[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverHealth(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerHealthResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the health request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseWarning\",\n\t\t\t\t\t\tdescription: \"The response for the health request with warnings.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\t\t\tdescription: \"slowRunning\",\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseError\",\n\t\t\t\t\t\tdescription: \"The response for the health request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"ok\",\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\t\t\t\t\t\tdescription: \"storageFull\",\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t};\n\n\treturn [healthRoute];\n}\n\n/**\n * Get the health for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverHealth(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerHealthResponse> {\n\tconst component = ComponentFactory.get<IHealthComponent>(componentName);\n\treturn {\n\t\tbody: await component.healthStatus()\n\t};\n}\n"]}
1
+ {"version":3,"file":"healthRoutes.js","sources":["../../src/healthRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// Copyright 2026 3Sixty Trade Ltd.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHealthCategory,\n\tHealthStatus,\n\ttype IHealthComponent,\n\ttype IHttpRequestContext,\n\ttype INoContentRequest,\n\ttype IRestRoute,\n\ttype IServerHealthResponse,\n\ttype ITag,\n\ttype IRouteAuthorization\n} from \"@3sixty/api-models\";\nimport { ComponentFactory } from \"@3sixty/core\";\n\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsHealth: ITag[] = [\n\t{\n\t\tname: \"Health\",\n\t\tdescription: \"Health endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The default authorization for the routes, used to seed authorization rules.\n */\nconst DEFAULT_AUTHORIZATION_READER: IRouteAuthorization = {\n\tpermission: \"health:read\",\n\trole: \"devops\"\n};\n\n/**\n * The REST routes for server health.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesHealth(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst healthRoute: IRestRoute<INoContentRequest, IServerHealthResponse> = {\n\t\toperationId: \"serverHealth\",\n\t\tsummary: \"Get the health for the server\",\n\t\ttag: tagsHealth[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverHealth(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerHealthResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the health request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseWarning\",\n\t\t\t\t\t\tdescription: \"The response for the health request with warnings.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\t\t\t\t\tdescription: \"slowRunning\",\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"healthResponseError\",\n\t\t\t\t\t\tdescription: \"The response for the health request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\t\t\t\tcomponents: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Database\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tsource: \"Storage\",\n\t\t\t\t\t\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\t\t\t\t\t\tdescription: \"storageFull\",\n\t\t\t\t\t\t\t\t\t\tcategory: HealthCategory.Connectivity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tdefaultAuthorization: DEFAULT_AUTHORIZATION_READER\n\t};\n\n\treturn [healthRoute];\n}\n\n/**\n * Get the health for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverHealth(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerHealthResponse> {\n\tconst component = ComponentFactory.get<IHealthComponent>(componentName);\n\treturn {\n\t\tbody: await component.healthStatus()\n\t};\n}\n"]}
@@ -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 {\n\ttype IHealth,\n\ttype IHealthComponent,\n\tHealthStatus,\n\ttype IHealthProviderComponent\n} from \"@3sixty/api-models\";\nimport type { IBackgroundTask, IBackgroundTaskComponent } from \"@3sixty/background-task-models\";\nimport { TaskStatus } from \"@3sixty/background-task-models\";\nimport type { IContextIds } from \"@3sixty/context\";\nimport { BaseError, ComponentFactory, Factory, type IComponent, Is } from \"@3sixty/core\";\nimport type { ILoggingComponent } from \"@3sixty/logging-models\";\nimport { ModuleHelper } from \"@3sixty/modules\";\nimport { nameof } from \"@3sixty/nameof\";\nimport type { IHealthServiceConstructorOptions } from \"./models/IHealthServiceConstructorOptions.js\";\n\n/**\n * The health service for the server.\n */\nexport class HealthService implements IHealthComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<HealthService>();\n\n\t/**\n\t * Task type identifier for the application health background task.\n\t * @internal\n\t */\n\tprivate static readonly _APPLICATION_HEALTH_TASK_TYPE: string = \"health-application-check\";\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents: IHealth[];\n\t};\n\n\t/**\n\t * The interval for checking the health of the components.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * The interval for running the application health lifecycle (init, application, teardown).\n\t * @internal\n\t */\n\tprivate readonly _healthApplicationCheckInterval: number;\n\n\t/**\n\t * The initial interval for checking the health of the components.\n\t * This is used to check the health of the components immediately after the service is started.\n\t * @internal\n\t */\n\tprivate readonly _initialInterval: number;\n\n\t/**\n\t * Timer for the regular health check.\n\t * @internal\n\t */\n\tprivate _healthTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Timer for the application health lifecycle.\n\t * @internal\n\t */\n\tprivate _healthApplicationTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Whether the service has been started.\n\t * @internal\n\t */\n\tprivate _started: boolean;\n\n\t/**\n\t * Health entries from the most recently completed regular health check.\n\t * @internal\n\t */\n\tprivate _regularComponents: IHealth[];\n\n\t/**\n\t * Health entries from the most recently completed application health cycle.\n\t * @internal\n\t */\n\tprivate _applicationComponents: IHealth[];\n\n\t/**\n\t * Whether to include stack traces in health check error details.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack?: boolean;\n\n\t/**\n\t * The background task component for running application health checks.\n\t * @internal\n\t */\n\tprivate readonly _backgroundTaskComponent: IBackgroundTaskComponent;\n\n\t/**\n\t * The URL of the module to use for the application health background task.\n\t * @internal\n\t */\n\tprivate readonly _applicationHealthTaskHandler: string;\n\n\t/**\n\t * Regular expression patterns for component types to exclude from the health engine clone,\n\t * verified when the service is started.\n\t * @internal\n\t */\n\tprivate _excludeCloneComponents: string[] | undefined;\n\n\t/**\n\t * Create a new instance of HealthService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IHealthServiceConstructorOptions) {\n\t\tthis._healthInfo = { status: HealthStatus.Ok, components: [] };\n\t\tthis._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;\n\t\tthis._healthApplicationCheckInterval =\n\t\t\toptions?.config?.healthCheckApplicationInterval ?? 300000;\n\t\tthis._initialInterval = options?.config?.initialInterval ?? 2000;\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack;\n\t\tthis._backgroundTaskComponent = ComponentFactory.get<IBackgroundTaskComponent>(\n\t\t\toptions?.backgroundTaskComponentType ?? \"background-task\"\n\t\t);\n\t\tthis._applicationHealthTaskHandler =\n\t\t\toptions?.config?.overrideApplicationHealthTaskHandler ??\n\t\t\tnew URL(\"./healthApplicationTask.js\", import.meta.url).href;\n\t\tthis._excludeCloneComponents = options?.config?.excludeCloneComponents;\n\t\tthis._started = false;\n\t\tthis._regularComponents = [];\n\t\tthis._applicationComponents = [];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn HealthService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the initial health check timers have been scheduled.\n\t * @throws GeneralError if an exclude clone component is not a valid regular expression.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (!this._started) {\n\t\t\t// Only load the engine models module when there is something to verify, so a node\n\t\t\t// with no configured exclusions never has to resolve it.\n\t\t\tif (Is.arrayValue(this._excludeCloneComponents)) {\n\t\t\t\tthis._excludeCloneComponents = await ModuleHelper.execModuleMethod<string[] | undefined>(\n\t\t\t\t\t\"@3sixty/engine-models\",\n\t\t\t\t\t\"EngineCloneHelper.verifyExcludeCloneComponents\",\n\t\t\t\t\t[this._excludeCloneComponents]\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis._started = true;\n\n\t\t\tawait this._backgroundTaskComponent.registerHandler<undefined, IHealth[]>(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE,\n\t\t\t\tthis._applicationHealthTaskHandler,\n\t\t\t\t\"healthApplicationTask\",\n\t\t\t\tasync (task: IBackgroundTask<undefined, IHealth[]>) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\ttask.status === TaskStatus.Success ||\n\t\t\t\t\t\ttask.status === TaskStatus.Failed ||\n\t\t\t\t\t\ttask.status === TaskStatus.Cancelled\n\t\t\t\t\t) {\n\t\t\t\t\t\tif (task.status === TaskStatus.Success && Is.array(task.result)) {\n\t\t\t\t\t\t\tthis._applicationComponents = task.result;\n\t\t\t\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\t\t\t} else if (task.status === TaskStatus.Failed) {\n\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"applicationHealthCheckTaskFailed\",\n\t\t\t\t\t\t\t\terror: Is.object(task.error) ? BaseError.fromError(task.error) : undefined\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.startApplicationTimer(this._healthApplicationCheckInterval);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tidleShutdownTimeout: -1,\n\t\t\t\t\tinitialiseMethod: \"healthApplicationTaskStart\",\n\t\t\t\t\tinitialiseMethodParams: async () => [this._excludeCloneComponents],\n\t\t\t\t\tshutdownMethod: \"healthApplicationTaskEnd\"\n\t\t\t\t}\n\t\t\t);\n\n\t\t\t// Immediately check health after a startup settling period.\n\t\t\t// The interval for the next checks are triggered on success of the current\n\t\t\t// check to prevent overlapping checks in case of long running health checks.\n\t\t\tthis.startTimer(nodeLoggingComponentType, this._initialInterval);\n\t\t\tthis.startApplicationTimer(this._initialInterval);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the health check timers have been cancelled.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._started) {\n\t\t\tthis._started = false;\n\t\t\tthis.stopTimer();\n\t\t\tthis.stopApplicationTimer();\n\n\t\t\tawait this._backgroundTaskComponent.unregisterHandler(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async healthStatus(): Promise<{ status: HealthStatus; components: IHealth[] }> {\n\t\treturn this._healthInfo;\n\t}\n\n\t/**\n\t * Check the health of all registered components by calling health() on each.\n\t * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.\n\t * @returns A promise that resolves when all component health checks are complete and the next check is scheduled.\n\t * @internal\n\t */\n\tprivate async checkHealth(nodeLoggingComponentType?: string): Promise<void> {\n\t\tthis.stopTimer();\n\n\t\tconst engineCoreFactory = Factory.getFactory(\"engine-core\");\n\n\t\tif (engineCoreFactory) {\n\t\t\t// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.\n\t\t\tconst engineCore = engineCoreFactory.getIfExists<{\n\t\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\t\tgetRegisteredComponents: () => Promise<\n\t\t\t\t\t{\n\t\t\t\t\t\tinstanceType: string;\n\t\t\t\t\t\tcomponent: IComponent;\n\t\t\t\t\t}[]\n\t\t\t\t>;\n\t\t\t}>(\"engine\");\n\n\t\t\tif (engineCore) {\n\t\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\t\t\tregisteredInstance.component\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\t\t\tdata: { className: registeredInstance.component.className() },\n\t\t\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis._regularComponents = allHealth;\n\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\tthis.startTimer(nodeLoggingComponentType, this._healthCheckInterval);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.\n\t * @param entries The flat list of health entries from all components.\n\t * @internal\n\t */\n\tprivate groupHealthByName(entries: IHealth[]): void {\n\t\tconst bySource = new Map<string, IHealth[]>();\n\t\tfor (const entry of entries) {\n\t\t\tconst existing = bySource.get(entry.source) ?? [];\n\t\t\texisting.push({\n\t\t\t\t...entry,\n\t\t\t\terror: !Is.empty(entry.error)\n\t\t\t\t\t? BaseError.fromError(entry.error).toJsonObject(this._includeErrorStack)\n\t\t\t\t\t: undefined\n\t\t\t});\n\t\t\tbySource.set(entry.source, existing);\n\t\t}\n\n\t\tconst result: IHealth[] = [];\n\t\tfor (const [source, group] of bySource) {\n\t\t\tif (group.length > 1) {\n\t\t\t\tlet parentStatus: HealthStatus = HealthStatus.Ok;\n\n\t\t\t\tif (group.some(e => e.status === HealthStatus.Error)) {\n\t\t\t\t\tparentStatus = HealthStatus.Error;\n\t\t\t\t} else if (group.some(e => e.status === HealthStatus.Warning)) {\n\t\t\t\t\tparentStatus = HealthStatus.Warning;\n\t\t\t\t}\n\n\t\t\t\tresult.push({ source, status: parentStatus, grouped: group });\n\t\t\t} else {\n\t\t\t\tresult.push(group[0]);\n\t\t\t}\n\t\t}\n\n\t\tlet finalStatus: HealthStatus = HealthStatus.Ok;\n\t\tif (result.some(e => e.status === HealthStatus.Error)) {\n\t\t\tfinalStatus = HealthStatus.Error;\n\t\t} else if (result.some(e => e.status === HealthStatus.Warning)) {\n\t\t\tfinalStatus = HealthStatus.Warning;\n\t\t}\n\n\t\tthis._healthInfo = { status: finalStatus, components: result };\n\t}\n\n\t/**\n\t * Start the regular health check timer.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startTimer(nodeLoggingComponentType: string | undefined, interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\t\tasync () => this.checkHealth(nodeLoggingComponentType),\n\t\t\t\tinterval\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the regular health check timer.\n\t * @internal\n\t */\n\tprivate stopTimer(): void {\n\t\tif (this._healthTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthTimer);\n\t\t\tthis._healthTimer = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Start the application health lifecycle timer.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startApplicationTimer(interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthApplicationTimer = globalThis.setTimeout(async () => {\n\t\t\t\tthis.stopApplicationTimer();\n\n\t\t\t\tawait this._backgroundTaskComponent.create(HealthService._APPLICATION_HEALTH_TASK_TYPE);\n\t\t\t}, interval);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the application health lifecycle timer.\n\t * @internal\n\t */\n\tprivate stopApplicationTimer(): void {\n\t\tif (this._healthApplicationTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthApplicationTimer);\n\t\t\tthis._healthApplicationTimer = undefined;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"healthService.js","sources":["../../src/healthService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\ttype IHealth,\n\ttype IHealthComponent,\n\tHealthStatus,\n\ttype IHealthProviderComponent\n} from \"@3sixty/api-models\";\nimport type { IBackgroundTask, IBackgroundTaskComponent } from \"@3sixty/background-task-models\";\nimport { TaskStatus } from \"@3sixty/background-task-models\";\nimport type { IContextIds } from \"@3sixty/context\";\nimport { BaseError, ComponentFactory, Factory, type IComponent, Is } from \"@3sixty/core\";\nimport type { ILoggingComponent } from \"@3sixty/logging-models\";\nimport { ModuleHelper } from \"@3sixty/modules\";\n\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 = \"HealthService\";\n\n\t/**\n\t * Task type identifier for the application health background task.\n\t * @internal\n\t */\n\tprivate static readonly _APPLICATION_HEALTH_TASK_TYPE: string = \"health-application-check\";\n\n\t/**\n\t * The server health.\n\t * @internal\n\t */\n\tprivate _healthInfo: {\n\t\tstatus: HealthStatus;\n\t\tcomponents: IHealth[];\n\t};\n\n\t/**\n\t * The interval for checking the health of the components.\n\t * @internal\n\t */\n\tprivate readonly _healthCheckInterval: number;\n\n\t/**\n\t * The interval for running the application health lifecycle (init, application, teardown).\n\t * @internal\n\t */\n\tprivate readonly _healthApplicationCheckInterval: number;\n\n\t/**\n\t * The initial interval for checking the health of the components.\n\t * This is used to check the health of the components immediately after the service is started.\n\t * @internal\n\t */\n\tprivate readonly _initialInterval: number;\n\n\t/**\n\t * Timer for the regular health check.\n\t * @internal\n\t */\n\tprivate _healthTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Timer for the application health lifecycle.\n\t * @internal\n\t */\n\tprivate _healthApplicationTimer: ReturnType<typeof globalThis.setTimeout> | undefined;\n\n\t/**\n\t * Whether the service has been started.\n\t * @internal\n\t */\n\tprivate _started: boolean;\n\n\t/**\n\t * Health entries from the most recently completed regular health check.\n\t * @internal\n\t */\n\tprivate _regularComponents: IHealth[];\n\n\t/**\n\t * Health entries from the most recently completed application health cycle.\n\t * @internal\n\t */\n\tprivate _applicationComponents: IHealth[];\n\n\t/**\n\t * Whether to include stack traces in health check error details.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack?: boolean;\n\n\t/**\n\t * The background task component for running application health checks.\n\t * @internal\n\t */\n\tprivate readonly _backgroundTaskComponent: IBackgroundTaskComponent;\n\n\t/**\n\t * The URL of the module to use for the application health background task.\n\t * @internal\n\t */\n\tprivate readonly _applicationHealthTaskHandler: string;\n\n\t/**\n\t * Regular expression patterns for component types to exclude from the health engine clone,\n\t * verified when the service is started.\n\t * @internal\n\t */\n\tprivate _excludeCloneComponents: string[] | undefined;\n\n\t/**\n\t * Create a new instance of HealthService.\n\t * @param options The constructor options.\n\t */\n\tconstructor(options?: IHealthServiceConstructorOptions) {\n\t\tthis._healthInfo = { status: HealthStatus.Ok, components: [] };\n\t\tthis._healthCheckInterval = options?.config?.healthCheckInterval ?? 60000;\n\t\tthis._healthApplicationCheckInterval =\n\t\t\toptions?.config?.healthCheckApplicationInterval ?? 300000;\n\t\tthis._initialInterval = options?.config?.initialInterval ?? 2000;\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack;\n\t\tthis._backgroundTaskComponent = ComponentFactory.get<IBackgroundTaskComponent>(\n\t\t\toptions?.backgroundTaskComponentType ?? \"background-task\"\n\t\t);\n\t\tthis._applicationHealthTaskHandler =\n\t\t\toptions?.config?.overrideApplicationHealthTaskHandler ??\n\t\t\tnew URL(\"./healthApplicationTask.js\", import.meta.url).href;\n\t\tthis._excludeCloneComponents = options?.config?.excludeCloneComponents;\n\t\tthis._started = false;\n\t\tthis._regularComponents = [];\n\t\tthis._applicationComponents = [];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn HealthService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the initial health check timers have been scheduled.\n\t * @throws GeneralError if an exclude clone component is not a valid regular expression.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (!this._started) {\n\t\t\t// Only load the engine models module when there is something to verify, so a node\n\t\t\t// with no configured exclusions never has to resolve it.\n\t\t\tif (Is.arrayValue(this._excludeCloneComponents)) {\n\t\t\t\tthis._excludeCloneComponents = await ModuleHelper.execModuleMethod<string[] | undefined>(\n\t\t\t\t\t\"@3sixty/engine-models\",\n\t\t\t\t\t\"EngineCloneHelper.verifyExcludeCloneComponents\",\n\t\t\t\t\t[this._excludeCloneComponents]\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis._started = true;\n\n\t\t\tawait this._backgroundTaskComponent.registerHandler<undefined, IHealth[]>(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE,\n\t\t\t\tthis._applicationHealthTaskHandler,\n\t\t\t\t\"healthApplicationTask\",\n\t\t\t\tasync (task: IBackgroundTask<undefined, IHealth[]>) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\ttask.status === TaskStatus.Success ||\n\t\t\t\t\t\ttask.status === TaskStatus.Failed ||\n\t\t\t\t\t\ttask.status === TaskStatus.Cancelled\n\t\t\t\t\t) {\n\t\t\t\t\t\tif (task.status === TaskStatus.Success && Is.array(task.result)) {\n\t\t\t\t\t\t\tthis._applicationComponents = task.result;\n\t\t\t\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\t\t\t} else if (task.status === TaskStatus.Failed) {\n\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"applicationHealthCheckTaskFailed\",\n\t\t\t\t\t\t\t\terror: Is.object(task.error) ? BaseError.fromError(task.error) : undefined\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.startApplicationTimer(this._healthApplicationCheckInterval);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tidleShutdownTimeout: -1,\n\t\t\t\t\tinitialiseMethod: \"healthApplicationTaskStart\",\n\t\t\t\t\tinitialiseMethodParams: async () => [this._excludeCloneComponents],\n\t\t\t\t\tshutdownMethod: \"healthApplicationTaskEnd\"\n\t\t\t\t}\n\t\t\t);\n\n\t\t\t// Immediately check health after a startup settling period.\n\t\t\t// The interval for the next checks are triggered on success of the current\n\t\t\t// check to prevent overlapping checks in case of long running health checks.\n\t\t\tthis.startTimer(nodeLoggingComponentType, this._initialInterval);\n\t\t\tthis.startApplicationTimer(this._initialInterval);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the health check timers have been cancelled.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (this._started) {\n\t\t\tthis._started = false;\n\t\t\tthis.stopTimer();\n\t\t\tthis.stopApplicationTimer();\n\n\t\t\tawait this._backgroundTaskComponent.unregisterHandler(\n\t\t\t\tHealthService._APPLICATION_HEALTH_TASK_TYPE\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the server health.\n\t * @returns The service health.\n\t */\n\tpublic async healthStatus(): Promise<{ status: HealthStatus; components: IHealth[] }> {\n\t\treturn this._healthInfo;\n\t}\n\n\t/**\n\t * Check the health of all registered components by calling health() on each.\n\t * @param nodeLoggingComponentType The node logging component type to log any errors that occur during health checks.\n\t * @returns A promise that resolves when all component health checks are complete and the next check is scheduled.\n\t * @internal\n\t */\n\tprivate async checkHealth(nodeLoggingComponentType?: string): Promise<void> {\n\t\tthis.stopTimer();\n\n\t\tconst engineCoreFactory = Factory.getFactory(\"engine-core\");\n\n\t\tif (engineCoreFactory) {\n\t\t\t// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.\n\t\t\tconst engineCore = engineCoreFactory.getIfExists<{\n\t\t\t\tgetContextIds: () => IContextIds | undefined;\n\t\t\t\tgetRegisteredComponents: () => Promise<\n\t\t\t\t\t{\n\t\t\t\t\t\tinstanceType: string;\n\t\t\t\t\t\tcomponent: IComponent;\n\t\t\t\t\t}[]\n\t\t\t\t>;\n\t\t\t}>(\"engine\");\n\n\t\t\tif (engineCore) {\n\t\t\t\tconst registeredInstances = await engineCore.getRegisteredComponents();\n\t\t\t\tconst allHealth: IHealth[] = [];\n\n\t\t\t\tfor (const registeredInstance of registeredInstances) {\n\t\t\t\t\tif (Is.object<IHealthProviderComponent>(registeredInstance.component)) {\n\t\t\t\t\t\tconst healthMethod = registeredInstance.component.health?.bind(\n\t\t\t\t\t\t\tregisteredInstance.component\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (Is.function(healthMethod)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tallHealth.push(...(await healthMethod()));\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconst nodeLogging =\n\t\t\t\t\t\t\t\t\tComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\t\t\t\t\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\t\t\t\t\tsource: HealthService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"componentHealthCheckFailed\",\n\t\t\t\t\t\t\t\t\tdata: { className: registeredInstance.component.className() },\n\t\t\t\t\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis._regularComponents = allHealth;\n\t\t\t\tthis.groupHealthByName([...this._regularComponents, ...this._applicationComponents]);\n\t\t\t\tthis.startTimer(nodeLoggingComponentType, this._healthCheckInterval);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Group raw health entries by name, collapsing duplicates into a parent with a grouped array.\n\t * @param entries The flat list of health entries from all components.\n\t * @internal\n\t */\n\tprivate groupHealthByName(entries: IHealth[]): void {\n\t\tconst bySource = new Map<string, IHealth[]>();\n\t\tfor (const entry of entries) {\n\t\t\tconst existing = bySource.get(entry.source) ?? [];\n\t\t\texisting.push({\n\t\t\t\t...entry,\n\t\t\t\terror: !Is.empty(entry.error)\n\t\t\t\t\t? BaseError.fromError(entry.error).toJsonObject(this._includeErrorStack)\n\t\t\t\t\t: undefined\n\t\t\t});\n\t\t\tbySource.set(entry.source, existing);\n\t\t}\n\n\t\tconst result: IHealth[] = [];\n\t\tfor (const [source, group] of bySource) {\n\t\t\tif (group.length > 1) {\n\t\t\t\tlet parentStatus: HealthStatus = HealthStatus.Ok;\n\n\t\t\t\tif (group.some(e => e.status === HealthStatus.Error)) {\n\t\t\t\t\tparentStatus = HealthStatus.Error;\n\t\t\t\t} else if (group.some(e => e.status === HealthStatus.Warning)) {\n\t\t\t\t\tparentStatus = HealthStatus.Warning;\n\t\t\t\t}\n\n\t\t\t\tresult.push({ source, status: parentStatus, grouped: group });\n\t\t\t} else {\n\t\t\t\tresult.push(group[0]);\n\t\t\t}\n\t\t}\n\n\t\tlet finalStatus: HealthStatus = HealthStatus.Ok;\n\t\tif (result.some(e => e.status === HealthStatus.Error)) {\n\t\t\tfinalStatus = HealthStatus.Error;\n\t\t} else if (result.some(e => e.status === HealthStatus.Warning)) {\n\t\t\tfinalStatus = HealthStatus.Warning;\n\t\t}\n\n\t\tthis._healthInfo = { status: finalStatus, components: result };\n\t}\n\n\t/**\n\t * Start the regular health check timer.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startTimer(nodeLoggingComponentType: string | undefined, interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthTimer = globalThis.setTimeout(\n\t\t\t\tasync () => this.checkHealth(nodeLoggingComponentType),\n\t\t\t\tinterval\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the regular health check timer.\n\t * @internal\n\t */\n\tprivate stopTimer(): void {\n\t\tif (this._healthTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthTimer);\n\t\t\tthis._healthTimer = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Start the application health lifecycle timer.\n\t * @param interval The interval for the timer.\n\t * @internal\n\t */\n\tprivate startApplicationTimer(interval: number): void {\n\t\tif (this._started) {\n\t\t\tthis._healthApplicationTimer = globalThis.setTimeout(async () => {\n\t\t\t\tthis.stopApplicationTimer();\n\n\t\t\t\tawait this._backgroundTaskComponent.create(HealthService._APPLICATION_HEALTH_TASK_TYPE);\n\t\t\t}, interval);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the application health lifecycle timer.\n\t * @internal\n\t */\n\tprivate stopApplicationTimer(): void {\n\t\tif (this._healthApplicationTimer) {\n\t\t\tglobalThis.clearTimeout(this._healthApplicationTimer);\n\t\t\tthis._healthApplicationTimer = undefined;\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./healthRoutes.js\";\nexport * from \"./healthService.js\";\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./models/IHealthServiceConfig.js\";\nexport * from \"./models/IHealthServiceConstructorOptions.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./models/IPlatformServiceConfig.js\";\nexport * from \"./models/IPlatformServiceConstructorOptions.js\";\nexport * from \"./platformService.js\";\nexport * from \"./restEntryPoints.js\";\n"]}
1
+ {"version":3,"file":"index.js","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./healthRoutes.js\";\nexport * from \"./healthService.js\";\nexport * from \"./informationRoutes.js\";\nexport * from \"./informationService.js\";\nexport * from \"./models/IHealthServiceConfig.js\";\nexport * from \"./models/IHealthServiceConstructorOptions.js\";\nexport * from \"./models/IInformationServiceConfig.js\";\nexport * from \"./models/IInformationServiceConstructorOptions.js\";\nexport * from \"./models/IPlatformServiceConfig.js\";\nexport * from \"./models/IPlatformServiceConstructorOptions.js\";\nexport * from \"./platformService.js\";\nexport * from \"./restEntryPoints.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"informationRoutes.js","sourceRoot":"","sources":["../../src/informationRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHttpRequestContext,\n\tIInformationComponent,\n\tINoContentRequest,\n\tIRestRoute,\n\tIServerFavIconResponse,\n\tIServerInfoResponse,\n\tIServerLivezResponse,\n\tIServerReadyzResponse,\n\tIServerRootResponse,\n\tIServerSpecResponse,\n\tITag\n} from \"@3sixty/api-models\";\nimport { ComponentFactory, Is } from \"@3sixty/core\";\nimport { nameof } from \"@3sixty/nameof\";\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@3sixty/web\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsInformation: ITag[] = [\n\t{\n\t\tname: \"Info\",\n\t\tdescription: \"Information endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for server information.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesInformation(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst rootRoute: IRestRoute = {\n\t\toperationId: \"serverRoot\",\n\t\tsummary: \"Get the root text page\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverRoot(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerRootResponse>(),\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"serverRootResponse\",\n\t\t\t\t\t\tdescription: \"The response for the root request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"API Server - 1.0.0\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst informationRoute: IRestRoute<INoContentRequest, IServerInfoResponse> = {\n\t\toperationId: \"serverInformation\",\n\t\tsummary: \"Get the information for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/info`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverInfo(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerInfoResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"informationResponse\",\n\t\t\t\t\t\tdescription: \"The response for the information request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tname: \"API Server\",\n\t\t\t\t\t\t\t\tversion: \"1.0.0\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst favIconRoute: IRestRoute<INoContentRequest, IServerFavIconResponse> = {\n\t\toperationId: \"serverFavIcon\",\n\t\tsummary: \"Get the favicon for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/favicon.ico`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverFavIcon(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerFavIconResponse>(),\n\t\t\t\tmimeType: \"image/x-icon\"\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst livezRoute: IRestRoute<INoContentRequest, IServerLivezResponse> = {\n\t\toperationId: \"serverLivez\",\n\t\tsummary: \"Get the livez status for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/livez`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverLivez(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerLivezResponse>(),\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"alive\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseFailure\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"dead\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst readyzRoute: IRestRoute<INoContentRequest, IServerReadyzResponse> = {\n\t\toperationId: \"serverReadyz\",\n\t\tsummary: \"Get the readyz status for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/readyz`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverReadyz(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerReadyzResponse>(),\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"readyzResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the readiness request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"ready\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"readyzResponseFailure\",\n\t\t\t\t\t\tdescription: \"The response for the readiness request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"not ready\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst specRoute: IRestRoute<INoContentRequest, IServerSpecResponse> = {\n\t\toperationId: \"serverSpec\",\n\t\tsummary: \"Get the OpenAPI specification for the endpoints\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/spec`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverSpec(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: nameof<IServerSpecResponse>(),\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"specResponse\",\n\t\t\t\t\t\tdescription: \"The response for the spec request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\topenapi: \"3.1.0\",\n\t\t\t\t\t\t\t\tinfo: {},\n\t\t\t\t\t\t\t\tpaths: {}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\treturn [rootRoute, favIconRoute, informationRoute, livezRoute, readyzRoute, specRoute];\n}\n\n/**\n * Get the root for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverRoot(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerRootResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: await component.root()\n\t};\n}\n\n/**\n * Get the information for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverInfo(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerInfoResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\tbody: await component.info()\n\t};\n}\n\n/**\n * Get the livez for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverLivez(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerLivezResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: (await component.livez()).status\n\t};\n}\n\n/**\n * Get the readyz for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverReadyz(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerReadyzResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: (await component.readyz()).status\n\t};\n}\n\n/**\n * Get the favicon for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverFavIcon(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerFavIconResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst favIcon = await component.favicon();\n\n\tif (Is.uint8Array(favIcon)) {\n\t\treturn {\n\t\t\theaders: {\n\t\t\t\t[HeaderTypes.ContentType]: \"image/x-icon\"\n\t\t\t},\n\t\t\tbody: favIcon\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n\n/**\n * Get the spec for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverSpec(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerSpecResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst spec = await component.spec();\n\n\tif (Is.objectValue(spec)) {\n\t\treturn {\n\t\t\tbody: spec\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n"]}
1
+ {"version":3,"file":"informationRoutes.js","sources":["../../src/informationRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHttpRequestContext,\n\tIInformationComponent,\n\tINoContentRequest,\n\tIRestRoute,\n\tIServerFavIconResponse,\n\tIServerInfoResponse,\n\tIServerLivezResponse,\n\tIServerReadyzResponse,\n\tIServerRootResponse,\n\tIServerSpecResponse,\n\tITag\n} from \"@3sixty/api-models\";\nimport { ComponentFactory, Is } from \"@3sixty/core\";\n\nimport { HeaderTypes, HttpStatusCode, MimeTypes } from \"@3sixty/web\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsInformation: ITag[] = [\n\t{\n\t\tname: \"Info\",\n\t\tdescription: \"Information endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for server information.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesInformation(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst rootRoute: IRestRoute = {\n\t\toperationId: \"serverRoot\",\n\t\tsummary: \"Get the root text page\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverRoot(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerRootResponse\",\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"serverRootResponse\",\n\t\t\t\t\t\tdescription: \"The response for the root request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"API Server - 1.0.0\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst informationRoute: IRestRoute<INoContentRequest, IServerInfoResponse> = {\n\t\toperationId: \"serverInformation\",\n\t\tsummary: \"Get the information for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/info`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverInfo(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerInfoResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"informationResponse\",\n\t\t\t\t\t\tdescription: \"The response for the information request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tname: \"API Server\",\n\t\t\t\t\t\t\t\tversion: \"1.0.0\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst favIconRoute: IRestRoute<INoContentRequest, IServerFavIconResponse> = {\n\t\toperationId: \"serverFavIcon\",\n\t\tsummary: \"Get the favicon for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/favicon.ico`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverFavIcon(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerFavIconResponse\",\n\t\t\t\tmimeType: \"image/x-icon\"\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst livezRoute: IRestRoute<INoContentRequest, IServerLivezResponse> = {\n\t\toperationId: \"serverLivez\",\n\t\tsummary: \"Get the livez status for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/livez`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverLivez(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerLivezResponse\",\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"alive\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"livezResponseFailure\",\n\t\t\t\t\t\tdescription: \"The response for the liveness request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"dead\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst readyzRoute: IRestRoute<INoContentRequest, IServerReadyzResponse> = {\n\t\toperationId: \"serverReadyz\",\n\t\tsummary: \"Get the readyz status for the server\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/readyz`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverReadyz(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerReadyzResponse\",\n\t\t\t\tmimeType: MimeTypes.PlainText,\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"readyzResponseOK\",\n\t\t\t\t\t\tdescription: \"The response for the readiness request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"ready\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"readyzResponseFailure\",\n\t\t\t\t\t\tdescription: \"The response for the readiness request with errors.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: \"not ready\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\tconst specRoute: IRestRoute<INoContentRequest, IServerSpecResponse> = {\n\t\toperationId: \"serverSpec\",\n\t\tsummary: \"Get the OpenAPI specification for the endpoints\",\n\t\ttag: tagsInformation[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/spec`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tserverSpec(httpRequestContext, componentName, request),\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IServerSpecResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"specResponse\",\n\t\t\t\t\t\tdescription: \"The response for the spec request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\topenapi: \"3.1.0\",\n\t\t\t\t\t\t\t\tinfo: {},\n\t\t\t\t\t\t\t\tpaths: {}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\treturn [rootRoute, favIconRoute, informationRoute, livezRoute, readyzRoute, specRoute];\n}\n\n/**\n * Get the root for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverRoot(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerRootResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: await component.root()\n\t};\n}\n\n/**\n * Get the information for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverInfo(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerInfoResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\tbody: await component.info()\n\t};\n}\n\n/**\n * Get the livez for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverLivez(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerLivezResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: (await component.livez()).status\n\t};\n}\n\n/**\n * Get the readyz for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverReadyz(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerReadyzResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\treturn {\n\t\theaders: {\n\t\t\t[HeaderTypes.ContentType]: MimeTypes.PlainText\n\t\t},\n\t\tbody: (await component.readyz()).status\n\t};\n}\n\n/**\n * Get the favicon for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverFavIcon(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerFavIconResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst favIcon = await component.favicon();\n\n\tif (Is.uint8Array(favIcon)) {\n\t\treturn {\n\t\t\theaders: {\n\t\t\t\t[HeaderTypes.ContentType]: \"image/x-icon\"\n\t\t\t},\n\t\t\tbody: favIcon\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n\n/**\n * Get the spec for the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function serverSpec(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IServerSpecResponse> {\n\tconst component = ComponentFactory.get<IInformationComponent>(componentName);\n\tconst spec = await component.spec();\n\n\tif (Is.objectValue(spec)) {\n\t\treturn {\n\t\t\tbody: spec\n\t\t};\n\t}\n\treturn {\n\t\tstatusCode: HttpStatusCode.notFound\n\t};\n}\n"]}
@@ -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 { IInformationComponent, IServerInfo } from \"@3sixty/api-models\";\nimport { Factory, Guards, Is } from \"@3sixty/core\";\nimport { nameof } from \"@3sixty/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 engineCoreFactory = Factory.getFactory(\"engine-core\");\n\n\t\tif (engineCoreFactory) {\n\t\t\t// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.\n\t\t\tconst engine = engineCoreFactory.getIfExists<{ isStarted: () => boolean }>(\"engine\");\n\n\t\t\tif (engine?.isStarted()) {\n\t\t\t\treturn { status: \"ready\" };\n\t\t\t}\n\t\t}\n\n\t\treturn { status: \"not ready\" };\n\t}\n}\n"]}
1
+ {"version":3,"file":"informationService.js","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 { IInformationComponent, IServerInfo } from \"@3sixty/api-models\";\nimport { Factory, Guards, Is } from \"@3sixty/core\";\n\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 = \"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, \"options\", options);\n\t\tGuards.object(InformationService.CLASS_NAME, \"options.config\", options.config);\n\t\tGuards.object(\n\t\t\tInformationService.CLASS_NAME,\n\t\t\t\"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 engineCoreFactory = Factory.getFactory(\"engine-core\");\n\n\t\tif (engineCoreFactory) {\n\t\t\t// Use a replica of the IEngineCore interface to avoid a circular dependency on the engine-core package.\n\t\t\tconst engine = engineCoreFactory.getIfExists<{ isStarted: () => boolean }>(\"engine\");\n\n\t\t\tif (engine?.isStarted()) {\n\t\t\t\treturn { status: \"ready\" };\n\t\t\t}\n\t\t}\n\n\t\treturn { status: \"not ready\" };\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.\n\t * @default 60000\n\t */\n\thealthCheckInterval?: number;\n\n\t/**\n\t * The interval for running the application health lifecycle (init, application, teardown).\n\t * @default 300000\n\t */\n\thealthCheckApplicationInterval?: 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\t/**\n\t * Whether to include stack traces in health check error details.\n\t * @default false\n\t */\n\tincludeErrorStack?: boolean;\n\n\t/**\n\t * The URL of the module to use for the application health background task.\n\t * If not provided, the default healthApplicationTask module will be used.\n\t */\n\toverrideApplicationHealthTaskHandler?: string;\n\n\t/**\n\t * The component types to exclude from the engine clone used by the application health\n\t * background task. Each entry is a regular expression matched against the engine config\n\t * type keys, for example \"^rightsManagement\" or \"messaging.*Connector\".\n\t */\n\texcludeCloneComponents?: string[];\n}\n"]}
1
+ {"version":3,"file":"IHealthServiceConfig.js","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.\n\t * @default 60000\n\t */\n\thealthCheckInterval?: number;\n\n\t/**\n\t * The interval for running the application health lifecycle (init, application, teardown).\n\t * @default 300000\n\t */\n\thealthCheckApplicationInterval?: 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\t/**\n\t * Whether to include stack traces in health check error details.\n\t * @default false\n\t */\n\tincludeErrorStack?: boolean;\n\n\t/**\n\t * The URL of the module to use for the application health background task.\n\t * If not provided, the default healthApplicationTask module will be used.\n\t */\n\toverrideApplicationHealthTaskHandler?: string;\n\n\t/**\n\t * The component types to exclude from the engine clone used by the application health\n\t * background task. Each entry is a regular expression matched against the engine config\n\t * type keys, for example \"^rightsManagement\" or \"messaging.*Connector\".\n\t */\n\texcludeCloneComponents?: string[];\n}\n"]}
@@ -1 +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 background task component type to use for running application health checks.\n\t * @default background-task\n\t */\n\tbackgroundTaskComponentType?: string;\n\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig?: IHealthServiceConfig;\n}\n"]}
1
+ {"version":3,"file":"IHealthServiceConstructorOptions.js","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 background task component type to use for running application health checks.\n\t * @default background-task\n\t */\n\tbackgroundTaskComponentType?: string;\n\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig?: IHealthServiceConfig;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IInformationServiceConfig.js","sourceRoot":"","sources":["../../../src/models/IInformationServiceConfig.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IServerInfo } from \"@3sixty/api-models\";\n\n/**\n * Configuration for the information service.\n */\nexport interface IInformationServiceConfig {\n\t/**\n\t * The server information.\n\t */\n\tserverInfo: IServerInfo;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t */\n\topenApiSpecPath?: string;\n\n\t/**\n\t * The path to the favicon.\n\t */\n\tfavIconPath?: string;\n}\n"]}
1
+ {"version":3,"file":"IInformationServiceConfig.js","sources":["../../../src/models/IInformationServiceConfig.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IServerInfo } from \"@3sixty/api-models\";\n\n/**\n * Configuration for the information service.\n */\nexport interface IInformationServiceConfig {\n\t/**\n\t * The server information.\n\t */\n\tserverInfo: IServerInfo;\n\n\t/**\n\t * The path to the OpenAPI Spec.\n\t */\n\topenApiSpecPath?: string;\n\n\t/**\n\t * The path to the favicon.\n\t */\n\tfavIconPath?: string;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IInformationServiceConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IInformationServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IInformationServiceConfig } from \"./IInformationServiceConfig.js\";\n\n/**\n * Options for the InformationService constructor.\n */\nexport interface IInformationServiceConstructorOptions {\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig: IInformationServiceConfig;\n}\n"]}
1
+ {"version":3,"file":"IInformationServiceConstructorOptions.js","sources":["../../../src/models/IInformationServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IInformationServiceConfig } from \"./IInformationServiceConfig.js\";\n\n/**\n * Options for the InformationService constructor.\n */\nexport interface IInformationServiceConstructorOptions {\n\t/**\n\t * The configuration for the service.\n\t */\n\tconfig: IInformationServiceConfig;\n}\n"]}
@@ -1 +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"]}
1
+ {"version":3,"file":"IPlatformServiceConfig.js","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"]}
@@ -1 +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"]}
1
+ {"version":3,"file":"IPlatformServiceConstructorOptions.js","sources":["../../../src/models/IPlatformServiceConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// Copyright 2026 3Sixty Trade Ltd.\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 * The logging component type to use for error logging.\n\t */\n\tloggingComponentType?: string;\n\n\t/**\n\t * Configuration for the service.\n\t */\n\tconfig?: IPlatformServiceConfig;\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  // Copyright 2026 IOTA Stiftung.
2
+ // Copyright 2026 3Sixty Trade Ltd.
2
3
  // SPDX-License-Identifier: Apache-2.0.
3
4
  import { HttpContextIdKeys, HttpUrlHelper } from "@3sixty/api-models";
4
5
  import { ContextIdKeys, ContextIdStore } from "@3sixty/context";
5
- import { Guards, Is } from "@3sixty/core";
6
+ import { BaseError, ComponentFactory, Guards, Is } from "@3sixty/core";
6
7
  import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models";
7
8
  /**
8
9
  * Service for performing platform operations.
@@ -27,6 +28,16 @@ export class PlatformService {
27
28
  * @internal
28
29
  */
29
30
  _isMultiTenant;
31
+ /**
32
+ * The logging component type for error logging.
33
+ * @internal
34
+ */
35
+ _loggingComponentType;
36
+ /**
37
+ * Registered tenant event callbacks.
38
+ * @internal
39
+ */
40
+ _tenantEventCallbacks;
30
41
  /**
31
42
  * Create a new instance of PlatformService.
32
43
  * @param options The options for the connector.
@@ -34,6 +45,8 @@ export class PlatformService {
34
45
  constructor(options) {
35
46
  this._tenantEntityStorageConnectorType = options?.tenantEntityStorageType ?? "tenant";
36
47
  this._isMultiTenant = options?.config?.isMultiTenant ?? false;
48
+ this._loggingComponentType = options?.loggingComponentType;
49
+ this._tenantEventCallbacks = new Map();
37
50
  }
38
51
  /**
39
52
  * Returns the class name of the component.
@@ -94,7 +107,7 @@ export class PlatformService {
94
107
  if (this._isMultiTenant) {
95
108
  const tenantEntityStorageConnector = this.ensureEntityStorageConnector();
96
109
  if (!Is.empty(tenantEntityStorageConnector)) {
97
- // Post-#203 organization routing: when the URL carries an ?organization=<org-did>
110
+ // When the URL carries an ?organization=<org-did>
98
111
  // query param, that param - not the origin - identifies the target tenant. A single
99
112
  // node hosts many tenants behind one shared origin, so an origin match alone cannot
100
113
  // distinguish them and would incorrectly return the caller's own context. Resolve the
@@ -144,6 +157,45 @@ export class PlatformService {
144
157
  }
145
158
  }
146
159
  }
160
+ /**
161
+ * Registers a callback to be invoked when a tenant event occurs.
162
+ * @param callbackId A unique identifier for the callback.
163
+ * @param callback The callback to invoke when a tenant event occurs.
164
+ */
165
+ registerTenantEventCallback(callbackId, callback) {
166
+ this._tenantEventCallbacks.set(callbackId, callback);
167
+ }
168
+ /**
169
+ * Unregisters a previously registered tenant event callback.
170
+ * @param callbackId The identifier of the callback to unregister.
171
+ */
172
+ unregisterTenantEventCallback(callbackId) {
173
+ this._tenantEventCallbacks.delete(callbackId);
174
+ }
175
+ /**
176
+ * Fires all registered tenant event callbacks.
177
+ * @param tenantId The ID of the tenant for which the event occurred.
178
+ * @param eventType The type of event that occurred.
179
+ * @returns A promise that resolves when all callbacks have been invoked.
180
+ */
181
+ async fireTenantEvent(tenantId, eventType) {
182
+ for (const callback of this._tenantEventCallbacks.values()) {
183
+ try {
184
+ // Run with an empty context so callbacks do not inherit the caller's tenant
185
+ await ContextIdStore.run({}, async () => callback(tenantId, eventType));
186
+ }
187
+ catch (error) {
188
+ const logging = ComponentFactory.getIfExists(this._loggingComponentType);
189
+ await logging?.log({
190
+ level: "error",
191
+ source: PlatformService.CLASS_NAME,
192
+ message: "tenantEventCallbackFailed",
193
+ data: { tenantId, eventType },
194
+ error: BaseError.fromError(error)
195
+ });
196
+ }
197
+ }
198
+ }
147
199
  /**
148
200
  * Ensure that the entity storage connector is initialized and return it.
149
201
  * @returns The entity storage connector.
@@ -1 +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 \"@3sixty/api-models\";\nimport { ContextIdKeys, ContextIdStore, type IContextIds } from \"@3sixty/context\";\nimport { Guards, Is } from \"@3sixty/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\nimport { nameof } from \"@3sixty/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, returning false will stop any further iterations.\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<undefined | boolean> | 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\t\t\t\tlet stopped = false;\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\tstopped = (await method()) === false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (stopped) {\n\t\t\t\t\t\t\tbreak;\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 (!stopped && 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[HttpContextIdKeys.PublicOrigin]: Is.stringValue(orgTenant.publicOrigin)\n\t\t\t\t\t\t\t\t? orgTenant.publicOrigin\n\t\t\t\t\t\t\t\t: contextIds?.[HttpContextIdKeys.PublicOrigin]\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[HttpContextIdKeys.PublicOrigin]: Is.stringValue(tenant.publicOrigin)\n\t\t\t\t\t\t\t? tenant.publicOrigin\n\t\t\t\t\t\t\t: contextIds?.[HttpContextIdKeys.PublicOrigin]\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
+ {"version":3,"file":"platformService.js","sources":["../../src/platformService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// Copyright 2026 3Sixty Trade Ltd.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpContextIdKeys,\n\tHttpUrlHelper,\n\ttype IPlatformComponent,\n\ttype ITenant,\n\ttype TenantEventCallback,\n\ttype TenantEventType\n} from \"@3sixty/api-models\";\nimport { ContextIdKeys, ContextIdStore, type IContextIds } from \"@3sixty/context\";\nimport { BaseError, ComponentFactory, Guards, Is } from \"@3sixty/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@3sixty/entity-storage-models\";\nimport type { ILoggingComponent } from \"@3sixty/logging-models\";\n\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 = \"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 * The logging component type for error logging.\n\t * @internal\n\t */\n\tprivate readonly _loggingComponentType?: string;\n\n\t/**\n\t * Registered tenant event callbacks.\n\t * @internal\n\t */\n\tprivate readonly _tenantEventCallbacks: Map<string, TenantEventCallback>;\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\tthis._loggingComponentType = options?.loggingComponentType;\n\t\tthis._tenantEventCallbacks = new Map();\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, returning false will stop any further iterations.\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<undefined | boolean> | 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\t\t\t\tlet stopped = false;\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\tstopped = (await method()) === false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (stopped) {\n\t\t\t\t\t\t\tbreak;\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 (!stopped && 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, \"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// 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[HttpContextIdKeys.PublicOrigin]: Is.stringValue(orgTenant.publicOrigin)\n\t\t\t\t\t\t\t\t? orgTenant.publicOrigin\n\t\t\t\t\t\t\t\t: contextIds?.[HttpContextIdKeys.PublicOrigin]\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[HttpContextIdKeys.PublicOrigin]: Is.stringValue(tenant.publicOrigin)\n\t\t\t\t\t\t\t? tenant.publicOrigin\n\t\t\t\t\t\t\t: contextIds?.[HttpContextIdKeys.PublicOrigin]\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 * Registers a callback to be invoked when a tenant event occurs.\n\t * @param callbackId A unique identifier for the callback.\n\t * @param callback The callback to invoke when a tenant event occurs.\n\t */\n\tpublic registerTenantEventCallback(callbackId: string, callback: TenantEventCallback): void {\n\t\tthis._tenantEventCallbacks.set(callbackId, callback);\n\t}\n\n\t/**\n\t * Unregisters a previously registered tenant event callback.\n\t * @param callbackId The identifier of the callback to unregister.\n\t */\n\tpublic unregisterTenantEventCallback(callbackId: string): void {\n\t\tthis._tenantEventCallbacks.delete(callbackId);\n\t}\n\n\t/**\n\t * Fires all registered tenant event callbacks.\n\t * @param tenantId The ID of the tenant for which the event occurred.\n\t * @param eventType The type of event that occurred.\n\t * @returns A promise that resolves when all callbacks have been invoked.\n\t */\n\tpublic async fireTenantEvent(tenantId: string, eventType: TenantEventType): Promise<void> {\n\t\tfor (const callback of this._tenantEventCallbacks.values()) {\n\t\t\ttry {\n\t\t\t\t// Run with an empty context so callbacks do not inherit the caller's tenant\n\t\t\t\tawait ContextIdStore.run({}, async () => callback(tenantId, eventType));\n\t\t\t} catch (error) {\n\t\t\t\tconst logging = ComponentFactory.getIfExists<ILoggingComponent>(this._loggingComponentType);\n\t\t\t\tawait logging?.log({\n\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\tsource: PlatformService.CLASS_NAME,\n\t\t\t\t\tmessage: \"tenantEventCallbackFailed\",\n\t\t\t\t\tdata: { tenantId, eventType },\n\t\t\t\t\terror: BaseError.fromError(error)\n\t\t\t\t});\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 +1 @@
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 \"@3sixty/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"]}
1
+ {"version":3,"file":"restEntryPoints.js","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 \"@3sixty/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"]}
@@ -8,6 +8,10 @@ export interface IPlatformServiceConstructorOptions {
8
8
  * @default tenant
9
9
  */
10
10
  tenantEntityStorageType?: string;
11
+ /**
12
+ * The logging component type to use for error logging.
13
+ */
14
+ loggingComponentType?: string;
11
15
  /**
12
16
  * Configuration for the service.
13
17
  */
@@ -1,4 +1,4 @@
1
- import { type IPlatformComponent } from "@3sixty/api-models";
1
+ import { type IPlatformComponent, type TenantEventCallback, type TenantEventType } from "@3sixty/api-models";
2
2
  import { type IContextIds } from "@3sixty/context";
3
3
  import type { IPlatformServiceConstructorOptions } from "./models/IPlatformServiceConstructorOptions.js";
4
4
  /**
@@ -36,4 +36,22 @@ export declare class PlatformService implements IPlatformComponent {
36
36
  * @returns A promise that resolves to the context IDs if the URL is a local origin, undefined otherwise.
37
37
  */
38
38
  getLocalOriginContext(url: string): Promise<IContextIds | undefined>;
39
+ /**
40
+ * Registers a callback to be invoked when a tenant event occurs.
41
+ * @param callbackId A unique identifier for the callback.
42
+ * @param callback The callback to invoke when a tenant event occurs.
43
+ */
44
+ registerTenantEventCallback(callbackId: string, callback: TenantEventCallback): void;
45
+ /**
46
+ * Unregisters a previously registered tenant event callback.
47
+ * @param callbackId The identifier of the callback to unregister.
48
+ */
49
+ unregisterTenantEventCallback(callbackId: string): void;
50
+ /**
51
+ * Fires all registered tenant event callbacks.
52
+ * @param tenantId The ID of the tenant for which the event occurred.
53
+ * @param eventType The type of event that occurred.
54
+ * @returns A promise that resolves when all callbacks have been invoked.
55
+ */
56
+ fireTenantEvent(tenantId: string, eventType: TenantEventType): Promise<void>;
39
57
  }
package/docs/changelog.md CHANGED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## [0.20.0-next.1](https://github.com/3sixtyglobal/api/compare/api-service-v0.20.0-next.0...api-service-v0.20.0-next.1) (2026-10-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * authorization ([#51](https://github.com/3sixtyglobal/api/issues/51)) ([023ef94](https://github.com/3sixtyglobal/api/commit/023ef94925114e88522cbe6f703d9b05a202bc6d))
9
+ * publish packages under the [@3sixty](https://github.com/3sixty) namespace ([91b1822](https://github.com/3sixtyglobal/api/commit/91b1822dba38f70692087ad4643572531d6634dc))
10
+ * updated ts compile ([d9a5c86](https://github.com/3sixtyglobal/api/commit/d9a5c86ec7dd1faf3045b5cd1e0d38a4b331313d))
11
+
12
+
13
+ ### Dependencies
14
+
15
+ * The following workspace dependencies were updated
16
+ * dependencies
17
+ * @3sixty/api-models bumped from 0.20.0-next.0 to 0.20.0-next.1
@@ -121,3 +121,89 @@ A promise that resolves to the context IDs if the URL is a local origin, undefin
121
121
  #### Implementation of
122
122
 
123
123
  `IPlatformComponent.getLocalOriginContext`
124
+
125
+ ***
126
+
127
+ ### registerTenantEventCallback() {#registertenanteventcallback}
128
+
129
+ > **registerTenantEventCallback**(`callbackId`, `callback`): `void`
130
+
131
+ Registers a callback to be invoked when a tenant event occurs.
132
+
133
+ #### Parameters
134
+
135
+ ##### callbackId
136
+
137
+ `string`
138
+
139
+ A unique identifier for the callback.
140
+
141
+ ##### callback
142
+
143
+ `TenantEventCallback`
144
+
145
+ The callback to invoke when a tenant event occurs.
146
+
147
+ #### Returns
148
+
149
+ `void`
150
+
151
+ #### Implementation of
152
+
153
+ `IPlatformComponent.registerTenantEventCallback`
154
+
155
+ ***
156
+
157
+ ### unregisterTenantEventCallback() {#unregistertenanteventcallback}
158
+
159
+ > **unregisterTenantEventCallback**(`callbackId`): `void`
160
+
161
+ Unregisters a previously registered tenant event callback.
162
+
163
+ #### Parameters
164
+
165
+ ##### callbackId
166
+
167
+ `string`
168
+
169
+ The identifier of the callback to unregister.
170
+
171
+ #### Returns
172
+
173
+ `void`
174
+
175
+ #### Implementation of
176
+
177
+ `IPlatformComponent.unregisterTenantEventCallback`
178
+
179
+ ***
180
+
181
+ ### fireTenantEvent() {#firetenantevent}
182
+
183
+ > **fireTenantEvent**(`tenantId`, `eventType`): `Promise`\<`void`\>
184
+
185
+ Fires all registered tenant event callbacks.
186
+
187
+ #### Parameters
188
+
189
+ ##### tenantId
190
+
191
+ `string`
192
+
193
+ The ID of the tenant for which the event occurred.
194
+
195
+ ##### eventType
196
+
197
+ `TenantEventType`
198
+
199
+ The type of event that occurred.
200
+
201
+ #### Returns
202
+
203
+ `Promise`\<`void`\>
204
+
205
+ A promise that resolves when all callbacks have been invoked.
206
+
207
+ #### Implementation of
208
+
209
+ `IPlatformComponent.fireTenantEvent`
@@ -18,6 +18,14 @@ tenant
18
18
 
19
19
  ***
20
20
 
21
+ ### loggingComponentType? {#loggingcomponenttype}
22
+
23
+ > `optional` **loggingComponentType?**: `string`
24
+
25
+ The logging component type to use for error logging.
26
+
27
+ ***
28
+
21
29
  ### config? {#config}
22
30
 
23
31
  > `optional` **config?**: [`IPlatformServiceConfig`](IPlatformServiceConfig.md)
package/locales/en.json CHANGED
@@ -4,6 +4,9 @@
4
4
  "componentHealthCheckFailed": "Health check failed for component \"{className}\"",
5
5
  "applicationHealthCheckTaskFailed": "Application health check background task failed"
6
6
  },
7
+ "platformService": {
8
+ "tenantEventCallbackFailed": "Tenant event callback failed for tenant \"{tenantId}\" and event \"{eventType}\""
9
+ },
7
10
  "applicationHealthTask": {
8
11
  "engineNotStarted": "Application health engine is not started"
9
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3sixty/api-service",
3
- "version": "0.20.0-next.0",
3
+ "version": "0.20.0-next.1",
4
4
  "description": "Information and hosting service implementations with generated REST route handlers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "node": ">=24.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@3sixty/api-models": "0.20.0-next.0",
17
+ "@3sixty/api-models": "0.20.0-next.1",
18
18
  "@3sixty/background-task-models": "next",
19
19
  "@3sixty/context": "next",
20
20
  "@3sixty/core": "next",