Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/batch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@
"nodejs"
],
"dependencies": {
"@aws/lambda-invoke-store": "0.3.0",
"@aws-lambda-powertools/commons": "2.35.0",
"@standard-schema/spec": "^1.1.0"
},
"devDependencies": {
"@aws-lambda-powertools/parser": "2.35.0",
"@aws-lambda-powertools/testing-utils": "file:../testing",
"@aws-sdk/client-sqs": "^3.1121.0",
"@aws/lambda-invoke-store": "0.3.0",
"zod": "^4.5.4"
}
}
179 changes: 48 additions & 131 deletions packages/batch/src/BatchProcessingStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import '@aws/lambda-invoke-store';
import { shouldUseInvokeStore } from '@aws-lambda-powertools/commons/utils/env';
import { InvocationScoped } from '@aws-lambda-powertools/commons/utils/invocation-scoped';
import type {
BaseRecord,
BatchProcessingOptions,
Expand All @@ -10,178 +9,96 @@ import type {
/**
* Manages storage of batch processing state with automatic context detection.
*
* This class abstracts the storage mechanism for batch processing state,
* automatically choosing between InvokeStore (when in Lambda context) and
* fallback instance variables (when outside Lambda context). The decision is
* made at runtime on every method call to support Lambda's concurrent execution
* isolation.
* This class abstracts the storage mechanism for state that must be scoped to
* a single invocation, automatically choosing between the InvokeStore (when
* invocations run concurrently in the same execution environment) and an
* instance-level fallback shared across sequential invocations. The decision
* is made at runtime on every access to support Lambda's transition to async
* contexts.
*/
class BatchProcessingStore {
readonly #recordsKey = Symbol('powertools.batch.records');
readonly #handlerKey = Symbol('powertools.batch.handler');
readonly #optionsKey = Symbol('powertools.batch.options');
readonly #failureMessagesKey = Symbol('powertools.batch.failureMessages');
readonly #successMessagesKey = Symbol('powertools.batch.successMessages');
readonly #batchResponseKey = Symbol('powertools.batch.batchResponse');
readonly #errorsKey = Symbol('powertools.batch.errors');

#fallbackRecords: BaseRecord[] = [];
#fallbackHandler: CallableFunction = () => {};
#fallbackOptions?: BatchProcessingOptions;
#fallbackFailureMessages: EventSourceDataClassTypes[] = [];
#fallbackSuccessMessages: EventSourceDataClassTypes[] = [];
#fallbackBatchResponse: PartialItemFailureResponse = {
batchItemFailures: [],
};
#fallbackErrors: Error[] = [];
readonly #records = new InvocationScoped<BaseRecord[]>(
'powertools.batch.records',
{ fresh: () => [] }
);
readonly #handler = new InvocationScoped<CallableFunction>(
'powertools.batch.handler',
{ fresh: () => () => {} }
);
readonly #options = new InvocationScoped<BatchProcessingOptions | undefined>(
'powertools.batch.options',
{ initial: undefined }
);
readonly #failureMessages = new InvocationScoped<EventSourceDataClassTypes[]>(
'powertools.batch.failureMessages',
{ fresh: () => [] }
);
readonly #successMessages = new InvocationScoped<EventSourceDataClassTypes[]>(
'powertools.batch.successMessages',
{ fresh: () => [] }
);
readonly #batchResponse = new InvocationScoped<PartialItemFailureResponse>(
'powertools.batch.batchResponse',
{ fresh: () => ({ batchItemFailures: [] }) }
);
readonly #errors = new InvocationScoped<Error[]>('powertools.batch.errors', {
fresh: () => [],
});

public getRecords(): BaseRecord[] {
if (!shouldUseInvokeStore()) {
return this.#fallbackRecords;
}

if (globalThis.awslambda?.InvokeStore === undefined) {
throw new Error('InvokeStore is not available');
}

const store = globalThis.awslambda.InvokeStore;
return (store.get(this.#recordsKey) as BaseRecord[]) ?? [];
return this.#records.get();
}

public setRecords(records: BaseRecord[]): void {
if (!shouldUseInvokeStore()) {
this.#fallbackRecords = records;
return;
}

if (globalThis.awslambda?.InvokeStore === undefined) {
throw new Error('InvokeStore is not available');
}

const store = globalThis.awslambda.InvokeStore;
store.set(this.#recordsKey, records);
this.#records.set(records);
}

public getHandler(): CallableFunction {
if (!shouldUseInvokeStore()) {
return this.#fallbackHandler;
}

return (
(globalThis.awslambda?.InvokeStore?.get(
this.#handlerKey
) as CallableFunction) ?? (() => {})
);
return this.#handler.get();
}

public setHandler(handler: CallableFunction): void {
if (!shouldUseInvokeStore()) {
this.#fallbackHandler = handler;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#handlerKey, handler);
this.#handler.set(handler);
}

public getOptions(): BatchProcessingOptions | undefined {
if (!shouldUseInvokeStore()) {
return this.#fallbackOptions;
}

return globalThis.awslambda?.InvokeStore?.get(this.#optionsKey) as
| BatchProcessingOptions
| undefined;
return this.#options.get();
}

public setOptions(options: BatchProcessingOptions | undefined): void {
if (!shouldUseInvokeStore()) {
this.#fallbackOptions = options;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#optionsKey, options);
this.#options.set(options);
}

public getFailureMessages(): EventSourceDataClassTypes[] {
if (!shouldUseInvokeStore()) {
return this.#fallbackFailureMessages;
}

return (
(globalThis.awslambda?.InvokeStore?.get(
this.#failureMessagesKey
) as EventSourceDataClassTypes[]) ?? []
);
return this.#failureMessages.get();
}

public setFailureMessages(messages: EventSourceDataClassTypes[]): void {
if (!shouldUseInvokeStore()) {
this.#fallbackFailureMessages = messages;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#failureMessagesKey, messages);
this.#failureMessages.set(messages);
}

public getSuccessMessages(): EventSourceDataClassTypes[] {
if (!shouldUseInvokeStore()) {
return this.#fallbackSuccessMessages;
}

return (
(globalThis.awslambda?.InvokeStore?.get(
this.#successMessagesKey
) as EventSourceDataClassTypes[]) ?? []
);
return this.#successMessages.get();
}

public setSuccessMessages(messages: EventSourceDataClassTypes[]): void {
if (!shouldUseInvokeStore()) {
this.#fallbackSuccessMessages = messages;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#successMessagesKey, messages);
this.#successMessages.set(messages);
}

public getBatchResponse(): PartialItemFailureResponse {
if (!shouldUseInvokeStore()) {
return this.#fallbackBatchResponse;
}

return (
(globalThis.awslambda?.InvokeStore?.get(
this.#batchResponseKey
) as PartialItemFailureResponse) ?? { batchItemFailures: [] }
);
return this.#batchResponse.get();
}

public setBatchResponse(response: PartialItemFailureResponse): void {
if (!shouldUseInvokeStore()) {
this.#fallbackBatchResponse = response;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#batchResponseKey, response);
this.#batchResponse.set(response);
}

public getErrors(): Error[] {
if (!shouldUseInvokeStore()) {
return this.#fallbackErrors;
}

return (
(globalThis.awslambda?.InvokeStore?.get(this.#errorsKey) as Error[]) ?? []
);
return this.#errors.get();
}

public setErrors(errors: Error[]): void {
if (!shouldUseInvokeStore()) {
this.#fallbackErrors = errors;
return;
}

globalThis.awslambda?.InvokeStore?.set(this.#errorsKey, errors);
this.#errors.set(errors);
}
}

Expand Down
Loading
Loading