From 0ccfb91287ba57bc4104fdbd587e4b8868553491 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 12 Nov 2025 16:10:41 +0000 Subject: [PATCH 01/26] feat: multi base --- src/invoke-store.spec.ts | 2 +- src/invoke-store.ts | 195 ++++++------ yarn.lock | 637 +++++++++++++++++++-------------------- 3 files changed, 427 insertions(+), 407 deletions(-) diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index bff52e5..f7536f5 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -19,7 +19,7 @@ describe("InvokeStore", () => { const isolateTasks = Promise.all([ InvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 9cda776..955b89f 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -1,6 +1,10 @@ -import { AsyncLocalStorage } from "async_hooks"; +declare global { + var awslambda: { + InvokeStore?: InvokeStoreBase; + [key: string]: unknown; + }; +} -// AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA provides an escape hatch since we're modifying the global object which may not be expected to a customer's handler. const noGlobalAwsLambda = process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "1" || process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "true"; @@ -9,118 +13,139 @@ if (!noGlobalAwsLambda) { globalThis.awslambda = globalThis.awslambda || {}; } +interface Context { + [key: string]: unknown; + [key: symbol]: unknown; +} + const PROTECTED_KEYS = { REQUEST_ID: Symbol("_AWS_LAMBDA_REQUEST_ID"), X_RAY_TRACE_ID: Symbol("_AWS_LAMBDA_X_RAY_TRACE_ID"), TENANT_ID: Symbol("_AWS_LAMBDA_TENANT_ID"), } as const; -/** - * Generic store context that uses protected keys for Lambda fields - * and allows custom user properties - */ -export interface InvokeStoreContext { - [key: string | symbol]: unknown; +abstract class InvokeStoreBase { + readonly PROTECTED_KEYS = PROTECTED_KEYS; + + abstract getContext(): Context | undefined; + abstract hasContext(): boolean; + abstract get(key: string | symbol): T | undefined; + abstract set(key: string | symbol, value: T): void; + abstract run(context: Context, fn: () => T): T; + + protected isProtectedKey(key: string | symbol): boolean { + return Object.values(PROTECTED_KEYS).includes(key as symbol); + } + + public getRequestId(): string | undefined { + return this.get(PROTECTED_KEYS.REQUEST_ID) ?? "-"; + } + + public getXRayTraceId(): string | undefined { + return this.get(PROTECTED_KEYS.X_RAY_TRACE_ID); + } + + public getTenantId(): string | undefined { + return this.get(PROTECTED_KEYS.TENANT_ID); + } } -/** - * InvokeStore implementation class - */ -class InvokeStoreImpl { - private static storage = new AsyncLocalStorage(); - - // Protected keys for Lambda context fields - public static readonly PROTECTED_KEYS = PROTECTED_KEYS; - - /** - * Initialize and run code within an invoke context - */ - public static run( - context: InvokeStoreContext, - fn: () => T | Promise, - ): T | Promise { - return this.storage.run({ ...context }, fn); - } - - /** - * Get the complete current context - */ - public static getContext(): InvokeStoreContext | undefined { - return this.storage.getStore(); - } - - /** - * Get a specific value from the context by key - */ - public static get(key: string | symbol): T | undefined { - const context = this.storage.getStore(); - return context?.[key] as T | undefined; - } - - /** - * Set a custom value in the current context - * Protected Lambda context fields cannot be overwritten - */ - public static set(key: string | symbol, value: unknown): void { +class InvokeStoreSingle extends InvokeStoreBase { + private currentContext?: Context; + + getContext(): Context | undefined { + return this.currentContext; + } + + hasContext(): boolean { + return this.currentContext !== undefined; + } + + get(key: string | symbol): T | undefined { + return this.currentContext?.[key] as T | undefined; + } + + set(key: string | symbol, value: T): void { if (this.isProtectedKey(key)) { - throw new Error(`Cannot modify protected Lambda context field`); + throw new Error(`Cannot modify protected Lambda context field: ${String(key)}`); + } + + if (!this.currentContext) { + this.currentContext = {}; } + + this.currentContext[key] = value; + } - const context = this.storage.getStore(); - if (context) { - context[key] = value; + run(context: Context, fn: () => T): T { + this.currentContext = context; + try { + return fn(); + } finally { + this.currentContext = undefined; } } - /** - * Get the current request ID - */ - public static getRequestId(): string { - return this.get(this.PROTECTED_KEYS.REQUEST_ID) ?? "-"; +} + +class InvokeStoreMulti extends InvokeStoreBase { + private als!: import("node:async_hooks").AsyncLocalStorage; + + constructor() { + super(); + const asyncHooks = require('node:async_hooks') as typeof import("node:async_hooks"); + this.als = new asyncHooks.AsyncLocalStorage(); + this.getRequestId = super.getRequestId; + this.getXRayTraceId = super.getXRayTraceId; + this.getTenantId = super.getTenantId; + } + + getContext(): Context | undefined { + return this.als.getStore(); } - /** - * Get the current X-ray trace ID - */ - public static getXRayTraceId(): string | undefined { - return this.get(this.PROTECTED_KEYS.X_RAY_TRACE_ID); + hasContext(): boolean { + return this.als.getStore() !== undefined; } - /** - * Get the current tenant ID - */ - public static getTenantId(): string | undefined { - return this.get(this.PROTECTED_KEYS.TENANT_ID); + get(key: string | symbol): T | undefined { + return this.als.getStore()?.[key] as T | undefined; } - /** - * Check if we're currently within an invoke context - */ - public static hasContext(): boolean { - return this.storage.getStore() !== undefined; + set(key: string | symbol, value: T): void { + if (this.isProtectedKey(key)) { + throw new Error(`Cannot modify protected Lambda context field: ${String(key)}`); + } + + const store = this.als.getStore(); + if (!store) { + throw new Error('No context available'); + } + + store[key] = value; } - /** - * Check if a key is protected (readonly Lambda context field) - */ - private static isProtectedKey(key: string | symbol): boolean { - return ( - key === this.PROTECTED_KEYS.REQUEST_ID || - key === this.PROTECTED_KEYS.X_RAY_TRACE_ID - ); + run(context: Context, fn: () => T): T { + return this.als.run(context, fn); } } -let instance: typeof InvokeStoreImpl; +const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (process.env ?? {}); +const InvokeStoreImpl = isMulti ? InvokeStoreMulti : InvokeStoreSingle; -if (!noGlobalAwsLambda && globalThis.awslambda?.InvokeStore) { - instance = globalThis.awslambda.InvokeStore; -} else { - instance = InvokeStoreImpl; +const createInvokeStore = (): InvokeStoreBase => { + if (!noGlobalAwsLambda && globalThis.awslambda?.InvokeStore) { + return globalThis.awslambda.InvokeStore; + } + const instance = new InvokeStoreImpl(); + if (!noGlobalAwsLambda && globalThis.awslambda) { globalThis.awslambda.InvokeStore = instance; } + + return instance; } -export const InvokeStore = instance; +const log = createInvokeStore(); +export const InvokeStore = log; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 89f4ead..935a448 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,200 +262,216 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/aix-ppc64@npm:0.25.11" +"@esbuild/aix-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/aix-ppc64@npm:0.25.12" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/android-arm64@npm:0.25.11" +"@esbuild/android-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm64@npm:0.25.12" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/android-arm@npm:0.25.11" +"@esbuild/android-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm@npm:0.25.12" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/android-x64@npm:0.25.11" +"@esbuild/android-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-x64@npm:0.25.12" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/darwin-arm64@npm:0.25.11" +"@esbuild/darwin-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-arm64@npm:0.25.12" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/darwin-x64@npm:0.25.11" +"@esbuild/darwin-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-x64@npm:0.25.12" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/freebsd-arm64@npm:0.25.11" +"@esbuild/freebsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-arm64@npm:0.25.12" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/freebsd-x64@npm:0.25.11" +"@esbuild/freebsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-x64@npm:0.25.12" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-arm64@npm:0.25.11" +"@esbuild/linux-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm64@npm:0.25.12" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-arm@npm:0.25.11" +"@esbuild/linux-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm@npm:0.25.12" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-ia32@npm:0.25.11" +"@esbuild/linux-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ia32@npm:0.25.12" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-loong64@npm:0.25.11" +"@esbuild/linux-loong64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-loong64@npm:0.25.12" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-mips64el@npm:0.25.11" +"@esbuild/linux-mips64el@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-mips64el@npm:0.25.12" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-ppc64@npm:0.25.11" +"@esbuild/linux-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ppc64@npm:0.25.12" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-riscv64@npm:0.25.11" +"@esbuild/linux-riscv64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-riscv64@npm:0.25.12" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-s390x@npm:0.25.11" +"@esbuild/linux-s390x@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-s390x@npm:0.25.12" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/linux-x64@npm:0.25.11" +"@esbuild/linux-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-x64@npm:0.25.12" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/netbsd-arm64@npm:0.25.11" +"@esbuild/netbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-arm64@npm:0.25.12" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/netbsd-x64@npm:0.25.11" +"@esbuild/netbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-x64@npm:0.25.12" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/openbsd-arm64@npm:0.25.11" +"@esbuild/openbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-arm64@npm:0.25.12" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/openbsd-x64@npm:0.25.11" +"@esbuild/openbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-x64@npm:0.25.12" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/openharmony-arm64@npm:0.25.11" +"@esbuild/openharmony-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openharmony-arm64@npm:0.25.12" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/sunos-x64@npm:0.25.11" +"@esbuild/sunos-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/sunos-x64@npm:0.25.12" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/win32-arm64@npm:0.25.11" +"@esbuild/win32-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-arm64@npm:0.25.12" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/win32-ia32@npm:0.25.11" +"@esbuild/win32-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-ia32@npm:0.25.12" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.11": - version: 0.25.11 - resolution: "@esbuild/win32-x64@npm:0.25.11" +"@esbuild/win32-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-x64@npm:0.25.12" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@inquirer/external-editor@npm:^1.0.0": - version: 1.0.2 - resolution: "@inquirer/external-editor@npm:1.0.2" + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" dependencies: - chardet: "npm:^2.1.0" + chardet: "npm:^2.1.1" iconv-lite: "npm:^0.7.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/414a3a2a9733459c57452d84ef19ff002222303d19041580685681153132d2a30af8f90f269b3967c30c670fa689dbb7d4fc25a86dc66f029eebe90dc7467b0a + checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 languageName: node linkType: hard @@ -542,16 +558,16 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" + lru-cache: "npm:^11.2.1" socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 languageName: node linkType: hard @@ -564,13 +580,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - "@rollup/plugin-node-resolve@npm:^16.0.3": version: 16.0.3 resolution: "@rollup/plugin-node-resolve@npm:16.0.3" @@ -624,156 +633,156 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.52.5" +"@rollup/rollup-android-arm-eabi@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-android-arm64@npm:4.52.5" +"@rollup/rollup-android-arm64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-android-arm64@npm:4.53.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-darwin-arm64@npm:4.52.5" +"@rollup/rollup-darwin-arm64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.53.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-darwin-x64@npm:4.52.5" +"@rollup/rollup-darwin-x64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.53.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.52.5" +"@rollup/rollup-freebsd-arm64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-freebsd-x64@npm:4.52.5" +"@rollup/rollup-freebsd-x64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.53.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.52.5" +"@rollup/rollup-linux-arm-musleabihf@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.52.5" +"@rollup/rollup-linux-arm64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.52.5" +"@rollup/rollup-linux-arm64-musl@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.5" +"@rollup/rollup-linux-loong64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.2" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.52.5" +"@rollup/rollup-linux-ppc64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.52.5" +"@rollup/rollup-linux-riscv64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.52.5" +"@rollup/rollup-linux-riscv64-musl@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.52.5" +"@rollup/rollup-linux-s390x-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.52.5" +"@rollup/rollup-linux-x64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.52.5" +"@rollup/rollup-linux-x64-musl@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.52.5" +"@rollup/rollup-openharmony-arm64@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.2" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.52.5" +"@rollup/rollup-win32-arm64-msvc@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.52.5" +"@rollup/rollup-win32-ia32-msvc@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.5" +"@rollup/rollup-win32-x64-gnu@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.52.5": - version: 4.52.5 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.52.5" +"@rollup/rollup-win32-x64-msvc@npm:4.53.2": + version: 4.53.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -915,10 +924,10 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^3.0.0": - version: 3.0.1 - resolution: "abbrev@npm:3.0.1" - checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 languageName: node linkType: hard @@ -989,13 +998,6 @@ __metadata: languageName: node linkType: hard -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - "better-path-resolve@npm:1.0.0": version: 1.0.0 resolution: "better-path-resolve@npm:1.0.0" @@ -1005,15 +1007,6 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.1": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf - languageName: node - linkType: hard - "braces@npm:^3.0.3": version: 3.0.3 resolution: "braces@npm:3.0.3" @@ -1030,23 +1023,22 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" +"cacache@npm:^20.0.1": + version: 20.0.1 + resolution: "cacache@npm:20.0.1" dependencies: "@npmcli/fs": "npm:^4.0.0" fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" + glob: "npm:^11.0.3" + lru-cache: "npm:^11.1.0" minipass: "npm:^7.0.3" minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" p-map: "npm:^7.0.2" ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" unique-filename: "npm:^4.0.0" - checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + checksum: 10c0/e3efcf3af1c984e6e59e03372d9289861736a572e6e05b620606b87a67e71d04cff6dbc99607801cb21bcaae1fb4fb84d4cc8e3fda725e95881329ef03dac602 languageName: node linkType: hard @@ -1063,10 +1055,10 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.0": - version: 2.1.0 - resolution: "chardet@npm:2.1.0" - checksum: 10c0/d1b03e47371851ed72741a898281d58f8a9b577aeea6fdfa75a86832898b36c550b3ad057e66d50d774a9cebd9f56c66b6880e4fe75e387794538ba7565b0b6f +"chardet@npm:^2.1.1": + version: 2.1.1 + resolution: "chardet@npm:2.1.1" + checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 languageName: node linkType: hard @@ -1200,10 +1192,10 @@ __metadata: languageName: node linkType: hard -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 +"env-paths@npm:^3.0.0": + version: 3.0.0 + resolution: "env-paths@npm:3.0.0" + checksum: 10c0/76dec878cee47f841103bacd7fae03283af16f0702dad65102ef0a556f310b98a377885e0f32943831eb08b5ab37842a323d02529f3dfd5d0a40ca71b01b435f languageName: node linkType: hard @@ -1222,35 +1214,35 @@ __metadata: linkType: hard "esbuild@npm:^0.25.0": - version: 0.25.11 - resolution: "esbuild@npm:0.25.11" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.11" - "@esbuild/android-arm": "npm:0.25.11" - "@esbuild/android-arm64": "npm:0.25.11" - "@esbuild/android-x64": "npm:0.25.11" - "@esbuild/darwin-arm64": "npm:0.25.11" - "@esbuild/darwin-x64": "npm:0.25.11" - "@esbuild/freebsd-arm64": "npm:0.25.11" - "@esbuild/freebsd-x64": "npm:0.25.11" - "@esbuild/linux-arm": "npm:0.25.11" - "@esbuild/linux-arm64": "npm:0.25.11" - "@esbuild/linux-ia32": "npm:0.25.11" - "@esbuild/linux-loong64": "npm:0.25.11" - "@esbuild/linux-mips64el": "npm:0.25.11" - "@esbuild/linux-ppc64": "npm:0.25.11" - "@esbuild/linux-riscv64": "npm:0.25.11" - "@esbuild/linux-s390x": "npm:0.25.11" - "@esbuild/linux-x64": "npm:0.25.11" - "@esbuild/netbsd-arm64": "npm:0.25.11" - "@esbuild/netbsd-x64": "npm:0.25.11" - "@esbuild/openbsd-arm64": "npm:0.25.11" - "@esbuild/openbsd-x64": "npm:0.25.11" - "@esbuild/openharmony-arm64": "npm:0.25.11" - "@esbuild/sunos-x64": "npm:0.25.11" - "@esbuild/win32-arm64": "npm:0.25.11" - "@esbuild/win32-ia32": "npm:0.25.11" - "@esbuild/win32-x64": "npm:0.25.11" + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -1306,7 +1298,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/7f819b16a9f502091ddc6e1855291eaa5ede32c2b792cd8a8a60cc24faee469e3c7b607e2f22ea8684eb7c7bc377b2509e9f1cd50f10b3bf5042d1e9e4234be3 + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b languageName: node linkType: hard @@ -1410,7 +1402,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": +"foreground-child@npm:^3.3.1": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -1486,19 +1478,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2": - version: 10.4.5 - resolution: "glob@npm:10.4.5" +"glob@npm:^11.0.3": + version: 11.0.3 + resolution: "glob@npm:11.0.3" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.0.3" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" + path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 languageName: node linkType: hard @@ -1601,9 +1593,9 @@ __metadata: linkType: hard "ip-address@npm:^10.0.1": - version: 10.0.1 - resolution: "ip-address@npm:10.0.1" - checksum: 10c0/1634d79dae18394004775cb6d699dc46b7c23df6d2083164025a2b15240c1164fccde53d0e08bd5ee4fc53913d033ab6b5e395a809ad4b956a940c446e948843 + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 languageName: node linkType: hard @@ -1683,16 +1675,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" dependencies: "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 languageName: node linkType: hard @@ -1750,10 +1738,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.2 + resolution: "lru-cache@npm:11.2.2" + checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d languageName: node linkType: hard @@ -1766,12 +1754,12 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" +"make-fetch-happen@npm:^15.0.0": + version: 15.0.2 + resolution: "make-fetch-happen@npm:15.0.2" dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" http-cache-semantics: "npm:^4.1.1" minipass: "npm:^7.0.2" minipass-fetch: "npm:^4.0.0" @@ -1781,7 +1769,7 @@ __metadata: proc-log: "npm:^5.0.0" promise-retry: "npm:^2.0.1" ssri: "npm:^12.0.0" - checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + checksum: 10c0/3cc9b4e71bba88bcec53f5307f9c3096c6193a2357e825bf3a3a03c99896d2fa14abba8363a84199829dade639e85dc0eb07de77d247aa249d13ff80511adf2c languageName: node linkType: hard @@ -1802,12 +1790,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" +"minimatch@npm:^10.0.3": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 languageName: node linkType: hard @@ -1871,7 +1859,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 @@ -1918,33 +1906,33 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 11.5.0 - resolution: "node-gyp@npm:11.5.0" + version: 12.0.0 + resolution: "node-gyp@npm:12.0.0" dependencies: - env-paths: "npm:^2.2.0" + env-paths: "npm:^3.0.0" exponential-backoff: "npm:^3.1.1" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" semver: "npm:^7.3.5" - tar: "npm:^7.4.3" + tar: "npm:^7.5.2" tinyglobby: "npm:^0.2.12" - which: "npm:^5.0.0" + which: "npm:^6.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/31ff49586991b38287bb15c3d529dd689cfc32f992eed9e6997b9d712d5d21fe818a8b1bbfe3b76a7e33765c20210c5713212f4aa329306a615b87d8a786da3a + checksum: 10c0/74ff7eecc123896875290c7516627bd5b1d49868b9491897a1b3562145d49503e747338d2aeda44e36e056fb9cb27ef1231df4e21f5737e188455b1df7fde562 languageName: node linkType: hard -"nopt@npm:^8.0.0": - version: 8.1.0 - resolution: "nopt@npm:8.1.0" +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" dependencies: - abbrev: "npm:^3.0.0" + abbrev: "npm:^4.0.0" bin: nopt: bin/nopt.js - checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd languageName: node linkType: hard @@ -1990,9 +1978,9 @@ __metadata: linkType: hard "p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd languageName: node linkType: hard @@ -2040,13 +2028,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 languageName: node linkType: hard @@ -2126,6 +2114,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^6.0.0": + version: 6.0.0 + resolution: "proc-log@npm:6.0.0" + checksum: 10c0/40c5e2b4c55e395a3bd72e38cba9c26e58598a1f4844fa6a115716d5231a0919f46aa8e351147035d91583ad39a794593615078c948bc001fe3beb99276be776 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -2210,31 +2205,31 @@ __metadata: linkType: hard "rollup@npm:^4.43.0, rollup@npm:^4.52.5": - version: 4.52.5 - resolution: "rollup@npm:4.52.5" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.52.5" - "@rollup/rollup-android-arm64": "npm:4.52.5" - "@rollup/rollup-darwin-arm64": "npm:4.52.5" - "@rollup/rollup-darwin-x64": "npm:4.52.5" - "@rollup/rollup-freebsd-arm64": "npm:4.52.5" - "@rollup/rollup-freebsd-x64": "npm:4.52.5" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.52.5" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.52.5" - "@rollup/rollup-linux-arm64-gnu": "npm:4.52.5" - "@rollup/rollup-linux-arm64-musl": "npm:4.52.5" - "@rollup/rollup-linux-loong64-gnu": "npm:4.52.5" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.52.5" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.52.5" - "@rollup/rollup-linux-riscv64-musl": "npm:4.52.5" - "@rollup/rollup-linux-s390x-gnu": "npm:4.52.5" - "@rollup/rollup-linux-x64-gnu": "npm:4.52.5" - "@rollup/rollup-linux-x64-musl": "npm:4.52.5" - "@rollup/rollup-openharmony-arm64": "npm:4.52.5" - "@rollup/rollup-win32-arm64-msvc": "npm:4.52.5" - "@rollup/rollup-win32-ia32-msvc": "npm:4.52.5" - "@rollup/rollup-win32-x64-gnu": "npm:4.52.5" - "@rollup/rollup-win32-x64-msvc": "npm:4.52.5" + version: 4.53.2 + resolution: "rollup@npm:4.53.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.53.2" + "@rollup/rollup-android-arm64": "npm:4.53.2" + "@rollup/rollup-darwin-arm64": "npm:4.53.2" + "@rollup/rollup-darwin-x64": "npm:4.53.2" + "@rollup/rollup-freebsd-arm64": "npm:4.53.2" + "@rollup/rollup-freebsd-x64": "npm:4.53.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.53.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.53.2" + "@rollup/rollup-linux-loong64-gnu": "npm:4.53.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.53.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.53.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.53.2" + "@rollup/rollup-linux-x64-musl": "npm:4.53.2" + "@rollup/rollup-openharmony-arm64": "npm:4.53.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.53.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.53.2" + "@rollup/rollup-win32-x64-gnu": "npm:4.53.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.53.2" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -2286,7 +2281,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/faf1697b305d13a149bb64a2bb7378344becc7c8580f56225c4c00adbf493d82480a44b3e3b1cc82a3ac5d1d4cab6dfc89e6635443895a2dc488969075f5b94d + checksum: 10c0/427216da71c1ce7fefb0bef75f94c301afd858ac27e35898e098c2da5977325fa54c2edda867caf9675c8abfa8d8d94efa99c482fa04f5cd91f3a740112d4f4f languageName: node linkType: hard @@ -2490,16 +2485,16 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.4.3": - version: 7.5.1 - resolution: "tar@npm:7.5.1" +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10c0/0dad0596a61586180981133b20c32cfd93c5863c5b7140d646714e6ea8ec84583b879e5dc3928a4d683be6e6109ad7ea3de1cf71986d5194f81b3a016c8858c9 + checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 languageName: node linkType: hard @@ -2639,8 +2634,8 @@ __metadata: linkType: hard "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": - version: 7.1.12 - resolution: "vite@npm:7.1.12" + version: 7.2.2 + resolution: "vite@npm:7.2.2" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.5.0" @@ -2689,7 +2684,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/cef4d4b4a84e663e09b858964af36e916892ac8540068df42a05ced637ceeae5e9ef71c72d54f3cfc1f3c254af16634230e221b6e2327c2a66d794bb49203262 + checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c languageName: node linkType: hard @@ -2760,14 +2755,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" dependencies: isexe: "npm:^3.1.1" bin: node-which: bin/which.js - checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 languageName: node linkType: hard From 211a404e18ed8a7330a2b10bc38be4bd3ed2d29d Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 12 Nov 2025 16:11:37 +0000 Subject: [PATCH 02/26] feat: fix tess --- src/invoke-store.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index f7536f5..bff52e5 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -19,7 +19,7 @@ describe("InvokeStore", () => { const isolateTasks = Promise.all([ InvokeStore.run( { - [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { From bd3d9b13618cac959614bb4dbde192628f8aa723 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 12 Nov 2025 16:12:24 +0000 Subject: [PATCH 03/26] fix: discard yarn.lock --- yarn.lock | 637 +++++++++++++++++++++++++++--------------------------- 1 file changed, 321 insertions(+), 316 deletions(-) diff --git a/yarn.lock b/yarn.lock index 935a448..89f4ead 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,216 +262,200 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/aix-ppc64@npm:0.25.12" +"@esbuild/aix-ppc64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/aix-ppc64@npm:0.25.11" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm64@npm:0.25.12" +"@esbuild/android-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/android-arm64@npm:0.25.11" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm@npm:0.25.12" +"@esbuild/android-arm@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/android-arm@npm:0.25.11" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-x64@npm:0.25.12" +"@esbuild/android-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/android-x64@npm:0.25.11" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-arm64@npm:0.25.12" +"@esbuild/darwin-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/darwin-arm64@npm:0.25.11" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-x64@npm:0.25.12" +"@esbuild/darwin-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/darwin-x64@npm:0.25.11" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-arm64@npm:0.25.12" +"@esbuild/freebsd-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/freebsd-arm64@npm:0.25.11" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-x64@npm:0.25.12" +"@esbuild/freebsd-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/freebsd-x64@npm:0.25.11" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm64@npm:0.25.12" +"@esbuild/linux-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-arm64@npm:0.25.11" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm@npm:0.25.12" +"@esbuild/linux-arm@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-arm@npm:0.25.11" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ia32@npm:0.25.12" +"@esbuild/linux-ia32@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-ia32@npm:0.25.11" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-loong64@npm:0.25.12" +"@esbuild/linux-loong64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-loong64@npm:0.25.11" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-mips64el@npm:0.25.12" +"@esbuild/linux-mips64el@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-mips64el@npm:0.25.11" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ppc64@npm:0.25.12" +"@esbuild/linux-ppc64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-ppc64@npm:0.25.11" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-riscv64@npm:0.25.12" +"@esbuild/linux-riscv64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-riscv64@npm:0.25.11" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-s390x@npm:0.25.12" +"@esbuild/linux-s390x@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-s390x@npm:0.25.11" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-x64@npm:0.25.12" +"@esbuild/linux-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/linux-x64@npm:0.25.11" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-arm64@npm:0.25.12" +"@esbuild/netbsd-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/netbsd-arm64@npm:0.25.11" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-x64@npm:0.25.12" +"@esbuild/netbsd-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/netbsd-x64@npm:0.25.11" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-arm64@npm:0.25.12" +"@esbuild/openbsd-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/openbsd-arm64@npm:0.25.11" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-x64@npm:0.25.12" +"@esbuild/openbsd-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/openbsd-x64@npm:0.25.11" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openharmony-arm64@npm:0.25.12" +"@esbuild/openharmony-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/openharmony-arm64@npm:0.25.11" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/sunos-x64@npm:0.25.12" +"@esbuild/sunos-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/sunos-x64@npm:0.25.11" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-arm64@npm:0.25.12" +"@esbuild/win32-arm64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/win32-arm64@npm:0.25.11" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-ia32@npm:0.25.12" +"@esbuild/win32-ia32@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/win32-ia32@npm:0.25.11" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-x64@npm:0.25.12" +"@esbuild/win32-x64@npm:0.25.11": + version: 0.25.11 + resolution: "@esbuild/win32-x64@npm:0.25.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@inquirer/external-editor@npm:^1.0.0": - version: 1.0.3 - resolution: "@inquirer/external-editor@npm:1.0.3" + version: 1.0.2 + resolution: "@inquirer/external-editor@npm:1.0.2" dependencies: - chardet: "npm:^2.1.1" + chardet: "npm:^2.1.0" iconv-lite: "npm:^0.7.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10c0/82951cb7f3762dd78cca2ea291396841e3f4adfe26004b5badfed1cec4b6a04bb567dff94d0e41b35c61bdd7957317c64c22f58074d14b238d44e44d9e420019 - languageName: node - linkType: hard - -"@isaacs/balanced-match@npm:^4.0.1": - version: 4.0.1 - resolution: "@isaacs/balanced-match@npm:4.0.1" - checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 - languageName: node - linkType: hard - -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" - dependencies: - "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + checksum: 10c0/414a3a2a9733459c57452d84ef19ff002222303d19041580685681153132d2a30af8f90f269b3967c30c670fa689dbb7d4fc25a86dc66f029eebe90dc7467b0a languageName: node linkType: hard @@ -558,16 +542,16 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/agent@npm:4.0.0" +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^11.2.1" + lru-cache: "npm:^10.0.1" socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 languageName: node linkType: hard @@ -580,6 +564,13 @@ __metadata: languageName: node linkType: hard +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + "@rollup/plugin-node-resolve@npm:^16.0.3": version: 16.0.3 resolution: "@rollup/plugin-node-resolve@npm:16.0.3" @@ -633,156 +624,156 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.2" +"@rollup/rollup-android-arm-eabi@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.52.5" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-android-arm64@npm:4.53.2" +"@rollup/rollup-android-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-android-arm64@npm:4.52.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.53.2" +"@rollup/rollup-darwin-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-arm64@npm:4.52.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.53.2" +"@rollup/rollup-darwin-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-darwin-x64@npm:4.52.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.2" +"@rollup/rollup-freebsd-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.52.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.53.2" +"@rollup/rollup-freebsd-x64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-freebsd-x64@npm:4.52.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.52.5" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.52.5" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.52.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.2" +"@rollup/rollup-linux-arm64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.52.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.2" +"@rollup/rollup-linux-loong64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.5" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.2" +"@rollup/rollup-linux-ppc64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.52.5" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.52.5" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.52.5" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.52.5" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.2" +"@rollup/rollup-linux-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.52.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.2" +"@rollup/rollup-linux-x64-musl@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.52.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.2" +"@rollup/rollup-openharmony-arm64@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.52.5" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.2" +"@rollup/rollup-win32-arm64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.52.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.52.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.2" +"@rollup/rollup-win32-x64-gnu@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.53.2": - version: 4.53.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.2" +"@rollup/rollup-win32-x64-msvc@npm:4.52.5": + version: 4.52.5 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.52.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -924,10 +915,10 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf languageName: node linkType: hard @@ -998,6 +989,13 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + "better-path-resolve@npm:1.0.0": version: 1.0.0 resolution: "better-path-resolve@npm:1.0.0" @@ -1007,6 +1005,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + "braces@npm:^3.0.3": version: 3.0.3 resolution: "braces@npm:3.0.3" @@ -1023,22 +1030,23 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^20.0.1": - version: 20.0.1 - resolution: "cacache@npm:20.0.1" +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" dependencies: "@npmcli/fs": "npm:^4.0.0" fs-minipass: "npm:^3.0.0" - glob: "npm:^11.0.3" - lru-cache: "npm:^11.1.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" minipass: "npm:^7.0.3" minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" p-map: "npm:^7.0.2" ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" unique-filename: "npm:^4.0.0" - checksum: 10c0/e3efcf3af1c984e6e59e03372d9289861736a572e6e05b620606b87a67e71d04cff6dbc99607801cb21bcaae1fb4fb84d4cc8e3fda725e95881329ef03dac602 + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c languageName: node linkType: hard @@ -1055,10 +1063,10 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.1": - version: 2.1.1 - resolution: "chardet@npm:2.1.1" - checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 +"chardet@npm:^2.1.0": + version: 2.1.0 + resolution: "chardet@npm:2.1.0" + checksum: 10c0/d1b03e47371851ed72741a898281d58f8a9b577aeea6fdfa75a86832898b36c550b3ad057e66d50d774a9cebd9f56c66b6880e4fe75e387794538ba7565b0b6f languageName: node linkType: hard @@ -1192,10 +1200,10 @@ __metadata: languageName: node linkType: hard -"env-paths@npm:^3.0.0": - version: 3.0.0 - resolution: "env-paths@npm:3.0.0" - checksum: 10c0/76dec878cee47f841103bacd7fae03283af16f0702dad65102ef0a556f310b98a377885e0f32943831eb08b5ab37842a323d02529f3dfd5d0a40ca71b01b435f +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 languageName: node linkType: hard @@ -1214,35 +1222,35 @@ __metadata: linkType: hard "esbuild@npm:^0.25.0": - version: 0.25.12 - resolution: "esbuild@npm:0.25.12" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.12" - "@esbuild/android-arm": "npm:0.25.12" - "@esbuild/android-arm64": "npm:0.25.12" - "@esbuild/android-x64": "npm:0.25.12" - "@esbuild/darwin-arm64": "npm:0.25.12" - "@esbuild/darwin-x64": "npm:0.25.12" - "@esbuild/freebsd-arm64": "npm:0.25.12" - "@esbuild/freebsd-x64": "npm:0.25.12" - "@esbuild/linux-arm": "npm:0.25.12" - "@esbuild/linux-arm64": "npm:0.25.12" - "@esbuild/linux-ia32": "npm:0.25.12" - "@esbuild/linux-loong64": "npm:0.25.12" - "@esbuild/linux-mips64el": "npm:0.25.12" - "@esbuild/linux-ppc64": "npm:0.25.12" - "@esbuild/linux-riscv64": "npm:0.25.12" - "@esbuild/linux-s390x": "npm:0.25.12" - "@esbuild/linux-x64": "npm:0.25.12" - "@esbuild/netbsd-arm64": "npm:0.25.12" - "@esbuild/netbsd-x64": "npm:0.25.12" - "@esbuild/openbsd-arm64": "npm:0.25.12" - "@esbuild/openbsd-x64": "npm:0.25.12" - "@esbuild/openharmony-arm64": "npm:0.25.12" - "@esbuild/sunos-x64": "npm:0.25.12" - "@esbuild/win32-arm64": "npm:0.25.12" - "@esbuild/win32-ia32": "npm:0.25.12" - "@esbuild/win32-x64": "npm:0.25.12" + version: 0.25.11 + resolution: "esbuild@npm:0.25.11" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.11" + "@esbuild/android-arm": "npm:0.25.11" + "@esbuild/android-arm64": "npm:0.25.11" + "@esbuild/android-x64": "npm:0.25.11" + "@esbuild/darwin-arm64": "npm:0.25.11" + "@esbuild/darwin-x64": "npm:0.25.11" + "@esbuild/freebsd-arm64": "npm:0.25.11" + "@esbuild/freebsd-x64": "npm:0.25.11" + "@esbuild/linux-arm": "npm:0.25.11" + "@esbuild/linux-arm64": "npm:0.25.11" + "@esbuild/linux-ia32": "npm:0.25.11" + "@esbuild/linux-loong64": "npm:0.25.11" + "@esbuild/linux-mips64el": "npm:0.25.11" + "@esbuild/linux-ppc64": "npm:0.25.11" + "@esbuild/linux-riscv64": "npm:0.25.11" + "@esbuild/linux-s390x": "npm:0.25.11" + "@esbuild/linux-x64": "npm:0.25.11" + "@esbuild/netbsd-arm64": "npm:0.25.11" + "@esbuild/netbsd-x64": "npm:0.25.11" + "@esbuild/openbsd-arm64": "npm:0.25.11" + "@esbuild/openbsd-x64": "npm:0.25.11" + "@esbuild/openharmony-arm64": "npm:0.25.11" + "@esbuild/sunos-x64": "npm:0.25.11" + "@esbuild/win32-arm64": "npm:0.25.11" + "@esbuild/win32-ia32": "npm:0.25.11" + "@esbuild/win32-x64": "npm:0.25.11" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -1298,7 +1306,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + checksum: 10c0/7f819b16a9f502091ddc6e1855291eaa5ede32c2b792cd8a8a60cc24faee469e3c7b607e2f22ea8684eb7c7bc377b2509e9f1cd50f10b3bf5042d1e9e4234be3 languageName: node linkType: hard @@ -1402,7 +1410,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.3.1": +"foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -1478,19 +1486,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.3": - version: 11.0.3 - resolution: "glob@npm:11.0.3" +"glob@npm:^10.2.2": + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.0.3" + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" + path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e languageName: node linkType: hard @@ -1593,9 +1601,9 @@ __metadata: linkType: hard "ip-address@npm:^10.0.1": - version: 10.1.0 - resolution: "ip-address@npm:10.1.0" - checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + version: 10.0.1 + resolution: "ip-address@npm:10.0.1" + checksum: 10c0/1634d79dae18394004775cb6d699dc46b7c23df6d2083164025a2b15240c1164fccde53d0e08bd5ee4fc53913d033ab6b5e395a809ad4b956a940c446e948843 languageName: node linkType: hard @@ -1675,12 +1683,16 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.1.1": - version: 4.1.1 - resolution: "jackspeak@npm:4.1.1" +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" dependencies: "@isaacs/cliui": "npm:^8.0.2" - checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 languageName: node linkType: hard @@ -1738,10 +1750,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.2 - resolution: "lru-cache@npm:11.2.2" - checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb languageName: node linkType: hard @@ -1754,12 +1766,12 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^15.0.0": - version: 15.0.2 - resolution: "make-fetch-happen@npm:15.0.2" +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" dependencies: - "@npmcli/agent": "npm:^4.0.0" - cacache: "npm:^20.0.1" + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" http-cache-semantics: "npm:^4.1.1" minipass: "npm:^7.0.2" minipass-fetch: "npm:^4.0.0" @@ -1769,7 +1781,7 @@ __metadata: proc-log: "npm:^5.0.0" promise-retry: "npm:^2.0.1" ssri: "npm:^12.0.0" - checksum: 10c0/3cc9b4e71bba88bcec53f5307f9c3096c6193a2357e825bf3a3a03c99896d2fa14abba8363a84199829dade639e85dc0eb07de77d247aa249d13ff80511adf2c + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 languageName: node linkType: hard @@ -1790,12 +1802,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.3": - version: 10.1.1 - resolution: "minimatch@npm:10.1.1" +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed languageName: node linkType: hard @@ -1859,7 +1871,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 @@ -1906,33 +1918,33 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 12.0.0 - resolution: "node-gyp@npm:12.0.0" + version: 11.5.0 + resolution: "node-gyp@npm:11.5.0" dependencies: - env-paths: "npm:^3.0.0" + env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^15.0.0" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" semver: "npm:^7.3.5" - tar: "npm:^7.5.2" + tar: "npm:^7.4.3" tinyglobby: "npm:^0.2.12" - which: "npm:^6.0.0" + which: "npm:^5.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/74ff7eecc123896875290c7516627bd5b1d49868b9491897a1b3562145d49503e747338d2aeda44e36e056fb9cb27ef1231df4e21f5737e188455b1df7fde562 + checksum: 10c0/31ff49586991b38287bb15c3d529dd689cfc32f992eed9e6997b9d712d5d21fe818a8b1bbfe3b76a7e33765c20210c5713212f4aa329306a615b87d8a786da3a languageName: node linkType: hard -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" dependencies: - abbrev: "npm:^4.0.0" + abbrev: "npm:^3.0.0" bin: nopt: bin/nopt.js - checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef languageName: node linkType: hard @@ -1978,9 +1990,9 @@ __metadata: linkType: hard "p-map@npm:^7.0.2": - version: 7.0.4 - resolution: "p-map@npm:7.0.4" - checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c languageName: node linkType: hard @@ -2028,13 +2040,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.1 - resolution: "path-scurry@npm:2.0.1" +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" dependencies: - lru-cache: "npm:^11.0.0" - minipass: "npm:^7.1.2" - checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d languageName: node linkType: hard @@ -2114,13 +2126,6 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^6.0.0": - version: 6.0.0 - resolution: "proc-log@npm:6.0.0" - checksum: 10c0/40c5e2b4c55e395a3bd72e38cba9c26e58598a1f4844fa6a115716d5231a0919f46aa8e351147035d91583ad39a794593615078c948bc001fe3beb99276be776 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -2205,31 +2210,31 @@ __metadata: linkType: hard "rollup@npm:^4.43.0, rollup@npm:^4.52.5": - version: 4.53.2 - resolution: "rollup@npm:4.53.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.53.2" - "@rollup/rollup-android-arm64": "npm:4.53.2" - "@rollup/rollup-darwin-arm64": "npm:4.53.2" - "@rollup/rollup-darwin-x64": "npm:4.53.2" - "@rollup/rollup-freebsd-arm64": "npm:4.53.2" - "@rollup/rollup-freebsd-x64": "npm:4.53.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.53.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.53.2" - "@rollup/rollup-linux-loong64-gnu": "npm:4.53.2" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.53.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.53.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.53.2" - "@rollup/rollup-linux-x64-musl": "npm:4.53.2" - "@rollup/rollup-openharmony-arm64": "npm:4.53.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.53.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.53.2" - "@rollup/rollup-win32-x64-gnu": "npm:4.53.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.53.2" + version: 4.52.5 + resolution: "rollup@npm:4.52.5" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.52.5" + "@rollup/rollup-android-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-arm64": "npm:4.52.5" + "@rollup/rollup-darwin-x64": "npm:4.52.5" + "@rollup/rollup-freebsd-arm64": "npm:4.52.5" + "@rollup/rollup-freebsd-x64": "npm:4.52.5" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.52.5" + "@rollup/rollup-linux-arm64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-arm64-musl": "npm:4.52.5" + "@rollup/rollup-linux-loong64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-riscv64-musl": "npm:4.52.5" + "@rollup/rollup-linux-s390x-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-gnu": "npm:4.52.5" + "@rollup/rollup-linux-x64-musl": "npm:4.52.5" + "@rollup/rollup-openharmony-arm64": "npm:4.52.5" + "@rollup/rollup-win32-arm64-msvc": "npm:4.52.5" + "@rollup/rollup-win32-ia32-msvc": "npm:4.52.5" + "@rollup/rollup-win32-x64-gnu": "npm:4.52.5" + "@rollup/rollup-win32-x64-msvc": "npm:4.52.5" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -2281,7 +2286,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/427216da71c1ce7fefb0bef75f94c301afd858ac27e35898e098c2da5977325fa54c2edda867caf9675c8abfa8d8d94efa99c482fa04f5cd91f3a740112d4f4f + checksum: 10c0/faf1697b305d13a149bb64a2bb7378344becc7c8580f56225c4c00adbf493d82480a44b3e3b1cc82a3ac5d1d4cab6dfc89e6635443895a2dc488969075f5b94d languageName: node linkType: hard @@ -2485,16 +2490,16 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.5.2": - version: 7.5.2 - resolution: "tar@npm:7.5.2" +"tar@npm:^7.4.3": + version: 7.5.1 + resolution: "tar@npm:7.5.1" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 + checksum: 10c0/0dad0596a61586180981133b20c32cfd93c5863c5b7140d646714e6ea8ec84583b879e5dc3928a4d683be6e6109ad7ea3de1cf71986d5194f81b3a016c8858c9 languageName: node linkType: hard @@ -2634,8 +2639,8 @@ __metadata: linkType: hard "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": - version: 7.2.2 - resolution: "vite@npm:7.2.2" + version: 7.1.12 + resolution: "vite@npm:7.1.12" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.5.0" @@ -2684,7 +2689,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/9c76ee441f8dbec645ddaecc28d1f9cf35670ffa91cff69af7b1d5081545331603f0b1289d437b2fa8dc43cdc77b4d96b5bd9c9aed66310f490cb1a06f9c814c + checksum: 10c0/cef4d4b4a84e663e09b858964af36e916892ac8540068df42a05ced637ceeae5e9ef71c72d54f3cfc1f3c254af16634230e221b6e2327c2a66d794bb49203262 languageName: node linkType: hard @@ -2755,14 +2760,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^6.0.0": - version: 6.0.0 - resolution: "which@npm:6.0.0" +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" dependencies: isexe: "npm:^3.1.1" bin: node-which: bin/which.js - checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b languageName: node linkType: hard From 6a75ee4853e9a39414a8d1af2c5adba2aed1d992 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 09:47:26 +0000 Subject: [PATCH 04/26] fix: tests --- package.json | 4 +- ...voke-store.global.multiconcurrency.spec.ts | 66 +++ src/invoke-store.global.spec.ts | 361 ++++++------- src/invoke-store.multiconcurrency.spec.ts | 152 ++++++ src/invoke-store.spec.ts | 488 +++++++----------- ...oke-store.timers.multiconcurrency.spec.ts} | 6 +- src/invoke-store.ts | 21 +- 7 files changed, 603 insertions(+), 495 deletions(-) create mode 100644 src/invoke-store.global.multiconcurrency.spec.ts create mode 100644 src/invoke-store.multiconcurrency.spec.ts rename src/{invoke-store.timers.spec.ts => invoke-store.timers.multiconcurrency.spec.ts} (99%) diff --git a/package.json b/package.json index 3631aa5..fa5f2dc 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "build": "yarn clean && yarn build:types && node ./scripts/build-rollup.js", "build:types": "tsc -p tsconfig.types.json", "clean": "rm -rf dist-types dist-cjs dist-es", - "test": "vitest run", + "testMulticoncurrency": "AWS_LAMBDA_MAX_CONCURRENCY=2 vitest run src/*.multiconcurrency.spec.ts --reporter verbose", + "testOnDemand": "vitest run src/*.spec.ts --exclude \"src/*.multiconcurrency.spec.ts\" --reporter verbose", + "test": "yarn testOnDemand && yarn testMulticoncurrency", "test:watch": "vitest watch", "release": "yarn build && changeset publish" }, diff --git a/src/invoke-store.global.multiconcurrency.spec.ts b/src/invoke-store.global.multiconcurrency.spec.ts new file mode 100644 index 0000000..6e77e96 --- /dev/null +++ b/src/invoke-store.global.multiconcurrency.spec.ts @@ -0,0 +1,66 @@ +import { + describe, + it, + expect, + beforeAll, + afterAll, + beforeEach, + vi, +} from "vitest"; +import { InvokeStore as OriginalImport } from "./invoke-store.js"; + + +describe("InvokeStore Global Singleton", () => { + const originalGlobalAwsLambda = globalThis.awslambda; + const originalEnv = process.env; + + beforeAll(() => { + globalThis.awslambda = originalGlobalAwsLambda; + }); + + afterAll(() => { + delete (globalThis as any).awslambda; + process.env = originalEnv; + }); + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + it("should maintain singleton behavior with dynamic imports", async () => { + // GIVEN + const testRequestId = "dynamic-import-test"; + const testTenantId = "dynamic-import-tenant-id-test"; + const testKey = "dynamic-key"; + const testValue = "dynamic-value"; + + // WHEN - Set up context with original import + await OriginalImport.run( + { + [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [OriginalImport.PROTECTED_KEYS.TENANT_ID]: testTenantId, + }, + async () => { + OriginalImport.set(testKey, testValue); + + // Dynamically import the module again + const dynamicModule = await import("./invoke-store.js"); + const DynamicImport = dynamicModule.InvokeStore; + + // THEN - Dynamically imported instance should see the same context + expect(DynamicImport).toBe(OriginalImport); // Same instance + expect(DynamicImport.getRequestId()).toBe(testRequestId); + expect(DynamicImport.getTenantId()).toBe(testTenantId); + expect(DynamicImport.get(testKey)).toBe(testValue); + + // WHEN - Set a new value using dynamic import + const newKey = "new-dynamic-key"; + const newValue = "new-dynamic-value"; + DynamicImport.set(newKey, newValue); + + // THEN - Original import should see the new value + expect(OriginalImport.get(newKey)).toBe(newValue); + } + ); + }); +}); \ No newline at end of file diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index 251351f..9ff6f8a 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -7,204 +7,175 @@ import { beforeEach, vi, } from "vitest"; -import { InvokeStore as OriginalImport } from "./invoke-store.js"; - -describe("InvokeStore Global Singleton", () => { - const originalGlobalAwsLambda = globalThis.awslambda; - const originalEnv = process.env; - - beforeAll(() => { - globalThis.awslambda = originalGlobalAwsLambda; - }); - - afterAll(() => { - delete (globalThis as any).awslambda; - process.env = originalEnv; - }); - - beforeEach(() => { - process.env = { ...originalEnv }; - }); - - it("should store the instance in globalThis.awslambda", () => { - // THEN - expect(globalThis.awslambda.InvokeStore).toBeDefined(); - expect(globalThis.awslambda.InvokeStore).toBe(OriginalImport); - }); - - it("should share context between original import and global reference", async () => { - // GIVEN - const testRequestId = "shared-context-test"; - const testKey = "test-key"; - const testValue = "test-value"; - - // WHEN - Use the original import to set up context - await OriginalImport.run( - { [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, - () => { - OriginalImport.set(testKey, testValue); - - // THEN - Global reference should see the same context - const globalInstance = globalThis.awslambda.InvokeStore!; - expect(globalInstance.getRequestId()).toBe(testRequestId); - expect(globalInstance.get(testKey)).toBe(testValue); - } - ); - }); - - it("should maintain the same storage across different references", async () => { - // GIVEN - const globalInstance = globalThis.awslambda.InvokeStore!; - const testRequestId = "global-test"; - const testKey = "global-key"; - const testValue = "global-value"; - - // WHEN - Set context using global reference - await globalInstance.run( - { [globalInstance.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, - () => { - globalInstance.set(testKey, testValue); - - // THEN - Original import should see the same context - expect(OriginalImport.getRequestId()).toBe(testRequestId); - expect(OriginalImport.get(testKey)).toBe(testValue); - } - ); - }); - - it("should maintain singleton behavior with dynamic imports", async () => { - // GIVEN - const testRequestId = "dynamic-import-test"; - const testTenantId = "dynamic-import-tenant-id-test"; - const testKey = "dynamic-key"; - const testValue = "dynamic-value"; - - // WHEN - Set up context with original import - await OriginalImport.run( - { - [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId, - [OriginalImport.PROTECTED_KEYS.TENANT_ID]: testTenantId, - }, - async () => { - OriginalImport.set(testKey, testValue); - - // Dynamically import the module again - const dynamicModule = await import("./invoke-store.js"); - const DynamicImport = dynamicModule.InvokeStore; - - // THEN - Dynamically imported instance should see the same context - expect(DynamicImport).toBe(OriginalImport); // Same instance - expect(DynamicImport.getRequestId()).toBe(testRequestId); - expect(DynamicImport.getTenantId()).toBe(testTenantId); - expect(DynamicImport.get(testKey)).toBe(testValue); - - // WHEN - Set a new value using dynamic import - const newKey = "new-dynamic-key"; - const newValue = "new-dynamic-value"; - DynamicImport.set(newKey, newValue); - - // THEN - Original import should see the new value - expect(OriginalImport.get(newKey)).toBe(newValue); - } - ); - }); -}); - -describe("InvokeStore Existing Instance", () => { - const originalGlobalAwsLambda = globalThis.awslambda; - - beforeEach(() => { - delete (globalThis as any).awslambda; - globalThis.awslambda = {}; - - vi.resetModules(); - }); - - afterAll(() => { - globalThis.awslambda = originalGlobalAwsLambda; - }); - - it("should use existing instance from globalThis.awslambda.InvokeStore", async () => { - // GIVEN - const mockInstance = { - PROTECTED_KEYS: { - REQUEST_ID: "_AWS_LAMBDA_REQUEST_ID", - X_RAY_TRACE_ID: "_AWS_LAMBDA_TRACE_ID", - }, - run: vi.fn(), - getContext: vi.fn(), - get: vi.fn(), - set: vi.fn(), - getRequestId: vi.fn().mockReturnValue("mock-request-id"), - getXRayTraceId: vi.fn(), - getTenantId: vi.fn().mockReturnValue("my-test-tenant-id"), - hasContext: vi.fn(), - }; - - // @ts-expect-error - mockInstance can be loosely related to original type - globalThis.awslambda.InvokeStore = mockInstance; - - // WHEN - const { InvokeStore: ReimportedStore } = await import("./invoke-store.js"); - - // THEN - expect(ReimportedStore).toBe(mockInstance); - expect(ReimportedStore.getRequestId()).toBe("mock-request-id"); - expect(ReimportedStore.getTenantId()).toBe("my-test-tenant-id"); - }); -}); - -describe("InvokeStore Environment Variable Opt-Out", () => { - const originalEnv = process.env; - - beforeEach(() => { - process.env = { ...originalEnv }; - delete (globalThis as any).awslambda; - - vi.resetModules(); +import { createInvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; + + +describe.each([ + { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, + { label: 'single-concurrency', config: undefined } +])('InvokeStore with %s', ({ config }) => { + createInvokeStore(config); + + describe("InvokeStore Global Singleton", () => { + const originalGlobalAwsLambda = globalThis.awslambda; + const originalEnv = process.env; + + beforeAll(() => { + globalThis.awslambda = originalGlobalAwsLambda; + }); + + afterAll(() => { + delete (globalThis as any).awslambda; + process.env = originalEnv; + }); + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + it("should store the instance in globalThis.awslambda", () => { + // THEN + expect(globalThis.awslambda.InvokeStore).toBeDefined(); + expect(globalThis.awslambda.InvokeStore).toBe(OriginalImport); + }); + + it("should share context between original import and global reference", async () => { + // GIVEN + const testRequestId = "shared-context-test"; + const testKey = "test-key"; + const testValue = "test-value"; + + // WHEN - Use the original import to set up context + await OriginalImport.run( + { [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + () => { + OriginalImport.set(testKey, testValue); + + // THEN - Global reference should see the same context + const globalInstance = globalThis.awslambda.InvokeStore!; + expect(globalInstance.getRequestId()).toBe(testRequestId); + expect(globalInstance.get(testKey)).toBe(testValue); + } + ); + }); + + it("should maintain the same storage across different references", async () => { + // GIVEN + const globalInstance = globalThis.awslambda.InvokeStore!; + const testRequestId = "global-test"; + const testKey = "global-key"; + const testValue = "global-value"; + + // WHEN - Set context using global reference + await globalInstance.run( + { [globalInstance.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + () => { + globalInstance.set(testKey, testValue); + + // THEN - Original import should see the same context + expect(OriginalImport.getRequestId()).toBe(testRequestId); + expect(OriginalImport.get(testKey)).toBe(testValue); + } + ); + }); }); - afterAll(() => { - process.env = originalEnv; + describe("InvokeStore Existing Instance", () => { + const originalGlobalAwsLambda = globalThis.awslambda; + + beforeEach(() => { + delete (globalThis as any).awslambda; + globalThis.awslambda = {}; + + vi.resetModules(); + }); + + afterAll(() => { + globalThis.awslambda = originalGlobalAwsLambda; + }); + + it("should use existing instance from globalThis.awslambda.InvokeStore", async () => { + // GIVEN + const mockInstance = { + PROTECTED_KEYS: { + REQUEST_ID: "_AWS_LAMBDA_REQUEST_ID", + X_RAY_TRACE_ID: "_AWS_LAMBDA_TRACE_ID", + }, + run: vi.fn(), + getContext: vi.fn(), + get: vi.fn(), + set: vi.fn(), + getRequestId: vi.fn().mockReturnValue("mock-request-id"), + getXRayTraceId: vi.fn(), + getTenantId: vi.fn().mockReturnValue("my-test-tenant-id"), + hasContext: vi.fn(), + }; + + // @ts-expect-error - mockInstance can be loosely related to original type + globalThis.awslambda.InvokeStore = mockInstance; + + // WHEN + const { InvokeStore: ReimportedStore } = await import("./invoke-store.js"); + + // THEN + expect(ReimportedStore).toBe(mockInstance); + expect(ReimportedStore.getRequestId()).toBe("mock-request-id"); + expect(ReimportedStore.getTenantId()).toBe("my-test-tenant-id"); + }); }); - it("should respect AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA=1", async () => { - // GIVEN - process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA = "1"; - - // WHEN - Import the module with the environment variable set - const { InvokeStore } = await import("./invoke-store.js"); - - // THEN - The global namespace should not be modified - expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); - - let requestId: string | undefined; - await InvokeStore.run( - { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, - () => { - requestId = InvokeStore.getRequestId(); - } - ); - expect(requestId).toBe("test-id"); - }); - - it("should respect AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA=true", async () => { - // GIVEN - process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA = "true"; - - // WHEN - Import the module with the environment variable set - const { InvokeStore } = await import("./invoke-store.js"); - - // THEN - The global namespace should not be modified - expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); - - let requestId: string | undefined; - await InvokeStore.run( - { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, - () => { - requestId = InvokeStore.getRequestId(); - } - ); - expect(requestId).toBe("test-id"); + describe("InvokeStore Environment Variable Opt-Out", () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + delete (globalThis as any).awslambda; + + vi.resetModules(); + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it("should respect AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA=1", async () => { + // GIVEN + process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA = "1"; + + // WHEN - Import the module with the environment variable set + const { InvokeStore } = await import("./invoke-store.js"); + + // THEN - The global namespace should not be modified + expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); + + let requestId: string | undefined; + await InvokeStore.run( + { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + () => { + requestId = InvokeStore.getRequestId(); + } + ); + expect(requestId).toBe("test-id"); + }); + + it("should respect AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA=true", async () => { + // GIVEN + process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA = "true"; + + // WHEN - Import the module with the environment variable set + const { InvokeStore } = await import("./invoke-store.js"); + + // THEN - The global namespace should not be modified + expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); + + let requestId: string | undefined; + await InvokeStore.run( + { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + () => { + requestId = InvokeStore.getRequestId(); + } + ); + expect(requestId).toBe("test-id"); + }); }); }); diff --git a/src/invoke-store.multiconcurrency.spec.ts b/src/invoke-store.multiconcurrency.spec.ts new file mode 100644 index 0000000..c14869f --- /dev/null +++ b/src/invoke-store.multiconcurrency.spec.ts @@ -0,0 +1,152 @@ +import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; +import { InvokeStore } from "./invoke-store.js"; + +describe("InvokeStore", () => { + + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe("run", () => { + + it("should handle nested runs with different IDs", async () => { + // GIVEN + const traces: string[] = []; + + // WHEN + await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", + }, + async () => { + traces.push(`outer-${InvokeStore.getRequestId()}`); + await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", + }, + async () => { + traces.push(`inner-${InvokeStore.getRequestId()}`); + }, + ); + traces.push(`outer-again-${InvokeStore.getRequestId()}`); + }, + ); + + // THEN + expect(traces).toEqual([ + "outer-outer", + "inner-inner", + "outer-again-outer", + ]); + }); + + it("should maintain isolation between concurrent executions", async () => { + // GIVEN + const traces: string[] = []; + + // WHEN - Simulate concurrent invocations + const isolateTasks = Promise.all([ + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + }, + async () => { + traces.push(`start-1-${InvokeStore.getRequestId()}`); + await new Promise((resolve) => setTimeout(resolve, 10)); + traces.push(`end-1-${InvokeStore.getRequestId()}`); + }, + ), + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + }, + async () => { + traces.push(`start-2-${InvokeStore.getRequestId()}`); + await new Promise((resolve) => setTimeout(resolve, 5)); + traces.push(`end-2-${InvokeStore.getRequestId()}`); + }, + ), + ]); + vi.runAllTimers(); + await isolateTasks; + + // THEN + expect(traces).toEqual([ + "start-1-request-1", + "start-2-request-2", + "end-2-request-2", + "end-1-request-1", + ]); + }); + + it("should maintain isolation across async operations", async () => { + // GIVEN + const traces: string[] = []; + + // WHEN + await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + }, + async () => { + traces.push(`before-${InvokeStore.getRequestId()}`); + const task = new Promise((resolve) => { + setTimeout(resolve, 1); + }).then(() => { + traces.push(`inside-${InvokeStore.getRequestId()}`); + }); + vi.runAllTimers(); + await task; + traces.push(`after-${InvokeStore.getRequestId()}`); + }, + ); + + // THEN + expect(traces).toEqual([ + "before-request-1", + "inside-request-1", + "after-request-1", + ]); + }); + + + describe("hasContext", () => { + it("should handle errors in concurrent executions independently", async () => { + // GIVEN + const traces: string[] = []; + + // WHEN + await Promise.allSettled([ + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", + }, + async () => { + traces.push(`success-${InvokeStore.getRequestId()}`); + }, + ), + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", + }, + async () => { + traces.push(`before-error-${InvokeStore.getRequestId()}`); + throw new Error("test error"); + }, + ), + ]); + + // THEN + expect(traces).toContain("success-success-id"); + expect(traces).toContain("before-error-error-id"); + expect(InvokeStore.getRequestId()).toBe("-"); + }); + }); + }); +}); diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index bff52e5..f890940 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -1,338 +1,240 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; -import { InvokeStore } from "./invoke-store.js"; +import { createInvokeStore } from "./invoke-store.js"; + +describe.each([ + { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, + { label: 'single-concurrency', config: undefined } +])('InvokeStore with %s', ({ config }) => { + const InvokeStore = createInvokeStore(config); + describe("InvokeStore", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); -describe("InvokeStore", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); + afterEach(() => { + vi.useRealTimers(); + }); - afterEach(() => { - vi.useRealTimers(); - }); + describe("getRequestId and getXRayTraceId", () => { + it("should return placeholder when called outside run context", () => { + // WHEN + const requestId = InvokeStore.getRequestId(); + const traceId = InvokeStore.getXRayTraceId(); - describe("run", () => { - it("should maintain isolation between concurrent executions", async () => { - // GIVEN - const traces: string[] = []; + // THEN + expect(requestId).toBe("-"); + expect(traceId).toBeUndefined(); + }); - // WHEN - Simulate concurrent invocations - const isolateTasks = Promise.all([ - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", - }, - async () => { - traces.push(`start-1-${InvokeStore.getRequestId()}`); - await new Promise((resolve) => setTimeout(resolve, 10)); - traces.push(`end-1-${InvokeStore.getRequestId()}`); - }, - ), - InvokeStore.run( + it("should return current invoke IDs when called within run context", async () => { + // WHEN + const result = await InvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", }, - async () => { - traces.push(`start-2-${InvokeStore.getRequestId()}`); - await new Promise((resolve) => setTimeout(resolve, 5)); - traces.push(`end-2-${InvokeStore.getRequestId()}`); + () => { + return { + requestId: InvokeStore.getRequestId(), + traceId: InvokeStore.getXRayTraceId(), + }; }, - ), - ]); - vi.runAllTimers(); - await isolateTasks; + ); - // THEN - expect(traces).toEqual([ - "start-1-request-1", - "start-2-request-2", - "end-2-request-2", - "end-1-request-1", - ]); + // THEN + expect(result.requestId).toBe("test-id"); + expect(result.traceId).toBe("trace-id"); + }); }); - it("should maintain isolation across async operations", async () => { - // GIVEN - const traces: string[] = []; + describe("custom properties", () => { + it("should allow setting and getting custom properties", async () => { + // WHEN + const result = await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + customProp: "initial-value", + }, + () => { + InvokeStore.set("dynamicProp", "dynamic-value"); + return { + initial: InvokeStore.get("customProp"), + dynamic: InvokeStore.get("dynamicProp"), + }; + }, + ); - // WHEN - await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - }, - async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); - const task = new Promise((resolve) => { - setTimeout(resolve, 1); - }).then(() => { - traces.push(`inside-${InvokeStore.getRequestId()}`); - }); - vi.runAllTimers(); - await task; - traces.push(`after-${InvokeStore.getRequestId()}`); - }, - ); + // THEN + expect(result.initial).toBe("initial-value"); + expect(result.dynamic).toBe("dynamic-value"); + }); - // THEN - expect(traces).toEqual([ - "before-request-1", - "inside-request-1", - "after-request-1", - ]); + it("should prevent modifying protected Lambda fields", async () => { + // WHEN & THEN + await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + }, + () => { + expect(() => { + InvokeStore.set(InvokeStore.PROTECTED_KEYS.REQUEST_ID, "new-id"); + }).toThrow(/Cannot modify protected Lambda context field/); + + expect(() => { + InvokeStore.set( + InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID, + "new-trace", + ); + }).toThrow(/Cannot modify protected Lambda context field/); + }, + ); + }); }); - it("should handle nested runs with different IDs", async () => { - // GIVEN - const traces: string[] = []; - - // WHEN - await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", - }, - async () => { - traces.push(`outer-${InvokeStore.getRequestId()}`); - await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", - }, - async () => { - traces.push(`inner-${InvokeStore.getRequestId()}`); - }, - ); - traces.push(`outer-again-${InvokeStore.getRequestId()}`); - }, - ); - - // THEN - expect(traces).toEqual([ - "outer-outer", - "inner-inner", - "outer-again-outer", - ]); - }); - }); + describe("getContext", () => { + it("should return undefined when outside run context", () => { + // WHEN + const context = InvokeStore.getContext(); - describe("getRequestId and getXRayTraceId", () => { - it("should return placeholder when called outside run context", () => { - // WHEN - const requestId = InvokeStore.getRequestId(); - const traceId = InvokeStore.getXRayTraceId(); + // THEN + expect(context).toBeUndefined(); + }); - // THEN - expect(requestId).toBe("-"); - expect(traceId).toBeUndefined(); - }); + it("should return complete context with Lambda and custom fields", async () => { + // WHEN + const context = await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + customField: "custom-value", + }, + () => { + InvokeStore.set("dynamicField", "dynamic-value"); + return InvokeStore.getContext(); + }, + ); - it("should return current invoke IDs when called within run context", async () => { - // WHEN - const result = await InvokeStore.run( - { + // THEN + expect(context).toEqual({ [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", - }, - () => { - return { - requestId: InvokeStore.getRequestId(), - traceId: InvokeStore.getXRayTraceId(), - }; - }, - ); - - // THEN - expect(result.requestId).toBe("test-id"); - expect(result.traceId).toBe("trace-id"); - }); - }); - - describe("custom properties", () => { - it("should allow setting and getting custom properties", async () => { - // WHEN - const result = await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - customProp: "initial-value", - }, - () => { - InvokeStore.set("dynamicProp", "dynamic-value"); - return { - initial: InvokeStore.get("customProp"), - dynamic: InvokeStore.get("dynamicProp"), - }; - }, - ); - - // THEN - expect(result.initial).toBe("initial-value"); - expect(result.dynamic).toBe("dynamic-value"); - }); - - it("should prevent modifying protected Lambda fields", async () => { - // WHEN & THEN - await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - }, - () => { - expect(() => { - InvokeStore.set(InvokeStore.PROTECTED_KEYS.REQUEST_ID, "new-id"); - }).toThrow(/Cannot modify protected Lambda context field/); - - expect(() => { - InvokeStore.set( - InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID, - "new-trace", - ); - }).toThrow(/Cannot modify protected Lambda context field/); - }, - ); + customField: "custom-value", + dynamicField: "dynamic-value", + }); + }); }); - }); - describe("getContext", () => { - it("should return undefined when outside run context", () => { - // WHEN - const context = InvokeStore.getContext(); + describe("hasContext", () => { + it("should return false when outside run context", () => { + // WHEN + const hasContext = InvokeStore.hasContext(); - // THEN - expect(context).toBeUndefined(); - }); + // THEN + expect(hasContext).toBe(false); + }); - it("should return complete context with Lambda and custom fields", async () => { - // WHEN - const context = await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", - customField: "custom-value", - }, - () => { - InvokeStore.set("dynamicField", "dynamic-value"); - return InvokeStore.getContext(); - }, - ); + it("should return true when inside run context", async () => { + // WHEN + const result = await InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + }, + () => { + return InvokeStore.hasContext(); + }, + ); - // THEN - expect(context).toEqual({ - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", - customField: "custom-value", - dynamicField: "dynamic-value", + // THEN + expect(result).toBe(true); }); }); - }); - - describe("hasContext", () => { - it("should return false when outside run context", () => { - // WHEN - const hasContext = InvokeStore.hasContext(); - // THEN - expect(hasContext).toBe(false); - }); + describe("error handling", () => { + it("should propagate errors while maintaining isolation", async () => { + // GIVEN + const error = new Error("test error"); - it("should return true when inside run context", async () => { - // WHEN - const result = await InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - }, - () => { - return InvokeStore.hasContext(); - }, - ); + // WHEN + const promise = InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + }, + async () => { + throw error; + }, + ); - // THEN - expect(result).toBe(true); - }); - }); + // THEN + await expect(promise).rejects.toThrow(error); + expect(InvokeStore.getRequestId()).toBe("-"); + }); - describe("error handling", () => { - it("should propagate errors while maintaining isolation", async () => { - // GIVEN - const error = new Error("test error"); + it("should handle errors in concurrent executions independently", async () => { + // GIVEN + const traces: string[] = []; - // WHEN - const promise = InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - }, - async () => { - throw error; - }, - ); + // WHEN + await Promise.allSettled([ + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", + }, + async () => { + traces.push(`success-${InvokeStore.getRequestId()}`); + }, + ), + InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", + }, + async () => { + traces.push(`before-error-${InvokeStore.getRequestId()}`); + throw new Error("test error"); + }, + ), + ]); - // THEN - await expect(promise).rejects.toThrow(error); - expect(InvokeStore.getRequestId()).toBe("-"); + // THEN + expect(traces).toContain("success-success-id"); + expect(traces).toContain("before-error-error-id"); + expect(InvokeStore.getRequestId()).toBe("-"); + }); }); - it("should handle errors in concurrent executions independently", async () => { - // GIVEN - const traces: string[] = []; - - // WHEN - await Promise.allSettled([ - InvokeStore.run( + describe("edge cases", () => { + it("should handle synchronous functions", () => { + // WHEN + const result = InvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, - async () => { - traces.push(`success-${InvokeStore.getRequestId()}`); - }, - ), - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", + () => { + return InvokeStore.getRequestId(); }, - async () => { - traces.push(`before-error-${InvokeStore.getRequestId()}`); - throw new Error("test error"); - }, - ), - ]); + ); - // THEN - expect(traces).toContain("success-success-id"); - expect(traces).toContain("before-error-error-id"); - expect(InvokeStore.getRequestId()).toBe("-"); - }); - }); - - describe("edge cases", () => { - it("should handle synchronous functions", () => { - // WHEN - const result = InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - }, - () => { - return InvokeStore.getRequestId(); - }, - ); - - // THEN - expect(result).toBe("test-id"); - }); + // THEN + expect(result).toBe("test-id"); + }); - it("should handle promises that reject synchronously", async () => { - // GIVEN - const error = new Error("immediate rejection"); + it("should handle promises that reject synchronously", async () => { + // GIVEN + const error = new Error("immediate rejection"); - // WHEN - const promise = InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - }, - () => { - return Promise.reject(error); - }, - ); + // WHEN + const promise = InvokeStore.run( + { + [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + }, + () => { + return Promise.reject(error); + }, + ); - // THEN - await expect(promise).rejects.toThrow(error); - expect(InvokeStore.getRequestId()).toBe("-"); + // THEN + await expect(promise).rejects.toThrow(error); + expect(InvokeStore.getRequestId()).toBe("-"); + }); }); }); }); diff --git a/src/invoke-store.timers.spec.ts b/src/invoke-store.timers.multiconcurrency.spec.ts similarity index 99% rename from src/invoke-store.timers.spec.ts rename to src/invoke-store.timers.multiconcurrency.spec.ts index fb37ad1..f4d17df 100644 --- a/src/invoke-store.timers.spec.ts +++ b/src/invoke-store.timers.multiconcurrency.spec.ts @@ -1,11 +1,15 @@ import { describe, it, expect } from "vitest"; -import { InvokeStore } from "./invoke-store.js"; +import { createInvokeStore } from "./invoke-store.js"; /** * These tests specifically verify context preservation across various * timer and async APIs without using fake timers. */ describe("InvokeStore timer functions context preservation", () => { + const InvokeStore = createInvokeStore({ + env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } + }); + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 955b89f..3c2f0dc 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -53,6 +53,13 @@ abstract class InvokeStoreBase { class InvokeStoreSingle extends InvokeStoreBase { private currentContext?: Context; + constructor() { + super(); + this.getRequestId = super.getRequestId; + this.getXRayTraceId = super.getXRayTraceId; + this.getTenantId = super.getTenantId; + } + getContext(): Context | undefined { return this.currentContext; } @@ -73,7 +80,6 @@ class InvokeStoreSingle extends InvokeStoreBase { if (!this.currentContext) { this.currentContext = {}; } - this.currentContext[key] = value; } @@ -130,10 +136,15 @@ class InvokeStoreMulti extends InvokeStoreBase { } } -const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (process.env ?? {}); -const InvokeStoreImpl = isMulti ? InvokeStoreMulti : InvokeStoreSingle; +type InvokeStoreConfig = { + env?: NodeJS.ProcessEnv; +} + +export const createInvokeStore = (storeConfig?: InvokeStoreConfig): InvokeStoreBase => { + const env = storeConfig?.env ?? process.env; + const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); + const InvokeStoreImpl = isMulti ? InvokeStoreMulti : InvokeStoreSingle; -const createInvokeStore = (): InvokeStoreBase => { if (!noGlobalAwsLambda && globalThis.awslambda?.InvokeStore) { return globalThis.awslambda.InvokeStore; } @@ -148,4 +159,4 @@ const createInvokeStore = (): InvokeStoreBase => { } const log = createInvokeStore(); -export const InvokeStore = log; \ No newline at end of file +export const InvokeStore = log; From 68243d0110a180f056e8994b516f275ade2cff7f Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 12:05:44 +0000 Subject: [PATCH 05/26] fix: test --- src/invoke-store.module.loading.spec.ts | 22 ++++++++ src/invoke-store.ts | 68 +++++++++++-------------- 2 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 src/invoke-store.module.loading.spec.ts diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts new file mode 100644 index 0000000..ae2243a --- /dev/null +++ b/src/invoke-store.module.loading.spec.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest'; +import { createInvokeStore } from "./invoke-store.js" + +describe('InvokeStore implementations', () => { + it('should create different implementations based on environment', () => { + const singleStore = createInvokeStore({ + env: {} + }); + + expect(singleStore.constructor.name).toBe('InvokeStoreSingle'); + + if (globalThis.awslambda) { + globalThis.awslambda.InvokeStore = undefined; + } + + const multiStore = createInvokeStore({ + env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } + }); + + expect(multiStore.constructor.name).toBe('InvokeStoreMulti'); + }); +}); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 3c2f0dc..f3a4387 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -1,18 +1,3 @@ -declare global { - var awslambda: { - InvokeStore?: InvokeStoreBase; - [key: string]: unknown; - }; -} - -const noGlobalAwsLambda = - process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "1" || - process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "true"; - -if (!noGlobalAwsLambda) { - globalThis.awslambda = globalThis.awslambda || {}; -} - interface Context { [key: string]: unknown; [key: symbol]: unknown; @@ -24,8 +9,24 @@ const PROTECTED_KEYS = { TENANT_ID: Symbol("_AWS_LAMBDA_TENANT_ID"), } as const; +const NO_GLOBAL_AWS_LAMBDA = + + process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "1" || + process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "true"; + +declare global { + var awslambda: { + InvokeStore?: InvokeStoreBase; + [key: string]: unknown; + }; +} + +if (!NO_GLOBAL_AWS_LAMBDA) { + globalThis.awslambda = globalThis.awslambda || {}; +} + abstract class InvokeStoreBase { - readonly PROTECTED_KEYS = PROTECTED_KEYS; + protected readonly PROTECTED_KEYS = PROTECTED_KEYS; abstract getContext(): Context | undefined; abstract hasContext(): boolean; @@ -37,29 +38,23 @@ abstract class InvokeStoreBase { return Object.values(PROTECTED_KEYS).includes(key as symbol); } - public getRequestId(): string | undefined { + getRequestId(): string | undefined { return this.get(PROTECTED_KEYS.REQUEST_ID) ?? "-"; } - public getXRayTraceId(): string | undefined { + getXRayTraceId(): string | undefined { return this.get(PROTECTED_KEYS.X_RAY_TRACE_ID); } - public getTenantId(): string | undefined { + getTenantId(): string | undefined { return this.get(PROTECTED_KEYS.TENANT_ID); } } +// Single Context Implementation class InvokeStoreSingle extends InvokeStoreBase { private currentContext?: Context; - constructor() { - super(); - this.getRequestId = super.getRequestId; - this.getXRayTraceId = super.getXRayTraceId; - this.getTenantId = super.getTenantId; - } - getContext(): Context | undefined { return this.currentContext; } @@ -77,9 +72,7 @@ class InvokeStoreSingle extends InvokeStoreBase { throw new Error(`Cannot modify protected Lambda context field: ${String(key)}`); } - if (!this.currentContext) { - this.currentContext = {}; - } + this.currentContext = this.currentContext || {}; this.currentContext[key] = value; } @@ -91,9 +84,9 @@ class InvokeStoreSingle extends InvokeStoreBase { this.currentContext = undefined; } } - } +// Multi Context Implementation class InvokeStoreMulti extends InvokeStoreBase { private als!: import("node:async_hooks").AsyncLocalStorage; @@ -101,9 +94,6 @@ class InvokeStoreMulti extends InvokeStoreBase { super(); const asyncHooks = require('node:async_hooks') as typeof import("node:async_hooks"); this.als = new asyncHooks.AsyncLocalStorage(); - this.getRequestId = super.getRequestId; - this.getXRayTraceId = super.getXRayTraceId; - this.getTenantId = super.getTenantId; } getContext(): Context | undefined { @@ -136,27 +126,27 @@ class InvokeStoreMulti extends InvokeStoreBase { } } -type InvokeStoreConfig = { +interface InvokeStoreConfig { env?: NodeJS.ProcessEnv; } export const createInvokeStore = (storeConfig?: InvokeStoreConfig): InvokeStoreBase => { const env = storeConfig?.env ?? process.env; const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); + console.log(`isMulti is` + isMulti); const InvokeStoreImpl = isMulti ? InvokeStoreMulti : InvokeStoreSingle; - if (!noGlobalAwsLambda && globalThis.awslambda?.InvokeStore) { + if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { return globalThis.awslambda.InvokeStore; } const instance = new InvokeStoreImpl(); - if (!noGlobalAwsLambda && globalThis.awslambda) { + if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { globalThis.awslambda.InvokeStore = instance; } - + console.log('returning instance = ' + instance.constructor.name); return instance; } -const log = createInvokeStore(); -export const InvokeStore = log; +export const InvokeStore = createInvokeStore(); From 19329f598646960ab990b1f194a03bb77f8b4033 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 15:20:08 +0000 Subject: [PATCH 06/26] feat: use await --- ...voke-store.global.multiconcurrency.spec.ts | 16 ++-- src/invoke-store.global.spec.ts | 49 ++++++----- src/invoke-store.module.loading.spec.ts | 6 +- src/invoke-store.multiconcurrency.spec.ts | 82 ++++++------------- src/invoke-store.spec.ts | 5 +- ...voke-store.timers.multiconcurrency.spec.ts | 4 +- src/invoke-store.ts | 33 +++++--- 7 files changed, 91 insertions(+), 104 deletions(-) diff --git a/src/invoke-store.global.multiconcurrency.spec.ts b/src/invoke-store.global.multiconcurrency.spec.ts index 6e77e96..123bdf8 100644 --- a/src/invoke-store.global.multiconcurrency.spec.ts +++ b/src/invoke-store.global.multiconcurrency.spec.ts @@ -34,21 +34,23 @@ describe("InvokeStore Global Singleton", () => { const testKey = "dynamic-key"; const testValue = "dynamic-value"; + const originalImportAwaited = await OriginalImport; + // WHEN - Set up context with original import - await OriginalImport.run( + await originalImportAwaited.run( { - [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId, - [OriginalImport.PROTECTED_KEYS.TENANT_ID]: testTenantId, + [originalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [originalImportAwaited.PROTECTED_KEYS.TENANT_ID]: testTenantId, }, async () => { - OriginalImport.set(testKey, testValue); + originalImportAwaited.set(testKey, testValue); // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); - const DynamicImport = dynamicModule.InvokeStore; + const DynamicImport = await dynamicModule.InvokeStore; // THEN - Dynamically imported instance should see the same context - expect(DynamicImport).toBe(OriginalImport); // Same instance + expect(DynamicImport).toBe(originalImportAwaited); // Same instance expect(DynamicImport.getRequestId()).toBe(testRequestId); expect(DynamicImport.getTenantId()).toBe(testTenantId); expect(DynamicImport.get(testKey)).toBe(testValue); @@ -59,7 +61,7 @@ describe("InvokeStore Global Singleton", () => { DynamicImport.set(newKey, newValue); // THEN - Original import should see the new value - expect(OriginalImport.get(newKey)).toBe(newValue); + expect(originalImportAwaited.get(newKey)).toBe(newValue); } ); }); diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index 9ff6f8a..fbef128 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -13,8 +13,8 @@ import { createInvokeStore, InvokeStore as OriginalImport } from "./invoke-store describe.each([ { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, { label: 'single-concurrency', config: undefined } -])('InvokeStore with %s', ({ config }) => { - createInvokeStore(config); +])('InvokeStore with %s', async ({ config }) => { + await createInvokeStore(config); describe("InvokeStore Global Singleton", () => { const originalGlobalAwsLambda = globalThis.awslambda; @@ -33,10 +33,10 @@ describe.each([ process.env = { ...originalEnv }; }); - it("should store the instance in globalThis.awslambda", () => { + it("should store the instance in globalThis.awslambda", async () => { // THEN expect(globalThis.awslambda.InvokeStore).toBeDefined(); - expect(globalThis.awslambda.InvokeStore).toBe(OriginalImport); + expect(await globalThis.awslambda.InvokeStore).toBe(await OriginalImport); }); it("should share context between original import and global reference", async () => { @@ -45,11 +45,13 @@ describe.each([ const testKey = "test-key"; const testValue = "test-value"; + const originalImportAwaited = await OriginalImport; + // WHEN - Use the original import to set up context - await OriginalImport.run( - { [OriginalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, - () => { - OriginalImport.set(testKey, testValue); + await originalImportAwaited.run( + { [ originalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + async () => { + originalImportAwaited.set(testKey, testValue); // THEN - Global reference should see the same context const globalInstance = globalThis.awslambda.InvokeStore!; @@ -62,19 +64,21 @@ describe.each([ it("should maintain the same storage across different references", async () => { // GIVEN const globalInstance = globalThis.awslambda.InvokeStore!; + const OriginalImportAwaited = await OriginalImport; + const globalInstanceAwaited = await globalInstance; const testRequestId = "global-test"; const testKey = "global-key"; const testValue = "global-value"; // WHEN - Set context using global reference await globalInstance.run( - { [globalInstance.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, - () => { + { [OriginalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + async () => { globalInstance.set(testKey, testValue); // THEN - Original import should see the same context - expect(OriginalImport.getRequestId()).toBe(testRequestId); - expect(OriginalImport.get(testKey)).toBe(testValue); + expect(OriginalImportAwaited.getRequestId()).toBe(testRequestId); + expect(OriginalImportAwaited.get(testKey)).toBe(testValue); } ); }); @@ -116,11 +120,12 @@ describe.each([ // WHEN const { InvokeStore: ReimportedStore } = await import("./invoke-store.js"); + const awaitedReimportedStore = await ReimportedStore; // THEN - expect(ReimportedStore).toBe(mockInstance); - expect(ReimportedStore.getRequestId()).toBe("mock-request-id"); - expect(ReimportedStore.getTenantId()).toBe("my-test-tenant-id"); + expect(awaitedReimportedStore).toBe(mockInstance); + expect(awaitedReimportedStore.getRequestId()).toBe("mock-request-id"); + expect(awaitedReimportedStore.getTenantId()).toBe("my-test-tenant-id"); }); }); @@ -144,15 +149,16 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); + const awaitedInvokeStore = await InvokeStore; // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); let requestId: string | undefined; - await InvokeStore.run( - { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + await awaitedInvokeStore.run( + { [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { - requestId = InvokeStore.getRequestId(); + requestId = awaitedInvokeStore.getRequestId(); } ); expect(requestId).toBe("test-id"); @@ -164,15 +170,16 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); + const awaitedInvokeStore = await InvokeStore; // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); let requestId: string | undefined; - await InvokeStore.run( - { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + await awaitedInvokeStore.run( + { [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { - requestId = InvokeStore.getRequestId(); + requestId = awaitedInvokeStore.getRequestId(); } ); expect(requestId).toBe("test-id"); diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts index ae2243a..3a59a66 100644 --- a/src/invoke-store.module.loading.spec.ts +++ b/src/invoke-store.module.loading.spec.ts @@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest'; import { createInvokeStore } from "./invoke-store.js" describe('InvokeStore implementations', () => { - it('should create different implementations based on environment', () => { - const singleStore = createInvokeStore({ + it('should create different implementations based on environment', async () => { + const singleStore = await createInvokeStore({ env: {} }); @@ -13,7 +13,7 @@ describe('InvokeStore implementations', () => { globalThis.awslambda.InvokeStore = undefined; } - const multiStore = createInvokeStore({ + const multiStore = await createInvokeStore({ env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } }); diff --git a/src/invoke-store.multiconcurrency.spec.ts b/src/invoke-store.multiconcurrency.spec.ts index c14869f..72c866e 100644 --- a/src/invoke-store.multiconcurrency.spec.ts +++ b/src/invoke-store.multiconcurrency.spec.ts @@ -1,7 +1,9 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; import { InvokeStore } from "./invoke-store.js"; -describe("InvokeStore", () => { +describe("InvokeStore", async () => { + + const awaitedInvokeStore = await InvokeStore; beforeEach(() => { vi.useFakeTimers(); @@ -18,21 +20,21 @@ describe("InvokeStore", () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await awaitedInvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", + [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", }, async () => { - traces.push(`outer-${InvokeStore.getRequestId()}`); - await InvokeStore.run( + traces.push(`outer-${awaitedInvokeStore.getRequestId()}`); + await awaitedInvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", + [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", }, async () => { - traces.push(`inner-${InvokeStore.getRequestId()}`); + traces.push(`inner-${awaitedInvokeStore.getRequestId()}`); }, ); - traces.push(`outer-again-${InvokeStore.getRequestId()}`); + traces.push(`outer-again-${awaitedInvokeStore.getRequestId()}`); }, ); @@ -50,26 +52,26 @@ describe("InvokeStore", () => { // WHEN - Simulate concurrent invocations const isolateTasks = Promise.all([ - InvokeStore.run( + awaitedInvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [awaitedInvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { - traces.push(`start-1-${InvokeStore.getRequestId()}`); + traces.push(`start-1-${awaitedInvokeStore.getRequestId()}`); await new Promise((resolve) => setTimeout(resolve, 10)); - traces.push(`end-1-${InvokeStore.getRequestId()}`); + traces.push(`end-1-${awaitedInvokeStore.getRequestId()}`); }, ), - InvokeStore.run( + awaitedInvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [awaitedInvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", }, async () => { - traces.push(`start-2-${InvokeStore.getRequestId()}`); + traces.push(`start-2-${awaitedInvokeStore.getRequestId()}`); await new Promise((resolve) => setTimeout(resolve, 5)); - traces.push(`end-2-${InvokeStore.getRequestId()}`); + traces.push(`end-2-${awaitedInvokeStore.getRequestId()}`); }, ), ]); @@ -90,20 +92,20 @@ describe("InvokeStore", () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await awaitedInvokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", }, async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); + traces.push(`before-${awaitedInvokeStore.getRequestId()}`); const task = new Promise((resolve) => { setTimeout(resolve, 1); }).then(() => { - traces.push(`inside-${InvokeStore.getRequestId()}`); + traces.push(`inside-${awaitedInvokeStore.getRequestId()}`); }); vi.runAllTimers(); await task; - traces.push(`after-${InvokeStore.getRequestId()}`); + traces.push(`after-${awaitedInvokeStore.getRequestId()}`); }, ); @@ -114,39 +116,5 @@ describe("InvokeStore", () => { "after-request-1", ]); }); - - - describe("hasContext", () => { - it("should handle errors in concurrent executions independently", async () => { - // GIVEN - const traces: string[] = []; - - // WHEN - await Promise.allSettled([ - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", - }, - async () => { - traces.push(`success-${InvokeStore.getRequestId()}`); - }, - ), - InvokeStore.run( - { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", - }, - async () => { - traces.push(`before-error-${InvokeStore.getRequestId()}`); - throw new Error("test error"); - }, - ), - ]); - - // THEN - expect(traces).toContain("success-success-id"); - expect(traces).toContain("before-error-error-id"); - expect(InvokeStore.getRequestId()).toBe("-"); - }); - }); }); }); diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index f890940..4bff603 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -4,8 +4,8 @@ import { createInvokeStore } from "./invoke-store.js"; describe.each([ { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, { label: 'single-concurrency', config: undefined } -])('InvokeStore with %s', ({ config }) => { - const InvokeStore = createInvokeStore(config); +])('InvokeStore with %s', async ({ config }) => { + const InvokeStore = await createInvokeStore(config); describe("InvokeStore", () => { beforeEach(() => { vi.useFakeTimers(); @@ -204,6 +204,7 @@ describe.each([ describe("edge cases", () => { it("should handle synchronous functions", () => { // WHEN + console.log(InvokeStore) const result = InvokeStore.run( { [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", diff --git a/src/invoke-store.timers.multiconcurrency.spec.ts b/src/invoke-store.timers.multiconcurrency.spec.ts index f4d17df..8fb7975 100644 --- a/src/invoke-store.timers.multiconcurrency.spec.ts +++ b/src/invoke-store.timers.multiconcurrency.spec.ts @@ -5,8 +5,8 @@ import { createInvokeStore } from "./invoke-store.js"; * These tests specifically verify context preservation across various * timer and async APIs without using fake timers. */ -describe("InvokeStore timer functions context preservation", () => { - const InvokeStore = createInvokeStore({ +describe("InvokeStore timer functions context preservation", async () => { + const InvokeStore = await createInvokeStore({ env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } }); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index f3a4387..3aa6f7f 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -1,3 +1,5 @@ +import { time } from "node:console"; + interface Context { [key: string]: unknown; [key: symbol]: unknown; @@ -92,8 +94,13 @@ class InvokeStoreMulti extends InvokeStoreBase { constructor() { super(); - const asyncHooks = require('node:async_hooks') as typeof import("node:async_hooks"); - this.als = new asyncHooks.AsyncLocalStorage(); + } + + static async create(): Promise { + const instance = new InvokeStoreMulti(); + const asyncHooks = await import('node:async_hooks'); + instance.als = new asyncHooks.AsyncLocalStorage(); + return instance; } getContext(): Context | undefined { @@ -130,23 +137,25 @@ interface InvokeStoreConfig { env?: NodeJS.ProcessEnv; } -export const createInvokeStore = (storeConfig?: InvokeStoreConfig): InvokeStoreBase => { +const createInvokeStore = async (storeConfig?: InvokeStoreConfig): Promise => { const env = storeConfig?.env ?? process.env; const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); - console.log(`isMulti is` + isMulti); - const InvokeStoreImpl = isMulti ? InvokeStoreMulti : InvokeStoreSingle; + + const instanceItem = isMulti + ? await InvokeStoreMulti.create() + : new InvokeStoreSingle(); if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { return globalThis.awslambda.InvokeStore; } - - const instance = new InvokeStoreImpl(); if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { - globalThis.awslambda.InvokeStore = instance; + globalThis.awslambda.InvokeStore = instanceItem; } - console.log('returning instance = ' + instance.constructor.name); - return instance; -} -export const InvokeStore = createInvokeStore(); + return instanceItem; +}; + +let InvokeStore = createInvokeStore(); + +export { createInvokeStore, InvokeStore }; \ No newline at end of file From bc884e7e6314592e719c1df7f140c4a458774781 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 15:56:42 +0000 Subject: [PATCH 07/26] fix: multiconcurrency --- ...voke-store.global.multiconcurrency.spec.ts | 4 +- src/invoke-store.global.spec.ts | 41 ++-- ...e-store.module.loading.multiconcurrency.ts | 12 ++ src/invoke-store.module.loading.spec.ts | 18 +- src/invoke-store.multiconcurrency.spec.ts | 46 ++--- src/invoke-store.spec.ts | 94 ++++----- ...voke-store.timers.multiconcurrency.spec.ts | 184 +++++++++--------- src/invoke-store.ts | 57 ++++-- 8 files changed, 239 insertions(+), 217 deletions(-) create mode 100644 src/invoke-store.module.loading.multiconcurrency.ts diff --git a/src/invoke-store.global.multiconcurrency.spec.ts b/src/invoke-store.global.multiconcurrency.spec.ts index 123bdf8..1e1fac3 100644 --- a/src/invoke-store.global.multiconcurrency.spec.ts +++ b/src/invoke-store.global.multiconcurrency.spec.ts @@ -34,7 +34,7 @@ describe("InvokeStore Global Singleton", () => { const testKey = "dynamic-key"; const testValue = "dynamic-value"; - const originalImportAwaited = await OriginalImport; + const originalImportAwaited = await OriginalImport.getInstance(); // WHEN - Set up context with original import await originalImportAwaited.run( @@ -47,7 +47,7 @@ describe("InvokeStore Global Singleton", () => { // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); - const DynamicImport = await dynamicModule.InvokeStore; + const DynamicImport = await dynamicModule.InvokeStore.getInstance(); // THEN - Dynamically imported instance should see the same context expect(DynamicImport).toBe(originalImportAwaited); // Same instance diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index fbef128..fb23c60 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -7,14 +7,14 @@ import { beforeEach, vi, } from "vitest"; -import { createInvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; +import { InvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; describe.each([ { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, { label: 'single-concurrency', config: undefined } ])('InvokeStore with %s', async ({ config }) => { - await createInvokeStore(config); + const invokeStore = await InvokeStore.getInstance(config); describe("InvokeStore Global Singleton", () => { const originalGlobalAwsLambda = globalThis.awslambda; @@ -36,7 +36,7 @@ describe.each([ it("should store the instance in globalThis.awslambda", async () => { // THEN expect(globalThis.awslambda.InvokeStore).toBeDefined(); - expect(await globalThis.awslambda.InvokeStore).toBe(await OriginalImport); + expect(await globalThis.awslambda.InvokeStore).toBe(await OriginalImport.getInstance()); }); it("should share context between original import and global reference", async () => { @@ -45,13 +45,13 @@ describe.each([ const testKey = "test-key"; const testValue = "test-value"; - const originalImportAwaited = await OriginalImport; + const originalImport = await OriginalImport.getInstance(); // WHEN - Use the original import to set up context - await originalImportAwaited.run( - { [ originalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + await originalImport.run( + { [ originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, async () => { - originalImportAwaited.set(testKey, testValue); + originalImport.set(testKey, testValue); // THEN - Global reference should see the same context const globalInstance = globalThis.awslambda.InvokeStore!; @@ -64,21 +64,20 @@ describe.each([ it("should maintain the same storage across different references", async () => { // GIVEN const globalInstance = globalThis.awslambda.InvokeStore!; - const OriginalImportAwaited = await OriginalImport; - const globalInstanceAwaited = await globalInstance; + const originalImport = await OriginalImport.getInstance(); const testRequestId = "global-test"; const testKey = "global-key"; const testValue = "global-value"; // WHEN - Set context using global reference await globalInstance.run( - { [OriginalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + { [originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, async () => { globalInstance.set(testKey, testValue); // THEN - Original import should see the same context - expect(OriginalImportAwaited.getRequestId()).toBe(testRequestId); - expect(OriginalImportAwaited.get(testKey)).toBe(testValue); + expect(originalImport.getRequestId()).toBe(testRequestId); + expect(originalImport.get(testKey)).toBe(testValue); } ); }); @@ -120,7 +119,7 @@ describe.each([ // WHEN const { InvokeStore: ReimportedStore } = await import("./invoke-store.js"); - const awaitedReimportedStore = await ReimportedStore; + const awaitedReimportedStore = await ReimportedStore.getInstance(); // THEN expect(awaitedReimportedStore).toBe(mockInstance); @@ -149,16 +148,16 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); - const awaitedInvokeStore = await InvokeStore; + const invokeStore = await InvokeStore.getInstance(); // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); let requestId: string | undefined; - await awaitedInvokeStore.run( - { [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + await invokeStore.run( + { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { - requestId = awaitedInvokeStore.getRequestId(); + requestId = invokeStore.getRequestId(); } ); expect(requestId).toBe("test-id"); @@ -170,16 +169,16 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); - const awaitedInvokeStore = await InvokeStore; + const invokeStore = await InvokeStore.getInstance(); // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); let requestId: string | undefined; - await awaitedInvokeStore.run( - { [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + await invokeStore.run( + { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { - requestId = awaitedInvokeStore.getRequestId(); + requestId = invokeStore.getRequestId(); } ); expect(requestId).toBe("test-id"); diff --git a/src/invoke-store.module.loading.multiconcurrency.ts b/src/invoke-store.module.loading.multiconcurrency.ts new file mode 100644 index 0000000..357405c --- /dev/null +++ b/src/invoke-store.module.loading.multiconcurrency.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from 'vitest'; +import { InvokeStore } from "./invoke-store.js" + +describe('InvokeStore implementations', () => { + it('should load the correct class', async () => { + const singleStore = await InvokeStore.getInstance({ + env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } + }); + + expect(singleStore.constructor.name).toBe('InvokeStoreMulti'); + }); +}); \ No newline at end of file diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts index 3a59a66..05da6eb 100644 --- a/src/invoke-store.module.loading.spec.ts +++ b/src/invoke-store.module.loading.spec.ts @@ -1,22 +1,12 @@ import { describe, it, expect } from 'vitest'; -import { createInvokeStore } from "./invoke-store.js" +import { InvokeStore } from "./invoke-store.js" describe('InvokeStore implementations', () => { - it('should create different implementations based on environment', async () => { - const singleStore = await createInvokeStore({ + it('should load the correct class', async () => { + const singleStore = await InvokeStore.getInstance({ env: {} }); expect(singleStore.constructor.name).toBe('InvokeStoreSingle'); - - if (globalThis.awslambda) { - globalThis.awslambda.InvokeStore = undefined; - } - - const multiStore = await createInvokeStore({ - env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } - }); - - expect(multiStore.constructor.name).toBe('InvokeStoreMulti'); }); -}); +}); \ No newline at end of file diff --git a/src/invoke-store.multiconcurrency.spec.ts b/src/invoke-store.multiconcurrency.spec.ts index 72c866e..8d05080 100644 --- a/src/invoke-store.multiconcurrency.spec.ts +++ b/src/invoke-store.multiconcurrency.spec.ts @@ -3,7 +3,7 @@ import { InvokeStore } from "./invoke-store.js"; describe("InvokeStore", async () => { - const awaitedInvokeStore = await InvokeStore; + const invokeStore = await InvokeStore.getInstance(); beforeEach(() => { vi.useFakeTimers(); @@ -20,21 +20,21 @@ describe("InvokeStore", async () => { const traces: string[] = []; // WHEN - await awaitedInvokeStore.run( + await invokeStore.run( { - [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", }, async () => { - traces.push(`outer-${awaitedInvokeStore.getRequestId()}`); - await awaitedInvokeStore.run( + traces.push(`outer-${invokeStore.getRequestId()}`); + await invokeStore.run( { - [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", }, async () => { - traces.push(`inner-${awaitedInvokeStore.getRequestId()}`); + traces.push(`inner-${invokeStore.getRequestId()}`); }, ); - traces.push(`outer-again-${awaitedInvokeStore.getRequestId()}`); + traces.push(`outer-again-${invokeStore.getRequestId()}`); }, ); @@ -52,26 +52,26 @@ describe("InvokeStore", async () => { // WHEN - Simulate concurrent invocations const isolateTasks = Promise.all([ - awaitedInvokeStore.run( + invokeStore.run( { - [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [awaitedInvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { - traces.push(`start-1-${awaitedInvokeStore.getRequestId()}`); + traces.push(`start-1-${invokeStore.getRequestId()}`); await new Promise((resolve) => setTimeout(resolve, 10)); - traces.push(`end-1-${awaitedInvokeStore.getRequestId()}`); + traces.push(`end-1-${invokeStore.getRequestId()}`); }, ), - awaitedInvokeStore.run( + invokeStore.run( { - [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [awaitedInvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", }, async () => { - traces.push(`start-2-${awaitedInvokeStore.getRequestId()}`); + traces.push(`start-2-${invokeStore.getRequestId()}`); await new Promise((resolve) => setTimeout(resolve, 5)); - traces.push(`end-2-${awaitedInvokeStore.getRequestId()}`); + traces.push(`end-2-${invokeStore.getRequestId()}`); }, ), ]); @@ -92,20 +92,20 @@ describe("InvokeStore", async () => { const traces: string[] = []; // WHEN - await awaitedInvokeStore.run( + await invokeStore.run( { - [awaitedInvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", }, async () => { - traces.push(`before-${awaitedInvokeStore.getRequestId()}`); + traces.push(`before-${invokeStore.getRequestId()}`); const task = new Promise((resolve) => { setTimeout(resolve, 1); }).then(() => { - traces.push(`inside-${awaitedInvokeStore.getRequestId()}`); + traces.push(`inside-${invokeStore.getRequestId()}`); }); vi.runAllTimers(); await task; - traces.push(`after-${awaitedInvokeStore.getRequestId()}`); + traces.push(`after-${invokeStore.getRequestId()}`); }, ); diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index 4bff603..e88814b 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -1,11 +1,11 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; -import { createInvokeStore } from "./invoke-store.js"; +import { InvokeStore } from "./invoke-store.js"; describe.each([ { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, { label: 'single-concurrency', config: undefined } ])('InvokeStore with %s', async ({ config }) => { - const InvokeStore = await createInvokeStore(config); + const invokeStore = await InvokeStore.getInstance(config); describe("InvokeStore", () => { beforeEach(() => { vi.useFakeTimers(); @@ -18,8 +18,8 @@ describe.each([ describe("getRequestId and getXRayTraceId", () => { it("should return placeholder when called outside run context", () => { // WHEN - const requestId = InvokeStore.getRequestId(); - const traceId = InvokeStore.getXRayTraceId(); + const requestId = invokeStore.getRequestId(); + const traceId = invokeStore.getXRayTraceId(); // THEN expect(requestId).toBe("-"); @@ -28,15 +28,15 @@ describe.each([ it("should return current invoke IDs when called within run context", async () => { // WHEN - const result = await InvokeStore.run( + const result = await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", }, () => { return { - requestId: InvokeStore.getRequestId(), - traceId: InvokeStore.getXRayTraceId(), + requestId: invokeStore.getRequestId(), + traceId: invokeStore.getXRayTraceId(), }; }, ); @@ -50,16 +50,16 @@ describe.each([ describe("custom properties", () => { it("should allow setting and getting custom properties", async () => { // WHEN - const result = await InvokeStore.run( + const result = await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", customProp: "initial-value", }, () => { - InvokeStore.set("dynamicProp", "dynamic-value"); + invokeStore.set("dynamicProp", "dynamic-value"); return { - initial: InvokeStore.get("customProp"), - dynamic: InvokeStore.get("dynamicProp"), + initial: invokeStore.get("customProp"), + dynamic: invokeStore.get("dynamicProp"), }; }, ); @@ -71,18 +71,18 @@ describe.each([ it("should prevent modifying protected Lambda fields", async () => { // WHEN & THEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { expect(() => { - InvokeStore.set(InvokeStore.PROTECTED_KEYS.REQUEST_ID, "new-id"); + invokeStore.set(invokeStore.PROTECTED_KEYS.REQUEST_ID, "new-id"); }).toThrow(/Cannot modify protected Lambda context field/); expect(() => { - InvokeStore.set( - InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID, + invokeStore.set( + invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID, "new-trace", ); }).toThrow(/Cannot modify protected Lambda context field/); @@ -94,7 +94,7 @@ describe.each([ describe("getContext", () => { it("should return undefined when outside run context", () => { // WHEN - const context = InvokeStore.getContext(); + const context = invokeStore.getContext(); // THEN expect(context).toBeUndefined(); @@ -102,22 +102,22 @@ describe.each([ it("should return complete context with Lambda and custom fields", async () => { // WHEN - const context = await InvokeStore.run( + const context = await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", customField: "custom-value", }, () => { - InvokeStore.set("dynamicField", "dynamic-value"); - return InvokeStore.getContext(); + invokeStore.set("dynamicField", "dynamic-value"); + return invokeStore.getContext(); }, ); // THEN expect(context).toEqual({ - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", customField: "custom-value", dynamicField: "dynamic-value", }); @@ -127,7 +127,7 @@ describe.each([ describe("hasContext", () => { it("should return false when outside run context", () => { // WHEN - const hasContext = InvokeStore.hasContext(); + const hasContext = invokeStore.hasContext(); // THEN expect(hasContext).toBe(false); @@ -135,12 +135,12 @@ describe.each([ it("should return true when inside run context", async () => { // WHEN - const result = await InvokeStore.run( + const result = await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { - return InvokeStore.hasContext(); + return invokeStore.hasContext(); }, ); @@ -155,9 +155,9 @@ describe.each([ const error = new Error("test error"); // WHEN - const promise = InvokeStore.run( + const promise = invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, async () => { throw error; @@ -166,7 +166,7 @@ describe.each([ // THEN await expect(promise).rejects.toThrow(error); - expect(InvokeStore.getRequestId()).toBe("-"); + expect(invokeStore.getRequestId()).toBe("-"); }); it("should handle errors in concurrent executions independently", async () => { @@ -175,20 +175,20 @@ describe.each([ // WHEN await Promise.allSettled([ - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", }, async () => { - traces.push(`success-${InvokeStore.getRequestId()}`); + traces.push(`success-${invokeStore.getRequestId()}`); }, ), - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", }, async () => { - traces.push(`before-error-${InvokeStore.getRequestId()}`); + traces.push(`before-error-${invokeStore.getRequestId()}`); throw new Error("test error"); }, ), @@ -197,7 +197,7 @@ describe.each([ // THEN expect(traces).toContain("success-success-id"); expect(traces).toContain("before-error-error-id"); - expect(InvokeStore.getRequestId()).toBe("-"); + expect(invokeStore.getRequestId()).toBe("-"); }); }); @@ -205,12 +205,12 @@ describe.each([ it("should handle synchronous functions", () => { // WHEN console.log(InvokeStore) - const result = InvokeStore.run( + const result = invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { - return InvokeStore.getRequestId(); + return invokeStore.getRequestId(); }, ); @@ -223,9 +223,9 @@ describe.each([ const error = new Error("immediate rejection"); // WHEN - const promise = InvokeStore.run( + const promise = invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { return Promise.reject(error); @@ -234,7 +234,7 @@ describe.each([ // THEN await expect(promise).rejects.toThrow(error); - expect(InvokeStore.getRequestId()).toBe("-"); + expect(invokeStore.getRequestId()).toBe("-"); }); }); }); diff --git a/src/invoke-store.timers.multiconcurrency.spec.ts b/src/invoke-store.timers.multiconcurrency.spec.ts index 8fb7975..c665cce 100644 --- a/src/invoke-store.timers.multiconcurrency.spec.ts +++ b/src/invoke-store.timers.multiconcurrency.spec.ts @@ -1,12 +1,12 @@ import { describe, it, expect } from "vitest"; -import { createInvokeStore } from "./invoke-store.js"; +import { InvokeStore } from "./invoke-store.js"; /** * These tests specifically verify context preservation across various * timer and async APIs without using fake timers. */ describe("InvokeStore timer functions context preservation", async () => { - const InvokeStore = await createInvokeStore({ + const invokeStore = await InvokeStore.getInstance({ env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } }); @@ -20,21 +20,21 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); + traces.push(`before-${invokeStore.getRequestId()}`); await new Promise((resolve) => { setTimeout(() => { - traces.push(`inside-timeout-${InvokeStore.getRequestId()}`); + traces.push(`inside-timeout-${invokeStore.getRequestId()}`); resolve(); }, 10); }); - traces.push(`after-${InvokeStore.getRequestId()}`); + traces.push(`after-${invokeStore.getRequestId()}`); }, ); @@ -52,25 +52,25 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`level-0-${InvokeStore.getRequestId()}`); + traces.push(`level-0-${invokeStore.getRequestId()}`); await new Promise((resolve) => { setTimeout(() => { - traces.push(`level-1-${InvokeStore.getRequestId()}`); + traces.push(`level-1-${invokeStore.getRequestId()}`); setTimeout(() => { - traces.push(`level-2-${InvokeStore.getRequestId()}`); + traces.push(`level-2-${invokeStore.getRequestId()}`); resolve(); }, 10); }, 10); }); - traces.push(`done-${InvokeStore.getRequestId()}`); + traces.push(`done-${invokeStore.getRequestId()}`); }, ); @@ -91,21 +91,21 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); + traces.push(`before-${invokeStore.getRequestId()}`); await new Promise((resolve) => { setImmediate(() => { - traces.push(`inside-immediate-${InvokeStore.getRequestId()}`); + traces.push(`inside-immediate-${invokeStore.getRequestId()}`); resolve(); }); }); - traces.push(`after-${InvokeStore.getRequestId()}`); + traces.push(`after-${invokeStore.getRequestId()}`); }, ); @@ -123,25 +123,25 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`level-0-${InvokeStore.getRequestId()}`); + traces.push(`level-0-${invokeStore.getRequestId()}`); await new Promise((resolve) => { setImmediate(() => { - traces.push(`level-1-${InvokeStore.getRequestId()}`); + traces.push(`level-1-${invokeStore.getRequestId()}`); setImmediate(() => { - traces.push(`level-2-${InvokeStore.getRequestId()}`); + traces.push(`level-2-${invokeStore.getRequestId()}`); resolve(); }); }); }); - traces.push(`done-${InvokeStore.getRequestId()}`); + traces.push(`done-${invokeStore.getRequestId()}`); }, ); @@ -162,21 +162,21 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); + traces.push(`before-${invokeStore.getRequestId()}`); await new Promise((resolve) => { process.nextTick(() => { - traces.push(`inside-nexttick-${InvokeStore.getRequestId()}`); + traces.push(`inside-nexttick-${invokeStore.getRequestId()}`); resolve(); }); }); - traces.push(`after-${InvokeStore.getRequestId()}`); + traces.push(`after-${invokeStore.getRequestId()}`); }, ); @@ -194,25 +194,25 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`level-0-${InvokeStore.getRequestId()}`); + traces.push(`level-0-${invokeStore.getRequestId()}`); await new Promise((resolve) => { process.nextTick(() => { - traces.push(`level-1-${InvokeStore.getRequestId()}`); + traces.push(`level-1-${invokeStore.getRequestId()}`); process.nextTick(() => { - traces.push(`level-2-${InvokeStore.getRequestId()}`); + traces.push(`level-2-${invokeStore.getRequestId()}`); resolve(); }); }); }); - traces.push(`done-${InvokeStore.getRequestId()}`); + traces.push(`done-${invokeStore.getRequestId()}`); }, ); @@ -233,18 +233,18 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`before-${InvokeStore.getRequestId()}`); + traces.push(`before-${invokeStore.getRequestId()}`); await Promise.resolve().then(() => { - traces.push(`inside-promise-${InvokeStore.getRequestId()}`); + traces.push(`inside-promise-${invokeStore.getRequestId()}`); }); - traces.push(`after-${InvokeStore.getRequestId()}`); + traces.push(`after-${invokeStore.getRequestId()}`); }, ); @@ -262,27 +262,27 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`start-${InvokeStore.getRequestId()}`); + traces.push(`start-${invokeStore.getRequestId()}`); await Promise.resolve() .then(() => { - traces.push(`then-1-${InvokeStore.getRequestId()}`); + traces.push(`then-1-${invokeStore.getRequestId()}`); return delay(10); }) .then(() => { - traces.push(`then-2-${InvokeStore.getRequestId()}`); + traces.push(`then-2-${invokeStore.getRequestId()}`); return Promise.resolve(); }) .then(() => { - traces.push(`then-3-${InvokeStore.getRequestId()}`); + traces.push(`then-3-${invokeStore.getRequestId()}`); }); - traces.push(`end-${InvokeStore.getRequestId()}`); + traces.push(`end-${invokeStore.getRequestId()}`); }, ); @@ -304,36 +304,36 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`start-${InvokeStore.getRequestId()}`); + traces.push(`start-${invokeStore.getRequestId()}`); const immediatePromise = new Promise((resolve) => { setImmediate(() => { - traces.push(`immediate-${InvokeStore.getRequestId()}`); + traces.push(`immediate-${invokeStore.getRequestId()}`); resolve(); }); }); const timeoutPromise = new Promise((resolve) => { setTimeout(() => { - traces.push(`timeout-${InvokeStore.getRequestId()}`); + traces.push(`timeout-${invokeStore.getRequestId()}`); resolve(); }, 0); }); const nextTickPromise = new Promise((resolve) => { process.nextTick(() => { - traces.push(`nextTick-${InvokeStore.getRequestId()}`); + traces.push(`nextTick-${invokeStore.getRequestId()}`); resolve(); }); }); const promisePromise = Promise.resolve().then(() => { - traces.push(`promise-${InvokeStore.getRequestId()}`); + traces.push(`promise-${invokeStore.getRequestId()}`); }); await Promise.all([ @@ -343,7 +343,7 @@ describe("InvokeStore timer functions context preservation", async () => { promisePromise, ]); - traces.push(`end-${InvokeStore.getRequestId()}`); + traces.push(`end-${invokeStore.getRequestId()}`); }, ); @@ -365,26 +365,26 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN - Simulate concurrent invocations await Promise.all([ - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { - traces.push(`start-1-${InvokeStore.getRequestId()}`); + traces.push(`start-1-${invokeStore.getRequestId()}`); await delay(20); // Longer delay - traces.push(`end-1-${InvokeStore.getRequestId()}`); + traces.push(`end-1-${invokeStore.getRequestId()}`); }, ), - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", }, async () => { - traces.push(`start-2-${InvokeStore.getRequestId()}`); + traces.push(`start-2-${invokeStore.getRequestId()}`); await delay(10); // Shorter delay - traces.push(`end-2-${InvokeStore.getRequestId()}`); + traces.push(`end-2-${invokeStore.getRequestId()}`); }, ), ]); @@ -404,58 +404,58 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN - Simulate concurrent invocations with different async operations await Promise.all([ - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", }, async () => { - traces.push(`start-1-${InvokeStore.getRequestId()}`); + traces.push(`start-1-${invokeStore.getRequestId()}`); // Use setTimeout await new Promise((resolve) => { setTimeout(() => { - traces.push(`timeout-1-${InvokeStore.getRequestId()}`); + traces.push(`timeout-1-${invokeStore.getRequestId()}`); resolve(); }, 15); }); - traces.push(`end-1-${InvokeStore.getRequestId()}`); + traces.push(`end-1-${invokeStore.getRequestId()}`); }, ), - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", }, async () => { - traces.push(`start-2-${InvokeStore.getRequestId()}`); + traces.push(`start-2-${invokeStore.getRequestId()}`); // Use setImmediate await new Promise((resolve) => { setImmediate(() => { - traces.push(`immediate-2-${InvokeStore.getRequestId()}`); + traces.push(`immediate-2-${invokeStore.getRequestId()}`); resolve(); }); }); - traces.push(`end-2-${InvokeStore.getRequestId()}`); + traces.push(`end-2-${invokeStore.getRequestId()}`); }, ), - InvokeStore.run( + invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-3", + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-3", }, async () => { - traces.push(`start-3-${InvokeStore.getRequestId()}`); + traces.push(`start-3-${invokeStore.getRequestId()}`); // Use process.nextTick await new Promise((resolve) => { process.nextTick(() => { - traces.push(`nextTick-3-${InvokeStore.getRequestId()}`); + traces.push(`nextTick-3-${invokeStore.getRequestId()}`); resolve(); }); }); - traces.push(`end-3-${InvokeStore.getRequestId()}`); + traces.push(`end-3-${invokeStore.getRequestId()}`); }, ), ]); @@ -508,17 +508,17 @@ describe("InvokeStore timer functions context preservation", async () => { const iterations = 3; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`start-${InvokeStore.getRequestId()}`); + traces.push(`start-${invokeStore.getRequestId()}`); let count = 0; await new Promise((resolve) => { function recursive() { - traces.push(`iteration-${count}-${InvokeStore.getRequestId()}`); + traces.push(`iteration-${count}-${invokeStore.getRequestId()}`); count++; if (count < iterations) { @@ -531,7 +531,7 @@ describe("InvokeStore timer functions context preservation", async () => { recursive(); }); - traces.push(`end-${InvokeStore.getRequestId()}`); + traces.push(`end-${invokeStore.getRequestId()}`); }, ); @@ -551,30 +551,30 @@ describe("InvokeStore timer functions context preservation", async () => { const traces: string[] = []; // WHEN - await InvokeStore.run( + await invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { - traces.push(`start-${InvokeStore.getRequestId()}`); + traces.push(`start-${invokeStore.getRequestId()}`); // Queue a setTimeout that triggers setImmediate await new Promise((resolve) => { setTimeout(() => { - traces.push(`timeout-${InvokeStore.getRequestId()}`); + traces.push(`timeout-${invokeStore.getRequestId()}`); setImmediate(() => { - traces.push(`immediate-${InvokeStore.getRequestId()}`); + traces.push(`immediate-${invokeStore.getRequestId()}`); process.nextTick(() => { - traces.push(`nextTick-${InvokeStore.getRequestId()}`); + traces.push(`nextTick-${invokeStore.getRequestId()}`); resolve(); }); }); }, 10); }); - traces.push(`end-${InvokeStore.getRequestId()}`); + traces.push(`end-${invokeStore.getRequestId()}`); }, ); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 3aa6f7f..c2da6a8 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -86,6 +86,10 @@ class InvokeStoreSingle extends InvokeStoreBase { this.currentContext = undefined; } } + + getInstance(): InvokeStoreBase { + return this; + } } // Multi Context Implementation @@ -137,25 +141,42 @@ interface InvokeStoreConfig { env?: NodeJS.ProcessEnv; } -const createInvokeStore = async (storeConfig?: InvokeStoreConfig): Promise => { - const env = storeConfig?.env ?? process.env; - const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); - - const instanceItem = isMulti - ? await InvokeStoreMulti.create() - : new InvokeStoreSingle(); +export namespace InvokeStore { + let instance: InvokeStoreBase | null = null; + let initializationPromise: Promise | null = null; - if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { - return globalThis.awslambda.InvokeStore; - } - - if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { - globalThis.awslambda.InvokeStore = instanceItem; - } + export async function getInstance(storeConfig?: InvokeStoreConfig): Promise { - return instanceItem; -}; + if (instance && storeConfig ) { + console.log('return because instance is set'); + return instance; + } -let InvokeStore = createInvokeStore(); + if (initializationPromise) { + console.log('return because initializationPromise is set'); + return initializationPromise; + } -export { createInvokeStore, InvokeStore }; \ No newline at end of file + initializationPromise = (async () => { + const env = storeConfig?.env ?? process.env; + const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); + + const instanceItem = isMulti + ? await InvokeStoreMulti.create() + : new InvokeStoreSingle(); + + if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { + instance = globalThis.awslambda.InvokeStore; + } else if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { + instance = instanceItem; + globalThis.awslambda.InvokeStore = instanceItem; + } else { + instance = instanceItem; + } + + return instance; + })(); + + return initializationPromise; + } +} \ No newline at end of file From 23c57d72d7398f45b838b6c7cc3552ffcff1ec10 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 16:01:18 +0000 Subject: [PATCH 08/26] fix: readme --- README.md | 63 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 47f15e7..7a7b3c0 100644 --- a/README.md +++ b/README.md @@ -33,19 +33,20 @@ export const handler = async (event, context) => { // The RIC has already initialized the InvokeStore with requestId and X-Ray traceId // Access Lambda context data - console.log(`Processing request: ${InvokeStore.getRequestId()}`); + const invokeStore = await InvokeStore.getInstance(); + console.log(`Processing request: ${invokeStore.getRequestId()}`); // Store custom data - InvokeStore.set("userId", event.userId); + invokeStore.set("userId", event.userId); // Data persists across async operations await processData(event); // Retrieve custom data - const userId = InvokeStore.get("userId"); + const userId = invokeStore.get("userId"); return { - requestId: InvokeStore.getRequestId(), + requestId: invokeStore.getRequestId(), userId, }; }; @@ -53,89 +54,97 @@ export const handler = async (event, context) => { // Context is preserved in async operations async function processData(event) { // Still has access to the same invoke context - console.log(`Processing in same context: ${InvokeStore.getRequestId()}`); + const invokeStore = await InvokeStore.getInstance(); + console.log(`Processing in same context: ${invokeStore.getRequestId()}`); // Can set additional data - InvokeStore.set("processedData", { result: "success" }); + invokeStore.set("processedData", { result: "success" }); } ``` ## API Reference -### InvokeStore.getContext() +### InvokeStore.getInstance() +Initialization +First, get an instance of the InvokeStore: +```typescript +const invokeStore = await InvokeStore.getInstance(); +``` + +### invokeStore.getContext() Returns the complete current context or `undefined` if outside a context. ```typescript -const context = InvokeStore.getContext(); +const context = invokeStore.getContext(); ``` -### InvokeStore.get(key) +### invokeStore.get(key) Gets a value from the current context. ```typescript -const requestId = InvokeStore.get(InvokeStore.PROTECTED_KEYS.REQUEST_ID); -const customValue = InvokeStore.get("customKey"); +const requestId = invokeStore.get(InvokeStore.PROTECTED_KEYS.REQUEST_ID); +const customValue = invokeStore.get("customKey"); ``` -### InvokeStore.set(key, value) +### invokeStore.set(key, value) Sets a custom value in the current context. Protected Lambda fields cannot be modified. ```typescript -InvokeStore.set("userId", "user-123"); -InvokeStore.set("timestamp", Date.now()); +invokeStore.set("userId", "user-123"); +invokeStore.set("timestamp", Date.now()); // This will throw an error: -// InvokeStore.set(InvokeStore.PROTECTED_KEYS.REQUEST_ID, 'new-id'); +// invokeStore.set(invokeStore.PROTECTED_KEYS.REQUEST_ID, 'new-id'); ``` -### InvokeStore.getRequestId() +### invokeStore.getRequestId() Convenience method to get the current request ID. ```typescript -const requestId = InvokeStore.getRequestId(); // Returns '-' if outside context +const requestId = invokeStore.getRequestId(); // Returns '-' if outside context ``` -### InvokeStore.getTenantId() +### invokeStore.getTenantId() Convenience method to get the tenant ID. ```typescript -const requestId = InvokeStore.getTenantId(); +const requestId = invokeStore.getTenantId(); ``` -### InvokeStore.getXRayTraceId() +### invokeStore.getXRayTraceId() Convenience method to get the current [X-Ray trace ID](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-traces). This ID is used for distributed tracing across AWS services. ```typescript -const traceId = InvokeStore.getXRayTraceId(); // Returns undefined if not set or outside context +const traceId = invokeStore.getXRayTraceId(); // Returns undefined if not set or outside context ``` -### InvokeStore.hasContext() +### invokeStore.hasContext() Checks if code is currently running within an invoke context. ```typescript -if (InvokeStore.hasContext()) { +if (invokeStore.hasContext()) { // We're inside an invoke context } ``` -### InvokeStore.run(context, fn) +### invokeStore.run(context, fn) > **Note**: This method is primarily used by the Lambda Runtime Interface Client (RIC) to initialize the context for each invocation. Lambda function developers typically don't need to call this method directly. Runs a function within an invoke context. ```typescript -InvokeStore.run( +invokeStore.run( { - [InvokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-123", - [InvokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-456", // Optional X-Ray trace ID + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-123", + [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-456", // Optional X-Ray trace ID customField: "value", // Optional custom fields }, () => { From d9f9ebc203c2a29abe421d0c5e3069a071adf44c Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 16:07:18 +0000 Subject: [PATCH 09/26] fix: cleanup --- src/invoke-store.ts | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index c2da6a8..7dac93d 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -143,40 +143,28 @@ interface InvokeStoreConfig { export namespace InvokeStore { let instance: InvokeStoreBase | null = null; - let initializationPromise: Promise | null = null; export async function getInstance(storeConfig?: InvokeStoreConfig): Promise { - - if (instance && storeConfig ) { - console.log('return because instance is set'); + if (instance) { return instance; } - if (initializationPromise) { - console.log('return because initializationPromise is set'); - return initializationPromise; - } - - initializationPromise = (async () => { const env = storeConfig?.env ?? process.env; const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); - - const instanceItem = isMulti - ? await InvokeStoreMulti.create() - : new InvokeStoreSingle(); - - if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { - instance = globalThis.awslambda.InvokeStore; - } else if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { - instance = instanceItem; - globalThis.awslambda.InvokeStore = instanceItem; - } else { - instance = instanceItem; - } - - return instance; - })(); + + const newInstance = isMulti + ? await InvokeStoreMulti.create() + : new InvokeStoreSingle(); + + if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { + instance = globalThis.awslambda.InvokeStore; + } else if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { + instance = newInstance; + globalThis.awslambda.InvokeStore = newInstance; + } else { + instance = newInstance; + } - return initializationPromise; + return instance; } } \ No newline at end of file From a3d10b2ca33a0848a346f320ffee5e546d5a17e4 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 16:18:15 +0000 Subject: [PATCH 10/26] fix: cleanup --- src/invoke-store.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 7dac93d..a770f0b 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -1,5 +1,3 @@ -import { time } from "node:console"; - interface Context { [key: string]: unknown; [key: symbol]: unknown; From e3f640ece63273242953d3187f2e0ed3ae6ea8b4 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 17:29:06 +0000 Subject: [PATCH 11/26] fix: export type --- src/invoke-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index a770f0b..3a0693d 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -25,8 +25,8 @@ if (!NO_GLOBAL_AWS_LAMBDA) { globalThis.awslambda = globalThis.awslambda || {}; } -abstract class InvokeStoreBase { - protected readonly PROTECTED_KEYS = PROTECTED_KEYS; +export abstract class InvokeStoreBase { + public readonly PROTECTED_KEYS = PROTECTED_KEYS; abstract getContext(): Context | undefined; abstract hasContext(): boolean; From b05a31862d986d1004280b0ebd97184931b791c9 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 13 Nov 2025 23:41:20 +0000 Subject: [PATCH 12/26] fix: adressing first batch of PR comments --- README.md | 1 - src/invoke-store.ts | 27 +++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a7b3c0..8836b30 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,6 @@ async function processData(event) { ## API Reference ### InvokeStore.getInstance() -Initialization First, get an instance of the InvokeStore: ```typescript const invokeStore = await InvokeStore.getInstance(); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 3a0693d..8948be7 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -25,6 +25,15 @@ if (!NO_GLOBAL_AWS_LAMBDA) { globalThis.awslambda = globalThis.awslambda || {}; } +/** + * Base class for AWS Lambda context storage implementations. + * Provides core functionality for managing Lambda execution context. + * + * Implementations handle either single-context (InvokeStoreSingle) or + * multi-context (InvokeStoreMulti) scenarios based on Lambda's execution environment. + * + * @public + */ export abstract class InvokeStoreBase { public readonly PROTECTED_KEYS = PROTECTED_KEYS; @@ -51,7 +60,10 @@ export abstract class InvokeStoreBase { } } -// Single Context Implementation +/** + * Single Context Implementation + * @internal + */ class InvokeStoreSingle extends InvokeStoreBase { private currentContext?: Context; @@ -90,7 +102,10 @@ class InvokeStoreSingle extends InvokeStoreBase { } } -// Multi Context Implementation +/** + * Multi Context Implementation + * @internal + */ class InvokeStoreMulti extends InvokeStoreBase { private als!: import("node:async_hooks").AsyncLocalStorage; @@ -139,6 +154,14 @@ interface InvokeStoreConfig { env?: NodeJS.ProcessEnv; } +/** + * Provides access to AWS Lambda execution context storage. + * Supports both single-context and multi-context environments through different implementations. + * + * The store manages protected Lambda context fields and allows storing/retrieving custom values + * within the execution context. + * @public + */ export namespace InvokeStore { let instance: InvokeStoreBase | null = null; From 84d923507cd1ece6b755086a62753feef3a18660 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 00:01:24 +0000 Subject: [PATCH 13/26] fix: test refactoring --- package.json | 4 +--- ...ec.ts => invoke-store.concurrency.spec.ts} | 8 ++++--- ...> invoke-store.global.concurrency.spec.ts} | 22 +++++++++---------- src/invoke-store.global.spec.ts | 17 +++++++++----- ...-store.module.loading.concurrency.spec.ts} | 8 +++---- src/invoke-store.module.loading.spec.ts | 5 +---- src/invoke-store.spec.ts | 17 +++++++++----- ...> invoke-store.timers.concurrency.spec.ts} | 13 ++++++----- src/invoke-store.ts | 5 ++--- 9 files changed, 53 insertions(+), 46 deletions(-) rename src/{invoke-store.multiconcurrency.spec.ts => invoke-store.concurrency.spec.ts} (93%) rename src/{invoke-store.global.multiconcurrency.spec.ts => invoke-store.global.concurrency.spec.ts} (73%) rename src/{invoke-store.module.loading.multiconcurrency.ts => invoke-store.module.loading.concurrency.spec.ts} (57%) rename src/{invoke-store.timers.multiconcurrency.spec.ts => invoke-store.timers.concurrency.spec.ts} (98%) diff --git a/package.json b/package.json index fa5f2dc..902b16d 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,7 @@ "build": "yarn clean && yarn build:types && node ./scripts/build-rollup.js", "build:types": "tsc -p tsconfig.types.json", "clean": "rm -rf dist-types dist-cjs dist-es", - "testMulticoncurrency": "AWS_LAMBDA_MAX_CONCURRENCY=2 vitest run src/*.multiconcurrency.spec.ts --reporter verbose", - "testOnDemand": "vitest run src/*.spec.ts --exclude \"src/*.multiconcurrency.spec.ts\" --reporter verbose", - "test": "yarn testOnDemand && yarn testMulticoncurrency", + "test": "vitest run --reporter verbose", "test:watch": "vitest watch", "release": "yarn build && changeset publish" }, diff --git a/src/invoke-store.multiconcurrency.spec.ts b/src/invoke-store.concurrency.spec.ts similarity index 93% rename from src/invoke-store.multiconcurrency.spec.ts rename to src/invoke-store.concurrency.spec.ts index 8d05080..20dab29 100644 --- a/src/invoke-store.multiconcurrency.spec.ts +++ b/src/invoke-store.concurrency.spec.ts @@ -1,12 +1,14 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; -import { InvokeStore } from "./invoke-store.js"; +import { InvokeStore, InvokeStoreBase } from "./invoke-store.js"; describe("InvokeStore", async () => { - const invokeStore = await InvokeStore.getInstance(); + let invokeStore: InvokeStoreBase; - beforeEach(() => { + beforeEach(async () => { + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); vi.useFakeTimers(); + invokeStore = await InvokeStore.getInstance(); }); afterEach(() => { diff --git a/src/invoke-store.global.multiconcurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts similarity index 73% rename from src/invoke-store.global.multiconcurrency.spec.ts rename to src/invoke-store.global.concurrency.spec.ts index 1e1fac3..9742451 100644 --- a/src/invoke-store.global.multiconcurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -7,12 +7,12 @@ import { beforeEach, vi, } from "vitest"; -import { InvokeStore as OriginalImport } from "./invoke-store.js"; - +import { InvokeStoreBase, InvokeStore as OriginalImport } from "./invoke-store.js"; describe("InvokeStore Global Singleton", () => { const originalGlobalAwsLambda = globalThis.awslambda; const originalEnv = process.env; + let invokeStore: InvokeStoreBase; beforeAll(() => { globalThis.awslambda = originalGlobalAwsLambda; @@ -23,8 +23,10 @@ describe("InvokeStore Global Singleton", () => { process.env = originalEnv; }); - beforeEach(() => { + beforeEach(async () => { + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); process.env = { ...originalEnv }; + invokeStore = await OriginalImport.getInstance(); }); it("should maintain singleton behavior with dynamic imports", async () => { @@ -34,23 +36,21 @@ describe("InvokeStore Global Singleton", () => { const testKey = "dynamic-key"; const testValue = "dynamic-value"; - const originalImportAwaited = await OriginalImport.getInstance(); - // WHEN - Set up context with original import - await originalImportAwaited.run( + await invokeStore.run( { - [originalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId, - [originalImportAwaited.PROTECTED_KEYS.TENANT_ID]: testTenantId, + [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [invokeStore.PROTECTED_KEYS.TENANT_ID]: testTenantId, }, async () => { - originalImportAwaited.set(testKey, testValue); + invokeStore.set(testKey, testValue); // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); const DynamicImport = await dynamicModule.InvokeStore.getInstance(); // THEN - Dynamically imported instance should see the same context - expect(DynamicImport).toBe(originalImportAwaited); // Same instance + expect(DynamicImport).toBe(invokeStore); // Same instance expect(DynamicImport.getRequestId()).toBe(testRequestId); expect(DynamicImport.getTenantId()).toBe(testTenantId); expect(DynamicImport.get(testKey)).toBe(testValue); @@ -61,7 +61,7 @@ describe("InvokeStore Global Singleton", () => { DynamicImport.set(newKey, newValue); // THEN - Original import should see the new value - expect(originalImportAwaited.get(newKey)).toBe(newValue); + expect(invokeStore.get(newKey)).toBe(newValue); } ); }); diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index fb23c60..ef12fcb 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -7,14 +7,15 @@ import { beforeEach, vi, } from "vitest"; -import { InvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; +import { InvokeStoreBase, InvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; describe.each([ - { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, - { label: 'single-concurrency', config: undefined } -])('InvokeStore with %s', async ({ config }) => { - const invokeStore = await InvokeStore.getInstance(config); + { label: 'multi-concurrency', isMultiConcurrent: true }, + { label: 'single-concurrency', isMultiConcurrent: false } +])('InvokeStore with %s', async ({ isMultiConcurrent }) => { + + let invokeStore: InvokeStoreBase; describe("InvokeStore Global Singleton", () => { const originalGlobalAwsLambda = globalThis.awslambda; @@ -29,8 +30,12 @@ describe.each([ process.env = originalEnv; }); - beforeEach(() => { + beforeEach(async() => { + if(isMultiConcurrent) { + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); + } process.env = { ...originalEnv }; + invokeStore = await InvokeStore.getInstance(); }); it("should store the instance in globalThis.awslambda", async () => { diff --git a/src/invoke-store.module.loading.multiconcurrency.ts b/src/invoke-store.module.loading.concurrency.spec.ts similarity index 57% rename from src/invoke-store.module.loading.multiconcurrency.ts rename to src/invoke-store.module.loading.concurrency.spec.ts index 357405c..3aab9cb 100644 --- a/src/invoke-store.module.loading.multiconcurrency.ts +++ b/src/invoke-store.module.loading.concurrency.spec.ts @@ -1,12 +1,10 @@ -import { describe, it, expect } from 'vitest'; +import { describe, vi, it, expect } from 'vitest'; import { InvokeStore } from "./invoke-store.js" describe('InvokeStore implementations', () => { it('should load the correct class', async () => { - const singleStore = await InvokeStore.getInstance({ - env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } - }); - + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); + const singleStore = await InvokeStore.getInstance(); expect(singleStore.constructor.name).toBe('InvokeStoreMulti'); }); }); \ No newline at end of file diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts index 05da6eb..da29546 100644 --- a/src/invoke-store.module.loading.spec.ts +++ b/src/invoke-store.module.loading.spec.ts @@ -3,10 +3,7 @@ import { InvokeStore } from "./invoke-store.js" describe('InvokeStore implementations', () => { it('should load the correct class', async () => { - const singleStore = await InvokeStore.getInstance({ - env: {} - }); - + const singleStore = await InvokeStore.getInstance(); expect(singleStore.constructor.name).toBe('InvokeStoreSingle'); }); }); \ No newline at end of file diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index e88814b..14df18c 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -1,13 +1,16 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; -import { InvokeStore } from "./invoke-store.js"; +import { InvokeStoreBase, InvokeStore } from "./invoke-store.js"; describe.each([ - { label: 'multi-concurrency', config: { env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } } }, - { label: 'single-concurrency', config: undefined } -])('InvokeStore with %s', async ({ config }) => { - const invokeStore = await InvokeStore.getInstance(config); - describe("InvokeStore", () => { + { label: 'multi-concurrency', isMultiConcurrent: true }, + { label: 'single-concurrency', isMultiConcurrent: false } +])('InvokeStore with %s', async ({ isMultiConcurrent }) => { + describe("InvokeStore", async () => { + let invokeStore: InvokeStoreBase; beforeEach(() => { + if(isMultiConcurrent) { + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); + } vi.useFakeTimers(); }); @@ -15,6 +18,8 @@ describe.each([ vi.useRealTimers(); }); + invokeStore = await InvokeStore.getInstance(); + describe("getRequestId and getXRayTraceId", () => { it("should return placeholder when called outside run context", () => { // WHEN diff --git a/src/invoke-store.timers.multiconcurrency.spec.ts b/src/invoke-store.timers.concurrency.spec.ts similarity index 98% rename from src/invoke-store.timers.multiconcurrency.spec.ts rename to src/invoke-store.timers.concurrency.spec.ts index c665cce..ff39beb 100644 --- a/src/invoke-store.timers.multiconcurrency.spec.ts +++ b/src/invoke-store.timers.concurrency.spec.ts @@ -1,14 +1,17 @@ -import { describe, it, expect } from "vitest"; -import { InvokeStore } from "./invoke-store.js"; +import { describe, beforeEach, vi, it, expect } from "vitest"; +import { InvokeStore, InvokeStoreBase } from "./invoke-store.js"; /** * These tests specifically verify context preservation across various * timer and async APIs without using fake timers. */ describe("InvokeStore timer functions context preservation", async () => { - const invokeStore = await InvokeStore.getInstance({ - env: { AWS_LAMBDA_MAX_CONCURRENCY: '10' } - }); + let invokeStore: InvokeStoreBase; + + beforeEach(async () => { + vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); + invokeStore = await InvokeStore.getInstance(); + }); const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 8948be7..61b502a 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -165,13 +165,12 @@ interface InvokeStoreConfig { export namespace InvokeStore { let instance: InvokeStoreBase | null = null; - export async function getInstance(storeConfig?: InvokeStoreConfig): Promise { + export async function getInstance(): Promise { if (instance) { return instance; } - const env = storeConfig?.env ?? process.env; - const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (env ?? {}); + const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (process.env); const newInstance = isMulti ? await InvokeStoreMulti.create() From 8d79c7f5630bce15fee17f3a10acc1163a4e0f9a Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 00:05:51 +0000 Subject: [PATCH 14/26] fix: changelog --- .changeset/goofy-planes-tease.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/goofy-planes-tease.md diff --git a/.changeset/goofy-planes-tease.md b/.changeset/goofy-planes-tease.md new file mode 100644 index 0000000..90f7d34 --- /dev/null +++ b/.changeset/goofy-planes-tease.md @@ -0,0 +1,10 @@ +--- +"@aws/lambda-invoke-store": minor +--- + +- `InvokeStore` is now accessible via `InvokeStore.getInstance()` instead of direct instantiation + +- Lazy loading of `node:async_hooks` to improve startup performance +- Dynamic implementation selection based on Lambda environment: + - Single-context implementation for standard Lambda executions + - Multi-context implementation (using AsyncLocalStorage) From e77cebbda76eed071a498270d9cb3654ac5dc6c2 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:23:46 -0800 Subject: [PATCH 15/26] docs: update changelog --- .changeset/goofy-planes-tease.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.changeset/goofy-planes-tease.md b/.changeset/goofy-planes-tease.md index 90f7d34..9bc3e77 100644 --- a/.changeset/goofy-planes-tease.md +++ b/.changeset/goofy-planes-tease.md @@ -2,9 +2,8 @@ "@aws/lambda-invoke-store": minor --- -- `InvokeStore` is now accessible via `InvokeStore.getInstance()` instead of direct instantiation - -- Lazy loading of `node:async_hooks` to improve startup performance -- Dynamic implementation selection based on Lambda environment: +Invoke Store is now accessible via `InvokeStore.getInstance()` instead of direct instantiation +- Lazy loads `node:async_hooks` to improve startup performance +- Selects dynamic implementation based on Lambda environment: - Single-context implementation for standard Lambda executions - Multi-context implementation (using AsyncLocalStorage) From 88241c4a5a7acd0afb317aa398628bd26da0ee03 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 10:55:52 +0000 Subject: [PATCH 16/26] fix: PR comments dynamicImport --- src/invoke-store.global.concurrency.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/invoke-store.global.concurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts index 9742451..20321a8 100644 --- a/src/invoke-store.global.concurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -47,18 +47,18 @@ describe("InvokeStore Global Singleton", () => { // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); - const DynamicImport = await dynamicModule.InvokeStore.getInstance(); + const dynamicImport = await dynamicModule.InvokeStore.getInstance(); // THEN - Dynamically imported instance should see the same context - expect(DynamicImport).toBe(invokeStore); // Same instance - expect(DynamicImport.getRequestId()).toBe(testRequestId); - expect(DynamicImport.getTenantId()).toBe(testTenantId); - expect(DynamicImport.get(testKey)).toBe(testValue); + expect(dynamicImport).toBe(invokeStore); // Same instance + expect(dynamicImport.getRequestId()).toBe(testRequestId); + expect(dynamicImport.getTenantId()).toBe(testTenantId); + expect(dynamicImport.get(testKey)).toBe(testValue); // WHEN - Set a new value using dynamic import const newKey = "new-dynamic-key"; const newValue = "new-dynamic-value"; - DynamicImport.set(newKey, newValue); + dynamicImport.set(newKey, newValue); // THEN - Original import should see the new value expect(invokeStore.get(newKey)).toBe(newValue); From 3c762a8e07ff5a6b74db2b3fcc72b0c43aa43975 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 10:59:24 +0000 Subject: [PATCH 17/26] fix: PR comments, run prettier --- .changeset/changelog.mjs | 8 +++-- .changeset/goofy-planes-tease.md | 1 + src/invoke-store.concurrency.spec.ts | 2 -- src/invoke-store.global.concurrency.spec.ts | 9 +++-- src/invoke-store.global.spec.ts | 36 +++++++++++-------- ...e-store.module.loading.concurrency.spec.ts | 12 +++---- src/invoke-store.module.loading.spec.ts | 12 +++---- src/invoke-store.spec.ts | 10 +++--- src/invoke-store.timers.concurrency.spec.ts | 6 ++-- src/invoke-store.ts | 33 +++++++++-------- 10 files changed, 71 insertions(+), 58 deletions(-) diff --git a/.changeset/changelog.mjs b/.changeset/changelog.mjs index 474802a..b42de9d 100644 --- a/.changeset/changelog.mjs +++ b/.changeset/changelog.mjs @@ -14,7 +14,7 @@ export const getDependencyReleaseLine = (changesets, dependenciesUpdated) => { .join(", ")}]:`; const updatedDepsList = dependenciesUpdated.map( - (dependency) => ` - ${dependency.name}@${dependency.newVersion}` + (dependency) => ` - ${dependency.name}@${dependency.newVersion}`, ); return [changesetLink, ...updatedDepsList].join("\n"); @@ -22,9 +22,11 @@ export const getDependencyReleaseLine = (changesets, dependenciesUpdated) => { export const getReleaseLine = (changeset, _type) => { const { commit, summary } = changeset; - const [firstLine, ...futureLines] = summary.split("\n").map((l) => l.trimRight()); + const [firstLine, ...futureLines] = summary + .split("\n") + .map((l) => l.trimRight()); return `- ${firstLine} (${getGithubCommitWithLink(commit)})${ futureLines.length > 0 ? futureLines.map((l) => ` ${l}`).join("\n") : "" }`; -}; \ No newline at end of file +}; diff --git a/.changeset/goofy-planes-tease.md b/.changeset/goofy-planes-tease.md index 9bc3e77..d05eb52 100644 --- a/.changeset/goofy-planes-tease.md +++ b/.changeset/goofy-planes-tease.md @@ -3,6 +3,7 @@ --- Invoke Store is now accessible via `InvokeStore.getInstance()` instead of direct instantiation + - Lazy loads `node:async_hooks` to improve startup performance - Selects dynamic implementation based on Lambda environment: - Single-context implementation for standard Lambda executions diff --git a/src/invoke-store.concurrency.spec.ts b/src/invoke-store.concurrency.spec.ts index 20dab29..2665bc1 100644 --- a/src/invoke-store.concurrency.spec.ts +++ b/src/invoke-store.concurrency.spec.ts @@ -2,7 +2,6 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; import { InvokeStore, InvokeStoreBase } from "./invoke-store.js"; describe("InvokeStore", async () => { - let invokeStore: InvokeStoreBase; beforeEach(async () => { @@ -16,7 +15,6 @@ describe("InvokeStore", async () => { }); describe("run", () => { - it("should handle nested runs with different IDs", async () => { // GIVEN const traces: string[] = []; diff --git a/src/invoke-store.global.concurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts index 20321a8..650e200 100644 --- a/src/invoke-store.global.concurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -7,7 +7,10 @@ import { beforeEach, vi, } from "vitest"; -import { InvokeStoreBase, InvokeStore as OriginalImport } from "./invoke-store.js"; +import { + InvokeStoreBase, + InvokeStore as OriginalImport, +} from "./invoke-store.js"; describe("InvokeStore Global Singleton", () => { const originalGlobalAwsLambda = globalThis.awslambda; @@ -62,7 +65,7 @@ describe("InvokeStore Global Singleton", () => { // THEN - Original import should see the new value expect(invokeStore.get(newKey)).toBe(newValue); - } + }, ); }); -}); \ No newline at end of file +}); diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index ef12fcb..91a9d4f 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -7,14 +7,16 @@ import { beforeEach, vi, } from "vitest"; -import { InvokeStoreBase, InvokeStore, InvokeStore as OriginalImport } from "./invoke-store.js"; - +import { + InvokeStoreBase, + InvokeStore, + InvokeStore as OriginalImport, +} from "./invoke-store.js"; describe.each([ - { label: 'multi-concurrency', isMultiConcurrent: true }, - { label: 'single-concurrency', isMultiConcurrent: false } -])('InvokeStore with %s', async ({ isMultiConcurrent }) => { - + { label: "multi-concurrency", isMultiConcurrent: true }, + { label: "single-concurrency", isMultiConcurrent: false }, +])("InvokeStore with %s", async ({ isMultiConcurrent }) => { let invokeStore: InvokeStoreBase; describe("InvokeStore Global Singleton", () => { @@ -30,8 +32,8 @@ describe.each([ process.env = originalEnv; }); - beforeEach(async() => { - if(isMultiConcurrent) { + beforeEach(async () => { + if (isMultiConcurrent) { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); } process.env = { ...originalEnv }; @@ -41,7 +43,9 @@ describe.each([ it("should store the instance in globalThis.awslambda", async () => { // THEN expect(globalThis.awslambda.InvokeStore).toBeDefined(); - expect(await globalThis.awslambda.InvokeStore).toBe(await OriginalImport.getInstance()); + expect(await globalThis.awslambda.InvokeStore).toBe( + await OriginalImport.getInstance(), + ); }); it("should share context between original import and global reference", async () => { @@ -54,7 +58,7 @@ describe.each([ // WHEN - Use the original import to set up context await originalImport.run( - { [ originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + { [originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, async () => { originalImport.set(testKey, testValue); @@ -62,7 +66,7 @@ describe.each([ const globalInstance = globalThis.awslambda.InvokeStore!; expect(globalInstance.getRequestId()).toBe(testRequestId); expect(globalInstance.get(testKey)).toBe(testValue); - } + }, ); }); @@ -83,7 +87,7 @@ describe.each([ // THEN - Original import should see the same context expect(originalImport.getRequestId()).toBe(testRequestId); expect(originalImport.get(testKey)).toBe(testValue); - } + }, ); }); }); @@ -123,7 +127,9 @@ describe.each([ globalThis.awslambda.InvokeStore = mockInstance; // WHEN - const { InvokeStore: ReimportedStore } = await import("./invoke-store.js"); + const { InvokeStore: ReimportedStore } = await import( + "./invoke-store.js" + ); const awaitedReimportedStore = await ReimportedStore.getInstance(); // THEN @@ -163,7 +169,7 @@ describe.each([ { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { requestId = invokeStore.getRequestId(); - } + }, ); expect(requestId).toBe("test-id"); }); @@ -184,7 +190,7 @@ describe.each([ { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { requestId = invokeStore.getRequestId(); - } + }, ); expect(requestId).toBe("test-id"); }); diff --git a/src/invoke-store.module.loading.concurrency.spec.ts b/src/invoke-store.module.loading.concurrency.spec.ts index 3aab9cb..f2b125e 100644 --- a/src/invoke-store.module.loading.concurrency.spec.ts +++ b/src/invoke-store.module.loading.concurrency.spec.ts @@ -1,10 +1,10 @@ -import { describe, vi, it, expect } from 'vitest'; -import { InvokeStore } from "./invoke-store.js" +import { describe, vi, it, expect } from "vitest"; +import { InvokeStore } from "./invoke-store.js"; -describe('InvokeStore implementations', () => { - it('should load the correct class', async () => { +describe("InvokeStore implementations", () => { + it("should load the correct class", async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); const singleStore = await InvokeStore.getInstance(); - expect(singleStore.constructor.name).toBe('InvokeStoreMulti'); + expect(singleStore.constructor.name).toBe("InvokeStoreMulti"); }); -}); \ No newline at end of file +}); diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts index da29546..d2695ae 100644 --- a/src/invoke-store.module.loading.spec.ts +++ b/src/invoke-store.module.loading.spec.ts @@ -1,9 +1,9 @@ -import { describe, it, expect } from 'vitest'; -import { InvokeStore } from "./invoke-store.js" +import { describe, it, expect } from "vitest"; +import { InvokeStore } from "./invoke-store.js"; -describe('InvokeStore implementations', () => { - it('should load the correct class', async () => { +describe("InvokeStore implementations", () => { + it("should load the correct class", async () => { const singleStore = await InvokeStore.getInstance(); - expect(singleStore.constructor.name).toBe('InvokeStoreSingle'); + expect(singleStore.constructor.name).toBe("InvokeStoreSingle"); }); -}); \ No newline at end of file +}); diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index 14df18c..d7e1d24 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -2,13 +2,13 @@ import { describe, it, expect, afterEach, beforeEach, vi } from "vitest"; import { InvokeStoreBase, InvokeStore } from "./invoke-store.js"; describe.each([ - { label: 'multi-concurrency', isMultiConcurrent: true }, - { label: 'single-concurrency', isMultiConcurrent: false } -])('InvokeStore with %s', async ({ isMultiConcurrent }) => { + { label: "multi-concurrency", isMultiConcurrent: true }, + { label: "single-concurrency", isMultiConcurrent: false }, +])("InvokeStore with %s", async ({ isMultiConcurrent }) => { describe("InvokeStore", async () => { let invokeStore: InvokeStoreBase; beforeEach(() => { - if(isMultiConcurrent) { + if (isMultiConcurrent) { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); } vi.useFakeTimers(); @@ -209,7 +209,7 @@ describe.each([ describe("edge cases", () => { it("should handle synchronous functions", () => { // WHEN - console.log(InvokeStore) + console.log(InvokeStore); const result = invokeStore.run( { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", diff --git a/src/invoke-store.timers.concurrency.spec.ts b/src/invoke-store.timers.concurrency.spec.ts index ff39beb..0112c19 100644 --- a/src/invoke-store.timers.concurrency.spec.ts +++ b/src/invoke-store.timers.concurrency.spec.ts @@ -7,11 +7,11 @@ import { InvokeStore, InvokeStoreBase } from "./invoke-store.js"; */ describe("InvokeStore timer functions context preservation", async () => { let invokeStore: InvokeStoreBase; - - beforeEach(async () => { + + beforeEach(async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); invokeStore = await InvokeStore.getInstance(); - }); + }); const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 61b502a..7d4d6f3 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -10,7 +10,6 @@ const PROTECTED_KEYS = { } as const; const NO_GLOBAL_AWS_LAMBDA = - process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "1" || process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "true"; @@ -28,10 +27,10 @@ if (!NO_GLOBAL_AWS_LAMBDA) { /** * Base class for AWS Lambda context storage implementations. * Provides core functionality for managing Lambda execution context. - * - * Implementations handle either single-context (InvokeStoreSingle) or + * + * Implementations handle either single-context (InvokeStoreSingle) or * multi-context (InvokeStoreMulti) scenarios based on Lambda's execution environment. - * + * * @public */ export abstract class InvokeStoreBase { @@ -81,7 +80,9 @@ class InvokeStoreSingle extends InvokeStoreBase { set(key: string | symbol, value: T): void { if (this.isProtectedKey(key)) { - throw new Error(`Cannot modify protected Lambda context field: ${String(key)}`); + throw new Error( + `Cannot modify protected Lambda context field: ${String(key)}`, + ); } this.currentContext = this.currentContext || {}; @@ -115,7 +116,7 @@ class InvokeStoreMulti extends InvokeStoreBase { static async create(): Promise { const instance = new InvokeStoreMulti(); - const asyncHooks = await import('node:async_hooks'); + const asyncHooks = await import("node:async_hooks"); instance.als = new asyncHooks.AsyncLocalStorage(); return instance; } @@ -134,14 +135,16 @@ class InvokeStoreMulti extends InvokeStoreBase { set(key: string | symbol, value: T): void { if (this.isProtectedKey(key)) { - throw new Error(`Cannot modify protected Lambda context field: ${String(key)}`); + throw new Error( + `Cannot modify protected Lambda context field: ${String(key)}`, + ); } const store = this.als.getStore(); if (!store) { - throw new Error('No context available'); + throw new Error("No context available"); } - + store[key] = value; } @@ -157,7 +160,7 @@ interface InvokeStoreConfig { /** * Provides access to AWS Lambda execution context storage. * Supports both single-context and multi-context environments through different implementations. - * + * * The store manages protected Lambda context fields and allows storing/retrieving custom values * within the execution context. * @public @@ -170,10 +173,10 @@ export namespace InvokeStore { return instance; } - const isMulti = 'AWS_LAMBDA_MAX_CONCURRENCY' in (process.env); - - const newInstance = isMulti - ? await InvokeStoreMulti.create() + const isMulti = "AWS_LAMBDA_MAX_CONCURRENCY" in process.env; + + const newInstance = isMulti + ? await InvokeStoreMulti.create() : new InvokeStoreSingle(); if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { @@ -187,4 +190,4 @@ export namespace InvokeStore { return instance; } -} \ No newline at end of file +} From c45d6d7b862151b93ae6fec1ac8275292028a541 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:01:01 +0000 Subject: [PATCH 18/26] fix: PR comments, symbol.for --- src/invoke-store.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 7d4d6f3..1fd4d9d 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -4,9 +4,9 @@ interface Context { } const PROTECTED_KEYS = { - REQUEST_ID: Symbol("_AWS_LAMBDA_REQUEST_ID"), - X_RAY_TRACE_ID: Symbol("_AWS_LAMBDA_X_RAY_TRACE_ID"), - TENANT_ID: Symbol("_AWS_LAMBDA_TENANT_ID"), + REQUEST_ID: Symbol.for("_AWS_LAMBDA_REQUEST_ID"), + X_RAY_TRACE_ID: Symbol.for("_AWS_LAMBDA_X_RAY_TRACE_ID"), + TENANT_ID: Symbol.for("_AWS_LAMBDA_TENANT_ID"), } as const; const NO_GLOBAL_AWS_LAMBDA = From 39cffee55b6483de0477aa3b211cb245b1b80dc7 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:04:40 +0000 Subject: [PATCH 19/26] fix: PR comment, NO_GLOBAL_AWS_LAMBDA --- src/invoke-store.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 1fd4d9d..abf3eef 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -9,9 +9,9 @@ const PROTECTED_KEYS = { TENANT_ID: Symbol.for("_AWS_LAMBDA_TENANT_ID"), } as const; -const NO_GLOBAL_AWS_LAMBDA = - process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "1" || - process.env["AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA"] === "true"; +const NO_GLOBAL_AWS_LAMBDA = ["true", "1"].includes( + process.env?.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA ?? "", +); declare global { var awslambda: { From 151642c2abf8bbb824bce335aeb59d1a9205f313 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:09:23 +0000 Subject: [PATCH 20/26] fix: PR comment, getRequestId not undefined --- src/invoke-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index abf3eef..a02e120 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -46,7 +46,7 @@ export abstract class InvokeStoreBase { return Object.values(PROTECTED_KEYS).includes(key as symbol); } - getRequestId(): string | undefined { + getRequestId(): string { return this.get(PROTECTED_KEYS.REQUEST_ID) ?? "-"; } From af758063de3e50771fbe95321cba8e67867ed428 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:10:15 +0000 Subject: [PATCH 21/26] fix: PR comments, remove useless getInstance --- src/invoke-store.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index a02e120..63c6860 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -98,9 +98,6 @@ class InvokeStoreSingle extends InvokeStoreBase { } } - getInstance(): InvokeStoreBase { - return this; - } } /** From 0595d2cf0cb54d030c601dec2a072923648c98aa Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:10:50 +0000 Subject: [PATCH 22/26] fix: PR comments, remove useless ctor --- src/invoke-store.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 63c6860..583e5c3 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -107,10 +107,6 @@ class InvokeStoreSingle extends InvokeStoreBase { class InvokeStoreMulti extends InvokeStoreBase { private als!: import("node:async_hooks").AsyncLocalStorage; - constructor() { - super(); - } - static async create(): Promise { const instance = new InvokeStoreMulti(); const asyncHooks = await import("node:async_hooks"); From a8d1531a06415318f49ea8194eb0645e7fcf8113 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:12:26 +0000 Subject: [PATCH 23/26] fix: PR comments getInstance -> getInstanceAsync --- .changeset/goofy-planes-tease.md | 2 +- README.md | 8 ++++---- src/invoke-store.concurrency.spec.ts | 2 +- src/invoke-store.global.concurrency.spec.ts | 4 ++-- src/invoke-store.global.spec.ts | 14 +++++++------- ...invoke-store.module.loading.concurrency.spec.ts | 2 +- src/invoke-store.module.loading.spec.ts | 2 +- src/invoke-store.spec.ts | 2 +- src/invoke-store.timers.concurrency.spec.ts | 2 +- src/invoke-store.ts | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.changeset/goofy-planes-tease.md b/.changeset/goofy-planes-tease.md index d05eb52..1ae1b57 100644 --- a/.changeset/goofy-planes-tease.md +++ b/.changeset/goofy-planes-tease.md @@ -2,7 +2,7 @@ "@aws/lambda-invoke-store": minor --- -Invoke Store is now accessible via `InvokeStore.getInstance()` instead of direct instantiation +Invoke Store is now accessible via `InvokeStore.getInstanceAsync()` instead of direct instantiation - Lazy loads `node:async_hooks` to improve startup performance - Selects dynamic implementation based on Lambda environment: diff --git a/README.md b/README.md index 8836b30..a958d1b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ export const handler = async (event, context) => { // The RIC has already initialized the InvokeStore with requestId and X-Ray traceId // Access Lambda context data - const invokeStore = await InvokeStore.getInstance(); + const invokeStore = await InvokeStore.getInstanceAsync(); console.log(`Processing request: ${invokeStore.getRequestId()}`); // Store custom data @@ -54,7 +54,7 @@ export const handler = async (event, context) => { // Context is preserved in async operations async function processData(event) { // Still has access to the same invoke context - const invokeStore = await InvokeStore.getInstance(); + const invokeStore = await InvokeStore.getInstanceAsync(); console.log(`Processing in same context: ${invokeStore.getRequestId()}`); // Can set additional data @@ -64,10 +64,10 @@ async function processData(event) { ## API Reference -### InvokeStore.getInstance() +### InvokeStore.getInstanceAsync() First, get an instance of the InvokeStore: ```typescript -const invokeStore = await InvokeStore.getInstance(); +const invokeStore = await InvokeStore.getInstanceAsync(); ``` ### invokeStore.getContext() diff --git a/src/invoke-store.concurrency.spec.ts b/src/invoke-store.concurrency.spec.ts index 2665bc1..6f07ab8 100644 --- a/src/invoke-store.concurrency.spec.ts +++ b/src/invoke-store.concurrency.spec.ts @@ -7,7 +7,7 @@ describe("InvokeStore", async () => { beforeEach(async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); vi.useFakeTimers(); - invokeStore = await InvokeStore.getInstance(); + invokeStore = await InvokeStore.getInstanceAsync(); }); afterEach(() => { diff --git a/src/invoke-store.global.concurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts index 650e200..7358dec 100644 --- a/src/invoke-store.global.concurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -29,7 +29,7 @@ describe("InvokeStore Global Singleton", () => { beforeEach(async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); process.env = { ...originalEnv }; - invokeStore = await OriginalImport.getInstance(); + invokeStore = await OriginalImport.getInstanceAsync(); }); it("should maintain singleton behavior with dynamic imports", async () => { @@ -50,7 +50,7 @@ describe("InvokeStore Global Singleton", () => { // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); - const dynamicImport = await dynamicModule.InvokeStore.getInstance(); + const dynamicImport = await dynamicModule.InvokeStore.getInstanceAsync(); // THEN - Dynamically imported instance should see the same context expect(dynamicImport).toBe(invokeStore); // Same instance diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index 91a9d4f..a682796 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -37,14 +37,14 @@ describe.each([ vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); } process.env = { ...originalEnv }; - invokeStore = await InvokeStore.getInstance(); + invokeStore = await InvokeStore.getInstanceAsync(); }); it("should store the instance in globalThis.awslambda", async () => { // THEN expect(globalThis.awslambda.InvokeStore).toBeDefined(); expect(await globalThis.awslambda.InvokeStore).toBe( - await OriginalImport.getInstance(), + await OriginalImport.getInstanceAsync(), ); }); @@ -54,7 +54,7 @@ describe.each([ const testKey = "test-key"; const testValue = "test-value"; - const originalImport = await OriginalImport.getInstance(); + const originalImport = await OriginalImport.getInstanceAsync(); // WHEN - Use the original import to set up context await originalImport.run( @@ -73,7 +73,7 @@ describe.each([ it("should maintain the same storage across different references", async () => { // GIVEN const globalInstance = globalThis.awslambda.InvokeStore!; - const originalImport = await OriginalImport.getInstance(); + const originalImport = await OriginalImport.getInstanceAsync(); const testRequestId = "global-test"; const testKey = "global-key"; const testValue = "global-value"; @@ -130,7 +130,7 @@ describe.each([ const { InvokeStore: ReimportedStore } = await import( "./invoke-store.js" ); - const awaitedReimportedStore = await ReimportedStore.getInstance(); + const awaitedReimportedStore = await ReimportedStore.getInstanceAsync(); // THEN expect(awaitedReimportedStore).toBe(mockInstance); @@ -159,7 +159,7 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); - const invokeStore = await InvokeStore.getInstance(); + const invokeStore = await InvokeStore.getInstanceAsync(); // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); @@ -180,7 +180,7 @@ describe.each([ // WHEN - Import the module with the environment variable set const { InvokeStore } = await import("./invoke-store.js"); - const invokeStore = await InvokeStore.getInstance(); + const invokeStore = await InvokeStore.getInstanceAsync(); // THEN - The global namespace should not be modified expect(globalThis.awslambda?.InvokeStore).toBeUndefined(); diff --git a/src/invoke-store.module.loading.concurrency.spec.ts b/src/invoke-store.module.loading.concurrency.spec.ts index f2b125e..a79bcf5 100644 --- a/src/invoke-store.module.loading.concurrency.spec.ts +++ b/src/invoke-store.module.loading.concurrency.spec.ts @@ -4,7 +4,7 @@ import { InvokeStore } from "./invoke-store.js"; describe("InvokeStore implementations", () => { it("should load the correct class", async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); - const singleStore = await InvokeStore.getInstance(); + const singleStore = await InvokeStore.getInstanceAsync(); expect(singleStore.constructor.name).toBe("InvokeStoreMulti"); }); }); diff --git a/src/invoke-store.module.loading.spec.ts b/src/invoke-store.module.loading.spec.ts index d2695ae..144a38e 100644 --- a/src/invoke-store.module.loading.spec.ts +++ b/src/invoke-store.module.loading.spec.ts @@ -3,7 +3,7 @@ import { InvokeStore } from "./invoke-store.js"; describe("InvokeStore implementations", () => { it("should load the correct class", async () => { - const singleStore = await InvokeStore.getInstance(); + const singleStore = await InvokeStore.getInstanceAsync(); expect(singleStore.constructor.name).toBe("InvokeStoreSingle"); }); }); diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index d7e1d24..0be2d42 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -18,7 +18,7 @@ describe.each([ vi.useRealTimers(); }); - invokeStore = await InvokeStore.getInstance(); + invokeStore = await InvokeStore.getInstanceAsync(); describe("getRequestId and getXRayTraceId", () => { it("should return placeholder when called outside run context", () => { diff --git a/src/invoke-store.timers.concurrency.spec.ts b/src/invoke-store.timers.concurrency.spec.ts index 0112c19..fe7781a 100644 --- a/src/invoke-store.timers.concurrency.spec.ts +++ b/src/invoke-store.timers.concurrency.spec.ts @@ -10,7 +10,7 @@ describe("InvokeStore timer functions context preservation", async () => { beforeEach(async () => { vi.stubEnv("AWS_LAMBDA_MAX_CONCURRENCY", "2"); - invokeStore = await InvokeStore.getInstance(); + invokeStore = await InvokeStore.getInstanceAsync(); }); const delay = (ms: number) => diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 583e5c3..96e15f5 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -161,7 +161,7 @@ interface InvokeStoreConfig { export namespace InvokeStore { let instance: InvokeStoreBase | null = null; - export async function getInstance(): Promise { + export async function getInstanceAsync(): Promise { if (instance) { return instance; } From a1e9c596b007cd0a8e43503843951e3d4e2b7b3c Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 11:28:04 +0000 Subject: [PATCH 24/26] fix: PR comments, PROTECTED_KEYS are now static --- README.md | 8 ++--- src/invoke-store.concurrency.spec.ts | 14 ++++---- src/invoke-store.global.concurrency.spec.ts | 4 +-- src/invoke-store.global.spec.ts | 8 ++--- src/invoke-store.spec.ts | 32 +++++++++--------- src/invoke-store.timers.concurrency.spec.ts | 36 ++++++++++----------- src/invoke-store.ts | 2 +- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index a958d1b..5b91122 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ const context = invokeStore.getContext(); Gets a value from the current context. ```typescript -const requestId = invokeStore.get(InvokeStore.PROTECTED_KEYS.REQUEST_ID); +const requestId = invokeStore.get(InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID); const customValue = invokeStore.get("customKey"); ``` @@ -96,7 +96,7 @@ invokeStore.set("userId", "user-123"); invokeStore.set("timestamp", Date.now()); // This will throw an error: -// invokeStore.set(invokeStore.PROTECTED_KEYS.REQUEST_ID, 'new-id'); +// invokeStore.set(InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID, 'new-id'); ``` ### invokeStore.getRequestId() @@ -142,8 +142,8 @@ Runs a function within an invoke context. ```typescript invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-123", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-456", // Optional X-Ray trace ID + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-123", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-456", // Optional X-Ray trace ID customField: "value", // Optional custom fields }, () => { diff --git a/src/invoke-store.concurrency.spec.ts b/src/invoke-store.concurrency.spec.ts index 6f07ab8..3cf771d 100644 --- a/src/invoke-store.concurrency.spec.ts +++ b/src/invoke-store.concurrency.spec.ts @@ -22,13 +22,13 @@ describe("InvokeStore", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "outer", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "outer", }, async () => { traces.push(`outer-${invokeStore.getRequestId()}`); await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "inner", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "inner", }, async () => { traces.push(`inner-${invokeStore.getRequestId()}`); @@ -54,8 +54,8 @@ describe("InvokeStore", async () => { const isolateTasks = Promise.all([ invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { traces.push(`start-1-${invokeStore.getRequestId()}`); @@ -65,8 +65,8 @@ describe("InvokeStore", async () => { ), invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", }, async () => { traces.push(`start-2-${invokeStore.getRequestId()}`); @@ -94,7 +94,7 @@ describe("InvokeStore", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", }, async () => { traces.push(`before-${invokeStore.getRequestId()}`); diff --git a/src/invoke-store.global.concurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts index 7358dec..3417db4 100644 --- a/src/invoke-store.global.concurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -42,8 +42,8 @@ describe("InvokeStore Global Singleton", () => { // WHEN - Set up context with original import await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, - [invokeStore.PROTECTED_KEYS.TENANT_ID]: testTenantId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.TENANT_ID]: testTenantId, }, async () => { invokeStore.set(testKey, testValue); diff --git a/src/invoke-store.global.spec.ts b/src/invoke-store.global.spec.ts index a682796..cf165f5 100644 --- a/src/invoke-store.global.spec.ts +++ b/src/invoke-store.global.spec.ts @@ -58,7 +58,7 @@ describe.each([ // WHEN - Use the original import to set up context await originalImport.run( - { [originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + { [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, async () => { originalImport.set(testKey, testValue); @@ -80,7 +80,7 @@ describe.each([ // WHEN - Set context using global reference await globalInstance.run( - { [originalImport.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, + { [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId }, async () => { globalInstance.set(testKey, testValue); @@ -166,7 +166,7 @@ describe.each([ let requestId: string | undefined; await invokeStore.run( - { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + { [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { requestId = invokeStore.getRequestId(); }, @@ -187,7 +187,7 @@ describe.each([ let requestId: string | undefined; await invokeStore.run( - { [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, + { [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id" }, () => { requestId = invokeStore.getRequestId(); }, diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index 0be2d42..30b0b35 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -35,8 +35,8 @@ describe.each([ // WHEN const result = await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", }, () => { return { @@ -57,7 +57,7 @@ describe.each([ // WHEN const result = await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", customProp: "initial-value", }, () => { @@ -78,16 +78,16 @@ describe.each([ // WHEN & THEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { expect(() => { - invokeStore.set(invokeStore.PROTECTED_KEYS.REQUEST_ID, "new-id"); + invokeStore.set(InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID, "new-id"); }).toThrow(/Cannot modify protected Lambda context field/); expect(() => { invokeStore.set( - invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID, + InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID, "new-trace", ); }).toThrow(/Cannot modify protected Lambda context field/); @@ -109,8 +109,8 @@ describe.each([ // WHEN const context = await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", customField: "custom-value", }, () => { @@ -121,8 +121,8 @@ describe.each([ // THEN expect(context).toEqual({ - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-id", customField: "custom-value", dynamicField: "dynamic-value", }); @@ -142,7 +142,7 @@ describe.each([ // WHEN const result = await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { return invokeStore.hasContext(); @@ -162,7 +162,7 @@ describe.each([ // WHEN const promise = invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, async () => { throw error; @@ -182,7 +182,7 @@ describe.each([ await Promise.allSettled([ invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "success-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "success-id", }, async () => { traces.push(`success-${invokeStore.getRequestId()}`); @@ -190,7 +190,7 @@ describe.each([ ), invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "error-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "error-id", }, async () => { traces.push(`before-error-${invokeStore.getRequestId()}`); @@ -212,7 +212,7 @@ describe.each([ console.log(InvokeStore); const result = invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { return invokeStore.getRequestId(); @@ -230,7 +230,7 @@ describe.each([ // WHEN const promise = invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "test-id", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "test-id", }, () => { return Promise.reject(error); diff --git a/src/invoke-store.timers.concurrency.spec.ts b/src/invoke-store.timers.concurrency.spec.ts index fe7781a..b0a4bf1 100644 --- a/src/invoke-store.timers.concurrency.spec.ts +++ b/src/invoke-store.timers.concurrency.spec.ts @@ -25,7 +25,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`before-${invokeStore.getRequestId()}`); @@ -57,7 +57,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`level-0-${invokeStore.getRequestId()}`); @@ -96,7 +96,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`before-${invokeStore.getRequestId()}`); @@ -128,7 +128,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`level-0-${invokeStore.getRequestId()}`); @@ -167,7 +167,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`before-${invokeStore.getRequestId()}`); @@ -199,7 +199,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`level-0-${invokeStore.getRequestId()}`); @@ -238,7 +238,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`before-${invokeStore.getRequestId()}`); @@ -267,7 +267,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`start-${invokeStore.getRequestId()}`); @@ -309,7 +309,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`start-${invokeStore.getRequestId()}`); @@ -370,8 +370,8 @@ describe("InvokeStore timer functions context preservation", async () => { await Promise.all([ invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-1", }, async () => { traces.push(`start-1-${invokeStore.getRequestId()}`); @@ -381,8 +381,8 @@ describe("InvokeStore timer functions context preservation", async () => { ), invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", - [invokeStore.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [InvokeStoreBase.PROTECTED_KEYS.X_RAY_TRACE_ID]: "trace-2", }, async () => { traces.push(`start-2-${invokeStore.getRequestId()}`); @@ -409,7 +409,7 @@ describe("InvokeStore timer functions context preservation", async () => { await Promise.all([ invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-1", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-1", }, async () => { traces.push(`start-1-${invokeStore.getRequestId()}`); @@ -427,7 +427,7 @@ describe("InvokeStore timer functions context preservation", async () => { ), invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-2", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-2", }, async () => { traces.push(`start-2-${invokeStore.getRequestId()}`); @@ -445,7 +445,7 @@ describe("InvokeStore timer functions context preservation", async () => { ), invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: "request-3", + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: "request-3", }, async () => { traces.push(`start-3-${invokeStore.getRequestId()}`); @@ -513,7 +513,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`start-${invokeStore.getRequestId()}`); @@ -556,7 +556,7 @@ describe("InvokeStore timer functions context preservation", async () => { // WHEN await invokeStore.run( { - [invokeStore.PROTECTED_KEYS.REQUEST_ID]: testRequestId, + [InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID]: testRequestId, }, async () => { traces.push(`start-${invokeStore.getRequestId()}`); diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 96e15f5..10153d6 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -34,7 +34,7 @@ if (!NO_GLOBAL_AWS_LAMBDA) { * @public */ export abstract class InvokeStoreBase { - public readonly PROTECTED_KEYS = PROTECTED_KEYS; + public static readonly PROTECTED_KEYS = PROTECTED_KEYS; abstract getContext(): Context | undefined; abstract hasContext(): boolean; From e080cff05c394fc3398ba443521831df19ca5f06 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 13:35:35 +0000 Subject: [PATCH 25/26] fix: PR comments, import type and add benchmark --- src/invoke-store.benchmark.ts | 53 +++++++++++++++++++++ src/invoke-store.global.concurrency.spec.ts | 3 +- src/invoke-store.spec.ts | 5 +- src/invoke-store.ts | 18 +++++-- 4 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/invoke-store.benchmark.ts diff --git a/src/invoke-store.benchmark.ts b/src/invoke-store.benchmark.ts new file mode 100644 index 0000000..19508fc --- /dev/null +++ b/src/invoke-store.benchmark.ts @@ -0,0 +1,53 @@ +import { performance, PerformanceObserver } from "node:perf_hooks"; + +const obs = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + console.log(`${entry.name}: ${entry.duration}ms`); + }); +}); +obs.observe({ entryTypes: ["measure"] }); + +async function runBenchmark() { + const iterations = 1000; + process.env["AWS_LAMBDA_BENCHMARK_MODE"] = "1"; + + performance.mark("direct-single-start"); + for (let i = 0; i < iterations; i++) { + const invokeStore = (await import("./invoke-store.js")).InvokeStore; + await invokeStore.getInstanceAsync(); + const testing = invokeStore._testing; + if (testing) { + testing.reset(); + } else { + throw "testing needs to be defined"; + } + } + performance.mark("direct-single-end"); + performance.measure( + "Direct SingleStore Creation (1000 iterations)", + "direct-single-start", + "direct-single-end", + ); + + performance.mark("direct-multi-start"); + process.env["AWS_LAMBDA_MAX_CONCURRENCY"] = "2"; + for (let i = 0; i < iterations; i++) { + const invokeStore = (await import("./invoke-store.js")).InvokeStore; + await invokeStore.getInstanceAsync(); + const testing = invokeStore._testing; + if (testing) { + testing.reset(); + } else { + throw "testing needs to be defined"; + } + } + performance.mark("direct-multi-end"); + performance.measure( + "Direct MultiStore Creation (1000 iterations)", + "direct-multi-start", + "direct-multi-end", + ); +} + +runBenchmark().catch(console.error); diff --git a/src/invoke-store.global.concurrency.spec.ts b/src/invoke-store.global.concurrency.spec.ts index 3417db4..c4be9e2 100644 --- a/src/invoke-store.global.concurrency.spec.ts +++ b/src/invoke-store.global.concurrency.spec.ts @@ -50,7 +50,8 @@ describe("InvokeStore Global Singleton", () => { // Dynamically import the module again const dynamicModule = await import("./invoke-store.js"); - const dynamicImport = await dynamicModule.InvokeStore.getInstanceAsync(); + const dynamicImport = + await dynamicModule.InvokeStore.getInstanceAsync(); // THEN - Dynamically imported instance should see the same context expect(dynamicImport).toBe(invokeStore); // Same instance diff --git a/src/invoke-store.spec.ts b/src/invoke-store.spec.ts index 30b0b35..eff2e6d 100644 --- a/src/invoke-store.spec.ts +++ b/src/invoke-store.spec.ts @@ -82,7 +82,10 @@ describe.each([ }, () => { expect(() => { - invokeStore.set(InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID, "new-id"); + invokeStore.set( + InvokeStoreBase.PROTECTED_KEYS.REQUEST_ID, + "new-id", + ); }).toThrow(/Cannot modify protected Lambda context field/); expect(() => { diff --git a/src/invoke-store.ts b/src/invoke-store.ts index 10153d6..d165758 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -1,3 +1,4 @@ +import type { AsyncLocalStorage } from "node:async_hooks"; interface Context { [key: string]: unknown; [key: symbol]: unknown; @@ -97,7 +98,6 @@ class InvokeStoreSingle extends InvokeStoreBase { this.currentContext = undefined; } } - } /** @@ -105,7 +105,7 @@ class InvokeStoreSingle extends InvokeStoreBase { * @internal */ class InvokeStoreMulti extends InvokeStoreBase { - private als!: import("node:async_hooks").AsyncLocalStorage; + private als!: AsyncLocalStorage; static async create(): Promise { const instance = new InvokeStoreMulti(); @@ -167,7 +167,6 @@ export namespace InvokeStore { } const isMulti = "AWS_LAMBDA_MAX_CONCURRENCY" in process.env; - const newInstance = isMulti ? await InvokeStoreMulti.create() : new InvokeStoreSingle(); @@ -183,4 +182,17 @@ export namespace InvokeStore { return instance; } + + export const _testing = + process.env.AWS_LAMBDA_BENCHMARK_MODE === "1" + ? { + reset: () => { + instance = null; + if (globalThis.awslambda?.InvokeStore) { + delete globalThis.awslambda.InvokeStore; + } + globalThis.awslambda = {}; + }, + } + : undefined; } From 7cffc764c5e6fd0023c0bb15680ec1820012f5ec Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Nov 2025 17:21:21 +0000 Subject: [PATCH 26/26] fix: PR comment, race --- src/invoke-store.ts | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/invoke-store.ts b/src/invoke-store.ts index d165758..cc3d3a5 100644 --- a/src/invoke-store.ts +++ b/src/invoke-store.ts @@ -146,10 +146,6 @@ class InvokeStoreMulti extends InvokeStoreBase { } } -interface InvokeStoreConfig { - env?: NodeJS.ProcessEnv; -} - /** * Provides access to AWS Lambda execution context storage. * Supports both single-context and multi-context environments through different implementations. @@ -159,25 +155,26 @@ interface InvokeStoreConfig { * @public */ export namespace InvokeStore { - let instance: InvokeStoreBase | null = null; + let instance: Promise | null = null; export async function getInstanceAsync(): Promise { - if (instance) { - return instance; - } - - const isMulti = "AWS_LAMBDA_MAX_CONCURRENCY" in process.env; - const newInstance = isMulti - ? await InvokeStoreMulti.create() - : new InvokeStoreSingle(); - - if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { - instance = globalThis.awslambda.InvokeStore; - } else if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { - instance = newInstance; - globalThis.awslambda.InvokeStore = newInstance; - } else { - instance = newInstance; + if (!instance) { + // Lock synchronously on first invoke by immediately assigning the promise + instance = (async () => { + const isMulti = "AWS_LAMBDA_MAX_CONCURRENCY" in process.env; + const newInstance = isMulti + ? await InvokeStoreMulti.create() + : new InvokeStoreSingle(); + + if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda?.InvokeStore) { + return globalThis.awslambda.InvokeStore; + } else if (!NO_GLOBAL_AWS_LAMBDA && globalThis.awslambda) { + globalThis.awslambda.InvokeStore = newInstance; + return newInstance; + } else { + return newInstance; + } + })(); } return instance;