diff --git a/.markdownlintignore b/.markdownlintignore index 73eedf3c15..2cac804a00 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -13,7 +13,6 @@ LICENSE # these will be removed from the ignore and linted in future PRs packages/batch/README.md packages/commons/README.md -packages/idempotency/README.md packages/jmespath/README.md packages/logger/README.md packages/metrics/README.md diff --git a/docs/utilities/idempotency.md b/docs/utilities/idempotency.md index fb4bf2d42a..552fb4bcec 100644 --- a/docs/utilities/idempotency.md +++ b/docs/utilities/idempotency.md @@ -3,8 +3,6 @@ title: Idempotency description: Utility --- - - The idempotency utility provides a simple solution to convert your Lambda functions into idempotent operations which are safe to retry. ## Key features @@ -744,6 +742,30 @@ Below an example implementation of a custom persistence layer backed by a generi For example, the `_putRecord()` method needs to throw an error if a non-expired record already exists in the data store with a matching key. +## Testing your code + +The idempotency utility provides several routes to test your code. + +### Disabling the idempotency utility + +When testing your code, you may wish to disable the idempotency logic altogether and focus on testing your business logic. To do this, you can set the environment variable POWERTOOLS_IDEMPOTENCY_DISABLED with a truthy value. + +### Testing with local DynamoDB + +When testing your Lambda function locally, you can use a local DynamoDB instance to test the idempotency feature. You can use [DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html) or [LocalStack](https://localstack.cloud/){target="_blank"}. + +=== "handler.test.ts" + + ```typescript hl_lines="7-9" + --8<-- "examples/snippets/idempotency/workingWithLocalDynamoDB.test.ts" + ``` + +=== "handler.ts" + + ```typescript hl_lines="7-9" + --8<-- "examples/snippets/idempotency/workingWithLocalDynamoDB.ts" + ``` + ## Extra resources If you're interested in a deep dive on how Amazon uses idempotency when building our APIs, check out diff --git a/examples/snippets/idempotency/workingWithLocalDynamoDB.test.ts b/examples/snippets/idempotency/workingWithLocalDynamoDB.test.ts new file mode 100644 index 0000000000..f3cfdddbc9 --- /dev/null +++ b/examples/snippets/idempotency/workingWithLocalDynamoDB.test.ts @@ -0,0 +1,40 @@ +import { makeIdempotent } from '@aws-lambda-powertools/idempotency'; +import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; +import type { Context } from 'aws-lambda'; +import { handler } from './workingWithLocalDynamoDB'; + +describe('Idempotent handler', () => { + const lambdaContext = { + functionName: 'foo-bar-function', + memoryLimitInMB: '128', + invokedFunctionArn: + 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', + awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + getRemainingTimeInMillis: () => 1234, + } as Context; + + const mockPersistenceStore = new DynamoDBPersistenceLayer({ + tableName: 'IdempotencyTable', + clientConfig: { endpoint: 'http://localhost:8000' }, // 8000 for local DynamoDB and 4566 for LocalStack + }); + + const idempotentHandler = makeIdempotent(handler, { + persistenceStore: mockPersistenceStore, + }); + + it('should return the same response', async () => { + const response = await idempotentHandler( + { + foo: 'bar', + }, + lambdaContext + ); + expect(response).toEqual({ + statusCode: 200, + body: JSON.stringify({ + paymentId: '123', + message: 'Payment created', + }), + }); + }); +}); diff --git a/examples/snippets/idempotency/workingWithLocalDynamoDB.ts b/examples/snippets/idempotency/workingWithLocalDynamoDB.ts new file mode 100644 index 0000000000..f48430456e --- /dev/null +++ b/examples/snippets/idempotency/workingWithLocalDynamoDB.ts @@ -0,0 +1,23 @@ +import { makeIdempotent } from '@aws-lambda-powertools/idempotency'; +import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; +import type { Context } from 'aws-lambda'; + +const ddbPersistenceStore = new DynamoDBPersistenceLayer({ + tableName: 'IdempotencyTable', +}); + +const handler = async (event: unknown, context: Context) => { + return { + statusCode: 200, + body: JSON.stringify({ + paymentId: '123', + message: 'Payment created', + }), + }; +}; + +const idempotentHandler = makeIdempotent(handler, { + persistenceStore: ddbPersistenceStore, +}); + +export { idempotentHandler, handler }; diff --git a/package.json b/package.json index 9b2b5231c9..f172f5304a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "docs-generateApiDoc": "typedoc .", "docs-runLocalApiDoc": "npx live-server api", "postpublish": "git restore .", - "lint:markdown": "markdownlint-cli2 '**/*.md' '#node_modules' '#**/*/node_modules' '#docs/changelog.md' '#LICENSE.md' '#.github' '#**/*/CHANGELOG.md' '#examples/app/README.md' '#packages/commons/README.md' '#packages/idempotency/README.md' '#packages/jmespath/README.md' '#packages/logger/README.md' '#packages/metrics/README.md' '#packages/parameters/README.md' '#packages/parser/README.md' '#packages/tracer/README.md'" + "lint:markdown": "markdownlint-cli2 '**/*.md' '#node_modules' '#**/*/node_modules' '#docs/changelog.md' '#LICENSE.md' '#.github' '#**/*/CHANGELOG.md' '#examples/app/README.md' '#packages/commons/README.md' '#packages/jmespath/README.md' '#packages/logger/README.md' '#packages/metrics/README.md' '#packages/parameters/README.md' '#packages/parser/README.md' '#packages/tracer/README.md'" }, "repository": { "type": "git", diff --git a/packages/idempotency/README.md b/packages/idempotency/README.md index 8b0f568601..6a3abc1dab 100644 --- a/packages/idempotency/README.md +++ b/packages/idempotency/README.md @@ -1,39 +1,18 @@ -# Powertools for AWS Lambda (TypeScript) - Idempotency Utility +# Powertools for AWS Lambda (TypeScript) - Idempotency Utility -Powertools for AWS Lambda (TypeScript) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda/typescript/latest/#features). +Powertools for AWS Lambda (TypeScript) is a developer toolkit to implement +Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda/typescript/latest/#features). You can use the package in both TypeScript and JavaScript code bases. -- [Intro](#intro) -- [Key features](#key-features) -- [Usage](#usage) - - [Function wrapper](#function-wrapper) - - [Decorator](#decorator) - - [Middy middleware](#middy-middleware) - - [DynamoDB persistence layer](#dynamodb-persistence-layer) -- [Contribute](#contribute) -- [Roadmap](#roadmap) -- [Connect](#connect) -- [How to support Powertools for AWS Lambda (TypeScript)?](#how-to-support-powertools-for-aws-lambda-typescript) - - [Becoming a reference customer](#becoming-a-reference-customer) - - [Sharing your work](#sharing-your-work) - - [Using Lambda Layer](#using-lambda-layer) -- [License](#license) - ## Intro This package provides a utility to implement idempotency in your Lambda functions. -You can either use it to wrap a function, decorate a method, or as Middy middleware to make your AWS Lambda handler idempotent. - -The current implementation provides a persistence layer for Amazon DynamoDB, which offers a variety of configuration options. You can also bring your own persistence layer by extending the `BasePersistenceLayer` class. - -## Key features +You can either use it to wrap a function, decorate a method, or as Middy middleware to make your AWS Lambda handler +idempotent. -- Prevent Lambda handler from executing more than once on the same event payload during a time window -- Ensure Lambda handler returns the same result when called with the same payload -- Select a subset of the event as the idempotency key using JMESPath expressions -- Set a time window in which records with the same payload should be considered duplicates -- Expires in-progress executions if the Lambda function times out halfway through +The current implementation provides a persistence layer for Amazon DynamoDB, which offers a variety of configuration +options. You can also bring your own persistence layer by extending the `BasePersistenceLayer` class. ## Usage @@ -43,21 +22,27 @@ To get started, install the library by running: npm i @aws-lambda-powertools/idempotency @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb ``` -Next, review the IAM permissions attached to your AWS Lambda function and make sure you allow the [actions detailed](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/#iam-permissions) in the documentation of the utility. +Next, review the IAM permissions attached to your AWS Lambda function and make sure you allow +the [actions detailed](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/#iam-permissions) +in the documentation of the utility. ### Function wrapper -You can make any function idempotent, and safe to retry, by wrapping it using the `makeIdempotent` higher-order function. +You can make any function idempotent, and safe to retry, by wrapping it using the `makeIdempotent` higher-order +function. -The function wrapper takes a reference to the function to be made idempotent as first argument, and an object with options as second argument. +The `makeIdempotent` function takes a reference to the function to be made idempotent as first argument, and an object +with options as second argument. -When you wrap your Lambda handler function, the utility uses the content of the `event` parameter to handle the idempotency logic. +When you wrap your Lambda handler function, the utility uses the content of the `event` parameter to handle the +idempotency logic. ```ts -import { makeIdempotent } from '@aws-lambda-powertools/idempotency'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, APIGatewayProxyEvent } from 'aws-lambda'; +import {makeIdempotent} from '@aws-lambda-powertools/idempotency'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, APIGatewayProxyEvent} from 'aws-lambda'; +`` const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', }); @@ -77,9 +62,9 @@ export const handler = makeIdempotent(myHandler, { You can also use the `makeIdempotent` function to wrap any other arbitrary function, not just Lambda handlers. ```ts -import { makeIdempotent } from '@aws-lambda-powertools/idempotency'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, SQSEvent, SQSRecord } from 'aws-lambda'; +import {makeIdempotent} from '@aws-lambda-powertools/idempotency'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, SQSEvent, SQSRecord} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', @@ -103,12 +88,13 @@ export const handler = async ( }; ``` -If your function has multiple arguments, you can use the `dataIndexArgument` option to specify which argument should be used as the idempotency key. +If your function has multiple arguments, you can use the `dataIndexArgument` option to specify which argument should be +used as the idempotency key. ```ts -import { makeIdempotent } from '@aws-lambda-powertools/idempotency'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, SQSEvent, SQSRecord } from 'aws-lambda'; +import {makeIdempotent} from '@aws-lambda-powertools/idempotency'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, SQSEvent, SQSRecord} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', @@ -121,7 +107,7 @@ const processingFunction = async (payload: SQSRecord, customerId: string): Promi const processIdempotently = makeIdempotent(processingFunction, { persistenceStore, // this tells the utility to use the second argument (`customerId`) as the idempotency key - dataIndexArgument: 1, + dataIndexArgument: 1, }); export const handler = async ( @@ -134,15 +120,16 @@ export const handler = async ( }; ``` -Note that you can also specify a JMESPath expression in the Idempotency config object to select a subset of the event payload as the idempotency key. This is useful when dealing with payloads that contain timestamps or request ids. +Note that you can also specify a JMESPath expression in the Idempotency config object to select a subset of the event +payload as the idempotency key. This is useful when dealing with payloads that contain timestamps or request ids. ```ts -import { makeIdempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, APIGatewayProxyEvent } from 'aws-lambda'; +import {makeIdempotent, IdempotencyConfig} from '@aws-lambda-powertools/idempotency'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, APIGatewayProxyEvent} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ - tableName: 'idempotencyTableName', + tableName: 'idempotencyTableName', }); const myHandler = async ( @@ -160,12 +147,14 @@ export const handler = makeIdempotent(myHandler, { }); ``` -Additionally, you can also use one of the [JMESPath built-in functions](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/jmespath/#built-in-jmespath-functions) like `powertools_json()` to decode keys and use parts of the payload as the idempotency key. +Additionally, you can also use one of +the [JMESPath built-in functions](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/jmespath/#built-in-jmespath-functions) +like `powertools_json()` to decode keys and use parts of the payload as the idempotency key. ```ts -import { makeIdempotent, IdempotencyConfig } from '@aws-lambda-powertools/idempotency'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, APIGatewayProxyEvent } from 'aws-lambda'; +import {makeIdempotent, IdempotencyConfig} from '@aws-lambda-powertools/idempotency'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, APIGatewayProxyEvent} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', @@ -193,17 +182,17 @@ Check the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilit You can make any function idempotent, and safe to retry, by decorating it using the `@idempotent` decorator. ```ts -import { idempotent } from '@aws-lambda-powertools/idempotency'; -import { LambdaInterface } from '@aws-lambda-powertools/commons'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context, APIGatewayProxyEvent } from 'aws-lambda'; +import {idempotent} from '@aws-lambda-powertools/idempotency'; +import {LambdaInterface} from '@aws-lambda-powertools/commons'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context, APIGatewayProxyEvent} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', }); class MyHandler extends LambdaInterface { - @idempotent({ persistenceStore: dynamoDBPersistenceLayer }) + @idempotent({persistenceStore: dynamoDBPersistenceLayer}) public async handler( event: APIGatewayProxyEvent, context: Context @@ -219,27 +208,27 @@ export const handler = handlerClass.handler.bind(handlerClass); Using the same decorator, you can also make any other arbitrary method idempotent. ```ts -import { idempotent } from '@aws-lambda-powertools/idempotency'; -import { LambdaInterface } from '@aws-lambda-powertools/commons'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; -import type { Context } from 'aws-lambda'; +import {idempotent} from '@aws-lambda-powertools/idempotency'; +import {LambdaInterface} from '@aws-lambda-powertools/commons'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; +import type {Context} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', }); class MyHandler extends LambdaInterface { - + public async handler( event: unknown, context: Context ): Promise { - for(const record of event.Records) { + for (const record of event.Records) { await this.processIdempotently(record); } } - - @idempotent({ persistenceStore: dynamoDBPersistenceLayer }) + + @idempotent({persistenceStore: dynamoDBPersistenceLayer}) private async process(record: unknown): Promise { // process each code idempotently } @@ -249,20 +238,26 @@ const handlerClass = new MyHandler(); export const handler = handlerClass.handler.bind(handlerClass); ``` -The decorator configuration options are identical with the ones of the `makeIdempotent` function. Check the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/) for more examples. +The decorator configuration options are identical with the ones of the `makeIdempotent` function. Check +the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/) for more examples. ### Middy middleware -If instead you use Middy, you can use the `makeHandlerIdempotent` middleware. When using the middleware your Lambda handler becomes idempotent. +If instead you use Middy, you can use the `makeHandlerIdempotent` middleware. When using the middleware your Lambda +handler becomes idempotent. -By default, the Idempotency utility will use the full event payload to create an hash and determine if a request is idempotent, and therefore it should not be retried. When dealing with a more elaborate payload, where parts of the payload always change you should use the `IdempotencyConfig` object to instruct the utility to only use a portion of your payload. This is useful when dealing with payloads that contain timestamps or request ids. +By default, the Idempotency utility will use the full event payload to create an hash and determine if a request is +idempotent, and therefore it should not be retried. +When dealing with a more elaborate payload, where parts of the payload always change you should use +the `IdempotencyConfig` object to instruct the utility to only use a portion of your payload. This is useful when +dealing with payloads that contain timestamps or request ids. ```ts -import { IdempotencyConfig } from '@aws-lambda-powertools/idempotency'; -import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware'; -import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb'; +import {IdempotencyConfig} from '@aws-lambda-powertools/idempotency'; +import {makeHandlerIdempotent} from '@aws-lambda-powertools/idempotency/middleware'; +import {DynamoDBPersistenceLayer} from '@aws-lambda-powertools/idempotency/dynamodb'; import middy from '@middy/core'; -import type { Context, APIGatewayProxyEvent } from 'aws-lambda'; +import type {Context, APIGatewayProxyEvent} from 'aws-lambda'; const persistenceStore = new DynamoDBPersistenceLayer({ tableName: 'idempotencyTableName', @@ -291,19 +286,26 @@ Check the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilit ### DynamoDB persistence layer -You can use a DynamoDB Table to store the idempotency information. This enables you to keep track of the hash key, payload, status for progress, expiration, and much more. +You can use a DynamoDB Table to store the idempotency information. This enables you to keep track of the hash key, +payload, status for progress, expiration, and much more. You can customize most of the configuration options of the table, i.e the names of the attributes. -See the [API documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/api/types/_aws_lambda_powertools_idempotency.types.DynamoDBPersistenceOptions.html) for more details. +See +the [API documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/api/types/_aws_lambda_powertools_idempotency.types.DynamoDBPersistenceOptions.html) +for more details. ## Contribute -If you are interested in contributing to this project, please refer to our [Contributing Guidelines](https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/CONTRIBUTING.md). +If you are interested in contributing to this project, please refer to +our [Contributing Guidelines](https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/CONTRIBUTING.md). ## Roadmap The roadmap of Powertools for AWS Lambda (TypeScript) is driven by customersโ€™ demand. -Help us prioritize upcoming functionalities or utilities by [upvoting existing RFCs and feature requests](https://github.com/aws-powertools/powertools-lambda-typescript/issues), or [creating new ones](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose), in this GitHub repository. +Help us prioritize upcoming functionalities or utilities +by [upvoting existing RFCs and feature requests](https://github.com/aws-powertools/powertools-lambda-typescript/issues), +or [creating new ones](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose), in this GitHub +repository. ## Connect @@ -314,7 +316,10 @@ Help us prioritize upcoming functionalities or utilities by [upvoting existing R ### Becoming a reference customer -Knowing which companies are using this library is important to help prioritize the project internally. If your company is using Powertools for AWS Lambda (TypeScript), you can request to have your name and logo added to the README file by raising a [Support Powertools for AWS Lambda (TypeScript) (become a reference)](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new?assignees=&labels=customer-reference&template=support_powertools.yml&title=%5BSupport+Lambda+Powertools%5D%3A+%3Cyour+organization+name%3E) issue. +Knowing which companies are using this library is important to help prioritize the project internally. If your company +is using Powertools for AWS Lambda (TypeScript), you can request to have your name and logo added to the README file by +raising a [Support Powertools for AWS Lambda (TypeScript) (become a reference)](https://s12d.com/become-reference-pt-ts) +issue. The following companies, among others, use Powertools: @@ -336,11 +341,16 @@ The following companies, among others, use Powertools: ### Sharing your work -Share what you did with Powertools for AWS Lambda (TypeScript) ๐Ÿ’ž๐Ÿ’ž. Blog post, workshops, presentation, sample apps and others. Check out what the community has already shared about Powertools for AWS Lambda (TypeScript) [here](https://docs.powertools.aws.dev/lambda/typescript/latest/we_made_this). +Share what you did with Powertools for AWS Lambda (TypeScript) ๐Ÿ’ž๐Ÿ’ž. Blog post, workshops, presentation, sample apps and +others. Check out what the community has already shared about Powertools for AWS Lambda ( +TypeScript) [here](https://docs.powertools.aws.dev/lambda/typescript/latest/we_made_this). ### Using Lambda Layer -This helps us understand who uses Powertools for AWS Lambda (TypeScript) in a non-intrusive way, and helps us gain future investments for other Powertools for AWS Lambda languages. When [using Layers](https://docs.powertools.aws.dev/lambda/typescript/latest/#lambda-layer), you can add Powertools as a dev dependency to not impact the development process. +This helps us understand who uses Powertools for AWS Lambda (TypeScript) in a non-intrusive way, and helps us gain +future investments for other Powertools for AWS Lambda languages. +When [using Layers](https://docs.powertools.aws.dev/lambda/typescript/latest/#lambda-layer), you can add Powertools as a +dev dependency to not impact the development process. ## License diff --git a/packages/idempotency/src/IdempotencyConfig.ts b/packages/idempotency/src/IdempotencyConfig.ts index 0a89a4af4c..043fa828f8 100644 --- a/packages/idempotency/src/IdempotencyConfig.ts +++ b/packages/idempotency/src/IdempotencyConfig.ts @@ -1,8 +1,8 @@ -import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js'; +import { PowertoolsFunctions } from '@aws-lambda-powertools/jmespath/functions'; +import type { JMESPathParsingOptions } from '@aws-lambda-powertools/jmespath/types'; import type { Context } from 'aws-lambda'; +import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js'; import type { IdempotencyConfigOptions } from './types/IdempotencyOptions.js'; -import type { JMESPathParsingOptions } from '@aws-lambda-powertools/jmespath/types'; -import { PowertoolsFunctions } from '@aws-lambda-powertools/jmespath/functions'; /** * Configuration for the idempotency feature. @@ -52,9 +52,9 @@ class IdempotencyConfig { * @default false */ public throwOnNoIdempotencyKey: boolean; + /** * Use the local cache to store idempotency keys. - * @see {@link LRUCache} */ public useLocalCache: boolean; readonly #envVarsService: EnvironmentVariablesService; diff --git a/packages/idempotency/src/IdempotencyHandler.ts b/packages/idempotency/src/IdempotencyHandler.ts index ab2903063d..4dcfa03def 100644 --- a/packages/idempotency/src/IdempotencyHandler.ts +++ b/packages/idempotency/src/IdempotencyHandler.ts @@ -137,7 +137,7 @@ export class IdempotencyHandler { await this.#deleteInProgressRecord(); throw error; } - await this.#saveSuccessfullResult(result); + await this.#saveSuccessfulResult(result); return result; } @@ -208,7 +208,7 @@ export class IdempotencyHandler { * @param response The response returned by the handler. */ public async handleMiddyAfter(response: unknown): Promise { - await this.#saveSuccessfullResult(response as ReturnType); + await this.#saveSuccessfulResult(response as ReturnType); } /** @@ -401,7 +401,7 @@ export class IdempotencyHandler { * * @param result The result returned by the handler. */ - #saveSuccessfullResult = async (result: ReturnType): Promise => { + #saveSuccessfulResult = async (result: ReturnType): Promise => { try { await this.#persistenceStore.saveSuccess( this.#functionPayloadToBeHashed, diff --git a/packages/idempotency/src/idempotencyDecorator.ts b/packages/idempotency/src/idempotencyDecorator.ts index b997237486..64ad4c4fad 100644 --- a/packages/idempotency/src/idempotencyDecorator.ts +++ b/packages/idempotency/src/idempotencyDecorator.ts @@ -50,7 +50,9 @@ import type { * } * } * ``` - * @see {@link DynamoDBPersistenceLayer} + * + * @param options - Options to configure the idempotency behavior + * @see {@link persistence/DynamoDBPersistenceLayer.DynamoDBPersistenceLayer | DynamoDBPersistenceLayer} * @see https://www.typescriptlang.org/docs/handbook/decorators.html */ const idempotent = function ( diff --git a/packages/idempotency/src/makeIdempotent.ts b/packages/idempotency/src/makeIdempotent.ts index 47aa1f3f7d..251a72a8f8 100644 --- a/packages/idempotency/src/makeIdempotent.ts +++ b/packages/idempotency/src/makeIdempotent.ts @@ -71,6 +71,8 @@ const isOptionsWithDataIndexArgument = ( * return Promise.resolve(); * }; * + * @param fn - the function to make idempotent + * @param options - the options to configure the idempotency behavior * ``` */ function makeIdempotent( diff --git a/packages/idempotency/src/persistence/BasePersistenceLayer.ts b/packages/idempotency/src/persistence/BasePersistenceLayer.ts index cd883c4218..51e48f8b4f 100644 --- a/packages/idempotency/src/persistence/BasePersistenceLayer.ts +++ b/packages/idempotency/src/persistence/BasePersistenceLayer.ts @@ -21,9 +21,7 @@ import { IdempotencyRecord } from './IdempotencyRecord.js'; * Base class for all persistence layers. This class provides the basic functionality for * saving, retrieving, and deleting idempotency records. It also provides the ability to * configure the persistence layer from the idempotency config. - * @abstract * @class - * @implements {BasePersistenceLayerInterface} */ abstract class BasePersistenceLayer implements BasePersistenceLayerInterface { public idempotencyKeyPrefix: string; @@ -143,8 +141,8 @@ abstract class BasePersistenceLayer implements BasePersistenceLayerInterface { * * The record is also saved to the local cache if local caching is enabled. * - * @param record - the stored record to validate against - * @param data - the data payload being processed and to be validated against the stored record + * @param storedDataRecord - the stored record to validate against + * @param processedData - the data payload being processed and to be validated against the stored record */ public processExistingRecord( storedDataRecord: IdempotencyRecord, diff --git a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts index 37b75016cc..3c84f1850f 100644 --- a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts +++ b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts @@ -46,7 +46,6 @@ import { IdempotencyRecord } from './IdempotencyRecord.js'; * * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/index.html * @category Persistence Layer - * @implements {BasePersistenceLayer} */ class DynamoDBPersistenceLayer extends BasePersistenceLayer { private client: DynamoDBClient; diff --git a/packages/idempotency/src/persistence/IdempotencyRecord.ts b/packages/idempotency/src/persistence/IdempotencyRecord.ts index c5057e846f..2b99cadc4f 100644 --- a/packages/idempotency/src/persistence/IdempotencyRecord.ts +++ b/packages/idempotency/src/persistence/IdempotencyRecord.ts @@ -34,7 +34,7 @@ class IdempotencyRecord { /** * The idempotency record status can be COMPLETED, IN_PROGRESS or EXPIRED. * We check the status during idempotency processing to make sure we don't process an expired record and handle concurrent requests. - * @link {IdempotencyRecordStatusValue} + * {@link constants.IdempotencyRecordStatusValue | IdempotencyRecordStatusValue} * @private */ private status: IdempotencyRecordStatusValue; diff --git a/packages/idempotency/src/types/DynamoDBPersistence.ts b/packages/idempotency/src/types/DynamoDBPersistence.ts index 3a30a73639..97733e8732 100644 --- a/packages/idempotency/src/types/DynamoDBPersistence.ts +++ b/packages/idempotency/src/types/DynamoDBPersistence.ts @@ -33,7 +33,6 @@ interface DynamoDBPersistenceOptionsBase { * Interface for DynamoDBPersistenceOptions with clientConfig property. * * @interface - * @extends DynamoDBPersistenceOptionsBase * @property {DynamoDBClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. * @property {never} [awsSdkV3Client] - This property should never be passed. */ @@ -50,7 +49,6 @@ interface DynamoDBPersistenceOptionsWithClientConfig * Interface for DynamoDBPersistenceOptions with awsSdkV3Client property. * * @interface - * @extends DynamoDBPersistenceOptionsBase * @property {DynamoDBClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDB client instantiation * @property {never} [clientConfig] - This property should never be passed. */ diff --git a/packages/idempotency/src/types/IdempotencyOptions.ts b/packages/idempotency/src/types/IdempotencyOptions.ts index 3860c1f7eb..755e2edcac 100644 --- a/packages/idempotency/src/types/IdempotencyOptions.ts +++ b/packages/idempotency/src/types/IdempotencyOptions.ts @@ -7,7 +7,7 @@ import type { BasePersistenceLayer } from '../persistence/BasePersistenceLayer.j * Configuration options for the idempotency utility. * * When making a function idempotent you should always set - * a persistence store (i.e. {@link DynamoDBPersistenceLayer}). + * a persistence store (i.e. @see {@link persistence/DynamoDBPersistenceLayer.DynamoDBPersistenceLayer | DynamoDBPersistenceLayer}). * * Optionally, you can also pass a custom configuration object, * this allows you to customize the behavior of the idempotency utility. @@ -110,8 +110,7 @@ type ItempotentFunctionOptions> = T[1] extends Context * @internal * Options to configure the behavior of the idempotency logic. * - * This is an internal type that is used by the Idempotency utility to - * configure {@link IdempotencyHandler}. + * This is an internal type that is used for configuration. */ type IdempotencyHandlerOptions = { /** diff --git a/packages/idempotency/src/types/IdempotencyRecord.ts b/packages/idempotency/src/types/IdempotencyRecord.ts index 565ad8dac5..cfd01a44bf 100644 --- a/packages/idempotency/src/types/IdempotencyRecord.ts +++ b/packages/idempotency/src/types/IdempotencyRecord.ts @@ -1,9 +1,15 @@ import type { JSONValue } from '@aws-lambda-powertools/commons/types'; import type { IdempotencyRecordStatus } from '../constants.js'; +/** + * The status of an IdempotencyRecord + */ type IdempotencyRecordStatusValue = (typeof IdempotencyRecordStatus)[keyof typeof IdempotencyRecordStatus]; +/** + * Options for creating a new IdempotencyRecord + */ type IdempotencyRecordOptions = { idempotencyKey: string; status: IdempotencyRecordStatusValue; diff --git a/packages/idempotency/src/types/index.ts b/packages/idempotency/src/types/index.ts index 3f7d50ea34..c390a7d185 100644 --- a/packages/idempotency/src/types/index.ts +++ b/packages/idempotency/src/types/index.ts @@ -10,4 +10,12 @@ export type { IdempotencyConfigOptions, IdempotencyLambdaHandlerOptions, IdempotencyHandlerOptions, + ItempotentFunctionOptions, + AnyFunction, } from './IdempotencyOptions.js'; +export type { + DynamoDBPersistenceOptions, + DynamoDBPersistenceOptionsBase, + DynamoDBPersistenceOptionsWithClientConfig, + DynamoDBPersistenceOptionsWithClientInstance, +} from './DynamoDBPersistence.js'; diff --git a/packages/idempotency/typedoc.json b/packages/idempotency/typedoc.json index e2ece84d17..938073260b 100644 --- a/packages/idempotency/typedoc.json +++ b/packages/idempotency/typedoc.json @@ -1,13 +1,11 @@ { - "extends": [ - "../../typedoc.base.json" - ], + "extends": ["../../typedoc.base.json"], "entryPoints": [ "./src/index.ts", "./src/types/index.ts", - "./src/middleware/index.ts", + "./src/middleware/makeHandlerIdempotent.ts", "./src/persistence/index.ts", - "./src/persistence/DynamoDBPersistenceLayer.ts", + "./src/persistence/DynamoDBPersistenceLayer.ts" ], "readme": "README.md" -} \ No newline at end of file +}