From 3382648c84f74398c5e6e50f3534a7f1a7f9a171 Mon Sep 17 00:00:00 2001 From: svozza Date: Thu, 23 Oct 2025 11:14:41 +0100 Subject: [PATCH 1/3] bug(logger): Revert "feat(metrics): use async local storage for metrics (#4663)" This reverts commit 3886af3a275020ddae8d67cc9c5efaa74464db9c. --- package-lock.json | 142 ++++---- packages/metrics/package.json | 3 +- packages/metrics/src/DimensionsStore.ts | 104 ------ packages/metrics/src/MetadataStore.ts | 50 --- packages/metrics/src/Metrics.ts | 233 ++++++++----- packages/metrics/src/MetricsStore.ts | 157 --------- .../unit/concurrency/dimensionsStore.test.ts | 156 --------- .../unit/concurrency/metadataStore.test.ts | 193 ----------- .../tests/unit/concurrency/metrics.test.ts | 134 -------- .../unit/concurrency/metricsStore.test.ts | 319 ------------------ packages/metrics/tests/unit/helpers.ts | 134 -------- 11 files changed, 220 insertions(+), 1405 deletions(-) delete mode 100644 packages/metrics/src/DimensionsStore.ts delete mode 100644 packages/metrics/src/MetadataStore.ts delete mode 100644 packages/metrics/src/MetricsStore.ts delete mode 100644 packages/metrics/tests/unit/concurrency/dimensionsStore.test.ts delete mode 100644 packages/metrics/tests/unit/concurrency/metadataStore.test.ts delete mode 100644 packages/metrics/tests/unit/concurrency/metrics.test.ts delete mode 100644 packages/metrics/tests/unit/concurrency/metricsStore.test.ts delete mode 100644 packages/metrics/tests/unit/helpers.ts diff --git a/package-lock.json b/package-lock.json index 7ede6c8cbc..c6a39567de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,10 +29,10 @@ "devDependencies": { "@biomejs/biome": "^2.2.6", "@types/aws-lambda": "^8.10.156", - "@types/node": "^24.9.1", + "@types/node": "^24.8.1", "@vitest/coverage-v8": "^3.2.4", "husky": "^9.1.7", - "lint-staged": "^16.2.5", + "lint-staged": "^16.2.4", "markdownlint-cli2": "^0.18.1", "middy5": "npm:@middy/core@^5.4.3", "middy6": "npm:@middy/core@^6.0.0", @@ -47,20 +47,20 @@ }, "examples/app": { "name": "powertools-sample-app", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/batch": "^2.28.0", - "@aws-lambda-powertools/idempotency": "^2.28.0", - "@aws-lambda-powertools/logger": "^2.28.0", - "@aws-lambda-powertools/metrics": "^2.28.0", - "@aws-lambda-powertools/parameters": "^2.28.0", - "@aws-lambda-powertools/tracer": "^2.28.0", + "@aws-lambda-powertools/batch": "^2.27.0", + "@aws-lambda-powertools/idempotency": "^2.27.0", + "@aws-lambda-powertools/logger": "^2.27.0", + "@aws-lambda-powertools/metrics": "^2.27.0", + "@aws-lambda-powertools/parameters": "^2.27.0", + "@aws-lambda-powertools/tracer": "^2.27.0", "@aws-sdk/client-ssm": "^3.913.0", "@aws-sdk/lib-dynamodb": "^3.913.0", "@middy/core": "^4.7.0", "@types/aws-lambda": "^8.10.156", - "@types/node": "24.9.1", + "@types/node": "24.8.1", "aws-cdk": "^2.1030.0", "constructs": "^10.4.2", "esbuild": "^0.25.11", @@ -68,8 +68,8 @@ }, "devDependencies": { "@types/aws-lambda": "^8.10.156", - "@types/node": "24.9.1", - "aws-cdk-lib": "^2.220.0", + "@types/node": "24.8.1", + "aws-cdk-lib": "^2.219.0", "constructs": "^10.4.2", "source-map-support": "^0.5.21", "tsx": "^4.20.6", @@ -79,22 +79,22 @@ }, "examples/snippets": { "name": "code-snippets", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { "arktype": "^2.1.23", "valibot": "^1.1.0" }, "devDependencies": { - "@aws-lambda-powertools/batch": "^2.28.0", - "@aws-lambda-powertools/event-handler": "^2.28.0", - "@aws-lambda-powertools/idempotency": "^2.28.0", - "@aws-lambda-powertools/jmespath": "^2.28.0", - "@aws-lambda-powertools/logger": "^2.28.0", - "@aws-lambda-powertools/metrics": "^2.28.0", - "@aws-lambda-powertools/parameters": "^2.28.0", - "@aws-lambda-powertools/parser": "^2.28.0", - "@aws-lambda-powertools/tracer": "^2.28.0", + "@aws-lambda-powertools/batch": "^2.27.0", + "@aws-lambda-powertools/event-handler": "^2.27.0", + "@aws-lambda-powertools/idempotency": "^2.27.0", + "@aws-lambda-powertools/jmespath": "^2.27.0", + "@aws-lambda-powertools/logger": "^2.27.0", + "@aws-lambda-powertools/metrics": "^2.27.0", + "@aws-lambda-powertools/parameters": "^2.27.0", + "@aws-lambda-powertools/parser": "^2.27.0", + "@aws-lambda-powertools/tracer": "^2.27.0", "@aws-sdk/client-appconfigdata": "^3.913.0", "@aws-sdk/client-dynamodb": "^3.913.0", "@aws-sdk/client-secrets-manager": "^3.913.0", @@ -131,11 +131,11 @@ } }, "layers": { - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { "aws-cdk": "^2.1030.0", - "aws-cdk-lib": "^2.220.0", + "aws-cdk-lib": "^2.219.0", "esbuild": "^0.25.11", "tsx": "^4.20.6" }, @@ -5104,12 +5104,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", - "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "version": "24.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz", + "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/promise-retry": { @@ -6009,9 +6009,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.220.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.220.0.tgz", - "integrity": "sha512-mOEyPP1ymWiLnSE0xFxWjG00E1DQ5wtbcgKUmtGjxyNdoG/Qret1nDLqE43YGZEbwca43WO/a2LDuSL6+hN7Lg==", + "version": "2.219.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.219.0.tgz", + "integrity": "sha512-Rq1/f3exfFEWee1znNq8yvR1TuRQ4xQZz3JNkliBW9dFwyrDe7l/dmlAf6DVvB3nuiZAaUS+vh4ua1LZ7Ec8kg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -8048,9 +8048,9 @@ } }, "node_modules/lint-staged": { - "version": "16.2.5", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.5.tgz", - "integrity": "sha512-o36wH3OX0jRWqDw5dOa8a8x6GXTKaLM+LvhRaucZxez0IxA+KNDUCiyjBfNgsMNmchwSX6urLSL7wShcUqAang==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.4.tgz", + "integrity": "sha512-Pkyr/wd90oAyXk98i/2KwfkIhoYQUMtss769FIT9hFM5ogYZwrk+GRE46yKXSg2ZGhcJ1p38Gf5gmI5Ohjg2yg==", "dev": true, "license": "MIT", "dependencies": { @@ -10104,9 +10104,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -10671,21 +10671,21 @@ }, "packages/batch": { "name": "@aws-lambda-powertools/batch", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { - "@aws-lambda-powertools/parser": "2.28.0", + "@aws-lambda-powertools/parser": "2.27.0", "@aws-lambda-powertools/testing-utils": "file:../testing", "zod": "^4.1.12" } }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { "@aws/lambda-invoke-store": "0.1.0" @@ -10705,19 +10705,19 @@ }, "packages/event-handler": { "name": "@aws-lambda-powertools/event-handler", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0" + "@aws-lambda-powertools/commons": "2.27.0" } }, "packages/idempotency": { "name": "@aws-lambda-powertools/idempotency", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", - "@aws-lambda-powertools/jmespath": "2.28.0" + "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/jmespath": "2.27.0" }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing", @@ -10752,18 +10752,18 @@ }, "packages/jmespath": { "name": "@aws-lambda-powertools/jmespath", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0" + "@aws-lambda-powertools/commons": "2.27.0" } }, "packages/kafka": { "name": "@aws-lambda-powertools/kafka", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { @@ -10790,10 +10790,10 @@ }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", "lodash.merge": "^4.6.2" }, "devDependencies": { @@ -10801,7 +10801,7 @@ "@types/lodash.merge": "^4.6.9" }, "peerDependencies": { - "@aws-lambda-powertools/jmespath": "2.28.0", + "@aws-lambda-powertools/jmespath": "2.27.0", "@middy/core": "4.x || 5.x || 6.x" }, "peerDependenciesMeta": { @@ -10815,11 +10815,10 @@ }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", - "@aws/lambda-invoke-store": "0.1.0" + "@aws-lambda-powertools/commons": "2.27.0" }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing", @@ -10836,21 +10835,12 @@ } } }, - "packages/metrics/node_modules/@aws/lambda-invoke-store": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.0.tgz", - "integrity": "sha512-I1y5yahbSFTfKldV4qoKv2IEZ20QOhn5rPvWwGnswZ8hssN7tsLANLg9tL8dp2klz2MZDGL5jZrvBwplIWtM8A==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, "packages/parameters": { "name": "@aws-lambda-powertools/parameters", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0" + "@aws-lambda-powertools/commons": "2.27.0" }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing", @@ -10893,10 +10883,10 @@ }, "packages/parser": { "name": "@aws-lambda-powertools/parser", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { @@ -10917,13 +10907,13 @@ }, "packages/testing": { "name": "@aws-lambda-powertools/testing-utils", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { "@aws-cdk/toolkit-lib": "^1.10.0", "@aws-sdk/client-lambda": "^3.913.0", "@smithy/util-utf8": "^4.0.0", - "aws-cdk-lib": "^2.220.0", + "aws-cdk-lib": "^2.219.0", "esbuild": "^0.25.11", "promise-retry": "^2.0.1" }, @@ -10934,10 +10924,10 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", "aws-xray-sdk-core": "^3.11.0" }, "devDependencies": { @@ -10956,11 +10946,11 @@ }, "packages/validation": { "name": "@aws-lambda-powertools/validation", - "version": "2.28.0", + "version": "2.27.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", - "@aws-lambda-powertools/jmespath": "2.28.0", + "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/jmespath": "2.27.0", "ajv": "^8.17.1" } } diff --git a/packages/metrics/package.json b/packages/metrics/package.json index ddf3fb61f9..307b0cf640 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -88,8 +88,7 @@ "url": "https://github.com/aws-powertools/powertools-lambda-typescript/issues" }, "dependencies": { - "@aws-lambda-powertools/commons": "2.28.0", - "@aws/lambda-invoke-store": "0.1.0" + "@aws-lambda-powertools/commons": "2.27.0" }, "keywords": [ "aws", diff --git a/packages/metrics/src/DimensionsStore.ts b/packages/metrics/src/DimensionsStore.ts deleted file mode 100644 index 0ce43132e7..0000000000 --- a/packages/metrics/src/DimensionsStore.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; -import type { Dimensions } from './types/Metrics.js'; - -/** - * Manages storage of metrics dimensions with automatic context detection. - * - * This class abstracts the storage mechanism for metrics, automatically - * choosing between AsyncLocalStorage (when in async context) and a fallback - * object (when outside async context). The decision is made at runtime on - * every method call to support Lambda's transition to async contexts. - */ -class DimensionsStore { - readonly #dimensionsKey = Symbol('powertools.metrics.dimensions'); - readonly #dimensionSetsKey = Symbol('powertools.metrics.dimensionSets'); - - #fallbackDimensions: Dimensions = {}; - #fallbackDimensionSets: Dimensions[] = []; - #defaultDimensions: Dimensions = {}; - - #getDimensions(): Dimensions { - if (InvokeStore.getContext() === undefined) { - return this.#fallbackDimensions; - } - - let stored = InvokeStore.get(this.#dimensionsKey) as Dimensions | undefined; - if (stored == null) { - stored = {}; - InvokeStore.set(this.#dimensionsKey, stored); - } - return stored; - } - - #getDimensionSets(): Dimensions[] { - if (InvokeStore.getContext() === undefined) { - return this.#fallbackDimensionSets; - } - - let stored = InvokeStore.get(this.#dimensionSetsKey) as - | Dimensions[] - | undefined; - if (stored == null) { - stored = []; - InvokeStore.set(this.#dimensionSetsKey, stored); - } - return stored; - } - - public addDimension(name: string, value: string): string { - this.#getDimensions()[name] = value; - return value; - } - - public addDimensionSet(dimensionSet: Dimensions): Dimensions { - this.#getDimensionSets().push({ ...dimensionSet }); - return dimensionSet; - } - - public getDimensions(): Dimensions { - return { ...this.#getDimensions() }; - } - - public getDimensionSets(): Dimensions[] { - return this.#getDimensionSets().map((set) => ({ ...set })); - } - - public clearRequestDimensions(): void { - if (InvokeStore.getContext() === undefined) { - this.#fallbackDimensions = {}; - this.#fallbackDimensionSets = []; - return; - } - - InvokeStore.set(this.#dimensionsKey, {}); - InvokeStore.set(this.#dimensionSetsKey, []); - } - - public clearDefaultDimensions(): void { - this.#defaultDimensions = {}; - } - - public getDimensionCount(): number { - const dimensions = this.#getDimensions(); - const dimensionSets = this.#getDimensionSets(); - const dimensionSetsCount = dimensionSets.reduce( - (total, dimensionSet) => total + Object.keys(dimensionSet).length, - 0 - ); - return ( - Object.keys(dimensions).length + - Object.keys(this.#defaultDimensions).length + - dimensionSetsCount - ); - } - - public setDefaultDimensions(dimensions: Dimensions): void { - this.#defaultDimensions = { ...dimensions }; - } - - public getDefaultDimensions(): Dimensions { - return { ...this.#defaultDimensions }; - } -} - -export { DimensionsStore }; diff --git a/packages/metrics/src/MetadataStore.ts b/packages/metrics/src/MetadataStore.ts deleted file mode 100644 index 833048e442..0000000000 --- a/packages/metrics/src/MetadataStore.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; - -/** - * Manages storage of metrics #metadata with automatic context detection. - * - * This class abstracts the storage mechanism for metrics, automatically - * choosing between AsyncLocalStorage (when in async context) and a fallback - * object (when outside async context). The decision is made at runtime on - * every method call to support Lambda's transition to async contexts. - */ -class MetadataStore { - readonly #metadataKey = Symbol('powertools.metrics.metadata'); - - #fallbackStorage: Record = {}; - - #getStorage(): Record { - if (InvokeStore.getContext() === undefined) { - return this.#fallbackStorage; - } - - let stored = InvokeStore.get(this.#metadataKey) as - | Record - | undefined; - if (stored == null) { - stored = {}; - InvokeStore.set(this.#metadataKey, stored); - } - return stored; - } - - public set(key: string, value: string): string { - this.#getStorage()[key] = value; - return value; - } - - public getAll(): Record { - return { ...this.#getStorage() }; - } - - public clear(): void { - if (InvokeStore.getContext() === undefined) { - this.#fallbackStorage = {}; - return; - } - - InvokeStore.set(this.#metadataKey, {}); - } -} - -export { MetadataStore }; diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index c29160e89b..ad9e2de7f4 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -30,9 +30,6 @@ import { MetricUnit as MetricUnits, MIN_METRIC_NAME_LENGTH, } from './constants.js'; -import { DimensionsStore } from './DimensionsStore.js'; -import { MetadataStore } from './MetadataStore.js'; -import { MetricsStore } from './MetricsStore.js'; import type { ConfigServiceInterface, Dimensions, @@ -43,6 +40,7 @@ import type { MetricsInterface, MetricsOptions, MetricUnit, + StoredMetrics, } from './types/index.js'; /** @@ -158,9 +156,22 @@ class Metrics extends Utility implements MetricsInterface { private customConfigService?: ConfigServiceInterface; /** - * Storage for dimensions + * Default dimensions to be added to all metrics + * @default {} */ - readonly #dimensionsStore = new DimensionsStore(); + private defaultDimensions: Dimensions = {}; + + /** + * Additional dimensions for the current metrics context + * @default {} + */ + private dimensions: Dimensions = {}; + + /** + * Additional dimension sets for the current metrics context + * @default [] + */ + private dimensionSets: Dimensions[] = []; /** * Name of the Lambda function @@ -182,8 +193,9 @@ class Metrics extends Utility implements MetricsInterface { /** * Additional metadata to be included with metrics + * @default {} */ - readonly #metadataStore = new MetadataStore(); + private metadata: Record = {}; /** * Namespace for the metrics @@ -198,8 +210,9 @@ class Metrics extends Utility implements MetricsInterface { /** * Storage for metrics before they are published + * @default {} */ - readonly #metricsStore = new MetricsStore(); + private storedMetrics: StoredMetrics = {}; /** * Whether to disable metrics @@ -218,8 +231,15 @@ class Metrics extends Utility implements MetricsInterface { devMode: false, }; + /** + * Custom timestamp for the metrics + */ + #timestamp?: number; + public constructor(options: MetricsOptions = {}) { super(); + + this.dimensions = {}; this.setEnvConfig(); this.setConsole(); this.#logger = options.logger || this.console; @@ -246,22 +266,20 @@ class Metrics extends Utility implements MetricsInterface { ); return; } - if (MAX_DIMENSION_COUNT <= this.#dimensionsStore.getDimensionCount()) { + if (MAX_DIMENSION_COUNT <= this.getCurrentDimensionsCount()) { throw new RangeError( `The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}` ); } - const dimensions = this.#dimensionsStore.getDimensions(); - const defaultDimensions = this.#dimensionsStore.getDefaultDimensions(); if ( - Object.hasOwn(dimensions, name) || - Object.hasOwn(defaultDimensions, name) + Object.hasOwn(this.dimensions, name) || + Object.hasOwn(this.defaultDimensions, name) ) { this.#logger.warn( `Dimension "${name}" has already been added. The previous value will be overwritten.` ); } - this.#dimensionsStore.addDimension(name, value); + this.dimensions[name] = value; } /** @@ -278,7 +296,7 @@ class Metrics extends Utility implements MetricsInterface { */ public addDimensions(dimensions: Dimensions): void { const newDimensions = this.#sanitizeDimensions(dimensions); - const currentCount = this.#dimensionsStore.getDimensionCount(); + const currentCount = this.getCurrentDimensionsCount(); const newSetCount = Object.keys(newDimensions).length; if (currentCount + newSetCount >= MAX_DIMENSION_COUNT) { throw new RangeError( @@ -286,7 +304,7 @@ class Metrics extends Utility implements MetricsInterface { ); } - this.#dimensionsStore.addDimensionSet(newDimensions); + this.dimensionSets.push(newDimensions); } /** @@ -317,7 +335,7 @@ class Metrics extends Utility implements MetricsInterface { * @param value - The value of the metadata */ public addMetadata(key: string, value: string): void { - this.#metadataStore.set(key, value); + this.metadata[key] = value; } /** @@ -424,7 +442,7 @@ class Metrics extends Utility implements MetricsInterface { * ``` */ public clearDefaultDimensions(): void { - this.#dimensionsStore.clearDefaultDimensions(); + this.defaultDimensions = {}; } /** @@ -457,7 +475,8 @@ class Metrics extends Utility implements MetricsInterface { * The method is primarily intended for internal use, but it is exposed for advanced use cases. */ public clearDimensions(): void { - this.#dimensionsStore.clearRequestDimensions(); + this.dimensions = {}; + this.dimensionSets = []; } /** @@ -469,7 +488,7 @@ class Metrics extends Utility implements MetricsInterface { * The method is primarily intended for internal use, but it is exposed for advanced use cases. */ public clearMetadata(): void { - this.#metadataStore.clear(); + this.metadata = {}; } /** @@ -480,14 +499,14 @@ class Metrics extends Utility implements MetricsInterface { * The method is primarily intended for internal use, but it is exposed for advanced use cases. */ public clearMetrics(): void { - this.#metricsStore.clearMetrics(); + this.storedMetrics = {}; } /** * Check if there are stored metrics in the buffer. */ public hasStoredMetrics(): boolean { - return this.#metricsStore.hasMetrics(); + return Object.keys(this.storedMetrics).length > 0; } /** @@ -651,7 +670,7 @@ class Metrics extends Utility implements MetricsInterface { 'Ensure the timestamp is within 14 days in the past or up to 2 hours in the future and is also a valid number or Date object.' ); } - this.#metricsStore.setTimestamp(timestamp); + this.#timestamp = this.#convertTimestampToEmfFormat(timestamp); } /** @@ -667,17 +686,16 @@ class Metrics extends Utility implements MetricsInterface { * The object is then emitted to standard output, which in AWS Lambda is picked up by CloudWatch logs and processed asynchronously. */ public serializeMetrics(): EmfOutput { - const metricDefinitions: MetricDefinition[] = this.#metricsStore - .getAllMetrics() - .map((metricDefinition) => { - return { - Name: metricDefinition.name, - Unit: metricDefinition.unit, - ...(metricDefinition.resolution === MetricResolutions.High - ? { StorageResolution: metricDefinition.resolution } - : {}), - }; - }); + // Storage resolution is included only for High resolution metrics + const metricDefinitions: MetricDefinition[] = Object.values( + this.storedMetrics + ).map((metricDefinition) => ({ + Name: metricDefinition.name, + Unit: metricDefinition.unit, + ...(metricDefinition.resolution === MetricResolutions.High + ? { StorageResolution: metricDefinition.resolution } + : {}), + })); if (metricDefinitions.length === 0 && this.shouldThrowOnEmptyMetrics) { throw new RangeError( @@ -690,40 +708,32 @@ class Metrics extends Utility implements MetricsInterface { // We reduce the stored metrics to a single object with the metric // name as the key and the value as the value. - const metricValues = this.#metricsStore.getAllMetrics().reduce( + const metricValues = Object.values(this.storedMetrics).reduce( ( result: Record, - { - name, - value, - }: { - name: string; - value: number | number[]; - } + { name, value }: { name: string; value: number | number[] } ) => { result[name] = value; + return result; }, {} ); const dimensionNames = []; - const dimensions = this.#dimensionsStore.getDimensions(); - const dimensionSets = this.#dimensionsStore.getDimensionSets(); - const defaultDimensions = this.#dimensionsStore.getDefaultDimensions(); const allDimensionKeys = new Set([ - ...Object.keys(defaultDimensions), - ...Object.keys(dimensions), + ...Object.keys(this.defaultDimensions), + ...Object.keys(this.dimensions), ]); - if (Object.keys(dimensions).length > 0) { + if (Object.keys(this.dimensions).length > 0) { dimensionNames.push([...allDimensionKeys]); } - for (const dimensionSet of dimensionSets) { + for (const dimensionSet of this.dimensionSets) { const dimensionSetKeys = new Set([ - ...Object.keys(defaultDimensions), + ...Object.keys(this.defaultDimensions), ...Object.keys(dimensionSet), ]); dimensionNames.push([...dimensionSetKeys]); @@ -731,14 +741,14 @@ class Metrics extends Utility implements MetricsInterface { if ( dimensionNames.length === 0 && - Object.keys(defaultDimensions).length > 0 + Object.keys(this.defaultDimensions).length > 0 ) { - dimensionNames.push(Object.keys(defaultDimensions)); + dimensionNames.push([...Object.keys(this.defaultDimensions)]); } return { _aws: { - Timestamp: this.#metricsStore.getTimestamp() ?? Date.now(), + Timestamp: this.#timestamp ?? Date.now(), CloudWatchMetrics: [ { Namespace: this.namespace || DEFAULT_NAMESPACE, @@ -747,17 +757,17 @@ class Metrics extends Utility implements MetricsInterface { }, ], }, - ...defaultDimensions, - ...dimensions, + ...this.defaultDimensions, + ...this.dimensions, // Merge all dimension sets efficiently by mutating the accumulator - ...dimensionSets.reduce((acc, dims) => { + ...this.dimensionSets.reduce((acc, dims) => { for (const [key, value] of Object.entries(dims)) { acc[key] = value; } return acc; - }, {}), + }, {} as Dimensions), ...metricValues, - ...this.#metadataStore.getAll(), + ...this.metadata, }; } @@ -787,9 +797,7 @@ class Metrics extends Utility implements MetricsInterface { */ public setDefaultDimensions(dimensions: Dimensions): void { const newDimensions = this.#sanitizeDimensions(dimensions); - const currentDefaultDimensions = - this.#dimensionsStore.getDefaultDimensions(); - const currentCount = Object.keys(currentDefaultDimensions).length; + const currentCount = Object.keys(this.defaultDimensions).length; const newSetCount = Object.keys(newDimensions).length; if (currentCount + newSetCount >= MAX_DIMENSION_COUNT) { throw new RangeError( @@ -797,10 +805,10 @@ class Metrics extends Utility implements MetricsInterface { ); } - this.#dimensionsStore.setDefaultDimensions({ - ...currentDefaultDimensions, + this.defaultDimensions = { + ...this.defaultDimensions, ...newDimensions, - }); + }; } /** @@ -857,7 +865,7 @@ class Metrics extends Utility implements MetricsInterface { public singleMetric(): Metrics { return new Metrics({ namespace: this.namespace, - defaultDimensions: this.#dimensionsStore.getDefaultDimensions(), + defaultDimensions: this.defaultDimensions, singleMetric: true, logger: this.#logger, }); @@ -870,6 +878,21 @@ class Metrics extends Utility implements MetricsInterface { this.shouldThrowOnEmptyMetrics = true; } /* v8 ignore stop */ + /** + * Gets the current number of dimensions count. + */ + private getCurrentDimensionsCount(): number { + const dimensionSetsCount = this.dimensionSets.reduce( + (total, dimensionSet) => total + Object.keys(dimensionSet).length, + 0 + ); + return ( + Object.keys(this.dimensions).length + + Object.keys(this.defaultDimensions).length + + dimensionSetsCount + ); + } + /** * Get the custom config service if it exists. */ @@ -877,6 +900,32 @@ class Metrics extends Utility implements MetricsInterface { return this.customConfigService; } + /** + * Check if a metric is new or not. + * + * A metric is considered new if there is no metric with the same name already stored. + * + * When a metric is not new, we also check if the unit is consistent with the stored metric with + * the same name. If the units are inconsistent, we throw an error as this is likely a bug or typo. + * This can happen if a metric is added without using the `MetricUnit` helper in JavaScript codebases. + * + * @param name - The name of the metric + * @param unit - The unit of the metric + */ + private isNewMetric(name: string, unit: MetricUnit): boolean { + if (this.storedMetrics[name]) { + if (this.storedMetrics[name].unit !== unit) { + const currentUnit = this.storedMetrics[name].unit; + throw new Error( + `Metric "${name}" has already been added with unit "${currentUnit}", but we received unit "${unit}". Did you mean to use metric unit "${currentUnit}"?` + ); + } + + return false; + } + return true; + } + /** * Initialize the console property as an instance of the internal version of `Console()` class (PR #748) * or as the global node console if the `POWERTOOLS_DEV' env variable is set and has truthy value. @@ -1050,21 +1099,26 @@ class Metrics extends Utility implements MetricsInterface { `Invalid metric resolution '${resolution}', expected either option: ${Object.values(MetricResolutions).join(',')}` ); - if (this.#metricsStore.getMetricsCount() >= MAX_METRICS_SIZE) { + if (Object.keys(this.storedMetrics).length >= MAX_METRICS_SIZE) { this.publishStoredMetrics(); } - const storedMetric = this.#metricsStore.setMetric( - name, - unit, - value, - resolution - ); - if ( - Array.isArray(storedMetric.value) && - storedMetric.value.length === MAX_METRIC_VALUES_SIZE - ) { - this.publishStoredMetrics(); + if (this.isNewMetric(name, unit)) { + this.storedMetrics[name] = { + unit, + value, + name, + resolution, + }; + } else { + const storedMetric = this.storedMetrics[name]; + if (!Array.isArray(storedMetric.value)) { + storedMetric.value = [storedMetric.value]; + } + storedMetric.value.push(value); + if (storedMetric.value.length === MAX_METRIC_VALUES_SIZE) { + this.publishStoredMetrics(); + } } } @@ -1093,6 +1147,27 @@ class Metrics extends Utility implements MetricsInterface { return timestampMs >= minValidTimestamp && timestampMs <= maxValidTimestamp; } + /** + * Converts a given timestamp to EMF compatible format. + * + * @param timestamp - The timestamp to convert, which can be either a number (in milliseconds) or a Date object. + * @returns The timestamp in milliseconds. If the input is invalid, returns 0. + */ + #convertTimestampToEmfFormat(timestamp: number | Date): number { + if (isIntegerNumber(timestamp)) { + return timestamp; + } + if (timestamp instanceof Date) { + return timestamp.getTime(); + } + /** + * If this point is reached, it indicates timestamp was neither a valid number nor Date + * Returning zero represents the initial date of epoch time, + * which will be skipped by Amazon CloudWatch. + */ + return 0; + } + /** * Sanitizes the dimensions by removing invalid entries and skipping duplicates. * @@ -1100,7 +1175,6 @@ class Metrics extends Utility implements MetricsInterface { */ #sanitizeDimensions(dimensions: Dimensions): Dimensions { const newDimensions: Dimensions = {}; - const currentDimensions = this.#dimensionsStore.getDimensions(); for (const [key, value] of Object.entries(dimensions)) { if ( isStringUndefinedNullEmpty(key) || @@ -1111,10 +1185,9 @@ class Metrics extends Utility implements MetricsInterface { ); continue; } - const defaultDimensions = this.#dimensionsStore.getDefaultDimensions(); if ( - Object.hasOwn(currentDimensions, key) || - Object.hasOwn(defaultDimensions, key) || + Object.hasOwn(this.dimensions, key) || + Object.hasOwn(this.defaultDimensions, key) || Object.hasOwn(newDimensions, key) ) { this.#logger.warn( diff --git a/packages/metrics/src/MetricsStore.ts b/packages/metrics/src/MetricsStore.ts deleted file mode 100644 index d2c08b3559..0000000000 --- a/packages/metrics/src/MetricsStore.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; -import { isIntegerNumber } from '@aws-lambda-powertools/commons/typeutils'; -import { MetricResolution as MetricResolutions } from './constants.js'; -import type { - MetricResolution, - MetricUnit, - StoredMetric, - StoredMetrics, -} from './types/index.js'; - -/** - * Manages storage of metrics with automatic context detection. - * - * This class abstracts the storage mechanism for metrics, automatically - * choosing between AsyncLocalStorage (when in async context) and a fallback - * object (when outside async context). The decision is made at runtime on - * every method call to support Lambda's transition to async contexts. - */ -class MetricsStore { - readonly #storedMetricsKey = Symbol('powertools.metrics.storedMetrics'); - readonly #timestampKey = Symbol('powertools.metrics.timestamp'); - - #fallbackStorage: StoredMetrics = {}; - #fallbackTimestamp?: number; - - #getStorage(): StoredMetrics { - if (InvokeStore.getContext() === undefined) { - return this.#fallbackStorage; - } - - let stored = InvokeStore.get(this.#storedMetricsKey) as - | StoredMetrics - | undefined; - if (stored == null) { - stored = {}; - InvokeStore.set(this.#storedMetricsKey, stored); - } - return stored; - } - - public getMetric(name: string): StoredMetric | undefined { - return this.#getStorage()[name]; - } - - /** - * Adds a metric value to storage. If a metric with the same name already exists, - * the value is appended to an array. Validates that the unit matches any existing metric. - * - * @example - * ```typescript - * store.setMetric('latency', MetricUnit.Milliseconds, 100); - * // Returns: { name: 'latency', unit: 'Milliseconds', value: 100, resolution: 60 } - * - * store.setMetric('latency', MetricUnit.Milliseconds, 150); - * // Returns: { name: 'latency', unit: 'Milliseconds', value: [100, 150], resolution: 60 } - * ``` - * - * @param name - The metric name - * @param unit - The metric unit (must match existing metric if present) - * @param value - The metric value to add - * @param resolution - The metric resolution (defaults to Standard) - * @returns The stored metric with updated values - * @throws Error if unit doesn't match existing metric - */ - public setMetric( - name: string, - unit: MetricUnit, - value: number, - resolution: MetricResolution = MetricResolutions.Standard - ): StoredMetric { - const storage = this.#getStorage(); - const existingMetric = storage[name]; - - if (existingMetric === undefined) { - const newMetric: StoredMetric = { - name, - unit, - value, - resolution, - }; - storage[name] = newMetric; - return { ...newMetric }; - } - - if (existingMetric.unit !== unit) { - const currentUnit = existingMetric.unit; - throw new Error( - `Metric "${name}" has already been added with unit "${currentUnit}", but we received unit "${unit}". Did you mean to use metric unit "${currentUnit}"?` - ); - } - - if (!Array.isArray(existingMetric.value)) { - existingMetric.value = [existingMetric.value]; - } - existingMetric.value.push(value); - return { ...existingMetric, value: [...existingMetric.value] }; - } - - public getMetricNames(): string[] { - return Object.keys(this.#getStorage()); - } - - public getAllMetrics(): StoredMetric[] { - return Object.values(this.#getStorage()); - } - - public clearMetrics(): void { - if (InvokeStore.getContext() === undefined) { - this.#fallbackStorage = {}; - this.#fallbackTimestamp = undefined; - return; - } - - InvokeStore.set(this.#storedMetricsKey, {}); - InvokeStore.set(this.#timestampKey, undefined); - } - - public hasMetrics(): boolean { - return this.getMetricNames().length > 0; - } - - public getMetricsCount(): number { - return this.getMetricNames().length; - } - - public getTimestamp(): number | undefined { - if (InvokeStore.getContext() === undefined) { - return this.#fallbackTimestamp; - } - - return InvokeStore.get(this.#timestampKey) as number | undefined; - } - - public setTimestamp(timestamp: number | Date): number { - const timestampMs = this.#convertTimestampToEmfFormat(timestamp); - - if (InvokeStore.getContext() === undefined) { - this.#fallbackTimestamp = timestampMs; - return timestampMs; - } - - InvokeStore.set(this.#timestampKey, timestampMs); - return timestampMs; - } - - #convertTimestampToEmfFormat(timestamp: number | Date): number { - if (isIntegerNumber(timestamp)) { - return timestamp; - } - if (timestamp instanceof Date) { - return timestamp.getTime(); - } - return 0; - } -} - -export { MetricsStore }; diff --git a/packages/metrics/tests/unit/concurrency/dimensionsStore.test.ts b/packages/metrics/tests/unit/concurrency/dimensionsStore.test.ts deleted file mode 100644 index f1433e7f5c..0000000000 --- a/packages/metrics/tests/unit/concurrency/dimensionsStore.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { DimensionsStore } from '../../../src/DimensionsStore.js'; -import { sequence } from '../helpers.js'; - -describe('DimensionsStore concurrent invocation isolation', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { env: 'dev' }, - expectedResult2: { env: 'dev' }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { env: 'prod' }, - expectedResult2: { env: 'dev' }, - }, - ])( - 'handles storing dimensions $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new DimensionsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [() => store.addDimension('env', 'prod')], - return: () => store.getDimensions(), - }, - { - sideEffects: [() => store.addDimension('env', 'dev')], - return: () => store.getDimensions(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: [ - { service: 'api', version: '1.0' }, - { service: 'web', version: '2.0' }, - ], - expectedResult2: [ - { service: 'api', version: '1.0' }, - { service: 'web', version: '2.0' }, - ], - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: [{ service: 'api', version: '1.0' }], - expectedResult2: [{ service: 'web', version: '2.0' }], - }, - ])( - 'handles storing dimension sets $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new DimensionsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => store.addDimensionSet({ service: 'api', version: '1.0' }), - ], - return: () => store.getDimensionSets(), - }, - { - sideEffects: [ - () => store.addDimensionSet({ service: 'web', version: '2.0' }), - ], - return: () => store.getDimensionSets(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { dims: {}, sets: [] }, - expectedResult2: { dims: {}, sets: [] }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { dims: {}, sets: [] }, - expectedResult2: { - dims: { region: 'us-east-1' }, - sets: [{ version: '2.0' }], - }, - }, - ])( - 'handles clearing the store $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new DimensionsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => { - store.addDimension('env', 'prod'); - store.addDimensionSet({ service: 'api' }); - }, - () => {}, // Wait for inv2 to add - () => store.clearRequestDimensions(), - ], - return: () => ({ - dims: store.getDimensions(), - sets: store.getDimensionSets(), - }), - }, - { - sideEffects: [ - () => {}, // Wait for inv1 to add - () => { - store.addDimension('region', 'us-east-1'); - store.addDimensionSet({ version: '2.0' }); - }, - () => {}, // Wait for clear - ], - return: () => ({ - dims: store.getDimensions(), - sets: store.getDimensionSets(), - }), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); -}); diff --git a/packages/metrics/tests/unit/concurrency/metadataStore.test.ts b/packages/metrics/tests/unit/concurrency/metadataStore.test.ts deleted file mode 100644 index 8e31e45510..0000000000 --- a/packages/metrics/tests/unit/concurrency/metadataStore.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { MetadataStore } from '../../../src/MetadataStore.js'; -import { sequence } from '../helpers.js'; - -describe('MetadataStore concurrent invocation isolation', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { env: 'dev' }, - expectedResult2: { env: 'dev' }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { env: 'prod' }, - expectedResult2: { env: 'dev' }, - }, - ])( - 'handles storing metadata $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetadataStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [() => store.set('env', 'prod')], - return: () => store.getAll(), - }, - { - sideEffects: [() => store.set('env', 'dev')], - return: () => store.getAll(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { service: 'web', version: '1.0', region: 'us-east-1' }, - expectedResult2: { service: 'web', version: '1.0', region: 'us-east-1' }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { service: 'api', version: '1.0' }, - expectedResult2: { service: 'web', region: 'us-east-1' }, - }, - ])( - 'handles storing multiple metadata keys $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetadataStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => { - store.set('service', 'api'); - store.set('version', '1.0'); - }, - ], - return: () => store.getAll(), - }, - { - sideEffects: [ - () => { - store.set('service', 'web'); - store.set('region', 'us-east-1'); - }, - ], - return: () => store.getAll(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: {}, - expectedResult2: {}, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: {}, - expectedResult2: { region: 'us-east-1', env: 'prod' }, - }, - ])( - 'handles clearing the store $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetadataStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => { - store.set('service', 'api'); - store.set('version', '1.0'); - }, - () => {}, // Wait for inv2 to add - () => store.clear(), - ], - return: () => store.getAll(), - }, - { - sideEffects: [ - () => {}, // Wait for inv1 to add - () => { - store.set('region', 'us-east-1'); - store.set('env', 'prod'); - }, - () => {}, // Wait for clear - ], - return: () => store.getAll(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { key: 'value3' }, - expectedResult2: { key: 'value3' }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { key: 'value3' }, - expectedResult2: { key: 'value2' }, - }, - ])( - 'handles overwriting same key $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetadataStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => store.set('key', 'value1'), - () => {}, // Wait for inv2 - () => store.set('key', 'value3'), - ], - return: () => store.getAll(), - }, - { - sideEffects: [ - () => {}, // Wait for inv1 - () => store.set('key', 'value2'), - () => {}, // Wait for inv1's final write - ], - return: () => store.getAll(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); -}); diff --git a/packages/metrics/tests/unit/concurrency/metrics.test.ts b/packages/metrics/tests/unit/concurrency/metrics.test.ts deleted file mode 100644 index 950a3550a2..0000000000 --- a/packages/metrics/tests/unit/concurrency/metrics.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { Metrics, MetricUnit } from '../../../src/index.js'; -import { sequence } from '../helpers.js'; - -describe('Metrics concurrent invocation isolation', () => { - beforeEach(() => { - vi.stubEnv('POWERTOOLS_DEV', 'true'); - vi.stubEnv('POWERTOOLS_METRICS_DISABLED', 'false'); - vi.clearAllMocks(); - }); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedCallCount: 1, - expectedOutputs: [ - { - env: 'dev', - key: 'value2', - count: [1, 2], - }, - ], - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedCallCount: 2, - expectedOutputs: [ - { env: 'prod', key: 'value1', count: 1 }, - { env: 'dev', key: 'value2', count: 2 }, - ], - }, - ])( - 'handles metrics, metadata, and dimensions $description', - async ({ useInvokeStore, expectedCallCount, expectedOutputs }) => { - const metrics = new Metrics({ singleMetric: false }); - - await sequence( - { - sideEffects: [ - () => { - metrics.addDimension('env', 'prod'); - metrics.addMetric('count', MetricUnit.Count, 1); - metrics.addMetadata('key', 'value1'); - }, - ], - return: () => metrics.publishStoredMetrics(), - }, - { - sideEffects: [ - () => { - metrics.addDimension('env', 'dev'); - metrics.addMetric('count', MetricUnit.Count, 2); - metrics.addMetadata('key', 'value2'); - }, - ], - return: () => metrics.publishStoredMetrics(), - }, - { useInvokeStore } - ); - - expect(console.log).toHaveBeenCalledTimes(expectedCallCount); - for (const expectedOutput of expectedOutputs) { - expect(console.log).toHaveEmittedEMFWith( - expect.objectContaining(expectedOutput) - ); - } - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedCallCount: 1, - expectedOutputs: [ - { - _aws: expect.objectContaining({ Timestamp: 2000 }), - count: [1, 2], - }, - ], - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedCallCount: 2, - expectedOutputs: [ - { _aws: expect.objectContaining({ Timestamp: 1000 }), count: 1 }, - { _aws: expect.objectContaining({ Timestamp: 2000 }), count: 2 }, - ], - }, - ])( - 'handles timestamps $description', - async ({ useInvokeStore, expectedCallCount, expectedOutputs }) => { - const metrics = new Metrics({ singleMetric: false }); - const timestamp1 = 1000; - const timestamp2 = 2000; - - await sequence( - { - sideEffects: [ - () => { - metrics.setTimestamp(timestamp1); - metrics.addMetric('count', MetricUnit.Count, 1); - }, - () => {}, - () => metrics.publishStoredMetrics(), - ], - return: () => {}, - }, - { - sideEffects: [ - () => {}, - () => { - metrics.setTimestamp(timestamp2); - metrics.addMetric('count', MetricUnit.Count, 2); - }, - () => metrics.publishStoredMetrics(), - ], - return: () => {}, - }, - { useInvokeStore } - ); - - expect(console.log).toHaveBeenCalledTimes(expectedCallCount); - for (const expectedOutput of expectedOutputs) { - expect(console.log).toHaveEmittedEMFWith( - expect.objectContaining(expectedOutput) - ); - } - } - ); -}); diff --git a/packages/metrics/tests/unit/concurrency/metricsStore.test.ts b/packages/metrics/tests/unit/concurrency/metricsStore.test.ts deleted file mode 100644 index 75e83e7254..0000000000 --- a/packages/metrics/tests/unit/concurrency/metricsStore.test.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { MetricUnit } from '../../../src/index.js'; -import { MetricsStore } from '../../../src/MetricsStore.js'; -import type { StoredMetric } from '../../../src/types/index.js'; -import { sequence } from '../helpers.js'; - -describe('MetricsStore concurrent invocation isolation', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { - name: 'count', - unit: MetricUnit.Count, - value: [1, 2], - resolution: 60, - }, - expectedResult2: { - name: 'count', - unit: MetricUnit.Count, - value: [1, 2], - resolution: 60, - }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { - name: 'count', - unit: MetricUnit.Count, - value: 1, - resolution: 60, - }, - expectedResult2: { - name: 'count', - unit: MetricUnit.Count, - value: 2, - resolution: 60, - }, - }, - ])( - 'getMetric() $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => store.setMetric('count', MetricUnit.Count, 1, 60), - ], - return: () => store.getMetric('count'), - }, - { - sideEffects: [ - () => store.setMetric('count', MetricUnit.Count, 2, 60), - ], - return: () => store.getMetric('count'), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - const countMetric: StoredMetric = { - name: 'count', - unit: MetricUnit.Count, - value: 1, - resolution: 60, - }; - const latencyMetric: StoredMetric = { - name: 'latency', - unit: MetricUnit.Milliseconds, - value: 100, - resolution: 60, - }; - const errorMetric: StoredMetric = { - name: 'errors', - unit: MetricUnit.Count, - value: 1, - resolution: 60, - }; - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: [countMetric, latencyMetric, errorMetric], - expectedResult2: [countMetric, latencyMetric, errorMetric], - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: [countMetric, latencyMetric], - expectedResult2: [errorMetric], - }, - ])( - 'getAllMetrics() $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => { - store.setMetric('count', MetricUnit.Count, 1, 60); - store.setMetric('latency', MetricUnit.Milliseconds, 100, 60); - }, - ], - return: () => store.getAllMetrics(), - }, - { - sideEffects: [ - () => { - store.setMetric('errors', MetricUnit.Count, 1, 60); - }, - ], - return: () => store.getAllMetrics(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: 2000, - expectedResult2: 2000, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: 1000, - expectedResult2: 2000, - }, - ])( - 'timestamp $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - const timestamp1 = 1000; - const timestamp2 = 2000; - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [() => store.setTimestamp(timestamp1)], - return: () => store.getTimestamp(), - }, - { - sideEffects: [() => store.setTimestamp(timestamp2)], - return: () => store.getTimestamp(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toBe(expectedResult1); - expect(result2).toBe(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: { metrics: [], timestamp: undefined }, - expectedResult2: { metrics: [], timestamp: undefined }, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: { metrics: [], timestamp: undefined }, - expectedResult2: { metrics: [errorMetric], timestamp: 2000 }, - }, - ])( - 'clearMetrics() $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => { - store.setMetric('count', MetricUnit.Count, 1, 60); - store.setTimestamp(1000); - }, - () => {}, // Wait for inv2 to add - () => store.clearMetrics(), - ], - return: () => ({ - metrics: store.getAllMetrics(), - timestamp: store.getTimestamp(), - }), - }, - { - sideEffects: [ - () => {}, // Wait for inv1 to add - () => { - store.setMetric('errors', MetricUnit.Count, 1, 60); - store.setTimestamp(2000); - }, - () => {}, // Wait for clear - ], - return: () => ({ - metrics: store.getAllMetrics(), - timestamp: store.getTimestamp(), - }), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toEqual(expectedResult1); - expect(result2).toEqual(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: true, - expectedResult2: true, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: true, - expectedResult2: false, - }, - ])( - 'hasMetrics() $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => store.setMetric('count', MetricUnit.Count, 1, 60), - ], - return: () => store.hasMetrics(), - }, - { - sideEffects: [() => {}], // No-op - return: () => store.hasMetrics(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toBe(expectedResult1); - expect(result2).toBe(expectedResult2); - } - ); - - it.each([ - { - description: 'without InvokeStore', - useInvokeStore: false, - expectedResult1: 2, - expectedResult2: 2, - }, - { - description: 'with InvokeStore', - useInvokeStore: true, - expectedResult1: 1, - expectedResult2: 1, - }, - ])( - 'getMetricsCount() $description', - async ({ useInvokeStore, expectedResult1, expectedResult2 }) => { - // Prepare - const store = new MetricsStore(); - - // Act - const [result1, result2] = await sequence( - { - sideEffects: [ - () => store.setMetric('count', MetricUnit.Count, 1, 60), - ], - return: () => store.getMetricsCount(), - }, - { - sideEffects: [ - () => store.setMetric('errors', MetricUnit.Count, 1, 60), - ], - return: () => store.getMetricsCount(), - }, - { useInvokeStore } - ); - - // Assess - expect(result1).toBe(expectedResult1); - expect(result2).toBe(expectedResult2); - } - ); -}); diff --git a/packages/metrics/tests/unit/helpers.ts b/packages/metrics/tests/unit/helpers.ts deleted file mode 100644 index 87eb7973c6..0000000000 --- a/packages/metrics/tests/unit/helpers.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; - -type Invocation = { - sideEffects: (() => void)[]; - return: () => unknown; -}; - -/** - * Creates a Promise with externally accessible resolve and reject functions. - * - * This is a polyfill for the proposed Promise.withResolvers() method that provides - * a more convenient way to create promises that can be resolved or rejected from - * outside the Promise constructor. - * - * We need this polyfill because this function is not available in Node 20. When we drop - * support for this version of Node, then we should remove this function and use the - * inbuilt `Promise.withResolvers` static methods. - * - * @returns Object containing the promise and its resolve/reject functions - * - * @example - * ```typescript - * const { promise, resolve, reject } = withResolvers(); - * - * // Later, from somewhere else: - * resolve('success'); - * - * // Or: - * reject(new Error('failed')); - * ``` - */ -const withResolvers = () => { - let resolve: (value: T) => void = () => {}; - let reject: (reason?: unknown) => void = () => {}; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; -}; - -/** - * Executes two invocations concurrently with synchronized side effects to test isolation behavior. - * - * This function ensures that side effects are executed in a specific order across both - * invocations using barrier synchronization. Each step waits for the corresponding step - * in the other invocation to complete before proceeding to the next step. - * - * @param inv1 - First invocation configuration - * @param inv1.sideEffects - Array of functions to execute sequentially, synchronized with inv2 - * @param inv1.return - Function to call after all side effects, returns the test result - * @param inv2 - Second invocation configuration - * @param inv2.sideEffects - Array of functions to execute sequentially, synchronized with inv1 - * @param inv2.return - Function to call after all side effects, returns the test result - * @param options - Execution options - * @param options.useInvokeStore - Whether to run invocations in separate InvokeStore contexts - * - `true`: Each invocation runs in its own InvokeStore.run() context (isolated) - * - `false`: Both invocations run in shared context (no isolation) - * - * @returns Promise that resolves to tuple of [inv1Result, inv2Result] - * - * @example - * ```typescript - * // Basic 2-step sequencing: inv1 acts, then inv2 acts - * const [result1, result2] = await sequence({ - * sideEffects: [() => doSomething('A')], - * return: () => getResult() - * }, { - * sideEffects: [() => doSomething('B')], - * return: () => getResult() - * }, { useInvokeStore: true }); - * - * // Execution order: inv1 doSomething('A') → inv2 doSomething('B') → both return - * ``` - * - * @example - * ```typescript - * // Complex 3-step sequencing with barriers - * const [result1, result2] = await sequence({ - * sideEffects: [ - * () => action1(), // Step 1: inv1 acts first - * () => {}, // Step 2: inv1 waits for inv2 - * () => action3() // Step 3: inv1 acts after inv2 - * ], - * return: () => getResult() - * }, { - * sideEffects: [ - * () => {}, // Step 1: inv2 waits for inv1 - * () => action2(), // Step 2: inv2 acts after inv1 - * () => {} // Step 3: inv2 waits for inv1 - * ], - * return: () => getResult() - * }, { useInvokeStore: false }); - * - * // Execution order: action1() → action2() → action3() → both return - * ``` - */ -function sequence( - inv1: Invocation, - inv2: Invocation, - options: { useInvokeStore?: boolean } -) { - const executionEnv = options?.useInvokeStore - ? (f: () => unknown) => InvokeStore.run({}, f) - : (f: () => unknown) => f(); - - const inv1Barriers = inv1.sideEffects.map(() => withResolvers()); - const inv2Barriers = inv2.sideEffects.map(() => withResolvers()); - - const invocation1 = executionEnv(async () => { - for (let i = 0; i < inv1Barriers.length; i++) { - const sideEffect = inv1.sideEffects[i] ?? (() => {}); - sideEffect(); - inv1Barriers[i].resolve(); - await inv2Barriers[i].promise; - } - return inv1.return(); - }); - - const invocation2 = executionEnv(async () => { - for (let i = 0; i < inv2Barriers.length; i++) { - await inv1Barriers[i].promise; - const sideEffect = inv2.sideEffects[i] ?? (() => {}); - sideEffect(); - inv2Barriers[i].resolve(); - } - return inv2.return(); - }); - - return Promise.all([invocation1, invocation2]); -} - -export { withResolvers, sequence }; -export type { Invocation }; From 7f3ce360bf4a939b8dd250e0dcc679b19ba45d75 Mon Sep 17 00:00:00 2001 From: svozza Date: Thu, 23 Oct 2025 11:15:04 +0100 Subject: [PATCH 2/3] Revert "improv(commons): Make X-rRay trace ID access more robust (#4658)" This reverts commit 5199d3e3a5000d3b3b5f2906f3d62da5fc1c96ec. --- package-lock.json | 12 -- packages/commons/package.json | 3 - packages/commons/src/envUtils.ts | 27 +-- packages/commons/tests/unit/envUtils.test.ts | 198 +++++++------------ 4 files changed, 82 insertions(+), 158 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6a39567de..bb6c1e1f3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10687,22 +10687,10 @@ "name": "@aws-lambda-powertools/commons", "version": "2.27.0", "license": "MIT-0", - "dependencies": { - "@aws/lambda-invoke-store": "0.1.0" - }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing" } }, - "packages/commons/node_modules/@aws/lambda-invoke-store": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.0.tgz", - "integrity": "sha512-I1y5yahbSFTfKldV4qoKv2IEZ20QOhn5rPvWwGnswZ8hssN7tsLANLg9tL8dp2klz2MZDGL5jZrvBwplIWtM8A==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, "packages/event-handler": { "name": "@aws-lambda-powertools/event-handler", "version": "2.27.0", diff --git a/packages/commons/package.json b/packages/commons/package.json index e10df7ec77..fb23628ea2 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -110,9 +110,6 @@ "serverless", "nodejs" ], - "dependencies": { - "@aws/lambda-invoke-store": "0.1.0" - }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing" } diff --git a/packages/commons/src/envUtils.ts b/packages/commons/src/envUtils.ts index 751d109bfe..cfc4b2adfa 100644 --- a/packages/commons/src/envUtils.ts +++ b/packages/commons/src/envUtils.ts @@ -1,4 +1,3 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; import { POWERTOOLS_DEV_ENV_VAR, POWERTOOLS_SERVICE_NAME_ENV_VAR, @@ -250,19 +249,15 @@ const getServiceName = (): string => { }; /** - * Get the AWS X-Ray Trace data from the lambda RIC async context or the `_X_AMZN_TRACE_ID` environment variable. + * Get the AWS X-Ray Trace data from the environment variable. * - * Checks the async context first and if that returns undefined, falls back to the environment variable - * - * The method parses the value and returns an object with the key-value pairs. + * The method parses the environment variable `_X_AMZN_TRACE_ID` and returns an object with the key-value pairs. */ const getXrayTraceDataFromEnv = (): Record | undefined => { - const xRayTraceEnv = - InvokeStore.getXRayTraceId() ?? - getStringFromEnv({ - key: XRAY_TRACE_ID_ENV_VAR, - defaultValue: '', - }); + const xRayTraceEnv = getStringFromEnv({ + key: XRAY_TRACE_ID_ENV_VAR, + defaultValue: '', + }); if (xRayTraceEnv === '') { return undefined; } @@ -285,10 +280,8 @@ const getXrayTraceDataFromEnv = (): Record | undefined => { /** * Determine if the current invocation is part of a sampled X-Ray trace. * - * The AWS X-Ray Trace data is available in either the RIC async context or the `_X_AMZN_TRACE_ID` environment variable has this format: + * The AWS X-Ray Trace data available in the environment variable has this format: * `Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1`, - * - * Checks the async context first and if that returns undefined, falls back to the environment variable */ const isRequestXRaySampled = (): boolean => { const xRayTraceData = getXrayTraceDataFromEnv(); @@ -296,11 +289,9 @@ const isRequestXRaySampled = (): boolean => { }; /** - * AWS X-Ray Trace id from the lambda RIC async context or the `_X_AMZN_TRACE_ID` environment variable. - * - * Checks the async context first and if that returns undefined, falls back to the environment variable + * Get the value of the `_X_AMZN_TRACE_ID` environment variable. * - * The AWS X-Ray Trace data has this format: + * The AWS X-Ray Trace data available in the environment variable has this format: * `Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1`, * * The actual Trace ID is: `1-5759e988-bd862e3fe1be46a994272793`. diff --git a/packages/commons/tests/unit/envUtils.test.ts b/packages/commons/tests/unit/envUtils.test.ts index 74f7df741d..1c32bceb3d 100644 --- a/packages/commons/tests/unit/envUtils.test.ts +++ b/packages/commons/tests/unit/envUtils.test.ts @@ -1,5 +1,4 @@ -import { InvokeStore } from '@aws/lambda-invoke-store'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { getBooleanFromEnv, getNumberFromEnv, @@ -11,14 +10,16 @@ import { } from '../../src/envUtils.js'; describe('Functions: envUtils', () => { + const env = process.env; + beforeEach(() => { - vi.unstubAllEnvs(); + process.env = { ...env }; }); describe('Function: getStringFromEnv', () => { it('returns the value of the environment variable', () => { // Prepare - vi.stubEnv('TEST_ENV', 'testValue'); + process.env.TEST_ENV = 'testValue'; // Act const result = getStringFromEnv({ key: 'TEST_ENV' }); @@ -28,6 +29,9 @@ describe('Functions: envUtils', () => { }); it('returns the default value if the environment variable is not set', () => { + // Prepare + process.env.TEST_ENV = undefined; + // Act const result = getStringFromEnv({ key: 'TEST_ENV', @@ -39,6 +43,9 @@ describe('Functions: envUtils', () => { }); it('throws an error if the environment variable is not set', () => { + // Prepare + process.env.TEST_ENV = undefined; + // Act & Assess expect(() => getStringFromEnv({ key: 'TEST_ENV' })).toThrowError( 'Environment variable TEST_ENV is required' @@ -47,7 +54,7 @@ describe('Functions: envUtils', () => { it('returns the trimmed value of the environment variable', () => { // Prepare - vi.stubEnv('TEST_ENV', ' testValue '); + process.env.TEST_ENV = ' testValue '; // Act const result = getStringFromEnv({ key: 'TEST_ENV' }); @@ -57,6 +64,9 @@ describe('Functions: envUtils', () => { }); it('uses the provided error message if the environment variable is not set', () => { + // Prepare + process.env.TEST_ENV = undefined; + // Act & Assess expect(() => getStringFromEnv({ @@ -70,7 +80,7 @@ describe('Functions: envUtils', () => { describe('Function: getNumberFromEnv', () => { it('returns the value of the environment variable as a number', () => { // Prepare - vi.stubEnv('TEST_ENV', '123'); + process.env.TEST_ENV = '123'; // Act const result = getNumberFromEnv({ key: 'TEST_ENV' }); @@ -80,6 +90,9 @@ describe('Functions: envUtils', () => { }); it('returns the default value if the environment variable is not set', () => { + // Prepare + process.env.TEST_ENV = undefined; + // Act const result = getNumberFromEnv({ key: 'TEST_ENV', @@ -92,7 +105,7 @@ describe('Functions: envUtils', () => { it('throws an error if the environment variable is not a number', () => { // Prepare - vi.stubEnv('TEST_ENV', 'notANumber'); + process.env.TEST_ENV = 'notANumber'; // Act & Assess expect(() => getNumberFromEnv({ key: 'TEST_ENV' })).toThrowError( @@ -104,7 +117,7 @@ describe('Functions: envUtils', () => { describe('Function: getBooleanFromEnv', () => { it('returns true if the environment variable is set to a truthy value', () => { // Prepare - vi.stubEnv('TEST_ENV', 'true'); + process.env.TEST_ENV = 'true'; // Act const result = getBooleanFromEnv({ key: 'TEST_ENV' }); @@ -115,7 +128,7 @@ describe('Functions: envUtils', () => { it('returns false if the environment variable is set to a falsy value', () => { // Prepare - vi.stubEnv('TEST_ENV', 'false'); + process.env.TEST_ENV = 'false'; // Act const result = getBooleanFromEnv({ key: 'TEST_ENV' }); @@ -125,6 +138,9 @@ describe('Functions: envUtils', () => { }); it('returns the default value if the environment variable is not set', () => { + // Prepare + process.env.TEST_ENV = undefined; + // Act const result = getBooleanFromEnv({ key: 'TEST_ENV', @@ -137,7 +153,7 @@ describe('Functions: envUtils', () => { it('throws an error if the environment variable value is not a boolean', () => { // Prepare - vi.stubEnv('TEST_ENV', 'notABoolean'); + process.env.TEST_ENV = 'notABoolean'; // Act & Assess expect(() => getBooleanFromEnv({ key: 'TEST_ENV' })).toThrowError( @@ -156,7 +172,7 @@ describe('Functions: envUtils', () => { 'returns true if the environment variable is set to a truthy value: %s', (value, expected) => { // Prepare - vi.stubEnv('TEST_ENV', value); + process.env.TEST_ENV = value; // Act const result = getBooleanFromEnv({ @@ -180,7 +196,7 @@ describe('Functions: envUtils', () => { 'returns false if the environment variable is set to a falsy value: %s', (value, expected) => { // Prepare - vi.stubEnv('TEST_ENV', value); + process.env.TEST_ENV = value; // Act const result = getBooleanFromEnv({ @@ -197,7 +213,7 @@ describe('Functions: envUtils', () => { describe('Function: isDevMode', () => { it('returns true if the environment variable is set to a truthy value', () => { // Prepare - vi.stubEnv('POWERTOOLS_DEV', 'true'); + process.env.POWERTOOLS_DEV = 'true'; // Act const result = isDevMode(); @@ -208,7 +224,7 @@ describe('Functions: envUtils', () => { it('returns false if the environment variable is set to a falsy value', () => { // Prepare - vi.stubEnv('POWERTOOLS_DEV', 'false'); + process.env.POWERTOOLS_DEV = 'false'; // Act const result = isDevMode(); @@ -218,6 +234,9 @@ describe('Functions: envUtils', () => { }); it('returns false if the environment variable is not set', () => { + // Prepare + process.env.POWERTOOLS_DEV = undefined; + // Act const result = isDevMode(); @@ -229,7 +248,7 @@ describe('Functions: envUtils', () => { describe('Function: getServiceName', () => { it('returns the service name from the environment variable', () => { // Prepare - vi.stubEnv('POWERTOOLS_SERVICE_NAME', 'testService'); + process.env.POWERTOOLS_SERVICE_NAME = 'testService'; // Act const result = getServiceName(); @@ -240,7 +259,7 @@ describe('Functions: envUtils', () => { it('returns an empty string if the environment variable is not set', () => { // Prepare - vi.stubEnv('POWERTOOLS_SERVICE_NAME', undefined); + process.env.POWERTOOLS_SERVICE_NAME = undefined; // Act const result = getServiceName(); @@ -251,135 +270,64 @@ describe('Functions: envUtils', () => { }); describe('Function: getXrayTraceIdFromEnv', () => { - it.each<{ description: string; traceData: string; expected: string }>([ - { - description: - 'returns the value of the environment variable _X_AMZN_TRACE_ID', - traceData: 'abcd123456789', - expected: 'abcd123456789', - }, - { - description: - 'returns the value of the Root X-Ray segment ID properly formatted', - traceData: - 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1', - expected: '1-5759e988-bd862e3fe1be46a994272793', - }, - ])('$description', ({ traceData, expected }) => { + it('returns the value of the environment variable _X_AMZN_TRACE_ID', () => { // Prepare - vi.stubEnv('_X_AMZN_TRACE_ID', traceData); + process.env._X_AMZN_TRACE_ID = 'abcd123456789'; // Act const value = getXRayTraceIdFromEnv(); // Assess - expect(value).toEqual(expected); + expect(value).toEqual('abcd123456789'); }); - it.each<{ description: string; traceData: string; expected: string }>([ - { - description: 'returns trace id from async context', - traceData: 'xyz987654321', - expected: 'xyz987654321', - }, - { - description: - 'returns the Root X-Ray segment ID properly formatted from async context', - traceData: - 'Root=1-6849f099-ce973f4ea2c57e4f9a382904;Parent=668bfc7d9aa5b120;Sampled=0', - expected: '1-6849f099-ce973f4ea2c57e4f9a382904', - }, - ])('$description', ({ traceData, expected }) => { - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: traceData, - }, - () => { - // Act - const value = getXRayTraceIdFromEnv(); - - // Assess - expect(value).toEqual(expected); - } - ); + it('returns the value of the Root X-Ray segment ID properly formatted', () => { + // Prepare + process.env._X_AMZN_TRACE_ID = + 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; + + // Act + const value = getXRayTraceIdFromEnv(); + + // Assess + expect(value).toEqual('1-5759e988-bd862e3fe1be46a994272793'); }); }); describe('Function: isRequestXRaySampled', () => { - it.each<{ - description: string; - traceData: string | undefined; - expected: boolean; - }>([ - { - description: - 'returns true if the Sampled flag is set in the _X_AMZN_TRACE_ID environment variable', - traceData: - 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1', - expected: true, - }, - { - description: - 'returns false if the Sampled flag is not set in the _X_AMZN_TRACE_ID environment variable', - traceData: - 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047', - expected: false, - }, - { - description: - 'returns false when no _X_AMZN_TRACE_ID environment variable is present', - traceData: undefined, - expected: false, - }, - ])('$description', ({ traceData, expected }) => { + it('returns true if the Sampled flag is set in the _X_AMZN_TRACE_ID environment variable', () => { // Prepare - vi.stubEnv('_X_AMZN_TRACE_ID', traceData); + process.env._X_AMZN_TRACE_ID = + 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; // Act const value = isRequestXRaySampled(); // Assess - expect(value).toEqual(expected); + expect(value).toEqual(true); }); - it.each<{ - description: string; - traceData: string | undefined; - expected: boolean; - }>([ - { - description: - 'returns true if the Sampled flag is set from async context', - traceData: - 'Root=1-7a5bc3d2-ef456789abcdef012345678;Parent=9f8e7d6c5b4a3210;Sampled=1', - expected: true, - }, - { - description: - 'returns false if the Sampled flag is not set from async context', - traceData: - 'Root=1-8b6cd4e3-fg567890bcdefg123456789;Parent=0g9f8e7d6c5b4321', - expected: false, - }, - { - description: - 'returns false when no trace ID is present in async context', - traceData: undefined, - expected: false, - }, - ])('$description', ({ traceData, expected }) => { - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: traceData, - }, - () => { - // Act - const value = isRequestXRaySampled(); - - // Assess - expect(value).toEqual(expected); - } - ); + it('returns false if the Sampled flag is not set in the _X_AMZN_TRACE_ID environment variable', () => { + // Prepare + process.env._X_AMZN_TRACE_ID = + 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047'; + + // Act + const value = isRequestXRaySampled(); + + // Assess + expect(value).toEqual(false); + }); + + it('returns false when no _X_AMZN_TRACE_ID environment variable is present', () => { + // Prepare + process.env._X_AMZN_TRACE_ID = undefined; + + // Act + const value = isRequestXRaySampled(); + + // Assess + expect(value).toEqual(false); }); }); }); From dcae2e9d168708cf3f1ee3c54fbcdbaff1a19f00 Mon Sep 17 00:00:00 2001 From: svozza Date: Thu, 23 Oct 2025 11:17:25 +0100 Subject: [PATCH 3/3] chore: regenerate package-lock.json after reverts --- package-lock.json | 168 ++++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 81 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb6c1e1f3d..5c7d649fd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,10 +29,10 @@ "devDependencies": { "@biomejs/biome": "^2.2.6", "@types/aws-lambda": "^8.10.156", - "@types/node": "^24.8.1", + "@types/node": "^24.9.1", "@vitest/coverage-v8": "^3.2.4", "husky": "^9.1.7", - "lint-staged": "^16.2.4", + "lint-staged": "^16.2.5", "markdownlint-cli2": "^0.18.1", "middy5": "npm:@middy/core@^5.4.3", "middy6": "npm:@middy/core@^6.0.0", @@ -47,20 +47,20 @@ }, "examples/app": { "name": "powertools-sample-app", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/batch": "^2.27.0", - "@aws-lambda-powertools/idempotency": "^2.27.0", - "@aws-lambda-powertools/logger": "^2.27.0", - "@aws-lambda-powertools/metrics": "^2.27.0", - "@aws-lambda-powertools/parameters": "^2.27.0", - "@aws-lambda-powertools/tracer": "^2.27.0", + "@aws-lambda-powertools/batch": "^2.28.0", + "@aws-lambda-powertools/idempotency": "^2.28.0", + "@aws-lambda-powertools/logger": "^2.28.0", + "@aws-lambda-powertools/metrics": "^2.28.0", + "@aws-lambda-powertools/parameters": "^2.28.0", + "@aws-lambda-powertools/tracer": "^2.28.0", "@aws-sdk/client-ssm": "^3.913.0", "@aws-sdk/lib-dynamodb": "^3.913.0", "@middy/core": "^4.7.0", "@types/aws-lambda": "^8.10.156", - "@types/node": "24.8.1", + "@types/node": "24.9.1", "aws-cdk": "^2.1030.0", "constructs": "^10.4.2", "esbuild": "^0.25.11", @@ -68,8 +68,8 @@ }, "devDependencies": { "@types/aws-lambda": "^8.10.156", - "@types/node": "24.8.1", - "aws-cdk-lib": "^2.219.0", + "@types/node": "24.9.1", + "aws-cdk-lib": "^2.220.0", "constructs": "^10.4.2", "source-map-support": "^0.5.21", "tsx": "^4.20.6", @@ -79,22 +79,22 @@ }, "examples/snippets": { "name": "code-snippets", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { "arktype": "^2.1.23", "valibot": "^1.1.0" }, "devDependencies": { - "@aws-lambda-powertools/batch": "^2.27.0", - "@aws-lambda-powertools/event-handler": "^2.27.0", - "@aws-lambda-powertools/idempotency": "^2.27.0", - "@aws-lambda-powertools/jmespath": "^2.27.0", - "@aws-lambda-powertools/logger": "^2.27.0", - "@aws-lambda-powertools/metrics": "^2.27.0", - "@aws-lambda-powertools/parameters": "^2.27.0", - "@aws-lambda-powertools/parser": "^2.27.0", - "@aws-lambda-powertools/tracer": "^2.27.0", + "@aws-lambda-powertools/batch": "^2.28.0", + "@aws-lambda-powertools/event-handler": "^2.28.0", + "@aws-lambda-powertools/idempotency": "^2.28.0", + "@aws-lambda-powertools/jmespath": "^2.28.0", + "@aws-lambda-powertools/logger": "^2.28.0", + "@aws-lambda-powertools/metrics": "^2.28.0", + "@aws-lambda-powertools/parameters": "^2.28.0", + "@aws-lambda-powertools/parser": "^2.28.0", + "@aws-lambda-powertools/tracer": "^2.28.0", "@aws-sdk/client-appconfigdata": "^3.913.0", "@aws-sdk/client-dynamodb": "^3.913.0", "@aws-sdk/client-secrets-manager": "^3.913.0", @@ -131,11 +131,11 @@ } }, "layers": { - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { "aws-cdk": "^2.1030.0", - "aws-cdk-lib": "^2.219.0", + "aws-cdk-lib": "^2.220.0", "esbuild": "^0.25.11", "tsx": "^4.20.6" }, @@ -5104,12 +5104,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz", - "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==", + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", "dependencies": { - "undici-types": "~7.14.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/promise-retry": { @@ -5686,9 +5686,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.0.tgz", - "integrity": "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6009,9 +6009,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.219.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.219.0.tgz", - "integrity": "sha512-Rq1/f3exfFEWee1znNq8yvR1TuRQ4xQZz3JNkliBW9dFwyrDe7l/dmlAf6DVvB3nuiZAaUS+vh4ua1LZ7Ec8kg==", + "version": "2.220.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.220.0.tgz", + "integrity": "sha512-mOEyPP1ymWiLnSE0xFxWjG00E1DQ5wtbcgKUmtGjxyNdoG/Qret1nDLqE43YGZEbwca43WO/a2LDuSL6+hN7Lg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -6718,9 +6718,9 @@ } }, "node_modules/cli-truncate": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.0.tgz", - "integrity": "sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { @@ -8048,14 +8048,14 @@ } }, "node_modules/lint-staged": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.4.tgz", - "integrity": "sha512-Pkyr/wd90oAyXk98i/2KwfkIhoYQUMtss769FIT9hFM5ogYZwrk+GRE46yKXSg2ZGhcJ1p38Gf5gmI5Ohjg2yg==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, "license": "MIT", "dependencies": { "commander": "^14.0.1", - "listr2": "^9.0.4", + "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", @@ -8073,9 +8073,9 @@ } }, "node_modules/listr2": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.4.tgz", - "integrity": "sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { @@ -10104,9 +10104,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -10173,9 +10173,9 @@ } }, "node_modules/vite": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", - "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -10513,9 +10513,9 @@ } }, "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", - "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -10671,21 +10671,21 @@ }, "packages/batch": { "name": "@aws-lambda-powertools/batch", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { - "@aws-lambda-powertools/parser": "2.27.0", + "@aws-lambda-powertools/parser": "2.28.0", "@aws-lambda-powertools/testing-utils": "file:../testing", "zod": "^4.1.12" } }, "packages/commons": { "name": "@aws-lambda-powertools/commons", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing" @@ -10693,19 +10693,19 @@ }, "packages/event-handler": { "name": "@aws-lambda-powertools/event-handler", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0" + "@aws-lambda-powertools/commons": "2.28.0" } }, "packages/idempotency": { "name": "@aws-lambda-powertools/idempotency", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", - "@aws-lambda-powertools/jmespath": "2.27.0" + "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/jmespath": "2.28.0" }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing", @@ -10740,18 +10740,18 @@ }, "packages/jmespath": { "name": "@aws-lambda-powertools/jmespath", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0" + "@aws-lambda-powertools/commons": "2.28.0" } }, "packages/kafka": { "name": "@aws-lambda-powertools/kafka", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { @@ -10778,10 +10778,10 @@ }, "packages/logger": { "name": "@aws-lambda-powertools/logger", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", "lodash.merge": "^4.6.2" }, "devDependencies": { @@ -10789,7 +10789,7 @@ "@types/lodash.merge": "^4.6.9" }, "peerDependencies": { - "@aws-lambda-powertools/jmespath": "2.27.0", + "@aws-lambda-powertools/jmespath": "2.28.0", "@middy/core": "4.x || 5.x || 6.x" }, "peerDependenciesMeta": { @@ -10803,7 +10803,7 @@ }, "packages/metrics": { "name": "@aws-lambda-powertools/metrics", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { "@aws-lambda-powertools/commons": "2.27.0" @@ -10823,12 +10823,18 @@ } } }, + "packages/metrics/node_modules/@aws-lambda-powertools/commons": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@aws-lambda-powertools/commons/-/commons-2.27.0.tgz", + "integrity": "sha512-LjrMrSNcAOUw5g9frev4J0NEs3B7AU0uemB0BXbNLAHrYUdP2ls+ncohwNYVVN+DwjfpYj+FYjdRhUlmWzs8Cw==", + "license": "MIT-0" + }, "packages/parameters": { "name": "@aws-lambda-powertools/parameters", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0" + "@aws-lambda-powertools/commons": "2.28.0" }, "devDependencies": { "@aws-lambda-powertools/testing-utils": "file:../testing", @@ -10871,10 +10877,10 @@ }, "packages/parser": { "name": "@aws-lambda-powertools/parser", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", "@standard-schema/spec": "^1.0.0" }, "devDependencies": { @@ -10895,13 +10901,13 @@ }, "packages/testing": { "name": "@aws-lambda-powertools/testing-utils", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { "@aws-cdk/toolkit-lib": "^1.10.0", "@aws-sdk/client-lambda": "^3.913.0", "@smithy/util-utf8": "^4.0.0", - "aws-cdk-lib": "^2.219.0", + "aws-cdk-lib": "^2.220.0", "esbuild": "^0.25.11", "promise-retry": "^2.0.1" }, @@ -10912,10 +10918,10 @@ }, "packages/tracer": { "name": "@aws-lambda-powertools/tracer", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", "aws-xray-sdk-core": "^3.11.0" }, "devDependencies": { @@ -10934,11 +10940,11 @@ }, "packages/validation": { "name": "@aws-lambda-powertools/validation", - "version": "2.27.0", + "version": "2.28.0", "license": "MIT-0", "dependencies": { - "@aws-lambda-powertools/commons": "2.27.0", - "@aws-lambda-powertools/jmespath": "2.27.0", + "@aws-lambda-powertools/commons": "2.28.0", + "@aws-lambda-powertools/jmespath": "2.28.0", "ajv": "^8.17.1" } }