Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 36 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,109 +33,118 @@ 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,
};
};

// 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:
Comment thread
trivikr marked this conversation as resolved.
Outdated
```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
},
() => {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
trivikr marked this conversation as resolved.
Outdated
"test:watch": "vitest watch",
"release": "yarn build && changeset publish"
},
Expand Down
68 changes: 68 additions & 0 deletions src/invoke-store.global.multiconcurrency.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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";

const originalImportAwaited = await OriginalImport.getInstance();

// WHEN - Set up context with original import
await originalImportAwaited.run(
{
[originalImportAwaited.PROTECTED_KEYS.REQUEST_ID]: testRequestId,
[originalImportAwaited.PROTECTED_KEYS.TENANT_ID]: testTenantId,
},
async () => {
originalImportAwaited.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.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(originalImportAwaited.get(newKey)).toBe(newValue);
}
);
});
});
Loading