@twin.org/telemetry-service 0.9.3-next.1 → 0.9.3-next.3

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.
@@ -96,11 +96,23 @@ export class TelemetryService {
96
96
  * @param id The id of the metric.
97
97
  * @param value The value for the add operation.
98
98
  * @param customData The custom data for the add operation.
99
- * @returns The created metric value id.
99
+ * @returns The created metric value id. When fanned out per-tenant, this is the id from the last tenant written.
100
100
  */
101
101
  async addMetricValue(id, value, customData) {
102
102
  Guards.stringValue(TelemetryService.CLASS_NAME, "id", id);
103
103
  Guards.defined(TelemetryService.CLASS_NAME, "value", value);
104
+ // If we don't have a tenant context ID and the tenant component is multi-tenant,
105
+ // we consider the entry as per-tenant and run it in all the tenant context to ensure
106
+ // the value is recorded for each tenant.
107
+ const contextIds = (await ContextIdStore.getContextIds()) ?? {};
108
+ const perTenant = !Is.stringValue(contextIds[ContextIdKeys.Tenant]) && this._platformComponent.isMultiTenant();
109
+ if (perTenant) {
110
+ let valueId = "";
111
+ await this._platformComponent.execute(async () => {
112
+ valueId = await this._telemetryConnector.addMetricValue(id, value, customData);
113
+ });
114
+ return valueId;
115
+ }
104
116
  return this._telemetryConnector.addMetricValue(id, value, customData);
105
117
  }
106
118
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"telemetryService.js","sourceRoot":"","sources":["../../src/telemetryService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEnF,OAAO,EACN,yBAAyB,EAOzB,MAAM,4BAA4B,CAAC;AAGpC;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAC5B;;OAEG;IACI,MAAM,CAAU,UAAU,sBAAsC;IAEvE;;;OAGG;IACc,mBAAmB,CAAsB;IAE1D;;;OAGG;IACc,kBAAkB,CAAqB;IAExD;;;OAGG;IACH,YAAY,OAA6C;QACxD,IAAI,CAAC,mBAAmB,GAAG,yBAAyB,CAAC,GAAG,CACvD,OAAO,EAAE,sBAAsB,IAAI,WAAW,CAC9C,CAAC;QACF,IAAI,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,GAAG,CAC7C,OAAO,EAAE,qBAAqB,IAAI,UAAU,CAC5C,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,gBAAgB,CAAC,UAAU,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,YAAY,CAAC,MAAwB;QACjD,MAAM,CAAC,MAAM,CAAmB,gBAAgB,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QAErF,iFAAiF;QACjF,qFAAqF;QACrF,yCAAyC;QACzC,MAAM,UAAU,GAAG,CAAC,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,CAAC;QAChE,MAAM,SAAS,GACd,CAAC,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC;QAE9F,IAAI,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAChD,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,MAAM,CAAC,CAC7C,CAAC;QACH,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,SAAS,CAAC,EAAU;QAIhC,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAEhE,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC1F,IAAI,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,cAAc,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,cAAc,CAAC,EAAU,EAAE,OAAe;QACtD,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAE1E,MAAM,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,IAAI,CACxE,IAAI,CAAC,mBAAmB,CACxB,CAAC;QACF,IAAI,EAAE,CAAC,SAAS,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,mBAAmB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,YAAY,CAAC,MAAsC;QAC/D,MAAM,CAAC,MAAM,CAAmB,gBAAgB,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QACrF,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,eAAqB,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,cAAc,CAC1B,EAAU,EACV,KAAsC,EACtC,UAAuC;QAEvC,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,UAAU,WAAiB,KAAK,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,YAAY,CAAC,EAAU;QACnC,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,KAAK,CACjB,IAAiB,EACjB,MAAe,EACf,KAAc;QAYd,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAClF,IAAI,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;;;;OAUG;IACI,KAAK,CAAC,WAAW,CACvB,EAAU,EACV,SAAkB,EAClB,OAAgB,EAChB,MAAe,EACf,KAAc;QAed,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAEhE,MAAM,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC9F,IAAI,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAChE,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IPlatformComponent } from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { ComponentFactory, Guards, Is, NotImplementedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tTelemetryConnectorFactory,\n\ttype ITelemetryComponent,\n\ttype ITelemetryConnector,\n\ttype ITelemetryMetric,\n\ttype ITelemetryMetricValue,\n\ttype MetricCounterOperation,\n\ttype MetricType\n} from \"@twin.org/telemetry-models\";\nimport type { ITelemetryServiceConstructorOptions } from \"./models/ITelemetryServiceConstructorOptions.js\";\n\n/**\n * Service for performing telemetry operations to a connector.\n */\nexport class TelemetryService implements ITelemetryComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TelemetryService>();\n\n\t/**\n\t * Telemetry connector used by the service.\n\t * @internal\n\t */\n\tprivate readonly _telemetryConnector: ITelemetryConnector;\n\n\t/**\n\t * Platform component.\n\t * @internal\n\t */\n\tprivate readonly _platformComponent: IPlatformComponent;\n\n\t/**\n\t * Create a new instance of TelemetryService.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options?: ITelemetryServiceConstructorOptions) {\n\t\tthis._telemetryConnector = TelemetryConnectorFactory.get(\n\t\t\toptions?.telemetryConnectorType ?? \"telemetry\"\n\t\t);\n\t\tthis._platformComponent = ComponentFactory.get<IPlatformComponent>(\n\t\t\toptions?.platformComponentType ?? \"platform\"\n\t\t);\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 TelemetryService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Create a new metric.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been created.\n\t */\n\tpublic async createMetric(metric: ITelemetryMetric): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(TelemetryService.CLASS_NAME, nameof(metric), metric);\n\n\t\t// If we don't have a tenant context ID and the tenant component is multi-tenant,\n\t\t// we consider the entry as per-tenant and run it in all the tenant context to ensure\n\t\t// the metric is created for each tenant.\n\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\t\tconst perTenant =\n\t\t\t!Is.stringValue(contextIds[ContextIdKeys.Tenant]) && this._platformComponent.isMultiTenant();\n\n\t\tif (perTenant) {\n\t\t\tawait this._platformComponent.execute(async () =>\n\t\t\t\tthis._telemetryConnector.createMetric(metric)\n\t\t\t);\n\t\t} else {\n\t\t\tawait this._telemetryConnector.createMetric(metric);\n\t\t}\n\t}\n\n\t/**\n\t * Get the metric details and it's most recent value.\n\t * @param id The metric id.\n\t * @returns The metric details and it's most recent value.\n\t */\n\tpublic async getMetric(id: string): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tvalue?: ITelemetryMetricValue;\n\t}> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\n\t\tconst boundGetMetric = this._telemetryConnector.getMetric?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundGetMetric)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"getMetric\");\n\t\t}\n\t\treturn boundGetMetric(id);\n\t}\n\n\t/**\n\t * Get a specific metric value by its id.\n\t * @param id The id of the metric.\n\t * @param valueId The id of the metric value.\n\t * @returns The metric value.\n\t */\n\tpublic async getMetricValue(id: string, valueId: string): Promise<ITelemetryMetricValue> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(valueId), valueId);\n\n\t\tconst boundGetMetricValue = this._telemetryConnector.getMetricValue?.bind(\n\t\t\tthis._telemetryConnector\n\t\t);\n\t\tif (Is.undefined(boundGetMetricValue)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"getMetricValue\");\n\t\t}\n\t\treturn boundGetMetricValue(id, valueId);\n\t}\n\n\t/**\n\t * Update metric.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been updated.\n\t */\n\tpublic async updateMetric(metric: Omit<ITelemetryMetric, \"type\">): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(TelemetryService.CLASS_NAME, nameof(metric), metric);\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(metric.id), metric.id);\n\t\treturn this._telemetryConnector.updateMetric(metric);\n\t}\n\n\t/**\n\t * Add a metric value.\n\t * @param id The id of the metric.\n\t * @param value The value for the add operation.\n\t * @param customData The custom data for the add operation.\n\t * @returns The created metric value id.\n\t */\n\tpublic async addMetricValue(\n\t\tid: string,\n\t\tvalue: MetricCounterOperation | number,\n\t\tcustomData?: { [key: string]: unknown }\n\t): Promise<string> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\tGuards.defined(TelemetryService.CLASS_NAME, nameof(value), value);\n\t\treturn this._telemetryConnector.addMetricValue(id, value, customData);\n\t}\n\n\t/**\n\t * Remove metric.\n\t * @param id The id of the metric.\n\t * @returns A promise that resolves when the metric and all its values have been removed.\n\t */\n\tpublic async removeMetric(id: string): Promise<void> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\treturn this._telemetryConnector.removeMetric(id);\n\t}\n\n\t/**\n\t * Query the metrics.\n\t * @param type The type of the metric.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit Limit the number of entities to return.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t * @throws NotImplementedError if the implementation does not support retrieval.\n\t */\n\tpublic async query(\n\t\ttype?: MetricType,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The metrics.\n\t\t */\n\t\tentities: ITelemetryMetric[];\n\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more values.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\tconst boundQuery = this._telemetryConnector.query?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundQuery)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"query\");\n\t\t}\n\t\treturn boundQuery(type, cursor, limit);\n\t}\n\n\t/**\n\t * Query the metric values.\n\t * @param id The id of the metric.\n\t * @param timeStart The inclusive time as the start of the metric entries.\n\t * @param timeEnd The inclusive time as the end of the metric entries.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit Limit the number of entities to return.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t * @throws NotImplementedError if the implementation does not support retrieval.\n\t */\n\tpublic async queryValues(\n\t\tid: string,\n\t\ttimeStart?: number,\n\t\ttimeEnd?: number,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The metric details.\n\t\t */\n\t\tmetric: ITelemetryMetric;\n\t\t/**\n\t\t * The values for the metric.\n\t\t */\n\t\tentities: ITelemetryMetricValue[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more values.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\n\t\tconst boundQueryValues = this._telemetryConnector.queryValues?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundQueryValues)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"queryValues\");\n\t\t}\n\t\treturn boundQueryValues(id, timeStart, timeEnd, cursor, limit);\n\t}\n}\n"]}
1
+ {"version":3,"file":"telemetryService.js","sourceRoot":"","sources":["../../src/telemetryService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IPlatformComponent } from \"@twin.org/api-models\";\nimport { ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { ComponentFactory, Guards, Is, NotImplementedError } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tTelemetryConnectorFactory,\n\ttype ITelemetryComponent,\n\ttype ITelemetryConnector,\n\ttype ITelemetryMetric,\n\ttype ITelemetryMetricValue,\n\ttype MetricCounterOperation,\n\ttype MetricType\n} from \"@twin.org/telemetry-models\";\nimport type { ITelemetryServiceConstructorOptions } from \"./models/ITelemetryServiceConstructorOptions.js\";\n\n/**\n * Service for performing telemetry operations to a connector.\n */\nexport class TelemetryService implements ITelemetryComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TelemetryService>();\n\n\t/**\n\t * Telemetry connector used by the service.\n\t * @internal\n\t */\n\tprivate readonly _telemetryConnector: ITelemetryConnector;\n\n\t/**\n\t * Platform component.\n\t * @internal\n\t */\n\tprivate readonly _platformComponent: IPlatformComponent;\n\n\t/**\n\t * Create a new instance of TelemetryService.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options?: ITelemetryServiceConstructorOptions) {\n\t\tthis._telemetryConnector = TelemetryConnectorFactory.get(\n\t\t\toptions?.telemetryConnectorType ?? \"telemetry\"\n\t\t);\n\t\tthis._platformComponent = ComponentFactory.get<IPlatformComponent>(\n\t\t\toptions?.platformComponentType ?? \"platform\"\n\t\t);\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 TelemetryService.CLASS_NAME;\n\t}\n\n\t/**\n\t * Create a new metric.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been created.\n\t */\n\tpublic async createMetric(metric: ITelemetryMetric): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(TelemetryService.CLASS_NAME, nameof(metric), metric);\n\n\t\t// If we don't have a tenant context ID and the tenant component is multi-tenant,\n\t\t// we consider the entry as per-tenant and run it in all the tenant context to ensure\n\t\t// the metric is created for each tenant.\n\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\t\tconst perTenant =\n\t\t\t!Is.stringValue(contextIds[ContextIdKeys.Tenant]) && this._platformComponent.isMultiTenant();\n\n\t\tif (perTenant) {\n\t\t\tawait this._platformComponent.execute(async () =>\n\t\t\t\tthis._telemetryConnector.createMetric(metric)\n\t\t\t);\n\t\t} else {\n\t\t\tawait this._telemetryConnector.createMetric(metric);\n\t\t}\n\t}\n\n\t/**\n\t * Get the metric details and it's most recent value.\n\t * @param id The metric id.\n\t * @returns The metric details and it's most recent value.\n\t */\n\tpublic async getMetric(id: string): Promise<{\n\t\tmetric: ITelemetryMetric;\n\t\tvalue?: ITelemetryMetricValue;\n\t}> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\n\t\tconst boundGetMetric = this._telemetryConnector.getMetric?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundGetMetric)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"getMetric\");\n\t\t}\n\t\treturn boundGetMetric(id);\n\t}\n\n\t/**\n\t * Get a specific metric value by its id.\n\t * @param id The id of the metric.\n\t * @param valueId The id of the metric value.\n\t * @returns The metric value.\n\t */\n\tpublic async getMetricValue(id: string, valueId: string): Promise<ITelemetryMetricValue> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(valueId), valueId);\n\n\t\tconst boundGetMetricValue = this._telemetryConnector.getMetricValue?.bind(\n\t\t\tthis._telemetryConnector\n\t\t);\n\t\tif (Is.undefined(boundGetMetricValue)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"getMetricValue\");\n\t\t}\n\t\treturn boundGetMetricValue(id, valueId);\n\t}\n\n\t/**\n\t * Update metric.\n\t * @param metric The metric details.\n\t * @returns A promise that resolves when the metric has been updated.\n\t */\n\tpublic async updateMetric(metric: Omit<ITelemetryMetric, \"type\">): Promise<void> {\n\t\tGuards.object<ITelemetryMetric>(TelemetryService.CLASS_NAME, nameof(metric), metric);\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(metric.id), metric.id);\n\t\treturn this._telemetryConnector.updateMetric(metric);\n\t}\n\n\t/**\n\t * Add a metric value.\n\t * @param id The id of the metric.\n\t * @param value The value for the add operation.\n\t * @param customData The custom data for the add operation.\n\t * @returns The created metric value id. When fanned out per-tenant, this is the id from the last tenant written.\n\t */\n\tpublic async addMetricValue(\n\t\tid: string,\n\t\tvalue: MetricCounterOperation | number,\n\t\tcustomData?: { [key: string]: unknown }\n\t): Promise<string> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\tGuards.defined(TelemetryService.CLASS_NAME, nameof(value), value);\n\n\t\t// If we don't have a tenant context ID and the tenant component is multi-tenant,\n\t\t// we consider the entry as per-tenant and run it in all the tenant context to ensure\n\t\t// the value is recorded for each tenant.\n\t\tconst contextIds = (await ContextIdStore.getContextIds()) ?? {};\n\t\tconst perTenant =\n\t\t\t!Is.stringValue(contextIds[ContextIdKeys.Tenant]) && this._platformComponent.isMultiTenant();\n\n\t\tif (perTenant) {\n\t\t\tlet valueId = \"\";\n\t\t\tawait this._platformComponent.execute(async () => {\n\t\t\t\tvalueId = await this._telemetryConnector.addMetricValue(id, value, customData);\n\t\t\t});\n\t\t\treturn valueId;\n\t\t}\n\n\t\treturn this._telemetryConnector.addMetricValue(id, value, customData);\n\t}\n\n\t/**\n\t * Remove metric.\n\t * @param id The id of the metric.\n\t * @returns A promise that resolves when the metric and all its values have been removed.\n\t */\n\tpublic async removeMetric(id: string): Promise<void> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\t\treturn this._telemetryConnector.removeMetric(id);\n\t}\n\n\t/**\n\t * Query the metrics.\n\t * @param type The type of the metric.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit Limit the number of entities to return.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t * @throws NotImplementedError if the implementation does not support retrieval.\n\t */\n\tpublic async query(\n\t\ttype?: MetricType,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The metrics.\n\t\t */\n\t\tentities: ITelemetryMetric[];\n\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more values.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\tconst boundQuery = this._telemetryConnector.query?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundQuery)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"query\");\n\t\t}\n\t\treturn boundQuery(type, cursor, limit);\n\t}\n\n\t/**\n\t * Query the metric values.\n\t * @param id The id of the metric.\n\t * @param timeStart The inclusive time as the start of the metric entries.\n\t * @param timeEnd The inclusive time as the end of the metric entries.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit Limit the number of entities to return.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t * @throws NotImplementedError if the implementation does not support retrieval.\n\t */\n\tpublic async queryValues(\n\t\tid: string,\n\t\ttimeStart?: number,\n\t\ttimeEnd?: number,\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The metric details.\n\t\t */\n\t\tmetric: ITelemetryMetric;\n\t\t/**\n\t\t * The values for the metric.\n\t\t */\n\t\tentities: ITelemetryMetricValue[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more values.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\tGuards.stringValue(TelemetryService.CLASS_NAME, nameof(id), id);\n\n\t\tconst boundQueryValues = this._telemetryConnector.queryValues?.bind(this._telemetryConnector);\n\t\tif (Is.undefined(boundQueryValues)) {\n\t\t\tthrow new NotImplementedError(TelemetryService.CLASS_NAME, \"queryValues\");\n\t\t}\n\t\treturn boundQueryValues(id, timeStart, timeEnd, cursor, limit);\n\t}\n}\n"]}
@@ -51,7 +51,7 @@ export declare class TelemetryService implements ITelemetryComponent {
51
51
  * @param id The id of the metric.
52
52
  * @param value The value for the add operation.
53
53
  * @param customData The custom data for the add operation.
54
- * @returns The created metric value id.
54
+ * @returns The created metric value id. When fanned out per-tenant, this is the id from the last tenant written.
55
55
  */
56
56
  addMetricValue(id: string, value: MetricCounterOperation | number, customData?: {
57
57
  [key: string]: unknown;
package/docs/changelog.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.3-next.3](https://github.com/iotaledger/twin-telemetry/compare/telemetry-service-v0.9.3-next.2...telemetry-service-v0.9.3-next.3) (2026-09-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * metric value optimisation ([#114](https://github.com/iotaledger/twin-telemetry/issues/114)) ([8712b8d](https://github.com/iotaledger/twin-telemetry/commit/8712b8d8f0f0c78921c7b93979b9afffb45c5d79))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/telemetry-models bumped from 0.9.3-next.2 to 0.9.3-next.3
16
+
17
+ ## [0.9.3-next.2](https://github.com/iotaledger/twin-telemetry/compare/telemetry-service-v0.9.3-next.1...telemetry-service-v0.9.3-next.2) (2026-09-01)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * fan out tenant-less metric values per tenant and surface swallowed recording failures ([#110](https://github.com/iotaledger/twin-telemetry/issues/110)) ([cc87cd9](https://github.com/iotaledger/twin-telemetry/commit/cc87cd9b5d17269419c40abfed7070efb8cf8454))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/telemetry-models bumped from 0.9.3-next.1 to 0.9.3-next.2
30
+
3
31
  ## [0.9.3-next.1](https://github.com/iotaledger/twin-telemetry/compare/telemetry-service-v0.9.3-next.0...telemetry-service-v0.9.3-next.1) (2026-08-31)
4
32
 
5
33
 
@@ -192,7 +192,7 @@ The custom data for the add operation.
192
192
 
193
193
  `Promise`\<`string`\>
194
194
 
195
- The created metric value id.
195
+ The created metric value id. When fanned out per-tenant, this is the id from the last tenant written.
196
196
 
197
197
  #### Implementation of
198
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/telemetry-service",
3
- "version": "0.9.3-next.1",
3
+ "version": "0.9.3-next.3",
4
4
  "description": "Telemetry service implementation with REST entry points.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
  "@twin.org/entity": "next",
21
21
  "@twin.org/logging-models": "next",
22
22
  "@twin.org/nameof": "next",
23
- "@twin.org/telemetry-models": "0.9.3-next.1",
23
+ "@twin.org/telemetry-models": "0.9.3-next.3",
24
24
  "@twin.org/web": "next"
25
25
  },
26
26
  "main": "./dist/es/index.js",