diff --git a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml index be605cb13b..61d0361b31 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -85,17 +85,20 @@ jobs: run: working-directory: examples/${{ matrix.example }} steps: - - name: Checkout code + - &checkout_code + name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Setup NodeJS - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + - &setup_node + name: Setup Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version: 22 cache: "npm" - - name: Setup dependencies + - &setup_dependencies + name: Setup dependencies uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0 - name: Run linting - run: npm run lint + run: npm run lint:ci - name: Run tests run: npm t check-layer-publisher: @@ -103,17 +106,11 @@ jobs: env: NODE_ENV: dev steps: - - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Setup NodeJS - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 - with: - node-version: 22 - cache: "npm" - - name: Setup dependencies - uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0 + - *checkout_code + - *setup_node + - *setup_dependencies - name: Run linting - run: npm run lint -w layers + run: npm run lint:ci -w layers - name: Run tests run: npm run test:unit -w layers check-docs-snippets: @@ -121,30 +118,18 @@ jobs: env: NODE_ENV: dev steps: - - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Setup NodeJS - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 - with: - node-version: 22 - cache: "npm" - - name: Setup dependencies - uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0 + - *checkout_code + - *setup_node + - *setup_dependencies - name: Run linting - run: npm run lint -w examples/snippets + run: npm run lint:ci -w examples/snippets check-docs: runs-on: ubuntu-latest env: NODE_ENV: dev steps: - - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Setup NodeJS - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 - with: - node-version: 22 - cache: "npm" - - name: Setup dependencies - uses: aws-powertools/actions/.github/actions/cached-node-modules@29979bc5339bf54f76a11ac36ff67701986bb0f0 + - *checkout_code + - *setup_node + - *setup_dependencies - name: Run linting run: npm run lint:markdown diff --git a/biome.json b/biome.json index c675fdbf3b..2096e28b73 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,6 @@ { - "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json", + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "root": true, "assist": { "actions": { "source": { @@ -22,6 +23,32 @@ "useNumberNamespace": "error", "noInferrableTypes": "error", "noUselessElse": "error" + }, + "suspicious": { + "noTsIgnore": "error", + "noConfusingVoidType": "error", + "noDoubleEquals": "error", + "noGlobalIsNan": "error", + "noGlobalIsFinite": "error", + "useNumberToFixedDigitsArgument": "error", + "noPrototypeBuiltins": "error", + "noSkippedTests": "error", + "noFocusedTests": "error", + "noUnassignedVariables": "error", + "noVar": "error", + "useAdjacentOverloadSignatures": "error", + "useAwait": "error", + "useErrorMessage": "error", + "useIsArray": "error", + "useStaticResponseMethods": "error" + }, + "correctness": { + "useParseIntRadix": "error", + "useSingleJsDocAsterisk": "error", + "noUnusedVariables": "error", + "noUnusedPrivateClassMembers": "error", + "noUnusedImports": "error", + "noUnusedFunctionParameters": "error" } } }, diff --git a/docs/features/event-handler/appsync-events.md b/docs/features/event-handler/appsync-events.md index 58defee58e..a6398bbdd3 100644 --- a/docs/features/event-handler/appsync-events.md +++ b/docs/features/event-handler/appsync-events.md @@ -210,7 +210,7 @@ When processing batch of items with `aggregate` enabled, you must format the pay === "Error handling with batch of items" - ```typescript hl_lines="21-24" + ```typescript hl_lines="25-28" --8<-- "examples/snippets/event-handler/appsync-events/errorHandlingWithBatchOfItems.ts" ``` diff --git a/docs/features/event-handler/appsync-graphql.md b/docs/features/event-handler/appsync-graphql.md index 3cae9ed869..9112da2c01 100644 --- a/docs/features/event-handler/appsync-graphql.md +++ b/docs/features/event-handler/appsync-graphql.md @@ -176,7 +176,7 @@ You can use an AppSync JavaScript resolver or a VTL response mapping template to === "Exception Handling response" - ```json hl_lines="11 20" + ```json hl_lines="9 18" --8<-- "examples/snippets/event-handler/appsync-graphql/exceptionHandlingResponse.json" ``` diff --git a/docs/features/event-handler/rest.md b/docs/features/event-handler/rest.md index 136565eb2f..9fdfe3a46e 100644 --- a/docs/features/event-handler/rest.md +++ b/docs/features/event-handler/rest.md @@ -479,7 +479,7 @@ You can enable response compression by using the `compress` middleware. This wil === "Response" - ```json hl_lines="7-9 11 12" + ```json hl_lines="4-5 7-8" --8<-- "examples/snippets/event-handler/rest/samples/advanced_compress_res.json" ``` diff --git a/docs/features/tracer.md b/docs/features/tracer.md index 94c1081dd2..6cd6877e72 100644 --- a/docs/features/tracer.md +++ b/docs/features/tracer.md @@ -230,7 +230,7 @@ You can trace other class methods using the `captureMethod` decorator or any arb === "Manual" - ```typescript hl_lines="7-13 19 22 26-31" + ```typescript hl_lines="7-10 16 21 25 27" --8<-- "examples/snippets/tracer/captureMethodManual.ts" ``` diff --git a/examples/app/functions/process-items-stream.ts b/examples/app/functions/process-items-stream.ts index a8610f75ab..8a9da39348 100644 --- a/examples/app/functions/process-items-stream.ts +++ b/examples/app/functions/process-items-stream.ts @@ -83,16 +83,19 @@ export const handler = async ( event: DynamoDBStreamEvent, context: Context ): Promise => { - return tracer.provider.captureAsyncFunc('### handler', async (segment) => { - const result = await processPartialResponse( - event, - recordHandler, - processor, - { context } - ); + return (await tracer.provider.captureAsyncFunc( + '### handler', + async (segment) => { + const result = await processPartialResponse( + event, + recordHandler, + processor, + { context } + ); - segment?.close(); + segment?.close(); - return result; - }) as DynamoDBBatchResponse; + return result; + } + )) as DynamoDBBatchResponse; }; diff --git a/examples/app/package.json b/examples/app/package.json index 5912aa7d08..338dbebd36 100644 --- a/examples/app/package.json +++ b/examples/app/package.json @@ -13,6 +13,7 @@ "test": "npm run test:unit", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "test:unit": "export POWERTOOLS_DEV=true && vitest --run --silent", "test:e2e": "echo 'To be implemented ...'", "cdk": "cdk" diff --git a/examples/snippets/batch/advancedTracingRecordHandler.ts b/examples/snippets/batch/advancedTracingRecordHandler.ts index d92bb0d89a..fe330b30a5 100644 --- a/examples/snippets/batch/advancedTracingRecordHandler.ts +++ b/examples/snippets/batch/advancedTracingRecordHandler.ts @@ -22,7 +22,7 @@ const recordHandler = async (record: SQSRecord): Promise => { // do something with the item subsegment?.addMetadata('item', item); } catch (error) { - subsegment?.addError(error); + subsegment?.addError(error as Error); throw error; } } diff --git a/examples/snippets/biome.json b/examples/snippets/biome.json new file mode 100644 index 0000000000..6d29e1de97 --- /dev/null +++ b/examples/snippets/biome.json @@ -0,0 +1,12 @@ +{ + "$schema": "../../node_modules/@biomejs/biome/configuration_schema.json", + "extends": "//", + "root": false, + "linter": { + "rules": { + "suspicious": { + "useAwait": "off" + } + } + } +} diff --git a/examples/snippets/event-handler/appsync-events/errorHandlingWithBatchOfItems.ts b/examples/snippets/event-handler/appsync-events/errorHandlingWithBatchOfItems.ts index 9bf3b7024e..b4782946fe 100644 --- a/examples/snippets/event-handler/appsync-events/errorHandlingWithBatchOfItems.ts +++ b/examples/snippets/event-handler/appsync-events/errorHandlingWithBatchOfItems.ts @@ -18,9 +18,13 @@ app.onPublish( payload: { processed: true, original_payload: payload }, }); } catch (error) { + const errorString = + error instanceof Error + ? `${error.name} - ${error.message}` + : 'Unknown error'; returnValues.push({ id: payload.id, - error: `${error.name} - ${error.message}`, + error: errorString, }); } } diff --git a/examples/snippets/event-handler/appsync-graphql/advancedTestYourCode.ts b/examples/snippets/event-handler/appsync-graphql/advancedTestYourCode.ts index adf69ab2ee..06b8ac6f39 100644 --- a/examples/snippets/event-handler/appsync-graphql/advancedTestYourCode.ts +++ b/examples/snippets/event-handler/appsync-graphql/advancedTestYourCode.ts @@ -38,7 +38,7 @@ describe('Unit test for AppSync GraphQL Resolver', () => { const event = onGraphqlEventFactory('listLocations', 'Query'); // Act - const result = (await handler(event, {} as Context)) as Promise; + const result = (await handler(event, {} as Context)) as unknown[]; // Assess expect(result).toHaveLength(2); diff --git a/examples/snippets/event-handler/appsync-graphql/exceptionHandling.ts b/examples/snippets/event-handler/appsync-graphql/exceptionHandling.ts index fc354527e5..0979bf4863 100644 --- a/examples/snippets/event-handler/appsync-graphql/exceptionHandling.ts +++ b/examples/snippets/event-handler/appsync-graphql/exceptionHandling.ts @@ -1,6 +1,6 @@ +import { AssertionError } from 'node:assert'; import { AppSyncGraphQLResolver } from '@aws-lambda-powertools/event-handler/appsync-graphql'; import { Logger } from '@aws-lambda-powertools/logger'; -import { AssertionError } from 'node:assert'; import type { Context } from 'aws-lambda'; const logger = new Logger({ diff --git a/examples/snippets/event-handler/appsync-graphql/exceptionHandlingResponse.json b/examples/snippets/event-handler/appsync-graphql/exceptionHandlingResponse.json index 77c248e2f3..3ca50422a0 100644 --- a/examples/snippets/event-handler/appsync-graphql/exceptionHandlingResponse.json +++ b/examples/snippets/event-handler/appsync-graphql/exceptionHandlingResponse.json @@ -4,9 +4,7 @@ }, "errors": [ { - "path": [ - "createSomething" - ], + "path": ["createSomething"], "data": null, "errorType": "AssertionError", "errorInfo": null, @@ -20,4 +18,4 @@ "message": "This is an assertion Error" } ] -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_binary_req.json b/examples/snippets/event-handler/rest/samples/advanced_binary_req.json index 021832523b..83255cdf20 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_binary_req.json +++ b/examples/snippets/event-handler/rest/samples/advanced_binary_req.json @@ -6,4 +6,4 @@ "resource": "/logo", "path": "/logo", "httpMethod": "GET" -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_binary_res.json b/examples/snippets/event-handler/rest/samples/advanced_binary_res.json index 371248a1a8..ebd77ef84f 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_binary_res.json +++ b/examples/snippets/event-handler/rest/samples/advanced_binary_res.json @@ -1,13 +1,9 @@ { "body": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICAgIDx0aXRsZT5BV1MgTGFtYmRhPC90aXRsZT4KICAgIDxkZWZzPgogICAgICAgIDxsaW5lYXJHcmFkaWVudCB4MT0iMCUiIHkxPSIxMDAlIiB4Mj0iMTAwJSIgeTI9IjAlIiBpZD0ibGluZWFyR3JhZGllbnQtMSI+CiAgICAgICAgICAgIDxzdG9wIHN0b3AtY29sb3I9IiNDODUxMUIiIG9mZnNldD0iMCUiPjwvc3RvcD4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI0ZGOTkwMCIgb2Zmc2V0PSIxMDAlIj48L3N0b3A+CiAgICAgICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDwvZGVmcz4KICAgIDxnPgogICAgICAgIDxyZWN0IGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQtMSkiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2Ij48L3JlY3Q+CiAgICAgICAgPHBhdGggZD0iTTg5LjYyNDExMjYsMjExLjIgTDQ5Ljg5MDMyNzcsMjExLjIgTDkzLjgzNTQ4MzIsMTE5LjM0NzIgTDExMy43NDcyOCwxNjAuMzM5MiBMODkuNjI0MTEyNiwyMTEuMiBaIE05Ni43MDI5MzU3LDExMC41Njk2IEM5Ni4xNjQwODU4LDEwOS40NjU2IDk1LjA0MTQ4MTMsMTA4Ljc2NDggOTMuODE2MjM4NCwxMDguNzY0OCBMOTMuODA2NjE2MywxMDguNzY0OCBDOTIuNTcxNzUxNCwxMDguNzY4IDkxLjQ0OTE0NjYsMTA5LjQ3NTIgOTAuOTE5OTE4NywxMTAuNTg1NiBMNDEuOTEzNDIwOCwyMTMuMDIwOCBDNDEuNDM4NzE5NywyMTQuMDEyOCA0MS41MDYwNzU4LDIxNS4xNzc2IDQyLjA5NjI0NTEsMjE2LjEwODggQzQyLjY3OTk5OTQsMjE3LjAzNjggNDMuNzA2MzgwNSwyMTcuNiA0NC44MDY1MzMxLDIxNy42IEw5MS42NTQ0MjMsMjE3LjYgQzkyLjg5NTcwMjcsMjE3LjYgOTQuMDIxNTE0OSwyMTYuODg2NCA5NC41NTM5NTAxLDIxNS43Njk2IEwxMjAuMjAzODU5LDE2MS42ODk2IEMxMjAuNjE3NjE5LDE2MC44MTI4IDEyMC42MTQ0MTIsMTU5Ljc5ODQgMTIwLjE4NzgyMiwxNTguOTI4IEw5Ni43MDI5MzU3LDExMC41Njk2IFogTTIwNy45ODUxMTcsMjExLjIgTDE2OC41MDc5MjgsMjExLjIgTDEwNS4xNzM3ODksNzguNjI0IEMxMDQuNjQ0NTYxLDc3LjUxMDQgMTAzLjUxNTU0MSw3Ni44IDEwMi4yNzc0NjksNzYuOCBMNzYuNDQ3OTQzLDc2LjggTDc2LjQ3NjgwOTksNDQuOCBMMTI3LjEwMzA2Niw0NC44IEwxOTAuMTQ1MzI4LDE3Ny4zNzI4IEMxOTAuNjc0NTU2LDE3OC40ODY0IDE5MS44MDM1NzUsMTc5LjIgMTkzLjA0MTY0NywxNzkuMiBMMjA3Ljk4NTExNywxNzkuMiBMMjA3Ljk4NTExNywyMTEuMiBaIE0yMTEuMTkyNTU4LDE3Mi44IEwxOTUuMDcxOTU4LDE3Mi44IEwxMzIuMDI5Njk2LDQwLjIyNzIgQzEzMS41MDA0NjgsMzkuMTEzNiAxMzAuMzcxNDQ5LDM4LjQgMTI5LjEzMDE2OSwzOC40IEw3My4yNzI1NzYsMzguNCBDNzEuNTA1Mjc1OCwzOC40IDcwLjA2ODM0MjEsMzkuODMwNCA3MC4wNjUxMzQ0LDQxLjU5NjggTDcwLjAyOTg1MjgsNzkuOTk2OCBDNzAuMDI5ODUyOCw4MC44NDggNzAuMzYzNDI2Niw4MS42NjA4IDcwLjk2OTYzMyw4Mi4yNjI0IEM3MS41Njk0MjQ2LDgyLjg2NCA3Mi4zODQxMTQ2LDgzLjIgNzMuMjM3Mjk0MSw4My4yIEwxMDAuMjUzNTczLDgzLjIgTDE2My41OTA5MiwyMTUuNzc2IEMxNjQuMTIzMzU1LDIxNi44ODk2IDE2NS4yNDU5NiwyMTcuNiAxNjYuNDg0MDMyLDIxNy42IEwyMTEuMTkyNTU4LDIxNy42IEMyMTIuOTY2Mjc0LDIxNy42IDIxNC40LDIxNi4xNjY0IDIxNC40LDIxNC40IEwyMTQuNCwxNzYgQzIxNC40LDE3NC4yMzM2IDIxMi45NjYyNzQsMTcyLjggMjExLjE5MjU1OCwxNzIuOCBMMjExLjE5MjU1OCwxNzIuOCBaIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==", "multiValueHeaders": { - "Content-Type": [ - "image/png" - ], - "Content-Encoding": [ - "gzip" - ] + "Content-Type": ["image/png"], + "Content-Encoding": ["gzip"] }, "isBase64Encoded": true, "statusCode": 200 -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_compress_req.json b/examples/snippets/event-handler/rest/samples/advanced_compress_req.json index ed7979c17f..ae92174d01 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_compress_req.json +++ b/examples/snippets/event-handler/rest/samples/advanced_compress_req.json @@ -5,4 +5,4 @@ "resource": "/todos/1", "path": "/todos/1", "httpMethod": "GET" -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_compress_res.json b/examples/snippets/event-handler/rest/samples/advanced_compress_res.json index c05f566de1..e25f5f6316 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_compress_res.json +++ b/examples/snippets/event-handler/rest/samples/advanced_compress_res.json @@ -1,13 +1,9 @@ { "statusCode": 200, "multiValueHeaders": { - "Content-Type": [ - "application/json" - ], - "Content-Encoding": [ - "gzip" - ] + "Content-Type": ["application/json"], + "Content-Encoding": ["gzip"] }, "body": "H4sIAAAAAAACE42STU4DMQyFrxJl3QXln96AMyAW7sSDLCVxiJ0Kqerd8TCCUOgii1EmP/783pOPXjmw+N3L0TfB+hz8brvxtC5KGtHvfMCIkzZx0HT5MPmNnziViIr2dIYoeNr8Q1x3xHsjcVadIbkZJoq2RXU8zzQROLseQ9505NzeCNQdMJNBE+UmY4zbzjAJhWtlZ57sB84BWtul+rteH2HPlVgWARwjqXkxpklK5gmEHAQqJBMtFsGVygcKmNVRjG0wxvuzGF2L0dpVUOKMC3bfJNjJgWMrCuZk7cUp02AiD72D6WKHHwUDKbiJs6AZ0VZXKOUx4uNvzdxT+E4mLcMA+6G8nzrLQkaxkNEVrFKW2VGbJCoCY7q2V3+tiv5kGThyxfTecDWbgGz/NfYXhL6ePgF9PnFdPgMAAA==", "isBase64Encoded": true -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_cors_per_route.json b/examples/snippets/event-handler/rest/samples/advanced_cors_per_route.json index 12570fedfe..59a0cf594f 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_cors_per_route.json +++ b/examples/snippets/event-handler/rest/samples/advanced_cors_per_route.json @@ -6,4 +6,4 @@ }, "body": "{\"status\":\"ok\"}", "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_cors_simple.json b/examples/snippets/event-handler/rest/samples/advanced_cors_simple.json index 6980c59567..47b5f9e0a8 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_cors_simple.json +++ b/examples/snippets/event-handler/rest/samples/advanced_cors_simple.json @@ -24,4 +24,4 @@ }, "body": "{\"todoId\":\"123\",\"task\":\"Example task\",\"completed\":false}", "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_error_debug.json b/examples/snippets/event-handler/rest/samples/advanced_error_debug.json index a7b6ccaf54..f588ec46d4 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_error_debug.json +++ b/examples/snippets/event-handler/rest/samples/advanced_error_debug.json @@ -6,4 +6,4 @@ "details": { "errorName": "Name of the error class" } -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_error_prod.json b/examples/snippets/event-handler/rest/samples/advanced_error_prod.json index 7a3a12425d..cfe5a784fa 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_error_prod.json +++ b/examples/snippets/event-handler/rest/samples/advanced_error_prod.json @@ -2,4 +2,4 @@ "statusCode": 500, "error": "Internal Server Error", "message": "Internal Server Error" -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_fine_grained_responses.json b/examples/snippets/event-handler/rest/samples/advanced_fine_grained_responses.json index 3b4f079a6a..35769b0995 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_fine_grained_responses.json +++ b/examples/snippets/event-handler/rest/samples/advanced_fine_grained_responses.json @@ -6,4 +6,4 @@ "Location": "/todos/123" }, "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_mw_early_return.json b/examples/snippets/event-handler/rest/samples/advanced_mw_early_return.json index 75f76e9c68..7424d79a2d 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_mw_early_return.json +++ b/examples/snippets/event-handler/rest/samples/advanced_mw_early_return.json @@ -5,4 +5,4 @@ "Content-Type": "application/json" }, "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/advanced_mw_middleware_order.json b/examples/snippets/event-handler/rest/samples/advanced_mw_middleware_order.json index 0135c632cd..6651449b14 100644 --- a/examples/snippets/event-handler/rest/samples/advanced_mw_middleware_order.json +++ b/examples/snippets/event-handler/rest/samples/advanced_mw_middleware_order.json @@ -7,4 +7,4 @@ "x-post-processed-by": "global-middleware" }, "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/gettingStarted_dynamic_routes.json b/examples/snippets/event-handler/rest/samples/gettingStarted_dynamic_routes.json index dd5727cc1e..f9da1c31a4 100644 --- a/examples/snippets/event-handler/rest/samples/gettingStarted_dynamic_routes.json +++ b/examples/snippets/event-handler/rest/samples/gettingStarted_dynamic_routes.json @@ -2,4 +2,4 @@ "resource": "/todos/{id}", "path": "/todos/1", "httpMethod": "GET" -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/gettingStarted_methods.json b/examples/snippets/event-handler/rest/samples/gettingStarted_methods.json index 7be85e3400..9da1671479 100644 --- a/examples/snippets/event-handler/rest/samples/gettingStarted_methods.json +++ b/examples/snippets/event-handler/rest/samples/gettingStarted_methods.json @@ -3,4 +3,4 @@ "path": "/todos", "httpMethod": "POST", "body": "{\"title\": \"foo\", \"userId\": 1, \"completed\": false}" -} \ No newline at end of file +} diff --git a/examples/snippets/event-handler/rest/samples/gettingStarted_serialization.json b/examples/snippets/event-handler/rest/samples/gettingStarted_serialization.json index 12f3f9bdf1..5f70d19b32 100644 --- a/examples/snippets/event-handler/rest/samples/gettingStarted_serialization.json +++ b/examples/snippets/event-handler/rest/samples/gettingStarted_serialization.json @@ -5,4 +5,4 @@ }, "body": "{'message':'pong'}", "isBase64Encoded": false -} \ No newline at end of file +} diff --git a/examples/snippets/getting-started/patterns-functional.ts b/examples/snippets/getting-started/patterns-functional.ts index ad3a752ae5..80ec55db5d 100644 --- a/examples/snippets/getting-started/patterns-functional.ts +++ b/examples/snippets/getting-started/patterns-functional.ts @@ -18,7 +18,7 @@ export const handler = async (event: unknown, context: Context) => { throw new Error('An error occurred'); } catch (error) { logger.error('Error occurred', { error }); - tracer.addErrorAsMetadata(error); + tracer.addErrorAsMetadata(error as Error); throw error; } finally { subsegment?.close(); diff --git a/examples/snippets/logger/basicUsage.ts b/examples/snippets/logger/basicUsage.ts index 0d96caaf00..ad36be0c87 100644 --- a/examples/snippets/logger/basicUsage.ts +++ b/examples/snippets/logger/basicUsage.ts @@ -2,6 +2,6 @@ import { Logger } from '@aws-lambda-powertools/logger'; const logger = new Logger({ serviceName: 'serverlessAirline' }); -export const handler = async (_event, _context): Promise => { +export const handler = async () => { logger.info('Hello World'); }; diff --git a/examples/snippets/logger/unserializableValues.ts b/examples/snippets/logger/unserializableValues.ts index 0a5ff0e2c7..30851b947c 100644 --- a/examples/snippets/logger/unserializableValues.ts +++ b/examples/snippets/logger/unserializableValues.ts @@ -1,12 +1,12 @@ import { Logger } from '@aws-lambda-powertools/logger'; -import type { CustomReplacerFn } from '@aws-lambda-powertools/logger/types'; +import type { CustomJsonReplacerFn } from '@aws-lambda-powertools/logger/types'; -const jsonReplacerFn: CustomReplacerFn = (_: string, value: unknown) => +const jsonReplacerFn: CustomJsonReplacerFn = (_: string, value: unknown) => value instanceof Set ? [...value] : value; const logger = new Logger({ serviceName: 'serverlessAirline', jsonReplacerFn }); -export const handler = async (): Promise => { +export const handler = async () => { logger.info('Serialize with custom serializer', { serializedValue: new Set([1, 2, 3]), }); diff --git a/examples/snippets/package.json b/examples/snippets/package.json index 333273bd6c..0af61ca5b1 100644 --- a/examples/snippets/package.json +++ b/examples/snippets/package.json @@ -12,7 +12,8 @@ "test:e2e": "echo 'Not Applicable'", "build": "echo 'Not Applicable'", "lint": "biome lint .", - "lint:fix": "biome check --write ." + "lint:fix": "biome check --write .", + "lint:ci": "biome ci ." }, "license": "MIT-0", "repository": { diff --git a/examples/snippets/parameters/customProviderVault.ts b/examples/snippets/parameters/customProviderVault.ts index 0bbae8252b..5c2e02a6d9 100644 --- a/examples/snippets/parameters/customProviderVault.ts +++ b/examples/snippets/parameters/customProviderVault.ts @@ -31,13 +31,11 @@ class HashiCorpVaultProvider extends BaseProvider { /** * Retrieve a secret from HashiCorp Vault. * - * You can customize the retrieval of the secret by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `sdkOptions` - Extra options to pass to the HashiCorp Vault SDK, e.g. `mount` or `version` - * * @param name - The name of the secret * @param options - Options to customize the retrieval of the secret + * @param options.maxAge - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * @param options.forceFetch - Whether to always fetch a new value from the store regardless if already available in cache + * @param options.sdkOptions - Extra options to pass to the HashiCorp Vault SDK, e.g. `mount` or `version` */ public async get>( name: string, diff --git a/examples/snippets/tracer/basicUsage.ts b/examples/snippets/tracer/basicUsage.ts index 8f00dc4c7c..5495101763 100644 --- a/examples/snippets/tracer/basicUsage.ts +++ b/examples/snippets/tracer/basicUsage.ts @@ -2,6 +2,10 @@ import { Tracer } from '@aws-lambda-powertools/tracer'; const tracer = new Tracer({ serviceName: 'serverlessAirline' }); -export const handler = async (_event, _context): Promise => { - tracer.getSegment(); +export const handler = async () => { + const segment = tracer.getSegment(); + const subsegment = segment?.addNewSubsegment('subsegment'); + subsegment?.addAnnotation('annotationKey', 'annotationValue'); + subsegment?.addMetadata('metadataKey', { foo: 'bar' }); + subsegment?.close(); }; diff --git a/examples/snippets/tracer/captureMethodManual.ts b/examples/snippets/tracer/captureMethodManual.ts index d440745cef..76f4a8a7bf 100644 --- a/examples/snippets/tracer/captureMethodManual.ts +++ b/examples/snippets/tracer/captureMethodManual.ts @@ -6,31 +6,26 @@ const tracer = new Tracer({ serviceName: 'serverlessAirline' }); const getChargeId = async (): Promise => { const parentSubsegment = tracer.getSegment(); // This is the subsegment currently active let subsegment: Subsegment | undefined; - if (parentSubsegment) { - // Create subsegment for the function & set it as active - subsegment = parentSubsegment.addNewSubsegment('### chargeId'); - tracer.setSegment(subsegment); - } + subsegment = parentSubsegment?.addNewSubsegment('### chargeId'); + subsegment && tracer.setSegment(subsegment); - let res: unknown; try { - /* ... */ + const res = { chargeId: '1234' }; + // Add the response as metadata tracer.addResponseAsMetadata(res, 'chargeId'); + + return res; } catch (err) { // Add the error as metadata tracer.addErrorAsMetadata(err as Error); throw err; + } finally { + // Close subsegment + subsegment?.close(); + // Set the facade segment as active again, it'll be closed automatically + parentSubsegment && tracer.setSegment(parentSubsegment); } - - if (parentSubsegment && subsegment) { - // Close subsegment (the AWS Lambda one is closed automatically) - subsegment.close(); - // Set the facade segment as active again - tracer.setSegment(parentSubsegment); - } - - return res; }; export const handler = async ( diff --git a/layers/package.json b/layers/package.json index 225068157d..d8bdebfae9 100644 --- a/layers/package.json +++ b/layers/package.json @@ -20,6 +20,7 @@ "package": "echo 'Not applicable'", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "createLayerFolder": "cdk synth --context BuildFromLocal=true" }, "repository": { diff --git a/package.json b/package.json index 732b0b4f5b..788d8d8c03 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "vitest": "^3.0.9" }, "lint-staged": { - "*.{js,ts}": "biome check --write", + "*.{js,ts,json}": "biome check --write --no-errors-on-unmatched", "*.md": "markdownlint-cli2 --fix" }, "engines": { diff --git a/packages/batch/package.json b/packages/batch/package.json index e1da30bfee..ae2ba0818c 100644 --- a/packages/batch/package.json +++ b/packages/batch/package.json @@ -22,6 +22,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/batch#readme", @@ -89,4 +90,4 @@ "@aws-lambda-powertools/testing-utils": "file:../testing", "zod": "^4.1.11" } -} \ No newline at end of file +} diff --git a/packages/batch/src/BasePartialProcessor.ts b/packages/batch/src/BasePartialProcessor.ts index 23b7cdc9cc..da09f22557 100644 --- a/packages/batch/src/BasePartialProcessor.ts +++ b/packages/batch/src/BasePartialProcessor.ts @@ -239,9 +239,7 @@ abstract class BasePartialProcessor { /** * Processes records in parallel using `Promise.all`. */ - async #processRecordsInParallel(): Promise< - (SuccessResponse | FailureResponse)[] - > { + #processRecordsInParallel(): Promise<(SuccessResponse | FailureResponse)[]> { return Promise.all( this.records.map((record) => this.processRecord(record)) ); diff --git a/packages/batch/src/BatchProcessorSync.ts b/packages/batch/src/BatchProcessorSync.ts index a3613a3d51..45f47a6e21 100644 --- a/packages/batch/src/BatchProcessorSync.ts +++ b/packages/batch/src/BatchProcessorSync.ts @@ -88,7 +88,7 @@ import type { BaseRecord, FailureResponse, SuccessResponse } from './types.js'; * * @param _record The record to be processed */ - public async processRecord( + public processRecord( _record: BaseRecord ): Promise { throw new BatchProcessingError('Not implemented. Use process() instead.'); diff --git a/packages/batch/tests/helpers/handlers.ts b/packages/batch/tests/helpers/handlers.ts index 24dff02799..5f0b63eb88 100644 --- a/packages/batch/tests/helpers/handlers.ts +++ b/packages/batch/tests/helpers/handlers.ts @@ -1,4 +1,6 @@ +import { setTimeout } from 'node:timers/promises'; import type { + AttributeValue, Context, DynamoDBRecord, KinesisStreamRecord, @@ -8,19 +10,25 @@ import type { const sqsRecordHandler = (record: SQSRecord): string => { const body = record.body; if (body.includes('fail')) { - throw Error('Failed to process record.'); + throw new Error('Failed to process record.'); } return body; }; -const asyncSqsRecordHandler = async (record: SQSRecord): Promise => - Promise.resolve(sqsRecordHandler(record)); +const asyncSqsRecordHandler = async (record: SQSRecord): Promise => { + const body = record.body; + if (body.includes('fail')) { + throw new Error('Failed to process record.'); + } + await setTimeout(1); // simulate some processing time + return body; +}; const kinesisRecordHandler = (record: KinesisStreamRecord): string => { const body = record.kinesis.data; if (body.includes('fail')) { - throw Error('Failed to process record.'); + throw new Error('Failed to process record.'); } return body; @@ -28,12 +36,19 @@ const kinesisRecordHandler = (record: KinesisStreamRecord): string => { const asyncKinesisRecordHandler = async ( record: KinesisStreamRecord -): Promise => Promise.resolve(kinesisRecordHandler(record)); +): Promise => { + const body = record.kinesis.data; + if (body.includes('fail')) { + throw new Error('Failed to process record.'); + } + await setTimeout(1); // simulate some processing time + return body; +}; -const dynamodbRecordHandler = (record: DynamoDBRecord): object => { +const dynamodbRecordHandler = (record: DynamoDBRecord): AttributeValue => { const body = record.dynamodb?.NewImage?.Message || { S: 'fail' }; if (body.S?.includes('fail')) { - throw Error('Failed to process record.'); + throw new Error('Failed to process record.'); } return body; @@ -41,17 +56,24 @@ const dynamodbRecordHandler = (record: DynamoDBRecord): object => { const asyncDynamodbRecordHandler = async ( record: DynamoDBRecord -): Promise => { - return Promise.resolve(dynamodbRecordHandler(record)); +): Promise => { + const body = record.dynamodb?.NewImage?.Message || { S: 'fail' }; + if (body.S?.includes('fail')) { + throw new Error('Failed to process record.'); + } + await setTimeout(1); // simulate some processing time + return body; }; const handlerWithContext = (record: SQSRecord, context: Context): string => { try { if (context.getRemainingTimeInMillis() === 0) { - throw Error('No time remaining.'); + throw new Error('No time remaining.'); } } catch { - throw Error(`Context possibly malformed. Displaying context:\n${context}`); + throw new Error( + `Context possibly malformed. Displaying context:\n${context}` + ); } return record.body; @@ -61,7 +83,17 @@ const asyncHandlerWithContext = async ( record: SQSRecord, context: Context ): Promise => { - return Promise.resolve(handlerWithContext(record, context)); + try { + if (context.getRemainingTimeInMillis() === 0) { + throw new Error('No time remaining.'); + } + } catch { + throw new Error( + `Context possibly malformed. Displaying context:\n${context}` + ); + } + await setTimeout(1); // simulate some processing time + return record.body; }; export { diff --git a/packages/batch/tests/unit/BasePartialProcessor.test.ts b/packages/batch/tests/unit/BasePartialProcessor.test.ts index 6c5421c01b..5e61ebe294 100644 --- a/packages/batch/tests/unit/BasePartialProcessor.test.ts +++ b/packages/batch/tests/unit/BasePartialProcessor.test.ts @@ -25,7 +25,7 @@ describe('Class: BasePartialBatchProcessor', () => { super(EventType.SQS); } - public async processRecord( + public processRecord( _record: BaseRecord ): Promise { throw new Error('Not implemented'); diff --git a/packages/batch/tests/unit/SqsFifoPartialProcessor.test.ts b/packages/batch/tests/unit/SqsFifoPartialProcessor.test.ts index 6071464039..a4b55da062 100644 --- a/packages/batch/tests/unit/SqsFifoPartialProcessor.test.ts +++ b/packages/batch/tests/unit/SqsFifoPartialProcessor.test.ts @@ -186,7 +186,7 @@ describe('SQS FIFO Processors', () => { }); } - it('continues processing and moves to the next group when `skipGroupOnError` is true', async () => { + it('continues processing and moves to the next group when `skipGroupOnError` is true', () => { // Prepare const firstRecord = sqsRecordFactory('fail', '1'); const secondRecord = sqsRecordFactory('success', '2'); diff --git a/packages/batch/tests/unit/processPartialResponse.test.ts b/packages/batch/tests/unit/processPartialResponse.test.ts index d9c6d56a20..60b6a31cb4 100644 --- a/packages/batch/tests/unit/processPartialResponse.test.ts +++ b/packages/batch/tests/unit/processPartialResponse.test.ts @@ -36,10 +36,10 @@ describe('Function: processPartialResponse()', () => { context, }; - const handlerWithSqsEvent = async ( + const handlerWithSqsEvent = ( event: SQSEvent, options: BatchProcessingOptions - ) => { + ): Promise => { const processor = new BatchProcessor(EventType.SQS); const handler = async ( @@ -51,10 +51,10 @@ describe('Function: processPartialResponse()', () => { return handler(event, context); }; - const handlerWithKinesisEvent = async ( + const handlerWithKinesisEvent = ( event: KinesisStreamEvent, options: BatchProcessingOptions - ) => { + ): Promise => { const processor = new BatchProcessor(EventType.KinesisDataStreams); const handler = async ( @@ -71,10 +71,10 @@ describe('Function: processPartialResponse()', () => { return handler(event, context); }; - const handlerWithDynamoDBEvent = async ( + const handlerWithDynamoDBEvent = ( event: DynamoDBStreamEvent, options: BatchProcessingOptions - ) => { + ): Promise => { const processor = new BatchProcessor(EventType.DynamoDBStreams); const handler = async ( diff --git a/packages/commons/package.json b/packages/commons/package.json index 3f93b3f614..5d4bb706b8 100644 --- a/packages/commons/package.json +++ b/packages/commons/package.json @@ -21,6 +21,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/commons#readme", diff --git a/packages/commons/src/Utility.ts b/packages/commons/src/Utility.ts index 89f1dbd03d..4f21aab7ae 100644 --- a/packages/commons/src/Utility.ts +++ b/packages/commons/src/Utility.ts @@ -10,9 +10,9 @@ * To learn more about the Lambda execution environment lifecycle, see the [Execution environment section](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) of the AWS Lambda documentation. * * As a Powertools for AWS Lambda (TypeScript) user you probably won't be using this class directly, in fact if you use other Powertools for AWS utilities the cold start heuristic found here is already used to: - * * Add a `coldStart` key to the structured logs when injecting context information in `Logger` - * * Emit a metric during a cold start function invocation in `Metrics` - * * Annotate the invocation segment with a `coldStart` key in `Tracer` + * - Add a `coldStart` key to the structured logs when injecting context information in `Logger` + * - Emit a metric during a cold start function invocation in `Metrics` + * - Annotate the invocation segment with a `coldStart` key in `Tracer` * * If you want to use this logic in your own utilities, `Utility` provides two methods: * diff --git a/packages/commons/src/envUtils.ts b/packages/commons/src/envUtils.ts index b9848dc993..cfc4b2adfa 100644 --- a/packages/commons/src/envUtils.ts +++ b/packages/commons/src/envUtils.ts @@ -107,7 +107,7 @@ const getNumberFromEnv = ({ const parsedValue = Number(value); if (Number.isNaN(parsedValue)) { - throw new Error(`Environment variable ${key} must be a number`); + throw new TypeError(`Environment variable ${key} must be a number`); } return parsedValue; diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index f4eb6ce10b..aed1788618 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -1,10 +1,10 @@ import { PT_VERSION } from './version.js'; const env = process.env.AWS_EXECUTION_ENV || 'NA'; -if (!process.env.AWS_SDK_UA_APP_ID) { - process.env.AWS_SDK_UA_APP_ID = `PT/NO-OP/${PT_VERSION}/PTEnv/${env}`; -} else { +if (process.env.AWS_SDK_UA_APP_ID) { process.env.AWS_SDK_UA_APP_ID = `${process.env.AWS_SDK_UA_APP_ID}/PT/NO-OP/${PT_VERSION}/PTEnv/${env}`; +} else { + process.env.AWS_SDK_UA_APP_ID = `PT/NO-OP/${PT_VERSION}/PTEnv/${env}`; } export { addUserAgentMiddleware, isSdkClient } from './awsSdkUtils.js'; diff --git a/packages/commons/tests/types/LambdaInterface.test.ts b/packages/commons/tests/types/LambdaInterface.test.ts index 5b50d083fe..75be898c11 100644 --- a/packages/commons/tests/types/LambdaInterface.test.ts +++ b/packages/commons/tests/types/LambdaInterface.test.ts @@ -21,11 +21,12 @@ describe('Type: LambdaInterface', () => { expectTypeOf(lambda.handler).toBeFunction(); }); - it('works with an async handler', async () => { + it('works with an async handler', () => { // Prepare class Lambda implements LambdaInterface { public async handler(_event: unknown, context: Context) { context.getRemainingTimeInMillis(); + await new Promise((r) => setTimeout(r, 1)); return 'Hello World'; } } diff --git a/packages/commons/tests/unit/awsSdkUtils.test.ts b/packages/commons/tests/unit/awsSdkUtils.test.ts index 2029c2ecc7..5223605292 100644 --- a/packages/commons/tests/unit/awsSdkUtils.test.ts +++ b/packages/commons/tests/unit/awsSdkUtils.test.ts @@ -28,7 +28,7 @@ describe('Helpers: awsSdk', () => { expect(console.warn).toHaveBeenCalledTimes(1); }); - it('should return and do nothing if the client already has a Powertools UA middleware', async () => { + it('should return and do nothing if the client already has a Powertools UA middleware', () => { // Prepare const client = { middlewareStack: { @@ -51,7 +51,7 @@ describe('Helpers: awsSdk', () => { expect(client.middlewareStack.addRelativeTo).toHaveBeenCalledTimes(0); }); - it('should call the function to add the middleware with the correct arguments', async () => { + it('should call the function to add the middleware with the correct arguments', () => { // Prepare const client = { middlewareStack: { diff --git a/packages/event-handler/package.json b/packages/event-handler/package.json index 2bbfcf4357..8b6e8bcc6d 100644 --- a/packages/event-handler/package.json +++ b/packages/event-handler/package.json @@ -22,6 +22,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript#readme", diff --git a/packages/event-handler/src/appsync-events/Router.ts b/packages/event-handler/src/appsync-events/Router.ts index 38e189d65d..1298c82937 100644 --- a/packages/event-handler/src/appsync-events/Router.ts +++ b/packages/event-handler/src/appsync-events/Router.ts @@ -1,6 +1,9 @@ import type { GenericLogger } from '@aws-lambda-powertools/commons/types'; import { isRecord } from '@aws-lambda-powertools/commons/typeutils'; -import { getStringFromEnv, isDevMode } from '@aws-lambda-powertools/commons/utils/env'; +import { + getStringFromEnv, + isDevMode, +} from '@aws-lambda-powertools/commons/utils/env'; import type { OnPublishHandler, OnSubscribeHandler, @@ -31,7 +34,7 @@ class Router { * Whether the router is running in development mode. */ protected readonly isDev: boolean = false; - + public constructor(options?: RouterOptions) { const alcLogLevel = getStringFromEnv({ key: 'AWS_LAMBDA_LOG_LEVEL', diff --git a/packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts b/packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts index a338ef9c5b..6aede4c554 100644 --- a/packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts +++ b/packages/event-handler/src/appsync-graphql/AppSyncGraphQLResolver.ts @@ -152,11 +152,7 @@ class AppSyncGraphQLResolver extends Router { * @param context - The AWS Lambda context object. * @param options - Optional parameters for the resolver, such as the scope of the handler. */ - public async resolve( - event: unknown, - context: Context, - options?: ResolveOptions - ): Promise { + public resolve(event: unknown, context: Context, options?: ResolveOptions) { if (Array.isArray(event)) { if (event.some((e) => !isAppSyncGraphQLEvent(e))) { this.logger.warn( @@ -194,10 +190,10 @@ class AppSyncGraphQLResolver extends Router { * @param options - Optional resolve options for customizing resolver behavior. */ async #withErrorHandling( - fn: () => Promise, + fn: () => unknown, event: AppSyncResolverEvent>, options?: ResolveOptions - ): Promise { + ) { try { return await fn(); } catch (error) { @@ -377,11 +373,11 @@ class AppSyncGraphQLResolver extends Router { * @param options - Optional parameters for the resolver, such as the scope of the handler. * @throws {ResolverNotFoundException} If no resolver is registered for the given field and type. */ - async #executeSingleResolver( + #executeSingleResolver( event: AppSyncResolverEvent>, context: Context, options?: ResolveOptions - ): Promise { + ): unknown { const { fieldName, parentTypeName: typeName } = event.info; const resolverHandlerOptions = this.resolverRegistry.resolve( diff --git a/packages/event-handler/src/appsync-graphql/index.ts b/packages/event-handler/src/appsync-graphql/index.ts index 9fe91122f4..bffc35bad1 100644 --- a/packages/event-handler/src/appsync-graphql/index.ts +++ b/packages/event-handler/src/appsync-graphql/index.ts @@ -1,7 +1,7 @@ export { AppSyncGraphQLResolver } from './AppSyncGraphQLResolver.js'; export { - ResolverNotFoundException, InvalidBatchResponseException, + ResolverNotFoundException, } from './errors.js'; export { awsDate, diff --git a/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts b/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts index 5211ddade3..37e186dc69 100644 --- a/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts +++ b/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts @@ -146,7 +146,7 @@ class BedrockAgentFunctionResolver { public tool>( fn: ToolFunction, config: Configuration - ): undefined { + ) { const { name } = config; if (this.#tools.has(name)) { this.#logger.warn( diff --git a/packages/event-handler/src/rest/converters.ts b/packages/event-handler/src/rest/converters.ts index d47302ebf6..56c4271d0b 100644 --- a/packages/event-handler/src/rest/converters.ts +++ b/packages/event-handler/src/rest/converters.ts @@ -29,18 +29,16 @@ const createBody = (body: string | null, isBase64Encoded: boolean) => { * @param event - The API Gateway proxy event * @returns A Web API Request object */ -export const proxyEventToWebRequest = ( - event: APIGatewayProxyEvent -): Request => { +const proxyEventToWebRequest = (event: APIGatewayProxyEvent): Request => { const { httpMethod, path } = event; const { domainName } = event.requestContext; const headers = new Headers(); - for (const [name, value] of Object.entries(event.headers ?? {})) { - if (value != null) headers.set(name, value); + for (const [name, value] of Object.entries(event.headers)) { + if (value !== undefined) headers.set(name, value); } - for (const [name, values] of Object.entries(event.multiValueHeaders ?? {})) { + for (const [name, values] of Object.entries(event.multiValueHeaders)) { for (const value of values ?? []) { const headerValue = headers.get(name); if (!headerValue?.includes(value)) { @@ -79,7 +77,7 @@ export const proxyEventToWebRequest = ( * @param response - The Web API Response object * @returns An API Gateway proxy result */ -export const webResponseToProxyResult = async ( +const webResponseToProxyResult = async ( response: Response ): Promise => { const headers: Record = {}; @@ -136,10 +134,9 @@ export const webResponseToProxyResult = async ( * Handles APIGatewayProxyResult, Response objects, and plain objects. * * @param response - The handler response (APIGatewayProxyResult, Response, or plain object) - * @param headers - Optional headers to be included in the response - * @returns A Web API Response object + * @param resHeaders - Optional headers to be included in the response */ -export const handlerResultToWebResponse = ( +const handlerResultToWebResponse = ( response: HandlerResponse, resHeaders?: Headers ): Response => { @@ -188,7 +185,7 @@ export const handlerResultToWebResponse = ( * @param statusCode - The response status code to return * @returns An API Gateway proxy result */ -export const handlerResultToProxyResult = async ( +const handlerResultToProxyResult = async ( response: HandlerResponse, statusCode: HttpStatusCode = HttpStatusCodes.OK ): Promise => { @@ -205,3 +202,10 @@ export const handlerResultToProxyResult = async ( isBase64Encoded: false, }; }; + +export { + proxyEventToWebRequest, + webResponseToProxyResult, + handlerResultToWebResponse, + handlerResultToProxyResult, +}; diff --git a/packages/event-handler/src/rest/middleware/cors.ts b/packages/event-handler/src/rest/middleware/cors.ts index 25c780e39b..3a7d9b5f55 100644 --- a/packages/event-handler/src/rest/middleware/cors.ts +++ b/packages/event-handler/src/rest/middleware/cors.ts @@ -35,6 +35,7 @@ import { * })); * ``` * + * @param options - Configuration options for CORS * @param options.origin - The origin to allow requests from * @param options.allowMethods - The HTTP methods to allow * @param options.allowHeaders - The headers to allow diff --git a/packages/event-handler/src/rest/utils.ts b/packages/event-handler/src/rest/utils.ts index eb587a0b7d..ca41f0a5a7 100644 --- a/packages/event-handler/src/rest/utils.ts +++ b/packages/event-handler/src/rest/utils.ts @@ -150,7 +150,7 @@ export const isAPIGatewayProxyResult = ( * ``` */ export const composeMiddleware = (middleware: Middleware[]): Middleware => { - return async ({ reqCtx, next }): Promise => { + return async ({ reqCtx, next }) => { let index = -1; let result: HandlerResponse | undefined; diff --git a/packages/event-handler/src/types/bedrock-agent.ts b/packages/event-handler/src/types/bedrock-agent.ts index c649e1200d..8d99735cef 100644 --- a/packages/event-handler/src/types/bedrock-agent.ts +++ b/packages/event-handler/src/types/bedrock-agent.ts @@ -51,7 +51,10 @@ type ToolFunction> = ( event: BedrockAgentFunctionEvent; context: Context; } -) => Promise; +) => + | Promise + | JSONValue + | BedrockFunctionResponse; /** * Tool in the Bedrock Agent Function Resolver. @@ -159,7 +162,7 @@ type ResolverOptions = { * * When no logger is provided, we'll only log warnings and errors using the global `console` object. */ - logger?: GenericLogger; + logger?: Pick; }; export type { diff --git a/packages/event-handler/src/types/rest.ts b/packages/event-handler/src/types/rest.ts index 1c9925ce22..b7a0d2e21e 100644 --- a/packages/event-handler/src/types/rest.ts +++ b/packages/event-handler/src/types/rest.ts @@ -24,12 +24,13 @@ type ErrorHandler = ( ) => Promise; interface ErrorConstructor { + // biome-ignore lint/suspicious/noExplicitAny: this is a generic type that is intentionally open new (...args: any[]): T; prototype: T; } /** - * Options for the {@link Router} class + * Options for the {@link Router | `Router``} class */ type RestRouterOptions = { /** @@ -57,7 +58,7 @@ type HandlerResponse = Response | JSONObject; type RouteHandler = ( reqCtx: RequestContext -) => Promise; +) => Promise | TReturn; type HttpMethod = keyof typeof HttpVerbs; @@ -78,12 +79,14 @@ type RestRouteOptions = { middleware?: Middleware[]; }; +// biome-ignore lint/suspicious/noConfusingVoidType: To ensure next function is awaited type NextFunction = () => Promise; type Middleware = (args: { reqCtx: RequestContext; next: NextFunction; -}) => Promise; + // biome-ignore lint/suspicious/noConfusingVoidType: To ensure next function is awaited +}) => Promise; type RouteRegistryOptions = { /** @@ -176,4 +179,5 @@ export type { RouteRegistryOptions, ValidationResult, CompressionOptions, + NextFunction, }; diff --git a/packages/event-handler/tests/unit/appsync-events/AppSyncEventsResolver.test.ts b/packages/event-handler/tests/unit/appsync-events/AppSyncEventsResolver.test.ts index 16da12ce63..512ff7a095 100644 --- a/packages/event-handler/tests/unit/appsync-events/AppSyncEventsResolver.test.ts +++ b/packages/event-handler/tests/unit/appsync-events/AppSyncEventsResolver.test.ts @@ -87,7 +87,7 @@ describe('Class: AppSyncEventsResolver', () => { public scope = 'scoped'; @app.onPublish('/foo', { aggregate }) - public async handleFoo(payloads: OnPublishAggregatePayload) { + public handleFoo(payloads: OnPublishAggregatePayload) { return payloads.map((payload) => { return { id: payload.id, @@ -97,15 +97,15 @@ describe('Class: AppSyncEventsResolver', () => { } @app.onPublish('/bar') - public async handleBar(payload: string) { + public handleBar(payload: string) { return `${this.scope} ${payload}`; } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return this.stuff(event, context); } - async stuff(event: unknown, context: Context) { + stuff(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -146,15 +146,15 @@ describe('Class: AppSyncEventsResolver', () => { public scope = 'scoped'; @app.onSubscribe('/foo') - public async handleFoo(payload: AppSyncEventsSubscribeEvent) { + public handleFoo(payload: AppSyncEventsSubscribeEvent) { console.debug(`${this.scope} ${payload.info.channel.path}`); } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return this.stuff(event, context); } - async stuff(event: unknown, context: Context) { + stuff(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -222,7 +222,7 @@ describe('Class: AppSyncEventsResolver', () => { async ({ error, message }) => { // Prepare const app = new AppSyncEventsResolver({ logger: console }); - app.onSubscribe('/foo', async () => { + app.onSubscribe('/foo', () => { throw error; }); @@ -242,7 +242,7 @@ describe('Class: AppSyncEventsResolver', () => { it('throws an UnauthorizedException when thrown by the handler', async () => { // Prepare const app = new AppSyncEventsResolver({ logger: console }); - app.onSubscribe('/foo', async () => { + app.onSubscribe('/foo', () => { throw new UnauthorizedException('nah'); }); @@ -258,7 +258,7 @@ describe('Class: AppSyncEventsResolver', () => { it('returns the response of the onPublish handler', async () => { // Prepare const app = new AppSyncEventsResolver({ logger: console }); - app.onPublish('/foo', async (payload) => { + app.onPublish('/foo', (payload) => { if (payload === 'foo') { return true; } @@ -303,7 +303,7 @@ describe('Class: AppSyncEventsResolver', () => { const app = new AppSyncEventsResolver({ logger: console }); app.onPublish( '/foo', - async (payloads) => { + (payloads) => { return payloads.map((payload) => ({ id: payload.id, payload: true, @@ -353,7 +353,7 @@ describe('Class: AppSyncEventsResolver', () => { const app = new AppSyncEventsResolver({ logger: console }); app.onPublish( '/foo', - async () => { + () => { throw new Error('Error in handler'); }, { aggregate: true } @@ -379,7 +379,7 @@ describe('Class: AppSyncEventsResolver', () => { const app = new AppSyncEventsResolver({ logger: console }); app.onPublish( '/foo', - async () => { + () => { throw new UnauthorizedException('nah'); }, { aggregate: true } @@ -400,7 +400,7 @@ describe('Class: AppSyncEventsResolver', () => { const app = new AppSyncEventsResolver(); app.onPublish( '/foo', - async () => { + () => { throw new Error('Error in handler'); }, { aggregate: true } diff --git a/packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts b/packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts index 06b653fcd9..1100a41005 100644 --- a/packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts +++ b/packages/event-handler/tests/unit/appsync-graphql/AppSyncGraphQLResolver.test.ts @@ -93,7 +93,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); app.resolver<{ id: string }>( - async ({ id }) => { + ({ id }) => { return { id, title: 'Post Title', @@ -123,7 +123,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); app.resolver<{ title: string; content: string }>( - async ({ title, content }) => { + ({ title, content }) => { return { id: '123', title, @@ -166,7 +166,7 @@ describe('Class: AppSyncGraphQLResolver', () => { const app = new AppSyncGraphQLResolver(); app.onMutation<{ title: string; content: string }>( 'addPost', - async ({ title, content }) => { + ({ title, content }) => { return { id: '123', title, @@ -196,16 +196,13 @@ describe('Class: AppSyncGraphQLResolver', () => { it('resolver function has access to event and context', async () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); - app.onQuery<{ id: string }>( - 'getPost', - async ({ id }, { event, context }) => { - return { - id, - event, - context, - }; - } - ); + app.onQuery<{ id: string }>('getPost', ({ id }, { event, context }) => { + return { + id, + event, + context, + }; + }); // Act const event = onGraphqlEventFactory('getPost', 'Query', { id: '123' }); @@ -227,7 +224,7 @@ describe('Class: AppSyncGraphQLResolver', () => { public scope = 'scoped'; @app.onQuery('getPost') - public async handleGetPost({ id }: { id: string }) { + public handleGetPost({ id }: { id: string }) { return { id, scope: `${this.scope} id=${id}`, @@ -235,7 +232,7 @@ describe('Class: AppSyncGraphQLResolver', () => { } @app.onMutation('addPost') - public async handleAddPost({ + public handleAddPost({ title, content, }: { @@ -250,11 +247,11 @@ describe('Class: AppSyncGraphQLResolver', () => { }; } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return this.stuff(event, context); } - async stuff(event: unknown, context: Context) { + stuff(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -295,9 +292,7 @@ describe('Class: AppSyncGraphQLResolver', () => { public scope = 'scoped'; @app.batchResolver({ fieldName: 'batchGet' }) - public async handleBatchGet( - events: AppSyncResolverEvent<{ id: number }>[] - ) { + public handleBatchGet(events: AppSyncResolverEvent<{ id: number }>[]) { const ids = events.map((event) => event.arguments.id); return ids.map((id) => ({ id, @@ -305,7 +300,7 @@ describe('Class: AppSyncGraphQLResolver', () => { })); } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -351,14 +346,14 @@ describe('Class: AppSyncGraphQLResolver', () => { throwOnError, aggregate: false, }) - public async handleBatchGet({ id }: { id: string }) { + public handleBatchGet({ id }: { id: string }) { return { id, scope: `${this.scope} id=${id} throwOnError=${throwOnError} aggregate=false`, }; } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -395,7 +390,7 @@ describe('Class: AppSyncGraphQLResolver', () => { class Lambda { @app.resolver({ fieldName: 'getPost' }) - public async handleGetPost({ id }: { id: string }) { + public handleGetPost({ id }: { id: string }) { return { id, title: 'Post Title', @@ -403,7 +398,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -445,7 +440,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); app.resolver( - async () => { + () => { throw error; }, { @@ -677,9 +672,7 @@ describe('Class: AppSyncGraphQLResolver', () => { @app.onBatchQuery('batchGet', { throwOnError, }) - public async handleBatchGet( - events: AppSyncResolverEvent<{ id: number }>[] - ) { + public handleBatchGet(events: AppSyncResolverEvent<{ id: number }>[]) { const ids = events.map((event) => event.arguments.id); return ids.map((id) => ({ id, @@ -690,13 +683,11 @@ describe('Class: AppSyncGraphQLResolver', () => { @app.onBatchMutation('batchPut', { throwOnError, }) - public async handleBatchPut( - _events: AppSyncResolverEvent<{ id: number }>[] - ) { + public handleBatchPut(_events: AppSyncResolverEvent<{ id: number }>[]) { return [this.scope, this.scope]; } - public async handler(event: unknown, context: Context) { + public handler(event: unknown, context: Context) { return app.resolve(event, context, { scope: this }); } } @@ -776,9 +767,9 @@ describe('Class: AppSyncGraphQLResolver', () => { errorName: err.constructor.name, })); - app.onQuery('getUser', async () => { + app.onQuery('getUser', () => { throw errorClass === AggregateError - ? new errorClass([new Error()], message) + ? new errorClass([new Error('test error')], message) : new errorClass(message); }); @@ -800,7 +791,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver(); - app.exceptionHandler(ValidationError, async (error) => { + app.exceptionHandler(ValidationError, (error) => { return { message: 'Validation failed', details: error.message, @@ -808,7 +799,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.exceptionHandler(NotFoundError, async (error) => { + app.exceptionHandler(NotFoundError, (error) => { return { message: 'Resource not found', details: error.message, @@ -816,7 +807,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.onQuery<{ id: string }>('getUser', async ({ id }) => { + app.onQuery<{ id: string }>('getUser', ({ id }) => { if (!id) { throw new ValidationError('User ID is required'); } @@ -853,7 +844,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver(); - app.exceptionHandler(Error, async (error) => { + app.exceptionHandler(Error, (error) => { return { message: 'Generic error occurred', details: error.message, @@ -861,7 +852,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.exceptionHandler(ValidationError, async (error) => { + app.exceptionHandler(ValidationError, (error) => { return { message: 'Validation failed', details: error.message, @@ -869,7 +860,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.onQuery('getUser', async () => { + app.onQuery('getUser', () => { throw new ValidationError('Specific validation error'); }); @@ -891,7 +882,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver(); - app.exceptionHandler(AssertionError, async (error) => { + app.exceptionHandler(AssertionError, (error) => { return { message: 'Validation failed', details: error.message, @@ -899,7 +890,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.onQuery('getUser', async () => { + app.onQuery('getUser', () => { throw new DatabaseError('Database connection failed'); }); @@ -920,11 +911,11 @@ describe('Class: AppSyncGraphQLResolver', () => { const app = new AppSyncGraphQLResolver({ logger: console }); const errorToBeThrown = new Error('Exception handler failed'); - app.exceptionHandler(ValidationError, async () => { + app.exceptionHandler(ValidationError, () => { throw errorToBeThrown; }); - app.onQuery('getUser', async () => { + app.onQuery('getUser', () => { throw new ValidationError('Original error'); }); @@ -962,7 +953,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.onQuery('getUser', async () => { + app.onQuery('getUser', () => { throw new ValidationError('Sync error test'); }); @@ -984,7 +975,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver(); - app.exceptionHandler(RangeError, async (error) => { + app.exceptionHandler(RangeError, (error) => { return { message: 'This should not be called', details: error.message, @@ -1009,7 +1000,7 @@ describe('Class: AppSyncGraphQLResolver', () => { public readonly scope = 'scoped'; @app.exceptionHandler(ValidationError) - async handleValidationError(error: ValidationError) { + handleValidationError(error: ValidationError) { return { message: 'Decorator validation failed', details: error.message, @@ -1029,7 +1020,7 @@ describe('Class: AppSyncGraphQLResolver', () => { } @app.onQuery('getUser') - async getUser({ id, name }: { id: string; name: string }) { + getUser({ id, name }: { id: string; name: string }) { if (!id) { throw new ValidationError('Decorator error test'); } @@ -1039,7 +1030,7 @@ describe('Class: AppSyncGraphQLResolver', () => { return { id, name }; } - async handler(event: unknown, context: Context) { + handler(event: unknown, context: Context) { return app.resolve(event, context, { scope: this, }); @@ -1078,7 +1069,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); - app.exceptionHandler([ValidationError, NotFoundError], async (error) => { + app.exceptionHandler([ValidationError, NotFoundError], (error) => { return { message: 'User service error', details: error.message, @@ -1087,7 +1078,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.onQuery<{ id: string }>('getId', async ({ id }) => { + app.onQuery<{ id: string }>('getId', ({ id }) => { if (!id) { throw new ValidationError('User ID is required for retrieval'); } @@ -1146,7 +1137,7 @@ describe('Class: AppSyncGraphQLResolver', () => { public readonly serviceName = 'OrderService'; @app.exceptionHandler([ValidationError, NotFoundError]) - async handleOrderErrors(error: ValidationError | NotFoundError) { + handleOrderErrors(error: ValidationError | NotFoundError) { return { message: `${this.serviceName} encountered an error`, details: error.message, @@ -1157,7 +1148,7 @@ describe('Class: AppSyncGraphQLResolver', () => { } @app.onQuery('getOrder') - async getOrderById({ orderId }: { orderId: string }) { + getOrderById({ orderId }: { orderId: string }) { if (!orderId) { throw new ValidationError('Order ID is required'); } @@ -1170,7 +1161,7 @@ describe('Class: AppSyncGraphQLResolver', () => { return { orderId, status: 'found', service: this.serviceName }; } - async handler(event: unknown, context: Context) { + handler(event: unknown, context: Context) { const resolved = app.resolve(event, context, { scope: this, }); @@ -1228,7 +1219,7 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver(); - app.exceptionHandler([ValidationError, TypeError], async (error) => { + app.exceptionHandler([ValidationError, TypeError], (error) => { return { message: 'Payment validation error', details: error.message, @@ -1237,7 +1228,7 @@ describe('Class: AppSyncGraphQLResolver', () => { }; }); - app.exceptionHandler(ValidationError, async (error) => { + app.exceptionHandler(ValidationError, (error) => { return { message: 'Specific payment validation error', details: error.message, @@ -1248,7 +1239,7 @@ describe('Class: AppSyncGraphQLResolver', () => { app.onQuery<{ amount: number; currency: string }>( 'getPayment', - async ({ amount, currency }) => { + ({ amount, currency }) => { if (!amount || amount <= 0) { throw new ValidationError('Invalid payment amount'); } @@ -1304,14 +1295,14 @@ describe('Class: AppSyncGraphQLResolver', () => { // Prepare const app = new AppSyncGraphQLResolver({ logger: console }); - app.exceptionHandler([], async (error) => { + app.exceptionHandler([], (error) => { return { message: 'This should never be called', details: error.message, }; }); - app.onQuery<{ requestId: string }>('getId', async ({ requestId }) => { + app.onQuery<{ requestId: string }>('getId', ({ requestId }) => { if (requestId === 'validation-error') { throw new ValidationError('Invalid request format'); } diff --git a/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts b/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts index 1b21f6694c..ae2e34b452 100644 --- a/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts +++ b/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts @@ -82,7 +82,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async (params: { arg: string }) => { + (params: { arg: string }) => { return params.arg; }, { @@ -101,7 +101,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async (params: { arg: string }) => { + (params: { arg: string }) => { return params.arg; }, { @@ -132,7 +132,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { ]); app.tool( - async (params: { a: number; b: number }) => { + (params: { a: number; b: number }) => { return params.a + params.b; }, { @@ -148,7 +148,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { ); app.tool( - async (params: { a: number; b: number }) => { + (params: { a: number; b: number }) => { return params.a * params.b; }, { @@ -176,7 +176,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver({ logger }); app.tool( - async (params: { arg: string }) => { + (params: { arg: string }) => { return params.arg; }, { @@ -186,7 +186,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { ); app.tool( - async (params: { arg: string }) => { + (params: { arg: string }) => { return params.arg; }, { @@ -196,8 +196,8 @@ describe('Class: BedrockAgentFunctionResolver', () => { ); app.tool( - async (_params) => { - throw new Error(); + () => { + throw new Error('test error'); }, { name: 'error', @@ -220,7 +220,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async (_params, options) => { + (_params, options) => { return options?.event; }, { @@ -332,7 +332,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async () => { + () => { return new BedrockFunctionResponse({ body: 'I am not sure', responseState: 'REPROMPT', @@ -535,7 +535,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async (_params, _options) => { + (_params, _options) => { throw toThrow; }, { @@ -564,7 +564,7 @@ describe('Class: BedrockAgentFunctionResolver', () => { const app = new BedrockAgentFunctionResolver(); app.tool( - async (params, _options) => { + (params, _options) => { return `Hello, ${params.name}!`; }, { diff --git a/packages/event-handler/tests/unit/rest/RouteHandlerRegistry.test.ts b/packages/event-handler/tests/unit/rest/RouteHandlerRegistry.test.ts index 42dccf5f24..f27671fd38 100644 --- a/packages/event-handler/tests/unit/rest/RouteHandlerRegistry.test.ts +++ b/packages/event-handler/tests/unit/rest/RouteHandlerRegistry.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { HttpVerbs } from '../../../src/rest/index.js'; +import { + HttpVerbs, + ParameterValidationError, +} from '../../../src/rest/index.js'; import { Route } from '../../../src/rest/Route.js'; import { RouteHandlerRegistry } from '../../../src/rest/RouteHandlerRegistry.js'; import type { Path } from '../../../src/types/rest.js'; @@ -570,20 +573,20 @@ describe('Class: RouteHandlerRegistry', () => { it('throws ParameterValidationError with multiple error messages for multiple invalid parameters', () => { // Prepare const registry = new RouteHandlerRegistry({ logger: console }); - const handler = async () => ({ message: 'test' }); + const handler = () => ({ message: 'test' }); registry.register( new Route(HttpVerbs.GET, '/api/:version/:resource/:id', handler) ); // Act & Assess - expect(async () => { + expect(() => { registry.resolve(HttpVerbs.GET, '/api/%20/users/%20'); - }).rejects.toMatchObject({ - issues: [ + }).toThrow( + new ParameterValidationError([ "Parameter 'version' cannot be empty", "Parameter 'id' cannot be empty", - ], - }); + ]) + ); }); }); }); diff --git a/packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts b/packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts index a17ce1eb46..a2de67e709 100644 --- a/packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts +++ b/packages/event-handler/tests/unit/rest/Router/basic-routing.test.ts @@ -89,7 +89,7 @@ describe('Class: Router - Basic Routing', () => { const app = new Router(); const testEvent = createTestEvent('/test', 'GET'); - app.get('/test', async (reqCtx) => { + app.get('/test', (reqCtx) => { return { hasRequest: reqCtx.req instanceof Request, hasEvent: reqCtx.event === testEvent, @@ -107,7 +107,7 @@ describe('Class: Router - Basic Routing', () => { expect(actual.hasContext).toBe(true); }); - it('throws an internal server error for non-API Gateway events', async () => { + it('throws an internal server error for non-API Gateway events', () => { // Prepare const app = new Router(); const nonApiGatewayEvent = { Records: [] }; // SQS-like event @@ -123,10 +123,10 @@ describe('Class: Router - Basic Routing', () => { const app = new Router({ prefix: '/todos', }); - app.post('/', async () => { + app.post('/', () => { return { actualPath: '/todos' }; }); - app.get('/:todoId', async (reqCtx) => { + app.get('/:todoId', (reqCtx) => { return { actualPath: `/todos/${reqCtx.params.todoId}` }; }); @@ -153,11 +153,9 @@ describe('Class: Router - Basic Routing', () => { await next(); }); todoRouter.get('/', async () => ({ api: 'listTodos' })); - todoRouter.notFound(async () => { - return { - error: 'Route not found', - }; - }); + todoRouter.notFound(async () => ({ + error: 'Route not found', + })); const consoleLogSpy = vi.spyOn(console, 'log'); const consoleWarnSpy = vi.spyOn(console, 'warn'); diff --git a/packages/event-handler/tests/unit/rest/Router/decorators.test.ts b/packages/event-handler/tests/unit/rest/Router/decorators.test.ts index 0528e7a963..98c0ab7c5b 100644 --- a/packages/event-handler/tests/unit/rest/Router/decorators.test.ts +++ b/packages/event-handler/tests/unit/rest/Router/decorators.test.ts @@ -17,41 +17,41 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.get('/test') - public async getTest() { + public getTest() { return { result: 'get-test' }; } @app.post('/test') - public async postTest() { + public postTest() { return { result: 'post-test' }; } @app.put('/test') - public async putTest() { + public putTest() { return { result: 'put-test' }; } @app.patch('/test') - public async patchTest() { + public patchTest() { return { result: 'patch-test' }; } @app.delete('/test') - public async deleteTest() { + public deleteTest() { return { result: 'delete-test' }; } @app.head('/test') - public async headTest() { + public headTest() { return { result: 'head-test' }; } @app.options('/test') - public async optionsTest() { + public optionsTest() { return { result: 'options-test' }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -96,12 +96,12 @@ describe('Class: Router - Decorators', () => { public scope = 'class-scope'; @app.get('/test', [middleware]) - public async getTest() { + public getTest() { executionOrder.push('handler'); return { result: `${this.scope}: decorator-with-middleware` }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context, { scope: this }); } } @@ -149,41 +149,41 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.get('/test', [middleware]) - public async getTest() { + public getTest() { return { result: 'get-decorator-middleware' }; } @app.post('/test', [middleware]) - public async postTest() { + public postTest() { return { result: 'post-decorator-middleware' }; } @app.put('/test', [middleware]) - public async putTest() { + public putTest() { return { result: 'put-decorator-middleware' }; } @app.patch('/test', [middleware]) - public async patchTest() { + public patchTest() { return { result: 'patch-decorator-middleware' }; } @app.delete('/test', [middleware]) - public async deleteTest() { + public deleteTest() { return { result: 'delete-decorator-middleware' }; } @app.head('/test', [middleware]) - public async headTest() { + public headTest() { return { result: 'head-decorator-middleware' }; } @app.options('/test', [middleware]) - public async optionsTest() { + public optionsTest() { return { result: 'options-decorator-middleware' }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -218,7 +218,7 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.errorHandler(BadRequestError) - public async handleBadRequest(error: BadRequestError) { + public handleBadRequest(error: BadRequestError) { return { statusCode: HttpStatusCodes.BAD_REQUEST, error: 'Bad Request', @@ -227,11 +227,11 @@ describe('Class: Router - Decorators', () => { } @app.get('/test') - public async getTest() { + public getTest() { throw new BadRequestError('test error'); } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -265,7 +265,7 @@ describe('Class: Router - Decorators', () => { public scope = 'scoped'; @app.notFound() - public async handleNotFound(error: NotFoundError) { + public handleNotFound(error: NotFoundError) { return { statusCode: HttpStatusCodes.NOT_FOUND, error: 'Not Found', @@ -273,7 +273,7 @@ describe('Class: Router - Decorators', () => { }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context, { scope: this }); } } @@ -306,7 +306,7 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.methodNotAllowed() - public async handleMethodNotAllowed(error: MethodNotAllowedError) { + public handleMethodNotAllowed(error: MethodNotAllowedError) { return { statusCode: HttpStatusCodes.METHOD_NOT_ALLOWED, error: 'Method Not Allowed', @@ -315,11 +315,11 @@ describe('Class: Router - Decorators', () => { } @app.get('/test') - public async getTest() { + public getTest() { throw new MethodNotAllowedError('POST not allowed'); } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -353,7 +353,7 @@ describe('Class: Router - Decorators', () => { public scope = 'scoped'; @app.errorHandler(BadRequestError) - public async handleBadRequest(error: BadRequestError) { + public handleBadRequest(error: BadRequestError) { return { statusCode: HttpStatusCodes.BAD_REQUEST, error: 'Bad Request', @@ -362,11 +362,11 @@ describe('Class: Router - Decorators', () => { } @app.get('/test') - public async getTest() { + public getTest() { throw new BadRequestError('test error'); } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context, { scope: this }); } } @@ -399,7 +399,7 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.get('/test') - public async getTest(reqCtx: RequestContext) { + public getTest(reqCtx: RequestContext) { return { hasRequest: reqCtx.req instanceof Request, hasEvent: reqCtx.event === testEvent, @@ -407,7 +407,7 @@ describe('Class: Router - Decorators', () => { }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -431,7 +431,7 @@ describe('Class: Router - Decorators', () => { class Lambda { @app.errorHandler(BadRequestError) - public async handleBadRequest( + public handleBadRequest( error: BadRequestError, reqCtx: RequestContext ) { @@ -446,11 +446,11 @@ describe('Class: Router - Decorators', () => { } @app.get('/test') - public async getTest() { + public getTest() { throw new BadRequestError('test error'); } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context); } } @@ -475,13 +475,13 @@ describe('Class: Router - Decorators', () => { public scope = 'scoped'; @app.get('/test') - public async getTest() { + public getTest() { return { message: `${this.scope}: success`, }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context, { scope: this }); } } diff --git a/packages/event-handler/tests/unit/rest/Router/error-handling.test.ts b/packages/event-handler/tests/unit/rest/Router/error-handling.test.ts index f3921aa42b..87bc657628 100644 --- a/packages/event-handler/tests/unit/rest/Router/error-handling.test.ts +++ b/packages/event-handler/tests/unit/rest/Router/error-handling.test.ts @@ -107,7 +107,7 @@ describe('Class: Router - Error Handling', () => { vi.stubEnv('POWERTOOLS_DEV', ''); const app = new Router(); - app.errorHandler(BadRequestError, async () => { + app.errorHandler(BadRequestError, () => { throw new Error('Handler failed'); }); @@ -486,7 +486,7 @@ describe('Class: Router - Error Handling', () => { // Prepare const app = new Router(); - app.errorHandler(BadRequestError, async () => { + app.errorHandler(BadRequestError, () => { throw new NotFoundError('This error is thrown from the error handler'); }); @@ -514,7 +514,7 @@ describe('Class: Router - Error Handling', () => { // Prepare const app = new Router(); - app.errorHandler(BadRequestError, async () => { + app.errorHandler(BadRequestError, () => { throw new MethodNotAllowedError( 'This error is thrown from the error handler' ); @@ -545,7 +545,7 @@ describe('Class: Router - Error Handling', () => { vi.stubEnv('POWERTOOLS_DEV', 'true'); const app = new Router(); - app.errorHandler(BadRequestError, async () => { + app.errorHandler(BadRequestError, () => { throw new Error('This error is thrown from the error handler'); }); diff --git a/packages/event-handler/tests/unit/rest/Router/middleware.test.ts b/packages/event-handler/tests/unit/rest/Router/middleware.test.ts index 4d66055914..93b6abc230 100644 --- a/packages/event-handler/tests/unit/rest/Router/middleware.test.ts +++ b/packages/event-handler/tests/unit/rest/Router/middleware.test.ts @@ -57,7 +57,7 @@ describe('Class: Router - Middleware', () => { } ); - app.get(path as Path, middleware, async () => { + app.get(path as Path, middleware, () => { executionOrder.push('handler'); return { success: true }; }); @@ -79,7 +79,7 @@ describe('Class: Router - Middleware', () => { app.use(createTrackingMiddleware('middleware1', executionOrder)); app.use(createTrackingMiddleware('middleware2', executionOrder)); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -111,7 +111,7 @@ describe('Class: Router - Middleware', () => { ) ); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -190,7 +190,7 @@ describe('Class: Router - Middleware', () => { await next(); }); - app.use(async ({ next }) => { + app.use(({ next }) => { next(); }); @@ -221,7 +221,7 @@ describe('Class: Router - Middleware', () => { ); app.use(createTrackingMiddleware('middleware2', executionOrder)); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -249,7 +249,7 @@ describe('Class: Router - Middleware', () => { throw new Error('Cleanup error'); }); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -277,7 +277,7 @@ describe('Class: Router - Middleware', () => { app.use(createTrackingMiddleware('middleware1', executionOrder)); app.use(createTrackingMiddleware('middleware2', executionOrder)); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); throw new Error('Handler error'); }); @@ -305,7 +305,7 @@ describe('Class: Router - Middleware', () => { app.use(createNoNextMiddleware('middleware1', executionOrder)); app.use(createTrackingMiddleware('middleware2', executionOrder)); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -334,7 +334,7 @@ describe('Class: Router - Middleware', () => { }) ); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -369,7 +369,7 @@ describe('Class: Router - Middleware', () => { reqCtx.res.headers.set('x-request-id', '12345'); }); - app.get('/test', async () => ({ success: true })); + app.get('/test', () => ({ success: true })); // Act const result = await app.resolve( @@ -457,7 +457,7 @@ describe('Class: Router - Middleware', () => { await next(); }); - app.get('/handler-precedence', async () => { + app.get('/handler-precedence', () => { const response = Response.json({ success: true }); response.headers.set('x-before-handler', 'handler-value'); return response; @@ -542,12 +542,12 @@ describe('Class: Router - Middleware', () => { public scope = 'class-scope'; @app.get('/test') - public async getTest() { + public getTest() { executionOrder.push('handler'); return { message: `${this.scope}: success` }; } - public async handler(event: unknown, _context: Context) { + public handler(event: unknown, _context: Context) { return app.resolve(event, _context, { scope: this }); } } @@ -586,7 +586,7 @@ describe('Class: Router - Middleware', () => { executionOrder ); - app.get('/test', [routeMiddleware], async () => { + app.get('/test', [routeMiddleware], () => { executionOrder.push('handler'); return { success: true }; }); @@ -619,7 +619,7 @@ describe('Class: Router - Middleware', () => { executionOrder ); - app.get('/test', [routeMiddleware1, routeMiddleware2], async () => { + app.get('/test', [routeMiddleware1, routeMiddleware2], () => { executionOrder.push('handler'); return { success: true }; }); @@ -646,7 +646,7 @@ describe('Class: Router - Middleware', () => { app.use(createTrackingMiddleware('global-middleware', executionOrder)); - app.get('/test', async () => { + app.get('/test', () => { executionOrder.push('handler'); return { success: true }; }); @@ -674,7 +674,7 @@ describe('Class: Router - Middleware', () => { new Response('Route middleware response', { status: 403 }) ); - app.get('/test', [routeMiddleware], async () => { + app.get('/test', [routeMiddleware], () => { executionOrder.push('handler'); return { success: true }; }); diff --git a/packages/event-handler/tests/unit/rest/converters.test.ts b/packages/event-handler/tests/unit/rest/converters.test.ts index e33c6136e6..e6f5a9a893 100644 --- a/packages/event-handler/tests/unit/rest/converters.test.ts +++ b/packages/event-handler/tests/unit/rest/converters.test.ts @@ -1,4 +1,3 @@ -import type { APIGatewayProxyEvent } from 'aws-lambda'; import { describe, expect, it } from 'vitest'; import { handlerResultToProxyResult, @@ -6,36 +5,11 @@ import { proxyEventToWebRequest, webResponseToProxyResult, } from '../../../src/rest/index.js'; +import { createTestEvent } from './helpers.js'; describe('Converters', () => { describe('proxyEventToWebRequest', () => { - const baseEvent: APIGatewayProxyEvent = { - httpMethod: 'GET', - path: '/test', - resource: '/test', - headers: {}, - multiValueHeaders: {}, - queryStringParameters: null, - multiValueQueryStringParameters: {}, - pathParameters: null, - stageVariables: null, - requestContext: { - accountId: '123456789012', - apiId: 'test-api', - httpMethod: 'GET', - path: '/test', - requestId: 'test-request-id', - resourceId: 'test-resource', - resourcePath: '/test', - stage: 'test', - domainName: 'api.example.com', - identity: { - sourceIp: '127.0.0.1', - }, - } as any, - isBase64Encoded: false, - body: null, - }; + const baseEvent = createTestEvent('/test', 'GET'); it('converts basic GET request', () => { // Prepare & Act @@ -65,25 +39,24 @@ describe('Converters', () => { it('uses X-Forwarded-Proto header for protocol', () => { // Prepare - const event = { - ...baseEvent, - headers: { 'X-Forwarded-Proto': 'https' }, - }; + const event = createTestEvent('/test', 'GET', { + 'X-Forwarded-Proto': 'http', + }); // Act const request = proxyEventToWebRequest(event); // Assess expect(request).toBeInstanceOf(Request); - expect(request.url).toBe('https://api.example.com/test'); + expect(request.url).toBe('http://api.example.com/test'); }); - it('handles null values in multiValueHeaders arrays', () => { + it('handles undefined values in multiValueHeaders arrays', () => { // Prepare const event = { ...baseEvent, multiValueHeaders: { - Accept: null as any, + Accept: undefined, 'Custom-Header': ['value1'], }, }; @@ -97,12 +70,12 @@ describe('Converters', () => { expect(request.headers.get('Custom-Header')).toBe('value1'); }); - it('handles null values in multiValueQueryStringParameters arrays', () => { + it('handles undefined values in multiValueQueryStringParameters arrays', () => { // Prepare const event = { ...baseEvent, multiValueQueryStringParameters: { - filter: null as any, + filter: undefined, sort: ['desc'], }, }; @@ -117,7 +90,7 @@ describe('Converters', () => { expect(url.searchParams.get('sort')).toBe('desc'); }); - it('handles POST request with string body', async () => { + it('handles POST request with string body', () => { // Prepare const event = { ...baseEvent, @@ -136,7 +109,7 @@ describe('Converters', () => { expect(request.headers.get('Content-Type')).toBe('application/json'); }); - it('decodes base64 encoded body', async () => { + it('decodes base64 encoded body', () => { // Prepare const originalText = 'Hello World'; const base64Text = Buffer.from(originalText).toString('base64'); @@ -321,13 +294,13 @@ describe('Converters', () => { expect(url.searchParams.getAll('multi')).toEqual(['value1', 'value2']); }); - it('skips null queryStringParameter values', () => { + it('skips undefined queryStringParameter values', () => { // Prepare const event = { ...baseEvent, queryStringParameters: { valid: 'value', - null: null as any, + null: undefined, }, }; @@ -341,13 +314,13 @@ describe('Converters', () => { expect(url.searchParams.has('null')).toBe(false); }); - it('skips null header values', () => { + it('skips undefined header values', () => { // Prepare const event = { ...baseEvent, headers: { 'Valid-Header': 'value', - 'Null-Header': null as any, + 'Undefined-Header': undefined, }, }; @@ -357,26 +330,7 @@ describe('Converters', () => { // Assess expect(request).toBeInstanceOf(Request); expect(request.headers.get('Valid-Header')).toBe('value'); - expect(request.headers.get('Null-Header')).toBe(null); - }); - - it('handles null/undefined collections', () => { - // Prepare - const event = { - ...baseEvent, - headers: null as any, - multiValueHeaders: null as any, - queryStringParameters: null as any, - multiValueQueryStringParameters: null as any, - }; - - // Act - const request = proxyEventToWebRequest(event); - - // Assess - expect(request).toBeInstanceOf(Request); - expect(request.method).toBe('GET'); - expect(request.url).toBe('https://api.example.com/test'); + expect(request.headers.get('Undefined-Header')).toBe(null); }); }); @@ -568,7 +522,7 @@ describe('Converters', () => { expect(result.headers.get('content-type')).toBe('text/plain'); }); - it('converts APIGatewayProxyResult with multiValueHeaders', async () => { + it('converts APIGatewayProxyResult with multiValueHeaders', () => { // Prepare const proxyResult = { statusCode: 200, @@ -590,7 +544,7 @@ describe('Converters', () => { ); }); - it('converts plain object to JSON Response with default headers', async () => { + it('converts plain object to JSON Response with default headers', () => { // Prepare const obj = { message: 'success' }; @@ -604,7 +558,7 @@ describe('Converters', () => { expect(result.headers.get('Content-Type')).toBe('application/json'); }); - it('uses provided headers for plain object', async () => { + it('uses provided headers for plain object', () => { // Prepare const obj = { message: 'success' }; const headers = new Headers({ 'x-custom': 'value' }); @@ -617,7 +571,7 @@ describe('Converters', () => { expect(result.headers.get('x-custom')).toBe('value'); }); - it('handles APIGatewayProxyResult with undefined headers', async () => { + it('handles APIGatewayProxyResult with undefined headers', () => { // Prepare const proxyResult = { statusCode: 200, @@ -634,7 +588,7 @@ describe('Converters', () => { expect(result.status).toBe(200); }); - it('handles APIGatewayProxyResult with undefined multiValueHeaders', async () => { + it('handles APIGatewayProxyResult with undefined multiValueHeaders', () => { // Prepare const proxyResult = { statusCode: 200, @@ -651,7 +605,7 @@ describe('Converters', () => { expect(result.headers.get('content-type')).toBe('text/plain'); }); - it('handles APIGatewayProxyResult with undefined values in multiValueHeaders', async () => { + it('handles APIGatewayProxyResult with undefined values in multiValueHeaders', () => { // Prepare const proxyResult = { statusCode: 200, diff --git a/packages/event-handler/tests/unit/rest/helpers.ts b/packages/event-handler/tests/unit/rest/helpers.ts index 4f04e0e3c1..aeb4e51c2d 100644 --- a/packages/event-handler/tests/unit/rest/helpers.ts +++ b/packages/event-handler/tests/unit/rest/helpers.ts @@ -19,7 +19,7 @@ export const createTestEvent = ( requestContext: { httpMethod, path, - domainName: 'localhost', + domainName: 'api.example.com', } as APIGatewayProxyEvent['requestContext'], resource: '', }); @@ -40,7 +40,7 @@ export const createThrowingMiddleware = ( executionOrder: string[], errorMessage: string ): Middleware => { - return async () => { + return () => { executionOrder.push(name); throw new Error(errorMessage); }; @@ -51,7 +51,7 @@ export const createReturningMiddleware = ( executionOrder: string[], response: HandlerResponse ): Middleware => { - return async () => { + return () => { executionOrder.push(name); return response; }; @@ -61,7 +61,7 @@ export const createNoNextMiddleware = ( name: string, executionOrder: string[] ): Middleware => { - return async () => { + return () => { executionOrder.push(name); // Intentionally doesn't call next() }; diff --git a/packages/event-handler/tests/unit/rest/middleware/compress.test.ts b/packages/event-handler/tests/unit/rest/middleware/compress.test.ts index 0aa8a1e289..653bb62afd 100644 --- a/packages/event-handler/tests/unit/rest/middleware/compress.test.ts +++ b/packages/event-handler/tests/unit/rest/middleware/compress.test.ts @@ -22,7 +22,7 @@ describe('Compress Middleware', () => { it('compresses response when conditions are met', async () => { // Prepare - app.get('/test', async () => { + app.get('/test', () => { return body; }); @@ -49,7 +49,7 @@ describe('Compress Middleware', () => { 'content-length': '1', }), ], - async () => { + () => { return { test: 'x' }; } ); @@ -65,7 +65,7 @@ describe('Compress Middleware', () => { it('skips compression for HEAD requests', async () => { // Prepare const headEvent = createTestEvent('/test', 'HEAD'); - app.head('/test', async () => { + app.head('/test', () => { return body; }); @@ -93,7 +93,7 @@ describe('Compress Middleware', () => { 'content-length': '2000', }), ], - async () => { + () => { return body; } ); @@ -118,7 +118,7 @@ describe('Compress Middleware', () => { 'cache-control': 'no-transform', }), ], - async () => { + () => { return body; } ); @@ -144,7 +144,7 @@ describe('Compress Middleware', () => { 'content-length': '2000', }), ], - async () => { + () => { return body; } ); @@ -162,7 +162,7 @@ describe('Compress Middleware', () => { const noCompressionEvent = createTestEvent('/test', 'GET', { 'Accept-Encoding': 'identity', }); - app.get('/test', async () => { + app.get('/test', () => { return body; }); diff --git a/packages/event-handler/tests/unit/rest/utils.test.ts b/packages/event-handler/tests/unit/rest/utils.test.ts index 50de2f4f03..927fae01eb 100644 --- a/packages/event-handler/tests/unit/rest/utils.test.ts +++ b/packages/event-handler/tests/unit/rest/utils.test.ts @@ -473,7 +473,7 @@ describe('Path Utilities', () => { const composed = composeMiddleware(middleware); await composed({ reqCtx: mockOptions, - next: async () => { + next: () => { executionOrder.push('handler'); }, }); @@ -492,7 +492,7 @@ describe('Path Utilities', () => { async ({ next }) => { await next(); }, - async () => { + () => { return { shortCircuit: true }; }, ]; @@ -500,7 +500,7 @@ describe('Path Utilities', () => { const composed = composeMiddleware(middleware); const result = await composed({ reqCtx: mockOptions, - next: async () => { + next: () => { return { handler: true }; }, }); @@ -518,7 +518,7 @@ describe('Path Utilities', () => { const composed = composeMiddleware(middleware); const result = await composed({ reqCtx: mockOptions, - next: async () => { + next: () => { return { handler: true }; }, }); @@ -545,7 +545,7 @@ describe('Path Utilities', () => { const composed = composeMiddleware([]); const result = await composed({ reqCtx: mockOptions, - next: async () => { + next: () => { return { handler: true }; }, }); @@ -563,7 +563,7 @@ describe('Path Utilities', () => { const composed = composeMiddleware(middleware); const result = await composed({ reqCtx: mockOptions, - next: async () => { + next: () => { return undefined; }, }); diff --git a/packages/event-handler/typedoc.json b/packages/event-handler/typedoc.json index 779f9ec709..4f7c48f0ed 100644 --- a/packages/event-handler/typedoc.json +++ b/packages/event-handler/typedoc.json @@ -4,6 +4,7 @@ "./src/appsync-events/index.ts", "./src/appsync-graphql/index.ts", "./src/bedrock-agent/index.ts", + "./src/rest/index.ts", "./src/types/index.ts" ], "readme": "README.md" diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 0e8b845fae..9f106dc7b9 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/idempotency#readme", diff --git a/packages/idempotency/src/IdempotencyHandler.ts b/packages/idempotency/src/IdempotencyHandler.ts index c5d5e4ee6b..96505222b7 100644 --- a/packages/idempotency/src/IdempotencyHandler.ts +++ b/packages/idempotency/src/IdempotencyHandler.ts @@ -383,20 +383,20 @@ export class IdempotencyHandler { if (error.name === 'IdempotencyItemAlreadyExistsError') { let idempotencyRecord = (error as IdempotencyItemAlreadyExistsError) .existingRecord; - if (idempotencyRecord !== undefined) { - // If the error includes the existing record, we can use it to validate - // the record being processed and cache it in memory. - idempotencyRecord = this.#persistenceStore.processExistingRecord( - idempotencyRecord, - this.#functionPayloadToBeHashed - ); + if (idempotencyRecord === undefined) { // If the error doesn't include the existing record, we need to fetch // it from the persistence layer. In doing so, we also call the processExistingRecord // method to validate the record and cache it in memory. - } else { idempotencyRecord = await this.#persistenceStore.getRecord( this.#functionPayloadToBeHashed ); + } else { + // If the error includes the existing record, we can use it to validate + // the record being processed and cache it in memory. + idempotencyRecord = this.#persistenceStore.processExistingRecord( + idempotencyRecord, + this.#functionPayloadToBeHashed + ); } returnValue.isIdempotent = true; diff --git a/packages/idempotency/src/idempotencyDecorator.ts b/packages/idempotency/src/idempotencyDecorator.ts index 968b3bd10e..ba9e63effa 100644 --- a/packages/idempotency/src/idempotencyDecorator.ts +++ b/packages/idempotency/src/idempotencyDecorator.ts @@ -70,7 +70,7 @@ const idempotent = ( ) { const childFunction = descriptor.value; - descriptor.value = async function (this: Handler, ...args: unknown[]) { + descriptor.value = function (this: Handler, ...args: unknown[]) { return makeIdempotent(childFunction, options).bind(this)(...args); }; diff --git a/packages/idempotency/src/makeIdempotent.ts b/packages/idempotency/src/makeIdempotent.ts index 46ed2b09c7..2e6e9b274f 100644 --- a/packages/idempotency/src/makeIdempotent.ts +++ b/packages/idempotency/src/makeIdempotent.ts @@ -51,7 +51,7 @@ const isOptionsWithDataIndexArgument = ( * * By default, the entire first argument is hashed to create the idempotency key. You can customize this behavior: * - Use {@link IdempotencyConfig.eventKeyJmesPath | `eventKeyJmesPath`} to hash only a subset of the payload - * - Use {@link ItempotentFunctionOptions.dataIndexArgument | `dataIndexArgument`} to hash a different function argument + * - Use {@link ItempotentFunctionOptions | `ItempotentFunctionOptions`}`.dataIndexArgument` to hash a different function argument * * * **Using a subset of the payload** diff --git a/packages/idempotency/src/middleware/makeHandlerIdempotent.ts b/packages/idempotency/src/middleware/makeHandlerIdempotent.ts index d48dcc221d..0d75c65ab0 100644 --- a/packages/idempotency/src/middleware/makeHandlerIdempotent.ts +++ b/packages/idempotency/src/middleware/makeHandlerIdempotent.ts @@ -112,7 +112,7 @@ const makeHandlerIdempotent = ( * * @param request - The Middy request object */ - const before = async (request: MiddyLikeRequest): Promise => { + const before = (request: MiddyLikeRequest): unknown => { const idempotencyConfig = options.config ? options.config : new IdempotencyConfig({}); diff --git a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts index 4e88f99b51..6dfbeb63f5 100644 --- a/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts +++ b/packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts @@ -88,13 +88,13 @@ class DynamoDBPersistenceLayer extends BasePersistenceLayer { config.staticPkValue ?? `idempotency#${this.idempotencyKeyPrefix}`; if (config.awsSdkV3Client) { - if (!isSdkClient(config.awsSdkV3Client)) { + if (isSdkClient(config.awsSdkV3Client)) { + this.client = config.awsSdkV3Client; + } else { console.warn( 'awsSdkV3Client is not an AWS SDK v3 client, using default client' ); this.client = new DynamoDBClient(config.clientConfig ?? {}); - } else { - this.client = config.awsSdkV3Client; } } else { this.client = new DynamoDBClient(config.clientConfig ?? {}); @@ -168,9 +168,9 @@ class DynamoDBPersistenceLayer extends BasePersistenceLayer { * | (in_progress_expiry) (expiry) * * Conditions to successfully save a record: - * * The idempotency key does not exist: - * - first time that this invocation key is used - * - previous invocation with the same key was deleted due to TTL + * - The idempotency key does not exist: + * - first time that this invocation key is used + * - previous invocation with the same key was deleted due to TTL */ const idempotencyKeyDoesNotExist = 'attribute_not_exists(#id)'; // * The idempotency key exists but it is expired diff --git a/packages/idempotency/src/types/IdempotencyOptions.ts b/packages/idempotency/src/types/IdempotencyOptions.ts index 4c48e25579..e586764646 100644 --- a/packages/idempotency/src/types/IdempotencyOptions.ts +++ b/packages/idempotency/src/types/IdempotencyOptions.ts @@ -10,8 +10,6 @@ import type { IdempotencyRecord } from '../persistence/IdempotencyRecord.js'; * * When making a function idempotent you should always set a persistence store. * - * @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. * diff --git a/packages/idempotency/tests/e2e/idempotentDecorator.test.FunctionCode.ts b/packages/idempotency/tests/e2e/idempotentDecorator.test.FunctionCode.ts index 9063d652cd..e4bb5205e9 100644 --- a/packages/idempotency/tests/e2e/idempotentDecorator.test.FunctionCode.ts +++ b/packages/idempotency/tests/e2e/idempotentDecorator.test.FunctionCode.ts @@ -2,7 +2,7 @@ import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import { Logger } from '@aws-lambda-powertools/logger'; import type { Context } from 'aws-lambda'; import { IdempotencyConfig } from '../../src/IdempotencyConfig.js'; -import { idempotent } from '../../src/idempotencyDecorator'; +import { idempotent } from '../../src/idempotencyDecorator.js'; import { DynamoDBPersistenceLayer } from '../../src/persistence/DynamoDBPersistenceLayer.js'; const IDEMPOTENCY_TABLE_NAME = @@ -35,19 +35,13 @@ class DefaultLambda implements LambdaInterface { logger.info(`${this.message} ${JSON.stringify(_event)}`); // sleep to enforce error with parallel execution await new Promise((resolve) => setTimeout(resolve, 1000)); - - // We return void to test that the utility handles it correctly - return; } @idempotent({ persistenceStore: dynamoDBPersistenceLayerCustomized, config: config, }) - public async handlerCustomized( - event: { foo: string }, - context: Context - ): Promise { + public handlerCustomized(event: { foo: string }, context: Context) { config.registerLambdaContext(context); logger.info('Processed event', { details: event.foo }); @@ -62,10 +56,10 @@ class DefaultLambda implements LambdaInterface { eventKeyJmesPath: 'foo', }), }) - public async handlerExpired( + public handlerExpired( event: { foo: string; invocation: number }, context: Context - ): Promise<{ foo: string; invocation: number }> { + ) { logger.addContext(context); logger.info('Processed event', { details: event.foo }); @@ -77,10 +71,7 @@ class DefaultLambda implements LambdaInterface { } @idempotent({ persistenceStore: dynamoDBPersistenceLayer }) - public async handlerParallel( - event: { foo: string }, - context: Context - ): Promise { + public async handlerParallel(event: { foo: string }, context: Context) { logger.addContext(context); await new Promise((resolve) => setTimeout(resolve, 1500)); @@ -99,7 +90,7 @@ class DefaultLambda implements LambdaInterface { public async handlerTimeout( event: { foo: string; invocation: number }, context: Context - ): Promise<{ foo: string; invocation: number }> { + ) { logger.addContext(context); if (event.invocation === 0) { @@ -130,12 +121,9 @@ const handlerExpired = defaultLambda.handlerExpired.bind(defaultLambda); const logger = new Logger(); class LambdaWithKeywordArgument implements LambdaInterface { - public async handler( - event: { id: string }, - _context: Context - ): Promise { + public handler(event: { id: string }, _context: Context) { config.registerLambdaContext(_context); - await this.process(event.id, 'bar'); + this.process(event.id, 'bar'); return 'Hello World Keyword Argument'; } @@ -145,7 +133,7 @@ class LambdaWithKeywordArgument implements LambdaInterface { config: config, dataIndexArgument: 1, }) - public async process(id: string, foo: string): Promise { + public process(id: string, foo: string) { logger.info('Got test event', { id, foo }); return `idempotent result: ${foo}`; diff --git a/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.FunctionCode.ts b/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.FunctionCode.ts index 6ade59d2ef..62f132e8bb 100644 --- a/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.FunctionCode.ts +++ b/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.FunctionCode.ts @@ -16,14 +16,12 @@ const logger = new Logger(); /** * Test handler with sequential execution. */ -export const handler = middy( - async (event: { foo: string }, context: Context) => { - logger.addContext(context); - logger.info('foo', { details: event.foo }); +export const handler = middy((event: { foo: string }, context: Context) => { + logger.addContext(context); + logger.info('foo', { details: event.foo }); - return event.foo; - } -).use( + return event.foo; +}).use( makeHandlerIdempotent({ persistenceStore: dynamoDBPersistenceLayer, }) @@ -97,7 +95,7 @@ export const handlerTimeout = middy( * was processed by looking at the value in the stored idempotency record. */ export const handlerExpired = middy( - async (event: { foo: string; invocation: number }, context: Context) => { + (event: { foo: string; invocation: number }, context: Context) => { logger.addContext(context); logger.info('Processed event', { details: event.foo }); diff --git a/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.ts b/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.ts index 0c379b5f4e..21e53fde0e 100644 --- a/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.ts +++ b/packages/idempotency/tests/e2e/makeHandlerIdempotent.test.ts @@ -192,15 +192,13 @@ describe('Idempotency E2E tests, middy middleware usage', () => { * We filter the logs to find which one was successful and which one failed, then we check * that they contain the expected logs. */ - const successfulInvocationLogs = functionLogs.find( - (functionLog) => - functionLog.find((log) => log.includes('Processed event')) !== undefined + const successfulInvocationLogs = functionLogs.find((functionLog) => + functionLog.some((log) => log.includes('Processed event')) ); - const failedInvocationLogs = functionLogs.find( - (functionLog) => - functionLog.find((log) => - log.includes('There is already an execution in progress') - ) !== undefined + const failedInvocationLogs = functionLogs.find((functionLog) => + functionLog.some((log) => + log.includes('There is already an execution in progress') + ) ); expect(successfulInvocationLogs).toHaveLength(1); expect(failedInvocationLogs).toHaveLength(1); diff --git a/packages/idempotency/tests/e2e/makeIdempotent.test.FunctionCode.ts b/packages/idempotency/tests/e2e/makeIdempotent.test.FunctionCode.ts index 10e2ce5f73..b35b4444dc 100644 --- a/packages/idempotency/tests/e2e/makeIdempotent.test.FunctionCode.ts +++ b/packages/idempotency/tests/e2e/makeIdempotent.test.FunctionCode.ts @@ -90,7 +90,7 @@ export const handlerCustomized = async ( * Test idempotent Lambda handler with JMESPath expression to extract event key. */ export const handlerLambda = makeIdempotent( - async (event: { body: string }, context: Context) => { + (event: { body: string }, context: Context) => { logger.addContext(context); const body = JSON.parse(event.body); logger.info('foo', { details: body.foo }); diff --git a/packages/idempotency/tests/unit/IdempotencyConfig.test.ts b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts index 8174c7727d..4aec6d0927 100644 --- a/packages/idempotency/tests/unit/IdempotencyConfig.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts @@ -87,7 +87,7 @@ describe('Class: IdempotencyConfig', () => { }); describe('Method: registerLambdaContext', () => { - it('stores the provided context', async () => { + it('stores the provided context', () => { // Prepare const config = new IdempotencyConfig({}); diff --git a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts index ebcdbddf59..2481314dc1 100644 --- a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts @@ -68,7 +68,7 @@ describe('Class IdempotencyHandler', () => { }, ])( 'throws when the record is in progress and within expiry window ($case)', - async ({ keys, expectedErrorMsg }) => { + ({ keys, expectedErrorMsg }) => { // Prepare const stubRecord = new IdempotencyRecord({ ...keys, @@ -89,7 +89,7 @@ describe('Class IdempotencyHandler', () => { } ); - it('throws when the record is in progress and outside expiry window', async () => { + it('throws when the record is in progress and outside expiry window', () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', @@ -109,7 +109,7 @@ describe('Class IdempotencyHandler', () => { expect(mockResponseHook).not.toHaveBeenCalled(); }); - it('throws when the idempotency record is expired', async () => { + it('throws when the idempotency record is expired', () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', diff --git a/packages/idempotency/tests/unit/deepSort.test.ts b/packages/idempotency/tests/unit/deepSort.test.ts index e37c6c6d91..057643ab52 100644 --- a/packages/idempotency/tests/unit/deepSort.test.ts +++ b/packages/idempotency/tests/unit/deepSort.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { deepSort } from '../../src/deepSort'; +import { deepSort } from '../../src/deepSort.js'; describe('Function: deepSort', () => { it('can sort string correctly', () => { diff --git a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts index b153c01418..4954f8aba5 100644 --- a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; @@ -15,10 +16,8 @@ describe('Given a class with a function to decorate', () => { @idempotent({ persistenceStore: new PersistenceLayerTestClass(), }) - public async handler( - _event: unknown, - _context: Context - ): Promise { + public async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation return this.privateMethod(); } @@ -37,7 +36,7 @@ describe('Given a class with a function to decorate', () => { expect(result).toBe('private foo'); }); - it('passes the custom keyPrefix to the persistenceStore', async () => { + it('passes the custom keyPrefix to the persistenceStore', () => { // Prepare const configureSpy = vi.spyOn(BasePersistenceLayer.prototype, 'configure'); const idempotencyConfig = new IdempotencyConfig({}); @@ -48,10 +47,7 @@ describe('Given a class with a function to decorate', () => { config: idempotencyConfig, keyPrefix: 'my-custom-prefix', }) - public async handler( - _event: unknown, - _context: Context - ): Promise { + public handler(_event: unknown, _context: Context) { return true; } } @@ -60,7 +56,7 @@ describe('Given a class with a function to decorate', () => { const handler = handlerClass.handler.bind(handlerClass); // Act - const result = await handler({}, context); + const result = handler({}, context); // Assess expect(result).toBeTruthy(); diff --git a/packages/idempotency/tests/unit/makeIdempotent.test.ts b/packages/idempotency/tests/unit/makeIdempotent.test.ts index bc567d452f..1c8a15e99b 100644 --- a/packages/idempotency/tests/unit/makeIdempotent.test.ts +++ b/packages/idempotency/tests/unit/makeIdempotent.test.ts @@ -20,7 +20,7 @@ const mockIdempotencyOptions = { }; const remainingTImeInMillis = 1234; const fnSuccessfull = async () => true; -const fnError = async () => { +const fnError = () => { throw new Error('Something went wrong'); }; @@ -525,7 +525,7 @@ describe('Function: makeIdempotent', () => { it('throws immediately if an object other than an error was thrown (wrapper)', async () => { // Prepare - const fn = async (_event: unknown, _context: Context) => { + const fn = (_event: unknown, _context: Context) => { throw 'a string'; }; const handler = makeIdempotent(fn, mockIdempotencyOptions); diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts index f786774cbb..35121122e3 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -149,7 +149,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { ); }); - it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { + it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', () => { // Act const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ tableName: dummyTableName, @@ -424,7 +424,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { expiryTimestamp: 0, inProgressExpiryTimestamp: 0, }); - client.on(PutItemCommand).rejects(new Error()); + client.on(PutItemCommand).rejects(new Error('some error')); // Act & Assess await expect(persistenceLayer._putRecord(record)).rejects.toThrow(); @@ -576,7 +576,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - it('updates the item when the response_data is undefined', async () => { + it('updates the item when the response_data is undefined', () => { // Prepare const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = Date.now(); diff --git a/packages/idempotency/vitest.config.ts b/packages/idempotency/vitest.config.ts index baa5cf7463..e962bb6d1c 100644 --- a/packages/idempotency/vitest.config.ts +++ b/packages/idempotency/vitest.config.ts @@ -4,7 +4,7 @@ export default defineProject({ test: { environment: 'node', setupFiles: ['../testing/src/setupEnv.ts'], - hookTimeout: 1_000 * 60 * 10, // 10 minutes - testTimeout: 1_000 * 60 * 3, // 3 minutes + hookTimeout: 1000 * 60 * 10, // 10 minutes + testTimeout: 1000 * 60 * 3, // 3 minutes }, }); diff --git a/packages/jmespath/package.json b/packages/jmespath/package.json index 1c90f14c3e..09469107d8 100644 --- a/packages/jmespath/package.json +++ b/packages/jmespath/package.json @@ -20,6 +20,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript", diff --git a/packages/jmespath/src/Expression.ts b/packages/jmespath/src/Expression.ts index 94c69ef3c4..8c7919151f 100644 --- a/packages/jmespath/src/Expression.ts +++ b/packages/jmespath/src/Expression.ts @@ -16,9 +16,8 @@ class Expression { /** * Evaluate the expression against a JSON value. * - * @param value The JSON value to apply the expression to. - * @param node The node to visit. - * @returns The result of applying the expression to the value. + * @param value - The JSON value to apply the expression to. + * @param node - The node to visit. */ public visit(value: JSONObject, node?: Node): JSONObject { return this.#interpreter.visit(node ?? this.#expression, value); diff --git a/packages/jmespath/src/Functions.ts b/packages/jmespath/src/Functions.ts index 9b012dff08..5773c73b55 100644 --- a/packages/jmespath/src/Functions.ts +++ b/packages/jmespath/src/Functions.ts @@ -56,7 +56,7 @@ class Functions { /** * Get the absolute value of the provided number. * - * @param args The number to get the absolute value of + * @param args - The number to get the absolute value of */ @Functions.signature({ argumentsSpecs: [['number']] }) public funcAbs(args: number): number { @@ -66,7 +66,7 @@ class Functions { /** * Calculate the average of the numbers in the provided array. * - * @param args The numbers to average + * @param args - The numbers to average */ @Functions.signature({ argumentsSpecs: [['array-number']], @@ -78,7 +78,7 @@ class Functions { /** * Get the ceiling of the provided number. * - * @param args The number to get the ceiling of + * @param args - The number to get the ceiling of */ @Functions.signature({ argumentsSpecs: [['number']] }) public funcCeil(args: number): number { @@ -88,8 +88,8 @@ class Functions { /** * Determine if the given value is contained in the provided array or string. * - * @param haystack The array or string to check - * @param needle The value to check for + * @param haystack - The array or string to check + * @param needle - The value to check for */ @Functions.signature({ argumentsSpecs: [['array', 'string'], ['any']], @@ -101,8 +101,8 @@ class Functions { /** * Determines if the provided string ends with the provided suffix. * - * @param str The string to check - * @param suffix The suffix to check for + * @param str - The string to check + * @param suffix - The suffix to check for */ @Functions.signature({ argumentsSpecs: [['string'], ['string']], @@ -114,7 +114,7 @@ class Functions { /** * Get the floor of the provided number. * - * @param args The number to get the floor of + * @param args - The number to get the floor of */ @Functions.signature({ argumentsSpecs: [['number']] }) public funcFloor(args: number): number { @@ -124,8 +124,8 @@ class Functions { /** * Join the provided array into a single string. * - * @param separator The separator to use - * @param items The array of itmes to join + * @param separator - The separator to use + * @param items - The array of itmes to join */ @Functions.signature({ argumentsSpecs: [['string'], ['array-string']], @@ -137,7 +137,7 @@ class Functions { /** * Get the keys of the provided object. * - * @param arg The object to get the keys of + * @param arg - The object to get the keys of */ @Functions.signature({ argumentsSpecs: [['object']], @@ -149,7 +149,7 @@ class Functions { /** * Get the number of items in the provided item. * - * @param arg The array to get the length of + * @param arg - The array to get the length of */ @Functions.signature({ argumentsSpecs: [['array', 'string', 'object']], @@ -166,8 +166,8 @@ class Functions { /** * Map the provided function over the provided array. * - * @param expression The expression to map over the array - * @param args The array to map the expression over + * @param expression - The expression to map over the array + * @param args - The array to map the expression over */ @Functions.signature({ argumentsSpecs: [['any'], ['array']], @@ -184,7 +184,7 @@ class Functions { /** * Get the maximum value in the provided array. * - * @param arg The array to get the maximum value of + * @param arg - The array to get the maximum value of */ @Functions.signature({ argumentsSpecs: [['array-number', 'array-string']], @@ -204,8 +204,8 @@ class Functions { /** * Get the item in the provided array that has the maximum value when the provided expression is evaluated. * - * @param args The array of items to get the maximum value of - * @param expression The expression to evaluate for each item in the array + * @param args - The array of items to get the maximum value of + * @param expression - The expression to evaluate for each item in the array */ @Functions.signature({ argumentsSpecs: [['array'], ['expression']], @@ -252,7 +252,7 @@ class Functions { * * Note that this is a shallow merge and will not merge nested objects. * - * @param args The objects to merge + * @param args - The objects to merge */ @Functions.signature({ argumentsSpecs: [['object']], @@ -266,7 +266,7 @@ class Functions { /** * Get the minimum value in the provided array. * - * @param arg The array to get the minimum value of + * @param arg - The array to get the minimum value of */ @Functions.signature({ argumentsSpecs: [['array-number', 'array-string']], @@ -285,8 +285,8 @@ class Functions { /** * Get the item in the provided array that has the minimum value when the provided expression is evaluated. * - * @param args The array of items to get the minimum value of - * @param expression The expression to evaluate for each item in the array + * @param args - The array of items to get the minimum value of + * @param expression - The expression to evaluate for each item in the array */ @Functions.signature({ argumentsSpecs: [['array'], ['expression']], @@ -332,7 +332,7 @@ class Functions { * Get the first argument that does not evaluate to null. * If all arguments evaluate to null, then null is returned. * - * @param args The keys of the items to check + * @param args - The keys of the items to check */ @Functions.signature({ argumentsSpecs: [[]], @@ -345,7 +345,7 @@ class Functions { /** * Reverses the provided string or array. * - * @param arg The string or array to reverse + * @param arg - The string or array to reverse */ @Functions.signature({ argumentsSpecs: [['string', 'array']], @@ -359,7 +359,7 @@ class Functions { /** * Sort the provided array. * - * @param arg The array to sort + * @param arg - The array to sort */ @Functions.signature({ argumentsSpecs: [['array-number', 'array-string']], @@ -382,8 +382,8 @@ class Functions { /** * Sort the provided array by the provided expression. * - * @param args The array to sort - * @param expression The expression to sort by + * @param args - The array to sort + * @param expression - The expression to sort by */ @Functions.signature({ argumentsSpecs: [['array'], ['expression']], @@ -426,8 +426,8 @@ class Functions { /** * Determines if the provided string starts with the provided prefix. * - * @param str The string to check - * @param prefix The prefix to check for + * @param str - The string to check + * @param prefix - The prefix to check for */ @Functions.signature({ argumentsSpecs: [['string'], ['string']], @@ -439,7 +439,7 @@ class Functions { /** * Sum the provided numbers. * - * @param args The numbers to sum + * @param args - The numbers to sum */ @Functions.signature({ argumentsSpecs: [['array-number']], @@ -454,7 +454,7 @@ class Functions { * If the provided value is an array, then it is returned. * Otherwise, the value is wrapped in an array and returned. * - * @param arg The items to convert to an array + * @param arg - The items to convert to an array */ @Functions.signature({ argumentsSpecs: [['any']], @@ -473,7 +473,7 @@ class Functions { * * If the value cannot be converted to a number, then null is returned. * - * @param arg The value to convert to a number + * @param arg - The value to convert to a number */ @Functions.signature({ argumentsSpecs: [['any']], @@ -496,7 +496,7 @@ class Functions { * If the provided value is a string, then it is returned. * Otherwise, the value is converted to a string and returned. * - * @param arg The value to convert to a string + * @param arg - The value to convert to a string */ @Functions.signature({ argumentsSpecs: [['any']], @@ -508,7 +508,7 @@ class Functions { /** * Get the type of the provided value. * - * @param arg The value to check the type of + * @param arg - The value to check the type of */ @Functions.signature({ argumentsSpecs: [['any']], @@ -520,7 +520,7 @@ class Functions { /** * Get the values of the provided object. * - * @param arg The object to get the values of + * @param arg - The object to get the values of */ @Functions.signature({ argumentsSpecs: [['object']], @@ -544,7 +544,7 @@ class Functions { * to the `methods` set. Finally, when the recursion collects back to the current instance, * it adds the collected methods to the `this.methods` set so that they can be accessed later. * - * @param scope The scope of the class instance to introspect + * @param scope - The scope of the class instance to introspect */ public introspectMethods(scope?: Functions): Set { const prototype = Object.getPrototypeOf(this); @@ -598,7 +598,7 @@ class Functions { * } * ``` * - * @param options The options for the signature decorator + * @param options - The options for the signature decorator */ public static signature( options: FunctionSignatureOptions diff --git a/packages/jmespath/src/Lexer.ts b/packages/jmespath/src/Lexer.ts index 9136311888..7e83ca7b4e 100644 --- a/packages/jmespath/src/Lexer.ts +++ b/packages/jmespath/src/Lexer.ts @@ -55,7 +55,7 @@ class Lexer { const buff = this.#consumeNumber(); yield { type: 'number', - value: Number.parseInt(buff), + value: Number.parseInt(buff, 10), start: start, end: start + buff.length, }; @@ -155,7 +155,7 @@ class Lexer { if (buff.length > 1) { return { type: 'number', - value: Number.parseInt(buff), + value: Number.parseInt(buff, 10), start: start, end: start + buff.length, }; diff --git a/packages/jmespath/src/ParsedResult.ts b/packages/jmespath/src/ParsedResult.ts index c5436fabd1..02c0481592 100644 --- a/packages/jmespath/src/ParsedResult.ts +++ b/packages/jmespath/src/ParsedResult.ts @@ -19,8 +19,8 @@ class ParsedResult { /** * Perform a JMESPath search on a JSON value. * - * @param value The JSON value to search - * @param options The parsing options to use + * @param value - The JSON value to search + * @param options - The parsing options to use */ public search(value: JSONObject, options?: JMESPathParsingOptions): unknown { const interpreter = new TreeInterpreter(options); diff --git a/packages/jmespath/src/Parser.ts b/packages/jmespath/src/Parser.ts index c576d5ee2b..69d8c94d55 100644 --- a/packages/jmespath/src/Parser.ts +++ b/packages/jmespath/src/Parser.ts @@ -32,7 +32,6 @@ import type { Node, Token } from './types.js'; /** * Top down operator precedence parser for JMESPath. * - * ## References * The implementation of this Parser is based on the implementation of * [jmespath.py](https://github.com/jmespath/jmespath.py/), which in turn * is based on [Vaughan R. Pratt's "Top Down Operator Precedence"](http://dl.acm.org/citation.cfm?doid=512927.512931). @@ -72,7 +71,7 @@ class Parser { * The AST is cached, so if you parse the same expression multiple times, * the AST will be returned from the cache. * - * @param expression The JMESPath expression to parse. + * @param expression - The JMESPath expression to parse. */ public parse(expression: string): ParsedResult { const cached = this.#cache[expression]; @@ -98,7 +97,7 @@ class Parser { /** * Do the actual parsing of the expression. * - * @param expression The JMESPath expression to parse. + * @param expression - The JMESPath expression to parse. */ #doParse(expression: string): ParsedResult { try { @@ -153,7 +152,7 @@ class Parser { * Get the nud function for a token. This is the function that * is called when a token is found at the beginning of an expression. * - * @param tokenType The type of token to get the nud function for. + * @param tokenType - The type of token to get the nud function for. */ #getNudFunction(token: Token): Node { const { type: tokenType } = token; @@ -194,7 +193,7 @@ class Parser { * * @example s."foo" * - * @param token The token to process + * @param token - The token to process */ #processQuotedIdentifier(token: Token): Node { const fieldValue = field(token.value); @@ -288,7 +287,7 @@ class Parser { * A default token is a syntax that allows you to access * elements in a list or dictionary. * - * @param token The token to process + * @param token - The token to process */ #processDefaultToken(token: Token): Node { if (token.type === 'eof') { @@ -309,8 +308,8 @@ class Parser { * Get the led function for a token. This is the function that * is called when a token is found in the middle of an expression. * - * @param tokenType The type of token to get the led function for. - * @param leftNode The left hand side of the expression. + * @param tokenType - The type of token to get the led function for. + * @param leftNode - The left hand side of the expression. */ #getLedFunction(tokenType: Token['type'], leftNode: Node): Node { switch (tokenType) { @@ -350,7 +349,7 @@ class Parser { * * @example foo.bar * - * @param leftNode The left hand side of the expression. + * @param leftNode - The left hand side of the expression. */ #processDotToken(leftNode: Node): Node { if (this.#currentToken() !== 'star') { @@ -377,7 +376,7 @@ class Parser { * * @example foo | bar * - * @param leftNode The left hand side of the expression. + * @param leftNode - The left hand side of the expression. */ #processPipeToken(leftNode: Node): Node { const right = this.#expression(BINDING_POWER.pipe); @@ -393,7 +392,7 @@ class Parser { * * @example foo || bar * - * @param leftNode The left hand side of the expression. + * @param leftNode - The left hand side of the expression. */ #processOrToken(leftNode: Node): Node { const right = this.#expression(BINDING_POWER.or); @@ -409,7 +408,7 @@ class Parser { * * @example foo && bar * - * @param leftNode The left hand side of the expression. + * @param leftNode - The left hand side of the expression. */ #processAndToken(leftNode: Node): Node { const right = this.#expression(BINDING_POWER.and); @@ -417,6 +416,11 @@ class Parser { return andExpression(leftNode, right); } + /** + * Process a left parenthesis token. + * + * @param leftNode - The left hand side of the expression. + */ #processLParenToken(leftNode: Node): Node { const name = leftNode.value as string; const args = []; @@ -432,6 +436,11 @@ class Parser { return functionExpression(name, args); } + /** + * Process a filter token. + * + * @param leftNode - The left hand side of the expression. + */ #processFilterToken(leftNode: Node): Node { // Filters are projections const condition = this.#expression(0); @@ -446,6 +455,11 @@ class Parser { return filterProjection(leftNode, right, condition); } + /** + * Process a projection right hand side. + * + * @param leftNode - The left hand side of the expression. + */ #processFlattenToken(leftNode: Node): Node { const left = flatten(leftNode); const right = this.#parseProjectionRhs(BINDING_POWER.flatten); @@ -453,6 +467,11 @@ class Parser { return projection(left, right); } + /** + * Process a left bracket token. + * + * @param leftNode - The left hand side of the expression. + */ #processLBracketToken(leftNode: Node): Node { const token = this.#lookaheadToken(0); if (['number', 'colon'].includes(token.type)) { @@ -485,7 +504,7 @@ class Parser { * the error occurred, the value of the token that caused * the error, the type of the token, and an optional reason. * - * @param options The options to use when throwing the error. + * @param options - The options to use when throwing the error. */ #throwParseError(options?: { lexPosition?: number; @@ -575,8 +594,8 @@ class Parser { * Process a projection if the right hand side of the * projection is a slice. * - * @param left The left hand side of the projection. - * @param right The right hand side of the projection. + * @param left - The left hand side of the projection. + * @param right - The right hand side of the projection. */ #projectIfSlice(left: Node, right: Node): Node { const idxExpression = indexExpression([left, right]); @@ -596,8 +615,8 @@ class Parser { * two values. For example `foo == bar` compares the * value of `foo` with the value of `bar`. * - * @param left The left hand side of the comparator. - * @param comparatorChar The comparator character. + * @param left - The left hand side of the comparator. + * @param comparatorChar - The comparator character. */ #parseComparator(left: Node, comparatorChar: Token['type']): Node { return comparator( @@ -663,7 +682,7 @@ class Parser { /** * Process the right hand side of a projection. * - * @param bindingPower The binding power of the current token. + * @param bindingPower - The binding power of the current token. */ #parseProjectionRhs(bindingPower: number): Node { // Parse the right hand side of the projection. @@ -688,7 +707,7 @@ class Parser { /** * Process the right hand side of a dot expression. * - * @param bindingPower The binding power of the current token. + * @param bindingPower - The binding power of the current token. */ #parseDotRhs(bindingPower: number): Node { // From the grammar: @@ -722,7 +741,7 @@ class Parser { /** * Process a token and throw an error if it doesn't match the expected token. * - * @param tokenType The expected token type. + * @param tokenType - The expected token type. */ #match(tokenType: Token['type']): void { const currentToken = this.#currentToken(); @@ -748,7 +767,7 @@ class Parser { /** * Process a token and throw an error if it doesn't match the expected token. * - * @param tokenTypes The expected token types. + * @param tokenTypes - The expected token types. */ #matchMultipleTokens(tokenTypes: Token['type'][]): void { const currentToken = this.#currentToken(); @@ -787,7 +806,7 @@ class Parser { /** * Look ahead in the token stream and get the type of the token * - * @param number The number of tokens to look ahead. + * @param number - The number of tokens to look ahead. */ #lookahead(number: number): Token['type'] { return this.#tokens[this.#index + number].type; @@ -796,7 +815,7 @@ class Parser { /** * Look ahead in the token stream and get the token * - * @param number The number of tokens to look ahead. + * @param number - The number of tokens to look ahead. */ #lookaheadToken(number: number): Token { return this.#tokens[this.#index + number]; diff --git a/packages/jmespath/src/PowertoolsFunctions.ts b/packages/jmespath/src/PowertoolsFunctions.ts index fc353aa8b0..3dd21b8190 100644 --- a/packages/jmespath/src/PowertoolsFunctions.ts +++ b/packages/jmespath/src/PowertoolsFunctions.ts @@ -2,6 +2,7 @@ import { gunzipSync } from 'node:zlib'; import type { JSONValue } from '@aws-lambda-powertools/commons/types'; import { fromBase64 } from '@aws-lambda-powertools/commons/utils/base64'; import { Functions } from './Functions.js'; +import type { search } from './search.js'; const decoder = new TextDecoder('utf-8'); @@ -10,7 +11,7 @@ const decoder = new TextDecoder('utf-8'); * * Built-in JMESPath functions include: `powertools_json`, `powertools_base64`, `powertools_base64_gzip` * - * You can use these functions to decode and/or deserialize JSON objects when using the {@link index.search | search} function. + * You can use these functions to decode and/or deserialize JSON objects when using the {@link search | `search`} function. * * @example * ```typescript @@ -28,9 +29,6 @@ const decoder = new TextDecoder('utf-8'); * ); * console.log(result); // { foo: 'bar' } * ``` - * - * When using the {@link envelopes.extractDataFromEnvelope} function, the PowertoolsFunctions class is automatically used. - * */ class PowertoolsFunctions extends Functions { @Functions.signature({ @@ -58,4 +56,5 @@ class PowertoolsFunctions extends Functions { } } -export { PowertoolsFunctions, Functions }; +export { Functions } from './Functions.js'; +export { PowertoolsFunctions }; diff --git a/packages/jmespath/src/TreeInterpreter.ts b/packages/jmespath/src/TreeInterpreter.ts index 17be0bd49a..a23c0d07b7 100644 --- a/packages/jmespath/src/TreeInterpreter.ts +++ b/packages/jmespath/src/TreeInterpreter.ts @@ -215,10 +215,10 @@ class TreeInterpreter { try { // We know that methodName is a key of this.#functions, but TypeScript - // doesn't know that, so we have to use @ts-ignore to tell it that it's + // doesn't know that, so we suppress the issue to tell it that it's // okay. We could use a type assertion like `as keyof Functions`, but // we also want to keep the args generic, so for now we'll just ignore it. - // @ts-ignore-next-line + // @ts-expect-error return this.#functions[funcName](args); } catch (error) { if ( diff --git a/packages/jmespath/src/ast.ts b/packages/jmespath/src/ast.ts index c0f2eff39f..81bec422f1 100644 --- a/packages/jmespath/src/ast.ts +++ b/packages/jmespath/src/ast.ts @@ -6,9 +6,9 @@ import type { Node } from './types.js'; * * A comparator expression is a binary expression that compares two values. * - * @param name The name of the comparator - * @param first The left-hand side of the comparator - * @param second The right-hand side of the comparator + * @param name - The name of the comparator + * @param first - The left-hand side of the comparator + * @param second - The right-hand side of the comparator */ const comparator = (name: string, first: Node, second: Node): Node => ({ type: 'comparator', @@ -33,7 +33,7 @@ const currentNode = (): Node => ({ * An expression reference is a reference to another expression. * In JMESPath, an expression reference is represented by the `&` symbol. * - * @param expression The expression to reference + * @param expression - The expression to reference */ const expref = (expression: Node): Node => ({ type: 'expref', @@ -49,8 +49,8 @@ const expref = (expression: Node): Node => ({ * * Custom functions can be added by extending the `Functions` class. * - * @param name The name of the function - * @param args The arguments to the function + * @param name - The name of the function + * @param args - The arguments to the function */ const functionExpression = (name: string, args: Node[]): Node => ({ type: 'function_expression', @@ -62,6 +62,8 @@ const functionExpression = (name: string, args: Node[]): Node => ({ * AST node representing a field reference. * * A field reference is a reference to a field in an object. + * + * @param name - The name of the field */ const field = (name: JSONValue): Node => ({ type: 'field', @@ -79,9 +81,9 @@ const field = (name: JSONValue): Node => ({ * For example, `people[?age > 18]` filters the `people` array based on the * `age` field. * - * @param left The left-hand side of the filter projection - * @param right The right-hand side of the filter projection - * @param comparator The comparator to use for the filter + * @param left - The left-hand side of the filter projection + * @param right - The right-hand side of the filter projection + * @param comparator - The comparator to use for the filter */ const filterProjection = (left: Node, right: Node, comparator: Node): Node => ({ type: 'filter_projection', @@ -98,7 +100,7 @@ const filterProjection = (left: Node, right: Node, comparator: Node): Node => ({ * For example, `people[].name` flattens the `people` array and returns the * `name` field of each object in the array. * - * @param node The node to flatten + * @param node - The node to flatten */ const flatten = (node: Node): Node => ({ type: 'flatten', @@ -116,7 +118,7 @@ const identity = (): Node => ({ type: 'identity', children: [] }); * An index reference is a reference to an index in an array. * For example, `people[0]` references the first element in the `people` array. * - * @param index The index to reference + * @param index - The index to reference */ const index = (index: JSONValue): Node => ({ type: 'index', @@ -129,7 +131,7 @@ const index = (index: JSONValue): Node => ({ * * An index expression holds the index and the children of the expression. * - * @param children The children of the index expression + * @param children - The children of the index expression */ const indexExpression = (children: Node[]): Node => ({ type: 'index_expression', @@ -139,8 +141,8 @@ const indexExpression = (children: Node[]): Node => ({ /** * AST node representing a key-value pair. * - * @param keyName The name of the key - * @param node The value of the key + * @param keyName - The name of the key + * @param node - The value of the key */ const keyValPair = (keyName: JSONValue, node: Node): Node => ({ type: 'key_val_pair', @@ -153,7 +155,7 @@ const keyValPair = (keyName: JSONValue, node: Node): Node => ({ * * A literal value is a value that is not a reference to another node. * - * @param literalValue The value of the literal + * @param literalValue - The value of the literal */ const literal = (literalValue: JSONValue): Node => ({ type: 'literal', @@ -166,7 +168,7 @@ const literal = (literalValue: JSONValue): Node => ({ * * A multi-select object is a reference to multiple nodes in an object. * - * @param nodes + * @param nodes - The nodes to select */ const multiSelectObject = (nodes: Node[]): Node => ({ type: 'multi_select_object', @@ -176,7 +178,7 @@ const multiSelectObject = (nodes: Node[]): Node => ({ /** * AST node representing a multi-select list. * - * @param nodes + * @param nodes - The nodes to select */ const multiSelectList = (nodes: Node[]): Node => ({ type: 'multi_select_list', @@ -186,8 +188,8 @@ const multiSelectList = (nodes: Node[]): Node => ({ /** * AST node representing an or expression. * - * @param left The left-hand side of the or expression - * @param right The right-hand side of the or expression + * @param left - The left-hand side of the or expression + * @param right - The right-hand side of the or expression */ const orExpression = (left: Node, right: Node): Node => ({ type: 'or_expression', @@ -197,8 +199,8 @@ const orExpression = (left: Node, right: Node): Node => ({ /** * AST node representing an and expression. * - * @param left The left-hand side of the and expression - * @param right The right-hand side of the and expression + * @param left - The left-hand side of the and expression + * @param right - The right-hand side of the and expression */ const andExpression = (left: Node, right: Node): Node => ({ type: 'and_expression', @@ -208,8 +210,8 @@ const andExpression = (left: Node, right: Node): Node => ({ /** * AST node representing a not expression. * - * @param left The left-hand side of the not expression - * @param right The right-hand side of the not expression + * @param left - The left-hand side of the not expression + * @param right - The right-hand side of the not expression */ const notExpression = (expr: Node): Node => ({ type: 'not_expression', @@ -219,8 +221,8 @@ const notExpression = (expr: Node): Node => ({ /** * AST node representing a pipe expression. * - * @param left The left-hand side of the pipe expression - * @param right The right-hand side of the pipe expression + * @param left - The left-hand side of the pipe expression + * @param right - The right-hand side of the pipe expression */ const pipe = (left: Node, right: Node): Node => ({ type: 'pipe', @@ -230,8 +232,8 @@ const pipe = (left: Node, right: Node): Node => ({ /** * AST node representing a projection. * - * @param left The left-hand side of the projection - * @param right The right-hand side of the projection + * @param left - The left-hand side of the projection + * @param right - The right-hand side of the projection */ const projection = (left: Node, right: Node): Node => ({ type: 'projection', @@ -241,7 +243,7 @@ const projection = (left: Node, right: Node): Node => ({ /** * AST node representing a subexpression. * - * @param children The children of the subexpression + * @param children - The children of the subexpression */ const subexpression = (children: Node[]): Node => ({ type: 'subexpression', @@ -253,9 +255,9 @@ const subexpression = (children: Node[]): Node => ({ * * A slice is a reference to a range of values in an array. * - * @param start The start of the slice - * @param end The end of the slice - * @param step The step of the slice + * @param start - The start of the slice + * @param end - The end of the slice + * @param step - The step of the slice */ const slice = (start: JSONValue, end: JSONValue, step: JSONValue): Node => ({ type: 'slice', @@ -265,8 +267,8 @@ const slice = (start: JSONValue, end: JSONValue, step: JSONValue): Node => ({ /** * AST node representing a value projection. * - * @param left The left-hand side of the value projection - * @param right The right-hand side of the value projection + * @param left - The left-hand side of the value projection + * @param right - The right-hand side of the value projection */ const valueProjection = (left: Node, right: Node): Node => ({ type: 'value_projection', diff --git a/packages/jmespath/src/constants.ts b/packages/jmespath/src/constants.ts index de078f8754..0697ea8898 100644 --- a/packages/jmespath/src/constants.ts +++ b/packages/jmespath/src/constants.ts @@ -42,26 +42,32 @@ const BINDING_POWER = { * The set of ASCII lowercase letters allowed in JMESPath identifiers. */ const ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz'; + /** * The set of ASCII uppercase letters allowed in JMESPath identifiers. */ const ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + /** * The set of ASCII letters allowed in JMESPath identifiers. */ const ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE; + /** * The set of ASCII digits allowed in JMESPath identifiers. */ const DIGITS = '0123456789'; + /** * The set of ASCII letters and digits allowed in JMESPath identifiers. */ const START_IDENTIFIER = new Set(`${ASCII_LETTERS}_`); + /** * The set of ASCII letters and digits allowed in JMESPath identifiers. */ const VALID_IDENTIFIER = new Set(`${ASCII_LETTERS}${DIGITS}_`); + /** * The set of ASCII digits allowed in JMESPath identifiers. */ @@ -70,6 +76,7 @@ const VALID_NUMBER = new Set(DIGITS); * The set of ASCII whitespace characters allowed in JMESPath identifiers. */ const WHITESPACE = new Set(' \t\n\r'); + /** * The set of simple tokens in the JMESPath grammar. */ diff --git a/packages/jmespath/src/envelopes.ts b/packages/jmespath/src/envelopes.ts index 32107f0a5d..12400004de 100644 --- a/packages/jmespath/src/envelopes.ts +++ b/packages/jmespath/src/envelopes.ts @@ -50,9 +50,9 @@ import type { JMESPathParsingOptions, JSONObject } from './types.js'; * }; * ``` * - * @param data The JSON object to search - * @param envelope The JMESPath expression to use - * @param options The parsing options to use + * @param data - The JSON object to search + * @param envelope - The JMESPath expression to use + * @param options - The parsing options to use */ const extractDataFromEnvelope = ( data: JSONObject, diff --git a/packages/jmespath/src/errors.ts b/packages/jmespath/src/errors.ts index 87ec88ed0e..dd3958ac32 100644 --- a/packages/jmespath/src/errors.ts +++ b/packages/jmespath/src/errors.ts @@ -23,7 +23,7 @@ class JMESPathError extends Error { * thrown. In some instances the expression is not known until after the * error is thrown (i.e. the error is thrown down the call stack). * - * @param expression The expression that was being parsed when the error occurred. + * @param expression - The expression that was being parsed when the error occurred. */ public setExpression(expression: string): void { this.expression = expression; @@ -163,7 +163,7 @@ class FunctionError extends JMESPathError { * alias. To avoid passing the function name down the call stack, we set it * after the error is thrown. * - * @param functionName The function that was being validated or executed when the error occurred. + * @param functionName - The function that was being validated or executed when the error occurred. */ public setEvaluatedFunctionName(functionName: string): void { this.message = this.message.replace( @@ -202,6 +202,12 @@ class ArityError extends FunctionError { }, received ${this.actualArity}`; } + /** + * Pluralizes a word based on the count. + * + * @param word - The word to pluralize + * @param count - The count to determine if the word should be pluralized + */ protected pluralize(word: string, count: number): string { return count === 1 ? word : `${word}s`; } @@ -262,6 +268,9 @@ class JMESPathTypeError extends FunctionError { }"`; } + /** + * Serialize the expected types for the error message. + */ protected serializeExpectedTypes(): string { const types: string[] = []; for (const type of this.expectedTypes) { diff --git a/packages/jmespath/src/search.ts b/packages/jmespath/src/search.ts index 44afcd61c4..79381b3e80 100644 --- a/packages/jmespath/src/search.ts +++ b/packages/jmespath/src/search.ts @@ -45,9 +45,9 @@ const parser = new Parser(); * console.log(result); // { foo: 'bar' } * ``` * - * @param expression The JMESPath expression to use - * @param data The JSON object to search - * @param options The parsing options to use + * @param expression - The JMESPath expression to use + * @param data - The JSON object to search + * @param options - The parsing options to use */ const search = ( expression: string, diff --git a/packages/jmespath/src/types.ts b/packages/jmespath/src/types.ts index 485529d9f1..2cb0727b1e 100644 --- a/packages/jmespath/src/types.ts +++ b/packages/jmespath/src/types.ts @@ -81,8 +81,8 @@ type FunctionSignatureDecorator = ( * } * ``` * - * @param argumentsSpecs The expected arguments for the function. - * @param variadic Whether the function is variadic. + * @param argumentsSpecs - The expected arguments for the function. + * @param variadic - Whether the function is variadic. */ type FunctionSignatureOptions = { argumentsSpecs: Array>; diff --git a/packages/jmespath/src/utils.ts b/packages/jmespath/src/utils.ts index 9a27909c98..3751b98093 100644 --- a/packages/jmespath/src/utils.ts +++ b/packages/jmespath/src/utils.ts @@ -16,7 +16,7 @@ import { ArityError, JMESPathTypeError, VariadicArityError } from './errors.js'; * this reason we wrap the original isTruthy function from the commons package * and add a check for numbers. * - * @param value The value to check + * @param value - The value to check */ const isTruthy = (value: unknown): boolean => { if (isNumber(value)) { @@ -30,9 +30,9 @@ const isTruthy = (value: unknown): boolean => { * Cap a slice range value to the length of an array, taking into account * negative values and whether the step is negative. * - * @param arrayLength The length of the array - * @param value The value to cap - * @param isStepNegative Whether the step is negative + * @param arrayLength - The length of the array + * @param value - The value to cap + * @param isStepNegative - Whether the step is negative */ const capSliceRange = ( arrayLength: number, @@ -54,24 +54,24 @@ const capSliceRange = ( /** * Given a start, stop, and step value, the sub elements in an array are extracted as follows: - * * The first element in the extracted array is the index denoted by start. - * * The last element in the extracted array is the index denoted by end - 1. - * * The step value determines how many indices to skip after each element is selected from the array. An array of 1 (the default step) will not skip any indices. A step value of 2 will skip every other index while extracting elements from an array. A step value of -1 will extract values in reverse order from the array. + * - The first element in the extracted array is the index denoted by start. + * - The last element in the extracted array is the index denoted by end - 1. + * - The step value determines how many indices to skip after each element is selected from the array. An array of 1 (the default step) will not skip any indices. A step value of 2 will skip every other index while extracting elements from an array. A step value of -1 will extract values in reverse order from the array. * * Slice expressions adhere to the following rules: - * * If a negative start position is given, it is calculated as the total length of the array plus the given start position. - * * If no start position is given, it is assumed to be 0 if the given step is greater than 0 or the end of the array if the given step is less than 0. - * * If a negative stop position is given, it is calculated as the total length of the array plus the given stop position. - * * If no stop position is given, it is assumed to be the length of the array if the given step is greater than 0 or 0 if the given step is less than 0. - * * If the given step is omitted, it it assumed to be 1. - * * If the given step is 0, an invalid-value error MUST be raised (thrown before calling the function) - * * If the element being sliced is not an array, the result is null (returned before calling the function) - * * If the element being sliced is an array and yields no results, the result MUST be an empty array. + * - If a negative start position is given, it is calculated as the total length of the array plus the given start position. + * - If no start position is given, it is assumed to be 0 if the given step is greater than 0 or the end of the array if the given step is less than 0. + * - If a negative stop position is given, it is calculated as the total length of the array plus the given stop position. + * - If no stop position is given, it is assumed to be the length of the array if the given step is greater than 0 or 0 if the given step is less than 0. + * - If the given step is omitted, it it assumed to be 1. + * - If the given step is 0, an invalid-value error MUST be raised (thrown before calling the function) + * - If the element being sliced is not an array, the result is null (returned before calling the function) + * - If the element being sliced is an array and yields no results, the result MUST be an empty array. * - * @param array The array to slice - * @param start The start index - * @param end The end index - * @param step The step value + * @param array - The array to slice + * @param start - The start index + * @param end - The end index + * @param step - The step value */ const sliceArray = ({ array, @@ -119,10 +119,10 @@ const sliceArray = ({ * greater than or equal to the expected arity. If the number of arguments passed to the function * is less than the expected arity, a `VariadicArityError` is thrown. * - * @param args The arguments passed to the function - * @param argumentsSpecs The expected types for each argument - * @param decoratedFuncName The name of the function being called - * @param variadic Whether the function is variadic + * @param args - The arguments passed to the function + * @param argumentsSpecs - The expected types for each argument + * @param decoratedFuncName - The name of the function being called + * @param variadic - Whether the function is variadic */ const arityCheck = ( args: unknown[], @@ -160,8 +160,8 @@ const arityCheck = ( * passes. If the argument does not match any of the types, then * a JMESPathTypeError is thrown. * - * @param args The arguments passed to the function - * @param argumentsSpecs The expected types for each argument + * @param args - The arguments passed to the function + * @param argumentsSpecs - The expected types for each argument */ const typeCheck = ( args: unknown[], @@ -182,24 +182,24 @@ const typeCheck = ( * passes. If the argument does not match any of the types, then * a JMESPathTypeError is thrown. * - * @param arg - * @param argumentSpec + * @param arg - The argument to check + * @param argumentSpec - The expected types for the argument */ const typeCheckArgument = (arg: unknown, argumentSpec: Array): void => { let valid = false; - argumentSpec.forEach((type, index) => { + for (const [index, type] of argumentSpec.entries()) { if (valid) return; valid = checkIfArgumentTypeIsValid(arg, type, index, argumentSpec); - }); + } }; /** * Check if the argument is of the expected type. * - * @param arg The argument to check - * @param type The expected type - * @param index The index of the type we are checking - * @param argumentSpec The list of types to check against + * @param arg - The argument to check + * @param type - The expected type + * @param index - The index of the type we are checking + * @param argumentSpec - The list of types to check against */ const checkIfArgumentTypeIsValid = ( arg: unknown, @@ -243,10 +243,10 @@ const checkIfArgumentTypeIsValid = ( /** * Check if the argument is of the expected type. * - * @param arg The argument to check - * @param type The type to check against - * @param argumentSpec The list of types to check against - * @param hasMoreTypesToCheck Whether there are more types to check + * @param arg - The argument to check + * @param type - The type to check against + * @param argumentSpec - The list of types to check against + * @param hasMoreTypesToCheck - Whether there are more types to check */ const typeCheckType = ( arg: unknown, @@ -266,9 +266,9 @@ const typeCheckType = ( /** * Check if the argument is an array of complex types. * - * @param arg The argument to check - * @param type The type to check against - * @param hasMoreTypesToCheck Whether there are more types to check + * @param arg - The argument to check + * @param type - The type to check against + * @param hasMoreTypesToCheck - Whether there are more types to check */ const checkComplexArrayType = ( arg: unknown[], @@ -293,9 +293,9 @@ const checkComplexArrayType = ( /** * Check if the argument is an expression. * - * @param arg The argument to check - * @param type The type to check against - * @param hasMoreTypesToCheck Whether there are more types to check + * @param arg - The argument to check + * @param type - The type to check against + * @param hasMoreTypesToCheck - Whether there are more types to check */ const checkExpressionType = ( arg: unknown, @@ -314,9 +314,9 @@ const checkExpressionType = ( /** * Check if the argument is an object. * - * @param arg The argument to check - * @param type The type to check against - * @param hasMoreTypesToCheck Whether there are more types to check + * @param arg - The argument to check + * @param type - The type to check against + * @param hasMoreTypesToCheck - Whether there are more types to check */ const checkObjectType = ( arg: unknown, diff --git a/packages/kafka/package.json b/packages/kafka/package.json index 95bdf94360..783d42b4a0 100644 --- a/packages/kafka/package.json +++ b/packages/kafka/package.json @@ -22,6 +22,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js .", "proto:gen": "npx pbjs -t static-module -w es6 -o $(pwd)/tests/protos/product.es6.generated.js $(pwd)/tests/protos/product.proto && npx pbts -o $(pwd)/tests/protos/product.es6.generated.d.ts $(pwd)/tests/protos/product.es6.generated.js && npx pbjs -t static-module -w commonjs -o $(pwd)/tests/protos/product.cjs.generated.js $(pwd)/tests/protos/product.proto && npx pbts -o $(pwd)/tests/protos/product.cjs.generated.d.ts $(pwd)/tests/protos/product.cjs.generated.js" }, diff --git a/packages/kafka/tests/unit/consumer.test.ts b/packages/kafka/tests/unit/consumer.test.ts index 086a1a090d..8448d846a9 100644 --- a/packages/kafka/tests/unit/consumer.test.ts +++ b/packages/kafka/tests/unit/consumer.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { Context } from 'aws-lambda'; import { describe, expect, it } from 'vitest'; import { z } from 'zod/v4'; @@ -145,6 +146,7 @@ describe('Kafka consumer', () => { for (const record of event.records) { try { results.push(record.value); + await setTimeout(1); // simulate some processing time } catch (error) { return error; } @@ -247,6 +249,7 @@ describe('Kafka consumer', () => { for (const record of event.records) { try { const { value, key } = record; + await setTimeout(1); // simulate some processing time results.push([value, key]); } catch (error) { return error; @@ -444,6 +447,7 @@ describe('Kafka consumer', () => { try { const { value } = record; results.push(value); + await setTimeout(1); // simulate some processing time } catch (error) { results.push(error); } @@ -480,6 +484,7 @@ describe('Kafka consumer', () => { try { const { value } = record; results.push(value); + await setTimeout(1); // simulate some processing time } catch (error) { results.push(error); } @@ -531,6 +536,7 @@ describe('Kafka consumer', () => { try { const { value } = record; results.push(value); + await setTimeout(1); // simulate some processing time } catch (error) { results.push(error); } diff --git a/packages/kafka/tests/unit/deserializer.avro.test.ts b/packages/kafka/tests/unit/deserializer.avro.test.ts index 9893b5c258..7edccd5960 100644 --- a/packages/kafka/tests/unit/deserializer.avro.test.ts +++ b/packages/kafka/tests/unit/deserializer.avro.test.ts @@ -21,7 +21,7 @@ describe('Avro Deserializer: ', () => { expect(await deserialize(message, schema)).toEqual(expected); }); - it('throws when avro deserialiser fails', async () => { + it('throws when avro deserialiser fails', () => { // Prepare const message = '0g8MTGFwdG9wUrgehes/j0A='; const schema = `{ @@ -39,7 +39,7 @@ describe('Avro Deserializer: ', () => { ); }); - it('throws when avro deserialiser has not matching schema', async () => { + it('throws when avro deserialiser has not matching schema', () => { // Prepare const message = '0g8MTGFwdG9wUrgehes/j0A='; const schema = `{ diff --git a/packages/logger/package.json b/packages/logger/package.json index e0fe39665d..a5efab62aa 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/logger#readme", diff --git a/packages/logger/src/formatter/LogFormatter.ts b/packages/logger/src/formatter/LogFormatter.ts index 55bfe7088c..be14cc537d 100644 --- a/packages/logger/src/formatter/LogFormatter.ts +++ b/packages/logger/src/formatter/LogFormatter.ts @@ -139,7 +139,7 @@ abstract class LogFormatter { /** * If a specific timezone is configured and it's not the default `UTC`, * format the timestamp with the appropriate timezone offset. - **/ + */ const configuredTimezone = getStringFromEnv({ key: 'TZ', defaultValue: '', diff --git a/packages/logger/src/middleware/middy.ts b/packages/logger/src/middleware/middy.ts index 3bd10004ac..e674adab64 100644 --- a/packages/logger/src/middleware/middy.ts +++ b/packages/logger/src/middleware/middy.ts @@ -81,7 +81,7 @@ const injectLambdaContext = ( }; }; - const before = async (request: MiddyLikeRequest): Promise => { + const before = (request: MiddyLikeRequest) => { for (const logger of loggers) { if (isResetStateEnabled) { setCleanupFunction(request); @@ -102,7 +102,7 @@ const injectLambdaContext = ( } }; - const after = async (): Promise => { + const after = () => { for (const logger of loggers) { logger.clearBuffer(); @@ -112,7 +112,7 @@ const injectLambdaContext = ( } }; - const onError = async ({ error }: { error: unknown }): Promise => { + const onError = ({ error }: { error: unknown }) => { for (const logger of loggers) { if (options?.flushBufferOnUncaughtError) { logger.flushBuffer(); diff --git a/packages/logger/tests/e2e/advancedUses.test.FunctionCode.ts b/packages/logger/tests/e2e/advancedUses.test.FunctionCode.ts index 1e601c6097..93f7f19c24 100644 --- a/packages/logger/tests/e2e/advancedUses.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/advancedUses.test.FunctionCode.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import { Logger } from '@aws-lambda-powertools/logger'; import { correlationPaths, @@ -18,7 +19,7 @@ const logger = new Logger({ logger.debug('a never buffered debug log'); -export const handlerManual = async (event: unknown) => { +export const handlerManual = (event: unknown) => { logger.addContext({} as Context); // we want only the cold start value logger.setCorrelationId(event, correlationPaths.EVENT_BRIDGE); @@ -42,7 +43,7 @@ export const handlerMiddy = middy() flushBufferOnUncaughtError: true, }) ) - .handler(async () => { + .handler(() => { logger.debug('a buffered debug log'); logger.info('an info log'); throw new Error('ops'); @@ -56,6 +57,7 @@ class Lambda { public async handler(_event: unknown, _context: Context) { logger.debug('a buffered debug log'); logger.info('an info log'); + await setTimeout(1); // simulate async work throw new Error('ops'); } } diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.FunctionCode.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.FunctionCode.ts index 258c89299b..2765446681 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.FunctionCode.ts @@ -26,10 +26,10 @@ const logger = new Logger({ }, }); -const testFunction = async ( +const testFunction = ( _event: TestEvent, context: Context -): TestOutput => { +): Awaited => { // Test feature 1: Context data injection (all logs should have the same context data) // Test feature 2: Event log (this log should have the event data) // Test feature 3: Log level filtering (log level is set to INFO) diff --git a/packages/logger/tests/e2e/basicFeatures.middy.test.ts b/packages/logger/tests/e2e/basicFeatures.middy.test.ts index 56d0c28b19..5cef007bbc 100644 --- a/packages/logger/tests/e2e/basicFeatures.middy.test.ts +++ b/packages/logger/tests/e2e/basicFeatures.middy.test.ts @@ -65,7 +65,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('Log level filtering', () => { - it('should filter log based on POWERTOOLS_LOG_LEVEL (INFO) environment variable in Lambda', async () => { + it('should filter log based on POWERTOOLS_LOG_LEVEL (INFO) environment variable in Lambda', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation and filter by level const debugLogs = invocationLogs[i].getFunctionLogs('DEBUG'); @@ -76,7 +76,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('Context data', () => { - it('should inject context info in each log', async () => { + it('should inject context info in each log', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation const logMessages = invocationLogs[i].getFunctionLogs(); @@ -92,7 +92,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { } }); - it('should include coldStart equal to TRUE only on the first invocation, FALSE otherwise', async () => { + it('should include coldStart equal to TRUE only on the first invocation, FALSE otherwise', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation const logMessages = invocationLogs[i].getFunctionLogs(); @@ -109,7 +109,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); }); - it('logs the event for every invocation, only once, and without keys from previous invocations', async () => { + it('logs the event for every invocation, only once, and without keys from previous invocations', () => { const { RUNTIME_ADDED_KEY: runtimeAddedKey } = commonEnvironmentVars; for (let i = 0; i < invocationCount; i++) { @@ -132,7 +132,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('Persistent additional log keys and values', () => { - it('should contain persistent value in every log', async () => { + it('should contain persistent value in every log', () => { const { PERSISTENT_KEY: persistentKey, PERSISTENT_VALUE: persistentValue, @@ -151,7 +151,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { } }); - it('should not contain persistent keys that were removed on runtime', async () => { + it('should not contain persistent keys that were removed on runtime', () => { const { REMOVABLE_KEY: removableKey, REMOVABLE_VALUE: removableValue } = commonEnvironmentVars; @@ -176,7 +176,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('One-time additional log keys and values', () => { - it('should log additional keys and value only once', async () => { + it('should log additional keys and value only once', () => { const { SINGLE_LOG_ITEM_KEY: singleLogItemKey, SINGLE_LOG_ITEM_VALUE: singleLogItemValue, @@ -200,7 +200,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('Error logging', () => { - it('should log error only once', async () => { + it('should log error only once', () => { const { ERROR_MSG: errorMsg } = commonEnvironmentVars; for (let i = 0; i < invocationCount; i++) { @@ -226,7 +226,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('Arbitrary object logging', () => { - it('should log additional arbitrary object only once', async () => { + it('should log additional arbitrary object only once', () => { const { ARBITRARY_OBJECT_KEY: objectKey, ARBITRARY_OBJECT_DATA: objectData, @@ -263,7 +263,7 @@ describe('Logger E2E tests, basic functionalities middy usage', () => { }); describe('X-Ray Trace ID injection', () => { - it('should inject & parse the X-Ray Trace ID of the current invocation into every log', async () => { + it('should inject & parse the X-Ray Trace ID of the current invocation into every log', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation const logMessages = invocationLogs[i].getFunctionLogs(); diff --git a/packages/logger/tests/e2e/childLogger.manual.test.FunctionCode.ts b/packages/logger/tests/e2e/childLogger.manual.test.FunctionCode.ts index f7ba75b2a4..a01c6b8005 100644 --- a/packages/logger/tests/e2e/childLogger.manual.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/childLogger.manual.test.FunctionCode.ts @@ -20,10 +20,10 @@ const childLogger = parentLogger.createChild({ logLevel: CHILD_LOG_LEVEL, }); -export const handler = async ( +export const handler = ( _event: TestEvent, context: Context -): TestOutput => { +): Awaited => { parentLogger.addContext(context); childLogger.info(CHILD_LOG_MSG); diff --git a/packages/logger/tests/e2e/childLogger.manual.test.ts b/packages/logger/tests/e2e/childLogger.manual.test.ts index cc90c4be84..a4a7953871 100644 --- a/packages/logger/tests/e2e/childLogger.manual.test.ts +++ b/packages/logger/tests/e2e/childLogger.manual.test.ts @@ -57,7 +57,7 @@ describe('Logger E2E tests, child logger', () => { }); describe('Child logger', () => { - it('should not log at same level of parent because of its own logLevel', async () => { + it('should not log at same level of parent because of its own logLevel', () => { const { PARENT_LOG_MSG: parentLogMsg, CHILD_LOG_MSG: childLogMsg } = commonEnvironmentVars; @@ -77,7 +77,7 @@ describe('Logger E2E tests, child logger', () => { } }); - it('should log only level passed to a child', async () => { + it('should log only level passed to a child', () => { const { CHILD_LOG_MSG: childLogMsg } = commonEnvironmentVars; for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation @@ -95,7 +95,7 @@ describe('Logger E2E tests, child logger', () => { } }); - it('should NOT inject context into the child logger', async () => { + it('should NOT inject context into the child logger', () => { const { CHILD_LOG_MSG: childLogMsg } = commonEnvironmentVars; for (let i = 0; i < invocationCount; i++) { @@ -118,7 +118,7 @@ describe('Logger E2E tests, child logger', () => { } }); - it('both logger instances should have the same persistent key/value', async () => { + it('both logger instances should have the same persistent key/value', () => { const { PERSISTENT_KEY: persistentKey } = commonEnvironmentVars; for (let i = 0; i < invocationCount; i++) { diff --git a/packages/logger/tests/e2e/logEventEnvVarSetting.middy.test.ts b/packages/logger/tests/e2e/logEventEnvVarSetting.middy.test.ts index 63d1ed86ad..b303fde3e1 100644 --- a/packages/logger/tests/e2e/logEventEnvVarSetting.middy.test.ts +++ b/packages/logger/tests/e2e/logEventEnvVarSetting.middy.test.ts @@ -60,7 +60,7 @@ describe('Logger E2E tests, log event via env var setting with middy', () => { }); describe('Log event', () => { - it('should log the event as the first log of each invocation only', async () => { + it('should log the event as the first log of each invocation only', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation const logMessages = invocationLogs[i].getFunctionLogs(); diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts index b0cd540189..cf253ff8f8 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.FunctionCode.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import type { Context } from 'aws-lambda'; import { Logger } from '../../src/index.js'; @@ -16,6 +17,7 @@ class Lambda implements LambdaInterface { @logger.injectLambdaContext() public async handler(_event: TestEvent, context: Context): TestOutput { this.printLogInAllLevels(); + await setTimeout(1); // simulate some async work return { requestId: context.awsRequestId, diff --git a/packages/logger/tests/e2e/sampleRate.decorator.test.ts b/packages/logger/tests/e2e/sampleRate.decorator.test.ts index 2be3ea594a..27d888c3b1 100644 --- a/packages/logger/tests/e2e/sampleRate.decorator.test.ts +++ b/packages/logger/tests/e2e/sampleRate.decorator.test.ts @@ -59,7 +59,7 @@ describe('Logger E2E tests, sample rate and injectLambdaContext()', () => { }); describe('Enabling sample rate', () => { - it('should log all levels based on given sample rate, not just ERROR', async () => { + it('should log all levels based on given sample rate, not just ERROR', () => { // Fetch log streams from all invocations let countSampled = 0; let countNotSampled = 0; @@ -94,7 +94,7 @@ describe('Logger E2E tests, sample rate and injectLambdaContext()', () => { }); describe('Decorator injectLambdaContext()', () => { - it('should inject Lambda context into every log emitted', async () => { + it('should inject Lambda context into every log emitted', () => { for (let i = 0; i < invocationCount; i++) { // Get log messages of the invocation const logMessages = invocationLogs[i].getFunctionLogs('ERROR'); diff --git a/packages/logger/tests/helpers/resources.ts b/packages/logger/tests/helpers/resources.ts index e7db1993e4..ea811f8eea 100644 --- a/packages/logger/tests/helpers/resources.ts +++ b/packages/logger/tests/helpers/resources.ts @@ -5,7 +5,7 @@ import type { TestNodejsFunctionProps, } from '@aws-lambda-powertools/testing-utils/types'; import { CfnOutput } from 'aws-cdk-lib'; -import { commonEnvironmentVars } from '../e2e/constants'; +import { commonEnvironmentVars } from '../e2e/constants.js'; interface LoggerExtraTestProps extends ExtraTestProps { logGroupOutputKey?: string; diff --git a/packages/logger/tests/unit/injectLambdaContext.test.ts b/packages/logger/tests/unit/injectLambdaContext.test.ts index 48ad337693..4bc6bcec53 100644 --- a/packages/logger/tests/unit/injectLambdaContext.test.ts +++ b/packages/logger/tests/unit/injectLambdaContext.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; import type { Context } from 'aws-lambda'; @@ -78,7 +79,7 @@ describe('Inject Lambda Context', () => { it('adds the context to log messages when the feature is enabled in the Middy.js middleware', async () => { // Prepare const logger = new Logger(); - const handler = middy(async () => { + const handler = middy(() => { logger.info('Hello, world!'); }).use(injectLambdaContext(logger)); @@ -100,7 +101,7 @@ describe('Inject Lambda Context', () => { // Prepare const logger1 = new Logger({ serviceName: 'parent' }); const logger2 = logger1.createChild({ serviceName: 'child' }); - const handler = middy(async () => { + const handler = middy(() => { logger1.info('Hello, world!'); logger2.info('Hello, world!'); }).use(injectLambdaContext([logger1, logger2])); @@ -140,6 +141,7 @@ describe('Inject Lambda Context', () => { @logger.injectLambdaContext() async handler(_event: unknown, _context: Context) { + await setTimeout(1); // simulate some async operation this.logGreeting(); } @@ -189,7 +191,7 @@ describe('Inject Lambda Context', () => { { case: 'middleware', getHandler: (logger: Logger) => - middy(async () => { + middy(() => { logger.info('Hello, world!'); }).use(injectLambdaContext(logger)), }, @@ -202,6 +204,7 @@ describe('Inject Lambda Context', () => { _event: unknown, _context: Context ): Promise { + await setTimeout(1); // simulate some async operation logger.info('test'); } } @@ -245,6 +248,7 @@ describe('Inject Lambda Context', () => { case: 'middleware', getHandler: (logger: Logger) => middy(async () => { + await setTimeout(1); // simulate some async operation logger.info('Hello, world!'); }).use( injectLambdaContext(logger, { @@ -263,6 +267,7 @@ describe('Inject Lambda Context', () => { _event: unknown, _context: Context ): Promise { + await setTimeout(1); // simulate some async operation logger.info('Hello, world!'); } } @@ -306,7 +311,7 @@ describe('Inject Lambda Context', () => { }, }; // Act - const handler = middy(async () => { + const handler = middy(() => { logger.info('Hello, world!'); }).use( injectLambdaContext(logger, { @@ -329,7 +334,7 @@ describe('Inject Lambda Context', () => { const logger = new Logger({ correlationIdSearchFn: search }); // Act - Use middleware which will internally call setCorrelationIdFromPath - const handler = middy(async () => { + const handler = middy(() => { logger.info('Hello, world!'); }).use( injectLambdaContext(logger, { @@ -388,7 +393,7 @@ describe('Inject Lambda Context', () => { it('uses the API_GATEWAY_REST predefined path to extract correlation ID', async () => { // Prepare const logger = new Logger({ correlationIdSearchFn: search }); - const handler = middy(async () => { + const handler = middy(() => { logger.info('Using API Gateway request ID'); }).use( injectLambdaContext(logger, { @@ -419,7 +424,7 @@ describe('Inject Lambda Context', () => { { case: 'middleware', getHandler: (logger: Logger) => - middy(async (event: { id: number }) => { + middy((event: { id: number }) => { logger.info('Processing event'); logger.appendKeys({ id: event.id }); throw new Error('Test error'); @@ -440,6 +445,7 @@ describe('Inject Lambda Context', () => { event: { id: number }, _context: Context ): Promise { + await setTimeout(1); // simulate some async operation logger.info('Processing event'); logger.appendKeys({ id: event.id }); throw new Error('Test error'); @@ -480,7 +486,7 @@ describe('Inject Lambda Context', () => { { case: 'middleware', getHandler: (logger: Logger) => - middy(async (event: { id: number }) => { + middy((event: { id: number }) => { logger.info('Processing event'); logger.appendKeys({ id: event.id }); return true; @@ -498,6 +504,7 @@ describe('Inject Lambda Context', () => { resetKeys: true, }) public async handler(event: { id: number }, _context: Context) { + await setTimeout(1); // simulate some async operation logger.info('Processing event'); logger.appendKeys({ id: event.id }); return true; diff --git a/packages/logger/tests/unit/logBuffer.test.ts b/packages/logger/tests/unit/logBuffer.test.ts index ca1febdbce..c3d54133b5 100644 --- a/packages/logger/tests/unit/logBuffer.test.ts +++ b/packages/logger/tests/unit/logBuffer.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; import middy from 'middy5'; @@ -283,7 +284,7 @@ describe('Buffer logs', () => { .use( injectLambdaContext(logger, { flushBufferOnUncaughtError: true }) ) - .handler(async () => { + .handler(() => { logger.debug('This is a log message'); logger.info('This is an info message'); throw new Error('This is an error'); @@ -297,6 +298,7 @@ describe('Buffer logs', () => { async handler(_event: unknown, _context: Context) { logger.debug('This is a log message'); logger.info('This is an info message'); + await setTimeout(1); // simulate some async operation throw new Error('This is an error'); } } @@ -342,7 +344,7 @@ describe('Buffer logs', () => { .use( injectLambdaContext(logger, { flushBufferOnUncaughtError: false }) ) - .handler(async () => { + .handler(() => { logger.debug('This is a log message'); logger.info('This is an info message'); throw new Error('This is an error'); @@ -356,6 +358,7 @@ describe('Buffer logs', () => { async handler(_event: unknown, _context: Context) { logger.debug('This is a log message'); logger.info('This is an info message'); + await setTimeout(1); // simulate some async operation throw new Error('This is an error'); } } diff --git a/packages/logger/tests/unit/logEvent.test.ts b/packages/logger/tests/unit/logEvent.test.ts index a694e96984..e0388ebeac 100644 --- a/packages/logger/tests/unit/logEvent.test.ts +++ b/packages/logger/tests/unit/logEvent.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; import type { Context } from 'aws-lambda'; @@ -86,6 +87,7 @@ describe('Log event', () => { class Test { @logger.injectLambdaContext({ logEvent: true }) async handler(event: unknown, _context: Context) { + await setTimeout(1); // simulate some async operation return event; } } diff --git a/packages/logger/tests/unit/setPowertoolsLogData.test.ts b/packages/logger/tests/unit/setPowertoolsLogData.test.ts deleted file mode 100644 index 696aec4438..0000000000 --- a/packages/logger/tests/unit/setPowertoolsLogData.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Logger } from '../../src/Logger.js'; -import { describe, it, expect, vi, afterEach } from 'vitest'; - -describe('Logger PowertoolsLogData environment variables', () => { - afterEach(() => { - vi.unstubAllEnvs(); - }); - - it.each([ - [undefined, ''], - ['us-west-2', 'us-west-2'], - ])('sets awsRegion to %s when AWS_REGION env var is %s', (envValue, expected) => { - // Prepare - vi.stubEnv('AWS_REGION', envValue); - // Act - const logger = new Logger(); - // Assess - expect(logger['powertoolsLogData'].awsRegion).toBe(expected); - }); - - it.each([ - [undefined, ''], - ['prd', 'prd'], - ])('sets environment to %s when ENVIRONMENT env var is %s', (envValue, expected) => { - // Prepare - vi.stubEnv('ENVIRONMENT', envValue); - // Act - const logger = new Logger(); - // Assess - expect(logger['powertoolsLogData'].environment).toBe(expected); - }); - - it.each([ - [undefined, 'service_undefined'], - ['my-service', 'my-service'], - ])('sets serviceName to %s when POWERTOOLS_SERVICE_NAME env var is %s', (envValue, expected) => { - // Prepare - vi.stubEnv('POWERTOOLS_SERVICE_NAME', envValue); - // Act - const logger = new Logger(); - // Assess - expect(logger['powertoolsLogData'].serviceName).toBe(expected); - }); -}); \ No newline at end of file diff --git a/packages/logger/tests/unit/workingWithkeys.test.ts b/packages/logger/tests/unit/workingWithkeys.test.ts index ca18259ef3..f921ef9b12 100644 --- a/packages/logger/tests/unit/workingWithkeys.test.ts +++ b/packages/logger/tests/unit/workingWithkeys.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; import type { Context } from 'aws-lambda'; @@ -439,7 +440,7 @@ describe('Working with keys', () => { foo: 'bar', }, }); - const handler = middy(async (addKey: boolean) => { + const handler = middy((addKey: boolean) => { if (addKey) { logger.appendKeys({ foo: 'baz', @@ -483,6 +484,7 @@ describe('Working with keys', () => { foo: 'baz', }); } + await setTimeout(1); // simulate some async operation logger.info('Hello, world!'); } } diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 9f7ed0f48e..dbf2fc043f 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/metrics#readme", diff --git a/packages/metrics/src/Metrics.ts b/packages/metrics/src/Metrics.ts index f5f40b3abe..ad9e2de7f4 100644 --- a/packages/metrics/src/Metrics.ts +++ b/packages/metrics/src/Metrics.ts @@ -49,10 +49,10 @@ import type { * These metrics can be visualized through Amazon CloudWatch Console. * * **Key features** - * * Aggregating up to 100 metrics using a single CloudWatch EMF object (large JSON blob). - * * Validating your metrics against common metric definitions mistakes (for example, metric unit, values, max dimensions, max metrics). - * * Metrics are created asynchronously by the CloudWatch service. You do not need any custom stacks, and there is no impact to Lambda function latency. - * * Creating a one-off metric with different dimensions. + * - Aggregating up to 100 metrics using a single CloudWatch EMF object (large JSON blob). + * - Validating your metrics against common metric definitions mistakes (for example, metric unit, values, max dimensions, max metrics). + * - Metrics are created asynchronously by the CloudWatch service. You do not need any custom stacks, and there is no impact to Lambda function latency. + * - Creating a one-off metric with different dimensions. * * After initializing the Metrics class, you can add metrics using the {@link Metrics.addMetric | `addMetric()`} method. * The metrics are stored in a buffer and are flushed when calling {@link Metrics.publishStoredMetrics | `publishStoredMetrics()`}. @@ -1164,7 +1164,7 @@ class Metrics extends Utility implements MetricsInterface { * If this point is reached, it indicates timestamp was neither a valid number nor Date * Returning zero represents the initial date of epoch time, * which will be skipped by Amazon CloudWatch. - **/ + */ return 0; } diff --git a/packages/metrics/src/middleware/middy.ts b/packages/metrics/src/middleware/middy.ts index d06a0977e4..665261758b 100644 --- a/packages/metrics/src/middleware/middy.ts +++ b/packages/metrics/src/middleware/middy.ts @@ -60,7 +60,7 @@ const logMetrics = ( }; }; - const logMetricsBefore = async (request: MiddyLikeRequest): Promise => { + const logMetricsBefore = (request: MiddyLikeRequest) => { for (const metrics of metricsInstances) { const { throwOnEmptyMetrics, defaultDimensions, captureColdStartMetric } = options; @@ -78,7 +78,7 @@ const logMetrics = ( setCleanupFunction(request); }; - const logMetricsAfterOrError = async (): Promise => { + const logMetricsAfterOrError = () => { for (const metrics of metricsInstances) { metrics.publishStoredMetrics(); } diff --git a/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts index aa098a4abf..5cf695bfe3 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorator.test.functionCode.ts @@ -32,8 +32,8 @@ class Lambda implements LambdaInterface { defaultDimensions: JSON.parse(defaultDimensions), throwOnEmptyMetrics: true, }) - public async handler(_event: unknown, _context: Context): Promise { - metrics.addMetric(metricName, metricUnit, Number.parseInt(metricValue)); + public handler(_event: unknown, _context: Context) { + metrics.addMetric(metricName, metricUnit, Number.parseInt(metricValue, 10)); metrics.addDimension( Object.entries(JSON.parse(extraDimension))[0][0], Object.entries(JSON.parse(extraDimension))[0][1] as string @@ -52,7 +52,7 @@ class Lambda implements LambdaInterface { metricWithItsOwnDimensions.addMetric( singleMetricName, singleMetricUnit, - Number.parseInt(singleMetricValue) + Number.parseInt(singleMetricValue, 10) ); } } diff --git a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts index 2858aec832..063c393494 100644 --- a/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.decorators.test.ts @@ -168,7 +168,7 @@ describe('Metrics E2E tests, basic features decorator usage', () => { ? metricStat.Datapoints[0] : {}; expect(singleDataPoint?.Sum).toBeGreaterThanOrEqual( - Number.parseInt(expectedMetricValue) * invocations + Number.parseInt(expectedMetricValue, 10) * invocations ); }); }); diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts index e72736189d..f383d0c48b 100644 --- a/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.functionCode.ts @@ -25,14 +25,11 @@ const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? '2'; const metrics = new Metrics({ namespace: namespace, serviceName: serviceName }); -export const handler = async ( - _event: unknown, - _context: Context -): Promise => { +export const handler = (_event: unknown, _context: Context) => { metrics.captureColdStartMetric(); metrics.throwOnEmptyMetrics(); metrics.setDefaultDimensions(JSON.parse(defaultDimensions)); - metrics.addMetric(metricName, metricUnit, Number.parseInt(metricValue)); + metrics.addMetric(metricName, metricUnit, Number.parseInt(metricValue, 10)); metrics.addDimension( Object.entries(JSON.parse(extraDimension))[0][0], Object.entries(JSON.parse(extraDimension))[0][1] as string @@ -46,7 +43,7 @@ export const handler = async ( metricWithItsOwnDimensions.addMetric( singleMetricName, singleMetricUnit, - Number.parseInt(singleMetricValue) + Number.parseInt(singleMetricValue, 10) ); metrics.publishStoredMetrics(); diff --git a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts index 1fb1e40528..31fb75846b 100644 --- a/packages/metrics/tests/e2e/basicFeatures.manual.test.ts +++ b/packages/metrics/tests/e2e/basicFeatures.manual.test.ts @@ -154,7 +154,7 @@ describe('Metrics E2E tests, manual usage', () => { ? metricStat.Datapoints[0] : {}; expect(singleDataPoint.Sum).toBeGreaterThanOrEqual( - Number.parseInt(expectedMetricValue) * invocations + Number.parseInt(expectedMetricValue, 10) * invocations ); }); }); diff --git a/packages/metrics/tests/helpers/metricsUtils.ts b/packages/metrics/tests/helpers/metricsUtils.ts index 35bf3407d7..78718650a4 100644 --- a/packages/metrics/tests/helpers/metricsUtils.ts +++ b/packages/metrics/tests/helpers/metricsUtils.ts @@ -6,7 +6,7 @@ import { } from '@aws-sdk/client-cloudwatch'; import promiseRetry from 'promise-retry'; -const getMetrics = async ( +const getMetrics = ( cloudWatchClient: CloudWatchClient, namespace: string, metric: string, diff --git a/packages/metrics/tests/unit/logMetrics.test.ts b/packages/metrics/tests/unit/logMetrics.test.ts index 97c6ba7be9..62ec0ed77c 100644 --- a/packages/metrics/tests/unit/logMetrics.test.ts +++ b/packages/metrics/tests/unit/logMetrics.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import { cleanupMiddlewares } from '@aws-lambda-powertools/commons'; import middy from '@middy/core'; import type { Context } from 'aws-lambda'; @@ -39,6 +40,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { @metrics.logMetrics({ captureColdStartMetric: true }) async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation this.addGreetingMetric(); } @@ -105,6 +107,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { @metrics.logMetrics({ captureColdStartMetric: true }) async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation this.addGreetingMetric(); } @@ -150,6 +153,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { @metrics.logMetrics({ captureColdStartMetric: true }) async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation this.addGreetingMetric(); } @@ -184,6 +188,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { }); vi.spyOn(metrics, 'publishStoredMetrics'); const handler = middy(async () => { + await setTimeout(0); // Simulate some async operation metrics.addMetric('greetings', MetricUnit.Count, 1); }).use(logMetrics(metrics, { captureColdStartMetric: true })); @@ -210,7 +215,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { }); vi.spyOn(metrics, 'publishStoredMetrics'); - const handler = middy(async () => { + const handler = middy(() => { metrics.addMetric('greetings', MetricUnit.Count, 1); }).use(logMetrics(metrics, { captureColdStartMetric: true })); @@ -238,7 +243,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { }); vi.spyOn(metrics, 'publishStoredMetrics'); - const handler = middy(async () => { + const handler = middy(() => { metrics.addMetric('greetings', MetricUnit.Count, 1); }).use(logMetrics(metrics, { captureColdStartMetric: true })); @@ -266,6 +271,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { class Test { @metrics.logMetrics({ defaultDimensions: { environment: 'test' } }) async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation metrics.addMetric('test', MetricUnit.Count, 1); } } @@ -295,6 +301,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { }); vi.spyOn(metrics, 'publishStoredMetrics'); const handler = middy(async () => { + await setTimeout(0); // Simulate some async operation metrics.addMetric('greetings', MetricUnit.Count, 1); }).use( logMetrics(metrics, { @@ -328,6 +335,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { class Test { @metrics.logMetrics() async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation throw new Error('Something went wrong'); } } @@ -349,6 +357,7 @@ describe('LogMetrics decorator & Middy.js middleware', () => { class Test { @metrics.logMetrics({ throwOnEmptyMetrics: true }) async handler(_event: unknown, _context: Context) { + await setTimeout(0); // Simulate some async operation return 'Hello, world!'; } } @@ -367,12 +376,12 @@ describe('LogMetrics decorator & Middy.js middleware', () => { singleMetric: false, namespace: DEFAULT_NAMESPACE, }); - const handler = middy(async () => {}).use( - logMetrics([metrics], { throwOnEmptyMetrics: true }) - ); + const handler = middy(async () => { + await setTimeout(0); // Simulate some async operation + }).use(logMetrics([metrics], { throwOnEmptyMetrics: true })); // Act & Assess - expect(() => handler({}, {} as Context)).rejects.toThrowError( + await expect(() => handler({}, {} as Context)).rejects.toThrowError( 'The number of metrics recorded must be higher than zero' ); }); diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 56a4cfae5f..17f2f3dc4c 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/parameters#readme", diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 1975968c99..c78bdadf26 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -14,15 +14,12 @@ import type { } from '../types/AppConfigProvider.js'; /** - * ## Intro - * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. - * - * ## Getting started + * The Parameters utility provides an `AppConfigProvider` that allows to retrieve configuration profiles from AWS AppConfig. * * This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-appconfigdata`). This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -41,9 +38,7 @@ import type { * ``` * If you want to retrieve configs without customizing the provider, you can use the {@link getAppConfig} function instead. * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -82,7 +77,7 @@ import type { * }; * ``` * - * ### Transformations + * **Transformations** * * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. * @@ -118,7 +113,7 @@ import type { * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -144,7 +139,7 @@ import type { * * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). * - * ### Customize AWS SDK v3 for JavaScript client + * **Customize AWS SDK v3 for JavaScript client** * * By default, the provider will create a new AppConfigData client using the default configuration. * @@ -192,21 +187,20 @@ class AppConfigProvider extends BaseProvider { private readonly application?: string; private readonly environment: string; - /** - * It initializes the AppConfigProvider class. - * * - * @param {AppConfigProviderOptions} options - The configuration object. - */ - public constructor(options: AppConfigProviderOptions) { + public constructor({ + application, + environment, + clientConfig, + awsSdkV3Client, + }: AppConfigProviderOptions) { super({ awsSdkV3ClientPrototype: AppConfigDataClient as new ( config?: unknown ) => AppConfigDataClient, - clientConfig: options.clientConfig, - awsSdkV3Client: options.awsSdkV3Client, + clientConfig, + awsSdkV3Client, }); - const { application, environment } = options; this.application = application ?? getServiceName(); if (!this.application || this.application.trim().length === 0) { throw new Error( @@ -235,26 +229,23 @@ class AppConfigProvider extends BaseProvider { * }; * ``` * - * You can customize the retrieval of the configuration profile by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * - * For usage examples check {@link AppConfigProvider}. - * - * @param {string} name - The name of the configuration profile or its ID - * @param {AppConfigGetOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the configuration profile to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier` */ - public async get< + public get< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | AppConfigGetOptions | undefined = AppConfigGetOptions, >( name: string, - options?: InferredFromOptionsType & AppConfigGetOptions + options?: NonNullable ): Promise< | AppConfigGetOutput | undefined @@ -287,12 +278,16 @@ class AppConfigProvider extends BaseProvider { * polls the configuration multiple times, we return the most recent value by returning the cached * one if an empty response is returned by AppConfig. * - * @param {string} name - Name of the configuration or its ID - * @param {AppConfigGetOptions} options - SDK options to propagate to `StartConfigurationSession` API call + * @param name - Name of the configuration or its ID + * @param options - SDK options to propagate to `StartConfigurationSession` API call + * @param options.maxAge - Maximum age of the value in the cache, in seconds. + * @param options.forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @param options.sdkOptions - Additional options to pass to the AWS SDK v3 client. Supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. + * @param options.transform - Optional transform to be applied, can be 'json' or 'binary'. */ protected async _get( name: string, - options?: AppConfigGetOptions + options?: NonNullable ): Promise { if ( !this.configurationTokenStore.has(name) || @@ -339,7 +334,7 @@ class AppConfigProvider extends BaseProvider { /** When the response is not empty, stash the result locally before returning * See AppConfig docs: * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} - **/ + */ if ( response.Configuration !== undefined && response.Configuration?.length > 0 @@ -358,7 +353,7 @@ class AppConfigProvider extends BaseProvider { * * @throws Not Implemented Error. */ - protected async _getMultiple( + protected _getMultiple( _path: string, _sdkOptions?: unknown ): Promise | undefined> { diff --git a/packages/parameters/src/appconfig/getAppConfig.ts b/packages/parameters/src/appconfig/getAppConfig.ts index aa73d49741..0a3e11996a 100644 --- a/packages/parameters/src/appconfig/getAppConfig.ts +++ b/packages/parameters/src/appconfig/getAppConfig.ts @@ -6,39 +6,30 @@ import type { import { AppConfigProvider } from './AppConfigProvider.js'; /** - * ## Intro - * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. + * The Parameters utility provides an `AppConfigProvider` that allows to retrieve configuration profiles from AWS AppConfig. * - * ## Getting started - * - * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only + * This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-appconfigdata`). This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * - * ```sh - * npm install @aws-lambda-powertools/parameters @aws-sdk/client-appconfigdata - * ``` - * - * ## Basic usage + * **Basic usage** * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; * + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * * export const handler = async (): Promise => { - * // Retrieve a configuration profile - * const encodedConfig = await getAppConfig('my-config', { - * application: 'my-app', - * environment: 'prod', - * }); - * const config = new TextDecoder('utf-8').decode(encodedConfig); + * // Use the config variable as needed + * console.log(config); * }; * ``` * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -47,13 +38,15 @@ import { AppConfigProvider } from './AppConfigProvider.js'; * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; * + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * maxAge: 10, // Cache for 10 seconds + * }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * * export const handler = async (): Promise => { - * // Retrieve a configuration profile and cache it for 10 seconds - * const encodedConfig = await getAppConfig('my-config', { - * application: 'my-app', - * environment: 'prod', - * }); - * const config = new TextDecoder('utf-8').decode(encodedConfig); + * // Use the config variable as needed * }; * ``` * @@ -63,18 +56,20 @@ import { AppConfigProvider } from './AppConfigProvider.js'; * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; * + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * forceFetch: true, // Always fetch the latest value + * }); + * const config = new TextDecoder('utf-8').decode(encodedConfig); + * * export const handler = async (): Promise => { - * // Retrieve a config and always fetch the latest value - * const config = await getAppConfig('my-config', { - * application: 'my-app', - * environment: 'prod', - * forceFetch: true, - * }); - * const config = new TextDecoder('utf-8').decode(encodedConfig); + * // Use the config variable as needed + * console.log * }; * ``` * - * ### Transformations + * **Transformations** * * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. * @@ -82,13 +77,16 @@ import { AppConfigProvider } from './AppConfigProvider.js'; * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; * + * // Retrieve a JSON config and parse it as JSON + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * transform: 'json' + * }); + * * export const handler = async (): Promise => { - * // Retrieve a JSON config or Feature Flag and parse it as JSON - * const config = await getAppConfig('my-config', { - * application: 'my-app', - * environment: 'prod', - * transform: 'json' - * }); + * // Use the config variable as needed + * console.log(config); * }; * ``` * @@ -108,7 +106,7 @@ import { AppConfigProvider } from './AppConfigProvider.js'; * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -131,15 +129,18 @@ import { AppConfigProvider } from './AppConfigProvider.js'; * * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). * - * ### Built-in provider class + * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link AppConfigProvider | `AppConfigProvider`} class. * - * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link AppConfigProvider} class. - * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * @param {string} name - The name of the configuration profile or its ID - * @param {GetAppConfigOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the configuration profile to retrieve + * @param options - Options to configure the provider + * @param options.application - The application ID or the application name + * @param options.environment - The environment ID or the environment name + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier` */ const getAppConfig = < ExplicitUserProvidedType = undefined, @@ -148,7 +149,7 @@ const getAppConfig = < | undefined = GetAppConfigOptions, >( name: string, - options: InferredFromOptionsType & GetAppConfigOptions + options: NonNullable ): Promise< | AppConfigGetOutput | undefined diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index 2678d31113..a35127e2e4 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -18,15 +18,12 @@ import type { } from '../types/DynamoDBProvider.js'; /** - * ## Intro - * The Parameters utility provides a DynamoDBProvider that allows to retrieve values from Amazon DynamoDB. - * - * ## Getting started + * The Parameters utility provides a `DynamoDBProvider` that allows to retrieve values from Amazon DynamoDB. * * This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-dynamodb` and `@aws-sdk/util-dynamodb`). This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * ## Basic usage + * **Basic usage** * * Retrieve a value from DynamoDB: * @@ -60,9 +57,7 @@ import type { * }; * ``` * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -101,7 +96,7 @@ import type { * }; * ``` * - * ### Transformations + * **Transformations** * * For values stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. * @@ -157,7 +152,7 @@ import type { * }; * ``` * - * ### Custom key names + * **Custom key names** * * By default, the provider will use the following key names: `id` for the partition key, `sk` for the sort key, and `value` for the value. * You can adjust the key names by using the `keyAttr`, `sortAttr`, and `valueAttr` parameters. @@ -174,7 +169,7 @@ import type { * }); * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving values, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -198,7 +193,7 @@ import type { * * The objects accept the same options as respectively the [AWS SDK v3 for JavaScript PutItem command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/putitemcommand.html) and the [AWS SDK v3 for JavaScript DynamoDB client Query command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/querycommand.html). * - * ### Customize AWS SDK v3 for JavaScript client + * **Customize AWS SDK v3 for JavaScript client** * * By default, the provider will create a new DynamoDB client using the default configuration. * @@ -239,11 +234,6 @@ class DynamoDBProvider extends BaseProvider { protected tableName: string; protected valueAttr = 'value'; - /** - * It initializes the DynamoDBProvider class. - * - * @param {DynamoDBProviderOptions} config - The configuration object. - */ public constructor(config: DynamoDBProviderOptions) { super({ awsSdkV3ClientPrototype: DynamoDBClient as new ( @@ -276,26 +266,23 @@ class DynamoDBProvider extends BaseProvider { * }; * ``` * - * You can customize the retrieval of the value by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * - * For usage examples check {@link DynamoDBProvider}. - * - * @param {string} name - The name of the value to retrieve (i.e. the partition key) - * @param {DynamoDBGetOptionsInterface} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the value to retrieve (partition key) + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetItemCommandInput | `GetItemCommandInput`} except `Key`, `TableName`, and `ProjectionExpression` */ - public async get< + public get< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | DynamoDBGetOptions | undefined = DynamoDBGetOptions, >( name: string, - options?: InferredFromOptionsType & DynamoDBGetOptions + options?: NonNullable ): Promise< | DynamoDBGetOutput | undefined @@ -323,27 +310,24 @@ class DynamoDBProvider extends BaseProvider { * }; * ``` * - * You can customize the retrieval of the values by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) - * - * For usage examples check {@link DynamoDBProvider}. - * - * @param {string} path - The path of the values to retrieve (i.e. the partition key) - * @param {DynamoDBGetMultipleOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param path - The path of the values to retrieve (partition key) + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link QueryCommandInput | `QueryCommandInput`} except `TableName` and `KeyConditionExpression` + * @param options.throwOnTransformError - Optional flag to throw an error if the transform fails (default: `true`) */ - public async getMultiple< + public getMultiple< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | DynamoDBGetMultipleOptions | undefined = DynamoDBGetMultipleOptions, >( path: string, - options?: InferredFromOptionsType & DynamoDBGetMultipleOptions + options?: NonNullable ): Promise< | DynamoDBGetMultipleOutput< ExplicitUserProvidedType, @@ -363,12 +347,16 @@ class DynamoDBProvider extends BaseProvider { /** * Retrieve an item from Amazon DynamoDB. * - * @param {string} name - Key of the item to retrieve (i.e. the partition key) - * @param {DynamoDBGetOptions} options - Options to customize the retrieval + * @param name - Key of the item to retrieve (i.e. the partition key) + * @param options - Options to customize the retrieval + * @param options.maxAge - Maximum age of the value in the cache, in seconds. + * @param options.sdkOptions - Additional options to pass to the AWS SDK v3 client, supports all options from {@link GetItemCommandInput | `GetItemCommandInput`} except `Key`, `TableName`, and `ProjectionExpression`. + * @param options.forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @param options.transform - Transform to be applied, can be 'json' or 'binary'. */ protected async _get( name: string, - options?: DynamoDBGetOptions + options?: NonNullable ): Promise { const sdkOptions: GetItemCommandInput = { ...(options?.sdkOptions || {}), @@ -387,12 +375,17 @@ class DynamoDBProvider extends BaseProvider { /** * Retrieve multiple items from Amazon DynamoDB. * - * @param {string} path - The path of the values to retrieve (i.e. the partition key) - * @param {DynamoDBGetMultipleOptions} options - Options to customize the retrieval + * @param path - The path of the values to retrieve (i.e. the partition key) + * @param options - Options to customize the retrieval + * @param options.maxAge - Maximum age of the value in the cache, in seconds. + * @param options.forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @param options.sdkOptions - Additional options to pass to the AWS SDK v3 client, supports all options from {@link QueryCommandInput | `QueryCommandInput`} except `TableName` and `KeyConditionExpression`. + * @param options.transform - Transform to be applied, can be 'json' or 'binary'. + * @param options.throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) */ protected async _getMultiple( path: string, - options?: DynamoDBGetMultipleOptions + options?: NonNullable ): Promise> { const sdkOptions: QueryCommandInput = { ...(options?.sdkOptions || {}), diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 134e788536..f247a7aca8 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -11,15 +11,12 @@ import type { } from '../types/SecretsProvider.js'; /** - * ## Intro - * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. - * - * ## Getting started + * The Parameters utility provides a `SecretsProvider` that allows to retrieve secrets from AWS Secrets Manager. * * This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-secrets-manager`). This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -35,9 +32,7 @@ import type { * * If you want to retrieve secrets without customizing the provider, you can use the {@link getSecret} function instead. * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -68,7 +63,7 @@ import type { * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored in JSON or Base64 format, you can use the transform argument for deserialization. * @@ -84,7 +79,7 @@ import type { * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -106,7 +101,7 @@ import type { * * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). * - * ### Customize AWS SDK v3 for JavaScript client + * **Customize AWS SDK v3 for JavaScript client** * * By default, the provider will create a new Secrets Manager client using the default configuration. * @@ -140,18 +135,12 @@ import type { * * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). * - * @class * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ */ class SecretsProvider extends BaseProvider { public declare client: SecretsManagerClient; - /** - * It initializes the SecretsProvider class. - * - * @param {SecretsProviderOptions} config - The configuration object. - */ - public constructor(config?: SecretsProviderOptions) { + public constructor(config?: NonNullable) { super({ awsSdkV3ClientPrototype: SecretsManagerClient as new ( config?: unknown @@ -175,26 +164,23 @@ class SecretsProvider extends BaseProvider { * }; * ``` * - * You can customize the retrieval of the secret by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * - * For usage examples check {@link SecretsProvider}. - * - * @param {string} name - The name of the secret - * @param {SecretsGetOptions} options - Options to customize the retrieval of the secret * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the secret to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetSecretValueCommandInput | `GetSecretValueCommandInput`} except `SecretId` */ - public async get< + public get< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | SecretsGetOptions | undefined = SecretsGetOptions, >( name: string, - options?: InferredFromOptionsType & SecretsGetOptions + options?: NonNullable ): Promise< | SecretsGetOutput | undefined @@ -206,7 +192,7 @@ class SecretsProvider extends BaseProvider { } /** - * Retrieving multiple parameter values is not supported with AWS Secrets Manager. + * Retrieving multiple secrets is not supported with AWS Secrets Manager. */ /* v8 ignore start */ public async getMultiple( path: string, @@ -216,14 +202,15 @@ class SecretsProvider extends BaseProvider { } /* v8 ignore stop */ /** - * Retrieve a configuration from AWS Secrets Manager. + * Retrieve a secret from AWS Secrets Manager. * - * @param {string} name - Name of the configuration or its ID - * @param {SecretsGetOptions} options - SDK options to propagate to the AWS SDK v3 for JavaScript client + * @param name - Name of the secret or its ID + * @param options - SDK options to propagate to the AWS SDK v3 for JavaScript client + * @param options.sdkOptions - Extra options to pass to the AWS SDK v3 for JavaScript client, accepts the same options as {@link GetSecretValueCommandInput | `GetSecretValueCommandInput`} except `SecretId`. */ protected async _get( name: string, - options?: SecretsGetOptions + options?: NonNullable ): Promise { const sdkOptions: GetSecretValueCommandInput = { ...(options?.sdkOptions || {}), @@ -240,11 +227,11 @@ class SecretsProvider extends BaseProvider { } /** - * Retrieving multiple parameter values is not supported with AWS Secrets Manager. + * Retrieving multiple secrets is not supported with AWS Secrets Manager. * * @throws Not Implemented Error. */ - protected async _getMultiple( + protected _getMultiple( _path: string, _options?: unknown ): Promise | undefined> { diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index 6b47a3cc75..f1d04849e2 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -6,21 +6,12 @@ import type { import { SecretsProvider } from './SecretsProvider.js'; /** - * ## Intro - * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. - * - * ## Getting started + * The Parameters utility provides a `SecretsProvider` that allows to retrieve secrets from AWS Secrets Manager. * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for Secrets Manager: - * - * ```sh - * npm install @aws-lambda-powertools/parameters @aws-sdk/client-secrets-manager - * ``` - * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -32,9 +23,7 @@ import { SecretsProvider } from './SecretsProvider.js'; * }; * ``` * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -61,7 +50,7 @@ import { SecretsProvider } from './SecretsProvider.js'; * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored as JSON or base64-encoded strings, you can use the transform argument set to `json` or `binary` for deserialization. * @@ -75,7 +64,7 @@ import { SecretsProvider } from './SecretsProvider.js'; * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -95,25 +84,27 @@ import { SecretsProvider } from './SecretsProvider.js'; * * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). * - * ### Built-in provider class + * **Built-in provider class** * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SecretsProvider} class. * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * - * @param {string} name - The name of the secret to retrieve - * @param {SecretsGetOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the secret to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetSecretValueCommandInput | `GetSecretValueCommandInput`} except `SecretId` */ -const getSecret = async < +const getSecret = < ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | SecretsGetOptions | undefined = SecretsGetOptions, >( name: string, - options?: InferredFromOptionsType & SecretsGetOptions + options?: NonNullable ): Promise< | SecretsGetOutput | undefined diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index d905ab2681..03d47ef78b 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -35,15 +35,12 @@ import type { } from '../types/SSMProvider.js'; /** - * ## Intro - * The Parameters utility provides a SSMProvider that allows to retrieve parameters from AWS Systems Manager. - * - * ## Getting started + * The Parameters utility provides a `SSMProvider` that allows to retrieve parameters from AWS Systems Manager. * * This utility supports AWS SDK v3 for JavaScript only (`@aws-sdk/client-ssm`). This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * ## Basic usage + * **Basic usage** * * Retrieve a parameter from SSM: * @@ -96,9 +93,7 @@ import type { * * If you don't need to customize the provider, you can also use the {@link getParametersByName} function instead. * - * ## Advanced usage - * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -157,7 +152,7 @@ import type { * * Likewise, you can use the `forceFetch` parameter with the `getParametersByName` method both for individual parameters and for all parameters. * - * ### Decryption + * **Decryption** * * If you want to retrieve a parameter that is encrypted, you can use the `decrypt` parameter. This parameter is compatible with `get`, `getMultiple` and `getParametersByName`. * @@ -175,7 +170,7 @@ import type { * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. * @@ -211,7 +206,7 @@ import type { * * Both type of transformations are compatible also with the `getParametersByName` method. * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving parameters, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -233,7 +228,7 @@ import type { * * The objects accept the same options as respectively the [AWS SDK v3 for JavaScript GetParameter command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametercommand.html) and the [AWS SDK v3 for JavaScript GetParametersByPath command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/classes/getparametersbypathcommand.html). * - * ### Customize AWS SDK v3 for JavaScript client + * **Customize AWS SDK v3 for JavaScript client** * * By default, the provider will create a new SSM client using the default configuration. * @@ -272,11 +267,6 @@ class SSMProvider extends BaseProvider { protected errorsKey = '_errors'; protected maxGetParametersItems = 10; - /** - * It initializes the SSMProvider class. - * - * @param {SSMProviderOptions} config - The configuration object. - */ public constructor(config?: SSMProviderOptions) { super({ awsSdkV3ClientPrototype: SSMClient as new (config?: unknown) => SSMClient, @@ -298,26 +288,23 @@ class SSMProvider extends BaseProvider { * const parameter = await parametersProvider.get('/my-parameter'); * }; * ``` - * - * You can customize the retrieval of the value by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `decrypt` - Whether to decrypt the value before returning it. - * - * For usage examples check {@link SSMProvider}. - * - * @param {string} name - The name of the value to retrieve (i.e. the partition key) - * @param {SSMGetOptions} options - Options to configure the provider + * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the parameter to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetParameterCommandInput | `GetParameterCommandInput`} except `Name` + * @param options.decrypt - Optional flag to decrypt the value before returning it (default: `false`) */ - public async get< + public get< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends SSMGetOptions | undefined = SSMGetOptions, >( name: string, - options?: InferredFromOptionsType & SSMGetOptions + options?: NonNullable ): Promise< SSMGetOutput | undefined > { @@ -343,19 +330,17 @@ class SSMProvider extends BaseProvider { * }; * ``` * - * You can customize the storage of the value by passing options to the function: - * * `value` - The value of the parameter, which is a mandatory option. - * * `overwrite` - Whether to overwrite the value if it already exists (default: `false`) - * * `description` - The description of the parameter - * * `parameterType` - The type of the parameter, can be one of `String`, `StringList`, or `SecureString` (default: `String`) - * * `tier` - The parameter tier to use, can be one of `Standard`, `Advanced`, and `Intelligent-Tiering` (default: `Standard`) - * * `kmsKeyId` - The KMS key id to use to encrypt the parameter - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * - * @param {string} name - The name of the parameter - * @param {SSMSetOptions} options - Options to configure the parameter - * @returns {Promise} The version of the parameter * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the parameter + * @param options - Options to configure the parameter + * @param options.value - The value of the parameter + * @param options.overwrite - Whether to overwrite the value if it already exists (default: `false`) + * @param options.description - The description of the parameter + * @param options.parameterType - The type of the parameter, can be one of `String`, `StringList`, or `SecureString` (default: `String`) + * @param options.tier - The parameter tier to use, can be one of `Standard`, `Advanced`, and `Intelligent-Tiering` (default: `Standard`) + * @param options.kmsKeyId - The KMS key id to use to encrypt the parameter + * @param options.sdkOptions - Extra options to pass to the AWS SDK v3 for JavaScript client */ public async set< InferredFromOptionsType extends SSMSetOptions | undefined = SSMSetOptions, @@ -400,29 +385,28 @@ class SSMProvider extends BaseProvider { * }; * ``` * - * You can customize the retrieval of the values by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) - * * `decrypt` - Whether to decrypt the value before returning it. - * * `recursive` - Whether to recursively retrieve all parameters under the given path (default: `false`) - * - * For usage examples check {@link SSMProvider}. + * For usage examples check {@link SSMProvider | `SSMProvider`}. * - * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptions} options - Options to configure the retrieval * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param path - The path of the parameters to retrieve + * @param options - Optional options to configure the retrieval + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetParametersByPathCommandInput | `GetParametersByPathCommandInput`} except `Path` + * @param options.throwOnTransformError - Optional flag to throw an error if the transform fails (default: `true`) + * @param options.decrypt - Optional flag to decrypt the value before returning it (default: `false`) + * @param options.recursive - Optional flag to recursively retrieve all parameters under the given path (default: `false`) */ - public async getMultiple< + public getMultiple< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | SSMGetMultipleOptions | undefined = undefined, >( path: string, - options?: InferredFromOptionsType & SSMGetMultipleOptions + options?: NonNullable ): Promise< | SSMGetMultipleOutput | undefined @@ -450,15 +434,8 @@ class SSMProvider extends BaseProvider { * }); * }; * ``` - * You can customize the retrieval of the values by passing options to **both the function and the parameter**: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) - * * `decrypt` - Whether to decrypt the value before returning it - * - * `throwOnError` decides whether to throw an error if a parameter is not found: + * + * The `throwOnError` option decides whether to throw an error if a parameter is not found: * - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure. * - B) Gracefully aggregate all parameters that failed under "_errors" key. * @@ -479,13 +456,18 @@ class SSMProvider extends BaseProvider { * └────────────────────┘ * ``` * - * @param {Record} parameters - Object containing parameter names and any optional overrides - * @param {SSMGetParametersByNameOptions} options - Options to configure the retrieval * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param parameters - Object containing parameter names and any optional overrides + * @param options - Options to configure the retrieval + * @param options.maxAge - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * @param options.transform - Whether to transform the value before returning it. Supported values: `json`, `binary` + * @param options.decrypt - Whether to decrypt the value before returning it. + * @param options.throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error (default: `true`) */ public async getParametersByName( parameters: Record, - options?: SSMGetParametersByNameOptions + options?: NonNullable ): Promise> { const configs = { ...{ @@ -554,12 +536,15 @@ class SSMProvider extends BaseProvider { /** * Retrieve a parameter from AWS Systems Manager. * - * @param {string} name - Name of the parameter to retrieve - * @param {SSMGetOptions} options - Options to customize the retrieval + * @param name - Name of the parameter to retrieve + * @param options - Options to customize the retrieval + * @param options.sdkOptions - Extra options to pass to the AWS SDK v3 for JavaScript client + * @param options.decrypt - Whether to decrypt the value before returning it. + * @param options.transform - Whether to transform the value before returning it. Supported values: `json`, `binary`, or `auto` (default: `undefined`) */ protected async _get( name: string, - options?: SSMGetOptions + options?: NonNullable ): Promise { const sdkOptions: GetParameterCommandInput = { ...(options?.sdkOptions || {}), @@ -577,12 +562,19 @@ class SSMProvider extends BaseProvider { /** * Retrieve multiple items from AWS Systems Manager. * - * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptions} options - Options to configure the provider + * @param path - The path of the parameters to retrieve + * @param options - Options to configure the provider + * @param options.maxAge - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) + * @param options.forceFetch - Whether to always fetch a new value from the store regardless if already available in cache + * @param options.transform - Whether to transform the value before returning it. Supported values: `json`, `binary` + * @param options.sdkOptions - Extra options to pass to the AWS SDK v3 for JavaScript client + * @param options.throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) + * @param options.decrypt - Whether to decrypt the value before returning it. + * @param options.recursive - Whether to recursively retrieve all parameters under the given path (default: `false`) */ protected async _getMultiple( path: string, - options?: SSMGetMultipleOptions + options?: NonNullable ): Promise> { const sdkOptions: GetParametersByPathCommandInput = { ...(options?.sdkOptions || {}), @@ -613,7 +605,7 @@ class SSMProvider extends BaseProvider { * * The parameter name returned by SSM will contain the full path. * However, for readability, we should return only the part after the path. - **/ + */ // biome-ignore lint/style/noNonNullAssertion: If the parameter is present in the response, then it has a Name let name = parameter.Name!; name = name.replace(path, ''); @@ -630,9 +622,9 @@ class SSMProvider extends BaseProvider { /** * Retrieve multiple items by name from AWS Systems Manager. * - * @param {Record} parameters - An object of parameter names and their options - * @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully - * @param {boolean} decrypt - Whether to decrypt the parameters or not + * @param parameters - An object of parameter names and their options + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param decrypt - Whether to decrypt the parameters or not */ protected async _getParametersByName( parameters: Record, @@ -666,9 +658,9 @@ class SSMProvider extends BaseProvider { /** * Slice batch and fetch parameters using GetPrameters API by max permissible batch size * - * @param {Record} parameters - An object of parameter names and their options - * @param {throwOnError} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully - * @param {boolean} decrypt - Whether to decrypt the parameters or not + * @param parameters - An object of parameter names and their options + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param decrypt - Whether to decrypt the parameters or not */ protected async getParametersBatchByName( parameters: Record, @@ -679,8 +671,7 @@ class SSMProvider extends BaseProvider { let errors: string[] = []; // Fetch each possible batch param from cache and return if entire batch is cached - const { cached, toFetch } = - await this.getParametersByNameFromCache(parameters); + const { cached, toFetch } = this.getParametersByNameFromCache(parameters); if (Object.keys(cached).length >= Object.keys(parameters).length) { response = cached; @@ -705,11 +696,11 @@ class SSMProvider extends BaseProvider { /** * Fetch each parameter from batch that hasn't expired from cache * - * @param {Record} parameters - An object of parameter names and their options + * @param parameters - An object of parameter names and their options */ - protected async getParametersByNameFromCache( + protected getParametersByNameFromCache( parameters: Record - ): Promise { + ): SSMGetParametersByNameFromCacheOutputType { const cached: Record> = {}; const toFetch: Record = {}; @@ -737,9 +728,9 @@ class SSMProvider extends BaseProvider { /** * Slice object into chunks of max permissible batch size and fetch parameters * - * @param {Record} parameters - An object of parameter names and their options - * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully - * @param {boolean} decrypt - Whether to decrypt the parameters or not + * @param parameters - An object of parameter names and their options + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param decrypt - Whether to decrypt the parameters or not */ protected async getParametersByNameInChunks( parameters: Record, @@ -781,8 +772,8 @@ class SSMProvider extends BaseProvider { /** * Fetch parameters by name while also decrypting them * - * @param {Record} parameters - An object of parameter names and their options - * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param parameters - An object of parameter names and their options + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully */ protected async getParametersByNameWithDecryptOption( parameters: Record, @@ -814,11 +805,12 @@ class SSMProvider extends BaseProvider { } /** - * Handle any invalid parameters returned by GetParameters API + * Handle any invalid parameters returned by GetParameters API. + * * GetParameters is non-atomic. Failures don't always reflect in exceptions so we need to collect. * - * @param {GetParametersCommandOutput} result - The result of the GetParameters API call - * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param result - The result of the GetParameters API call + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully */ protected static handleAnyInvalidGetParameterErrors( result: Partial, @@ -855,8 +847,8 @@ class SSMProvider extends BaseProvider { /** * Split parameters that can be fetched by GetParameters vs GetParameter. * - * @param {Record} parameters - An object of parameter names and their options - * @param {SSMGetParametersByNameOptions} configs - The configs passed down + * @param parameters - An object of parameter names and their options + * @param configs - The configs passed down */ protected static splitBatchAndDecryptParameters( parameters: Record, @@ -896,9 +888,9 @@ class SSMProvider extends BaseProvider { /** * Throw a GetParameterError if fail-fast is disabled and `_errors` key is in parameters list. * - * @param {Record} parameters - * @param {string} reservedParameter - * @param {boolean} throwOnError + * @param parameters - An object of parameter names and their options + * @param reservedParameter - The reserved parameter name that cannot be used when fail-fast is disabled + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully */ protected static throwIfErrorsKeyIsPresent( parameters: Record, @@ -915,9 +907,9 @@ class SSMProvider extends BaseProvider { /** * Transform and cache the response from GetParameters API call * - * @param {GetParametersCommandOutput} response - The response from the GetParameters API call - * @param {Record} parameters - An object of parameter names and their options - * @param {boolean} throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully + * @param response - The response from the GetParameters API call + * @param parameters - An object of parameter names and their options + * @param throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error or handle them gracefully */ protected transformAndCacheGetParametersResponse( response: Partial, diff --git a/packages/parameters/src/ssm/getParameter.ts b/packages/parameters/src/ssm/getParameter.ts index 50a4ab93f3..359caebf7d 100644 --- a/packages/parameters/src/ssm/getParameter.ts +++ b/packages/parameters/src/ssm/getParameter.ts @@ -3,21 +3,12 @@ import type { SSMGetOptions, SSMGetOutput } from '../types/SSMProvider.js'; import { SSMProvider } from './SSMProvider.js'; /** - * ## Intro - * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. - * - * ## Getting started + * The Parameters utility provides an `SSMProvider` that allows to retrieve parameters from AWS Systems Manager. * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * - * ```sh - * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm - * ``` - * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -29,9 +20,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ## Advanced usage - * - * ### Decryption + * **Decryption** * * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. * @@ -45,7 +34,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -72,7 +61,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. * @@ -98,7 +87,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a parameter, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -118,31 +107,26 @@ import { SSMProvider } from './SSMProvider.js'; * * This object accepts the same options as the [AWS SDK v3 for JavaScript SSM GetParameter command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/interfaces/getparametercommandinput.html). * - * ### Built-in provider class + * **Built-in provider class** * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. * - * ### Options - * - * * You can customize the retrieval of the value by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `decrypt` - Whether to decrypt the value before returning it. - * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * @param {string} name - The name of the parameter to retrieve - * @param {SSMGetOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - The name of the parameter to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetParameterCommandInput | `GetParameterCommandInput`} except `Name` + * @param options.decrypt - Optional flag to decrypt the value before returning it (default: `false`) */ -const getParameter = async < +const getParameter = < ExplicitUserProvidedType = undefined, InferredFromOptionsType extends SSMGetOptions | undefined = SSMGetOptions, >( name: string, - options?: InferredFromOptionsType & SSMGetOptions + options?: NonNullable ): Promise< SSMGetOutput | undefined > => { diff --git a/packages/parameters/src/ssm/getParameters.ts b/packages/parameters/src/ssm/getParameters.ts index a00caee550..b8aca01a0d 100644 --- a/packages/parameters/src/ssm/getParameters.ts +++ b/packages/parameters/src/ssm/getParameters.ts @@ -6,21 +6,12 @@ import type { import { SSMProvider } from './SSMProvider.js'; /** - * ## Intro - * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. - * - * ## Getting started + * The Parameters utility provides an `SSMProvider` that allows to retrieve parameters from AWS Systems Manager. * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * - * ```sh - * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm - * ``` - * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -32,9 +23,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ## Advanced usage - * - * ### Decryption + * **Decryption** * * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. * @@ -48,7 +37,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -75,7 +64,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript objects instead of a strings. * @@ -101,7 +90,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Extra SDK options + * **Extra SDK options** * * When retrieving a parameter, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. * @@ -121,34 +110,27 @@ import { SSMProvider } from './SSMProvider.js'; * * This object accepts the same options as the [AWS SDK v3 for JavaScript SSM getParametersByPath command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-ssm/interfaces/getParameterssbypathcommandinput.html). * - * ### Built-in provider class - * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. * - * ### Options - * - * * You can customize the retrieval of the value by passing options to the function: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `decrypt` - Whether to decrypt the value before returning it. - * * `recursive` - Whether to recursively retrieve all parameters within the path. - * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * @param {string} path - The path of the parameters to retrieve - * @param {SSMGetMultipleOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param path - The path of the parameters to retrieve + * @param options - Optional options to configure the provider + * @param options.maxAge - Optional maximum age of the value in the cache, in seconds (default: `5`) + * @param options.forceFetch - Optional flag to always fetch a new value from the store regardless if already available in cache (default: `false`) + * @param options.transform - Optional transform to be applied, can be `json` or `binary` + * @param options.sdkOptions - Optional additional options to pass to the AWS SDK v3 client, supports all options from {@link GetParametersByPathCommandInput | `GetParametersByPathCommandInput`} except `Path` + * @param options.decrypt - Optional flag to decrypt the value before returning it (default: `false`) + * @param options.recursive - Optional flag to recursively retrieve all parameters within the path (default: `false`) */ -const getParameters = async < +const getParameters = < ExplicitUserProvidedType = undefined, InferredFromOptionsType extends | SSMGetMultipleOptions | undefined = SSMGetMultipleOptions, >( path: string, - options?: InferredFromOptionsType & SSMGetMultipleOptions + options?: NonNullable ): Promise< | SSMGetMultipleOutput | undefined diff --git a/packages/parameters/src/ssm/getParametersByName.ts b/packages/parameters/src/ssm/getParametersByName.ts index b609871b0f..57c5043c08 100644 --- a/packages/parameters/src/ssm/getParametersByName.ts +++ b/packages/parameters/src/ssm/getParametersByName.ts @@ -6,21 +6,12 @@ import type { import { SSMProvider } from './SSMProvider.js'; /** - * ## Intro - * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. - * - * ## Getting started + * The Parameters utility provides an `SSMProvider` that allows to retrieve parameters from AWS Systems Manager. * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. * - * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * - * ```sh - * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm - * ``` - * - * ## Basic usage + * **Basic usage** * * @example * ```typescript @@ -35,9 +26,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ## Advanced usage - * - * ### Decryption + * **Decryption** * * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. * @@ -54,7 +43,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Caching + * **Caching** * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. @@ -102,7 +91,7 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * ### Transformations + * **Transformations** * * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript objects instead of a strings. * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return decoded strings for each parameter. @@ -121,21 +110,9 @@ import { SSMProvider } from './SSMProvider.js'; * }; * ``` * - * - * ### Built-in provider class - * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. * - * ### Options - * - * * You can customize the retrieval of the value by passing options to **both the function and the parameter**: - * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) - * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache - * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * * `decrypt` - Whether to decrypt the value before returning it - * - * `throwOnError` decides whether to throw an error if a parameter is not found: + * The `throwOnError` parameter decides whether to throw an error if a parameter is not found: * - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure. * - B) Gracefully aggregate all parameters that failed under "_errors" key. * @@ -156,15 +133,18 @@ import { SSMProvider } from './SSMProvider.js'; * └────────────────────┘ * ``` * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * @param {Record} parameters - The path of the parameters to retrieve - * @param {SSMGetParametersByNameOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param parameters - The path of the parameters to retrieve + * @param options - Options to configure the provider + * @param options.maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. + * @param options.transform - Whether to transform the value before returning it. Supported values: `json`, `binary` + * @param options.decrypt - Whether to decrypt the values before returning them. If true, will use `GetParameter` API for each parameter. If false (default), will use `GetParametersByName` API. + * @param options.throwOnError - Whether to throw an error if any of the parameters' retrieval throws an error (default: `true`) */ -const getParametersByName = async ( +const getParametersByName = ( parameters: Record, - options?: SSMGetParametersByNameOptions + options?: NonNullable ): Promise> => { if (!Object.hasOwn(DEFAULT_PROVIDERS, 'ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); diff --git a/packages/parameters/src/ssm/setParameter.ts b/packages/parameters/src/ssm/setParameter.ts index a059d11dc5..21adbefff6 100644 --- a/packages/parameters/src/ssm/setParameter.ts +++ b/packages/parameters/src/ssm/setParameter.ts @@ -58,24 +58,17 @@ import { SSMProvider } from './SSMProvider.js'; * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} utility. * - * **Options** - * - * You can customize the storage of the value by passing options to the function: - * * `value` - The value of the parameter, which is a mandatory option. - * * `overwrite` - Whether to overwrite the value if it already exists (default: `false`) - * * `description` - The description of the parameter - * * `parameterType` - The type of the parameter, can be one of `String`, `StringList`, or `SecureString` (default: `String`) - * * `tier` - The parameter tier to use, can be one of `Standard`, `Advanced`, and `Intelligent-Tiering` (default: `Standard`) - * * `kmsKeyId` - The KMS key id to use to encrypt the parameter - * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/). - * - * @param name - Name of the parameter - * @param options - Options to configure the parameter - * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * @see https://docs.powertools.aws.dev/lambda/typescript/latest/features/parameters/ + * + * @param name - Name of the parameter + * @param options - Options to configure the parameter + * @param options.value - The value of the parameter + * @param options.overwrite - Whether to overwrite the value if it already exists (default: `false`) + * @param options.description - The description of the parameter + * @param options.parameterType - The type of the parameter, can be one of `String`, `StringList`, or `SecureString` (default: `String`) + * @param options.tier - The parameter tier to use, can be one of `Standard`, `Advanced`, and `Intelligent-Tiering` (default: `Standard`) */ -const setParameter = async < +const setParameter = < InferredFromOptionsType extends SSMSetOptions | undefined = SSMSetOptions, >( name: string, diff --git a/packages/parameters/src/types/AppConfigProvider.ts b/packages/parameters/src/types/AppConfigProvider.ts index 64dc4ee170..d9768f49ee 100644 --- a/packages/parameters/src/types/AppConfigProvider.ts +++ b/packages/parameters/src/types/AppConfigProvider.ts @@ -4,80 +4,85 @@ import type { AppConfigDataClientConfig, StartConfigurationSessionCommandInput, } from '@aws-sdk/client-appconfigdata'; +import type { AppConfigProvider } from '../appconfig/AppConfigProvider.js'; +import type { getAppConfig } from '../appconfig/getAppConfig.js'; import type { GetOptionsInterface } from './BaseProvider.js'; /** - * Base interface for AppConfigProviderOptions. + * Base interface for {@link AppConfigProviderOptions | `AppConfigProviderOptions`}. * - * @interface - * @property {string} environment - The environment ID or the environment name. - * @property {string} [application] - The application ID or the application name. + * @property environment - The environment ID or the environment name. + * @property application - The optional application ID or the application name. */ interface AppConfigProviderOptionsBaseInterface { + /** + * The environment ID or the environment name. + */ environment: string; + /** The optional application ID or the application name. + */ application?: string; } /** - * Interface for AppConfigProviderOptions with clientConfig property. + * Interface for {@link AppConfigProviderOptions | `AppConfigProviderOptions`} with `clientConfig` property. * - * @interface - * @extends AppConfigProviderOptionsBaseInterface - * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. - * @property {never} [awsSdkV3Client] - This property should never be passed. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. + * @property awsSdkV3Client - This property should never be passed when using `clientConfig`. */ interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOptionsBaseInterface { /** - * Optional configuration to pass during client initialization, e.g. AWS region. It accepts the same configuration object as the AWS SDK v3 client (`AppConfigDataClient`). + * Optional configuration to pass during client initialization, e.g. AWS region. Accepts the same configuration object as the AWS SDK v3 client ({@link AppConfigDataClientConfig | `AppConfigDataClientConfig`}). */ clientConfig?: AppConfigDataClientConfig; + /** + * This property should never be passed when using `clientConfig`. + */ awsSdkV3Client?: never; } /** - * Interface for AppConfigProviderOptions with awsSdkV3Client property. + * Interface for {@link AppConfigProviderOptions | `AppConfigProviderOptions`} with awsSdkV3Client property. * - * @interface - * @extends AppConfigProviderOptionsBaseInterface - * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation - * @property {never} [clientConfig] - This property should never be passed. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during the `AppConfigProvider` class instantiation, should be an instance of {@link AppConfigDataClient | `AppConfigDataClient`}. + * @property clientConfig - This property should never be passed when using `awsSdkV3Client`. */ interface AppConfigProviderOptionsWithClientInstance extends AppConfigProviderOptionsBaseInterface { /** - * Optional AWS SDK v3 client instance (`AppConfigDataClient`) to use for AppConfig operations. If not provided, we will create a new instance of `AppConfigDataClient`. + * Optional AWS SDK v3 client instance ({@link AppConfigDataClient | `AppConfigDataClient`}) to use for AppConfig operations. If not provided, we will create a new instance of the client. */ awsSdkV3Client?: AppConfigDataClient; + /** + * This property should never be passed when using `awsSdkV3Client`. + */ clientConfig?: never; } /** - * Options for the AppConfigProvider class constructor. + * Options for the `AppConfigProvider` class constructor. * - * @type AppConfigProviderOptions - * @property {string} environment - The environment ID or the environment name. - * @property {string} [application] - The application ID or the application name. - * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. - * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation. Mutually exclusive with clientConfig. + * @property environment - The environment ID or the environment name. + * @property application - Optional application ID or the application name, if not provided it will be inferred from the service name in the environment. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with `awsSdkV3Client`. Accepts the same configuration object as the AWS SDK v3 client ({@link AppConfigDataClientConfig | `AppConfigDataClientConfig`}). + * @property awsSdkV3Client - Optional ({@link AppConfigDataClient | `AppConfigDataClient`}) instance to pass during `AppConfigProvider` class instantiation. Mutually exclusive with `clientConfig`. */ type AppConfigProviderOptions = | AppConfigProviderOptionsWithClientConfig | AppConfigProviderOptionsWithClientInstance; /** - * Options for the AppConfigProvider get method. + * Options for the {@link AppConfigProvider.get | `AppConfigProvider.get()`} method. * - * @interface AppConfigGetOptionsBase - * @extends {GetOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {StartConfigurationSessionCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Additional options to pass to the AWS SDK v3 client. Supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. + * @property transform - Optional transform to be applied, can be 'json' or 'binary'. */ interface AppConfigGetOptionsBase extends GetOptionsInterface { /** - * Additional options to pass to the AWS SDK v3 client. Supports all options from `StartConfigurationSessionCommandInput` except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. + * Additional options to pass to the AWS SDK v3 client. Supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. */ sdkOptions?: Omit< Partial, @@ -99,6 +104,14 @@ interface AppConfigGetOptionsTransformNone extends AppConfigGetOptionsBase { transform?: never; } +/** + * Options for the {@link AppConfigProvider.get | `AppConfigProvider.get()`} method. + * + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Additional options to pass to the AWS SDK v3 client. Supports all options from {@link StartConfigurationSessionCommandInput | `StartConfigurationSessionCommandInput`} except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. + * @property transform - Optional transform to be applied, can be 'json' or 'binary'. + */ type AppConfigGetOptions = | AppConfigGetOptionsTransformNone | AppConfigGetOptionsTransformJson @@ -106,7 +119,7 @@ type AppConfigGetOptions = | undefined; /** - * Generic output type for the AppConfigProvider get method. + * Generic output type for the {@link AppConfigProvider.get | `AppConfigProvider.get()` } get method. */ type AppConfigGetOutput< ExplicitUserProvidedType = undefined, @@ -124,7 +137,7 @@ type AppConfigGetOutput< : ExplicitUserProvidedType; /** - * Combined options for the getAppConfig utility function. + * Combined options for the {@link getAppConfig | `getAppConfig()`} utility function. */ type GetAppConfigOptions = Omit< AppConfigProviderOptions, diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index 3154bb0893..d4ec626c9c 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -1,7 +1,7 @@ import type { Transform } from '../constants.js'; /** - * Options for the BaseProvider class constructor. + * Options for the `BaseProvider` class constructor. */ type BaseProviderConstructorOptions = { /** @@ -30,10 +30,10 @@ type TransformOptions = (typeof Transform)[keyof typeof Transform]; /** * Options for the `get` method. * - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {unknown} sdkOptions - Options to pass to the underlying SDK. - * @property {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto'. + * @property maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Options to pass to the underlying SDK. + * @property transform - Optional transform to be applied, can be 'json', 'binary', or 'auto'. */ interface GetOptionsInterface { /** @@ -57,11 +57,11 @@ interface GetOptionsInterface { /** * Options for the `getMultiple` method. * - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {unknown} sdkOptions - Options to pass to the underlying SDK. - * @property {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto'. - * @property {boolean} throwOnTransformError - Whether to throw an error if a value cannot be transformed. + * @property maxAge - Maximum age of the value in the cache, in seconds. Will be applied after the first API call. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Options to pass to the underlying SDK. + * @property transform - Transform to be applied, can be 'json', 'binary', or 'auto'. + * @property throwOnTransformError - Whether to throw an error if a value cannot be transformed. */ interface GetMultipleOptionsInterface extends GetOptionsInterface { /** diff --git a/packages/parameters/src/types/DynamoDBProvider.ts b/packages/parameters/src/types/DynamoDBProvider.ts index 97b6241636..664e0dbac7 100644 --- a/packages/parameters/src/types/DynamoDBProvider.ts +++ b/packages/parameters/src/types/DynamoDBProvider.ts @@ -5,87 +5,101 @@ import type { GetItemCommandInput, QueryCommandInput, } from '@aws-sdk/client-dynamodb'; +import type { DynamoDBProvider } from '../dynamodb/DynamoDBProvider.js'; import type { GetMultipleOptionsInterface, GetOptionsInterface, } from './BaseProvider.js'; /** - * Base interface for DynamoDBProviderOptions. + * Base interface for {@link DynamoDBProviderOptions | `DynamoDBProviderOptions`}. * - * @interface - * @property {string} tableName - The DynamoDB table name. - * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. - * @property {string} [sortAttr] - The DynamoDB table sort attribute name. Defaults to 'sk'. - * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. + * @property tableName - The DynamoDB table name. + * @property keyAttr - Optional DynamoDB table key attribute name. Defaults to 'id'. + * @property sortAttr - Optional DynamoDB table sort attribute name. Defaults to 'sk'. + * @property valueAttr - Optional DynamoDB table value attribute name. Defaults to 'value'. */ interface DynamoDBProviderOptionsBase { + /** + * The DynamoDB table name. + */ tableName: string; + /** + * Optional DynamoDB table key attribute name. Defaults to 'id'. + */ keyAttr?: string; + /** + * Optional DynamoDB table sort attribute name. Defaults to 'sk'. + */ sortAttr?: string; + /** + * Optional DynamoDB table value attribute name. Defaults to 'value'. + */ valueAttr?: string; } /** - * Interface for DynamoDBProviderOptions with clientConfig property. + * Interface for {@link DynamoDBProviderOptions | `DynamoDBProviderOptions`} with `clientConfig` property. * - * @interface - * @extends DynamoDBProviderOptionsBase - * @property {DynamoDBClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. - * @property {never} [awsSdkV3Client] - This property should never be passed. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. Accepts the same options as the AWS SDK v3 client ({@link DynamoDBClient | `DynamoDBClient`}). + * @property awsSdkV3Client - This property should never be passed when using `clientConfig`. */ interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOptionsBase { /** - * Optional configuration to pass during client initialization, e.g. AWS region. It accepts the same configuration object as the AWS SDK v3 client (`DynamoDBClient`). + * Optional configuration to pass during client initialization, e.g. AWS region. Accepts the same options as the AWS SDK v3 client ({@link DynamoDBClient | `DynamoDBClient`}). */ clientConfig?: DynamoDBClientConfig; + /** + * This property should never be passed when using `clientConfig`. + */ awsSdkV3Client?: never; } /** - * Interface for DynamoDBProviderOptions with awsSdkV3Client property. + * Interface for {@link DynamoDBProviderOptions | `DynamoDBProviderOptions`} with `awsSdkV3Client` property. * - * @interface - * @extends DynamoDBProviderOptionsBase - * @property {DynamoDBClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation - * @property {never} [clientConfig] - This property should never be passed. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during `DynamoDBProvider` class instantiation, should be an instance of {@link DynamoDBClient | `DynamoDBClient`}. + * @property clientConfig - This property should never be passed when using `awsSdkV3Client`. */ interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOptionsBase { /** - * Optional AWS SDK v3 client instance (`DynamoDBClient`) to use for DynamoDB operations. If not provided, we will create a new instance of `DynamoDBClient`. + * Optional AWS SDK v3 client instance ({@link DynamoDBClient | `DynamoDBClient`}) to use for DynamoDB operations. If not provided, we will create a new instance of the client. */ awsSdkV3Client?: DynamoDBClient; + /** + * This property should never be passed when using `awsSdkV3Client`. + */ clientConfig?: never; } /** - * Options for the DynamoDBProvider class constructor. + * Options for the {@link DynamoDBProvider | `DynamoDBProvider`} class constructor. * - * @type DynamoDBProviderOptions - * @property {string} tableName - The DynamoDB table name. - * @property {string} [keyAttr] - The DynamoDB table key attribute name. Defaults to 'id'. - * @property {string} [sortAttr] - The DynamoDB table sort attribute name. Defaults to 'sk'. - * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. - * @property {DynamoDBClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. - * @property {DynamoDBClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + * @property tableName - The DynamoDB table name. + * @property keyAttr - Optional DynamoDB table key attribute name. Defaults to 'id'. + * @property sortAttr - Optional DynamoDB table sort attribute name. Defaults to 'sk'. + * @property valueAttr - Optional DynamoDB table value attribute name. Defaults to 'value'. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with `awsSdkV3Client`, accepts the same options as the AWS SDK v3 client ({@link DynamoDBClient | `DynamoDBClient`}). + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with `clientConfig`, should be an instance of {@link DynamoDBClient | `DynamoDBClient`}. */ type DynamoDBProviderOptions = | DynamoDBProviderOptionsWithClientConfig | DynamoDBProviderOptionsWithClientInstance; /** - * Options for the DynamoDBProvider get method. + * Options for the {@link DynamoDBProvider.get | `DynamoDBProvider.get()`} get method. * - * @interface DynamoDBGetOptionsBase - * @extends {GetOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Additional options to pass to the AWS SDK v3 client, supports all options from {@link GetItemCommandInput | `GetItemCommandInput`} except `Key`, `TableName`, and `ProjectionExpression`. + * @property transform - Transform to be applied, can be 'json' or 'binary'. */ interface DynamoDBGetOptionsBase extends GetOptionsInterface { + /** + * Additional options to pass to the AWS SDK v3 client, supports all options from {@link GetItemCommandInput | `GetItemCommandInput`} except `Key`, `TableName`, and `ProjectionExpression`. + */ sdkOptions?: Omit< Partial, 'Key' | 'TableName' | 'ProjectionExpression' @@ -104,6 +118,14 @@ interface DynamoDBGetOptionsTransformNone extends DynamoDBGetOptionsBase { transform?: never; } +/** + * Options for the {@link DynamoDBProvider.get | `DynamoDBProvider.get()`} get method. + * + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Additional options to pass to the AWS SDK v3 client, supports all options from {@link GetItemCommandInput | `GetItemCommandInput`} except `Key`, `TableName`, and `ProjectionExpression`. + * @property transform - Transform to be applied, can be 'json' or 'binary'. + */ type DynamoDBGetOptions = | DynamoDBGetOptionsTransformNone | DynamoDBGetOptionsTransformJson @@ -111,7 +133,7 @@ type DynamoDBGetOptions = | undefined; /** - * Generic output type for DynamoDBProvider get method. + * Generic output type for {@link DynamoDBProvider.get | `DynamoDBProvider.get()`} method. */ type DynamoDBGetOutput< ExplicitUserProvidedType = undefined, @@ -129,15 +151,13 @@ type DynamoDBGetOutput< : ExplicitUserProvidedType; /** - * Options for the DynamoDBProvider getMultiple method. + * Options for the {@link DynamoDBProvider.getMultiple | `DynamoDBProvider.getMultiple()`} method. * - * @interface DynamoDBGetMultipleOptions - * @extends {GetMultipleOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {QueryCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. - * @property {boolean} throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Additional options to pass to the AWS SDK v3 client, supports all options from {@link QueryCommandInput | `QueryCommandInput`} except `TableName` and `KeyConditionExpression`. + * @property transform - Transform to be applied, can be 'json' or 'binary'. + * @property throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) */ interface DynamoDBGetMultipleOptionsBase extends GetMultipleOptionsInterface { sdkOptions?: Partial; @@ -170,7 +190,7 @@ type DynamoDBGetMultipleOptions = | DynamoDBGetMultipleOptionsTransformNone; /** - * Generic output type for DynamoDBProvider getMultiple method. + * Generic output type for {@link DynamoDBProvider.getMultiple | `DynamoDBProvider.getMultiple()`} method. */ type DynamoDBGetMultipleOutput< ExplicitUserProvidedType = undefined, diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index 889438db7e..547f5ffa04 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -15,9 +15,8 @@ import type { /** * Interface for SSMProvider with clientConfig property. * - * @interface - * @property {SSMClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. - * @property {never} [awsSdkV3Client] - This property should never be passed. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. + * @property awsSdkV3Client - This property should never be passed. */ interface SSMProviderOptionsWithClientConfig { /** @@ -30,9 +29,8 @@ interface SSMProviderOptionsWithClientConfig { /** * Interface for SSMProvider with awsSdkV3Client property. * - * @interface - * @property {SSMClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SSMProvider class instantiation - * @property {never} [clientConfig] - This property should never be passed. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during SSMProvider class instantiation + * @property clientConfig - This property should never be passed. */ interface SSMProviderOptionsWithClientInstance { /** @@ -45,9 +43,8 @@ interface SSMProviderOptionsWithClientInstance { /** * Options for the SSMProvider class constructor. * - * @type SSMProviderOptions - * @property {SSMClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. - * @property {SSMClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. */ type SSMProviderOptions = | SSMProviderOptionsWithClientConfig @@ -58,11 +55,12 @@ type SSMProviderOptions = * * @interface SSMGetOptionsBase * @extends {GetOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParameterCommandInput`. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. - * @property {boolean} decrypt - If true, the parameter will be decrypted. Defaults to `false`. + * + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Optional options to pass to the AWS SDK v3 client. Supports all options from {@link GetParameterCommandInput | `GetParameterCommandInput`}. + * @property transform - Transform to be applied, can be 'json' or 'binary'. + * @property decrypt - If true, the parameter will be decrypted. Defaults to `false`. */ interface SSMGetOptionsBase extends GetOptionsInterface { /** @@ -154,15 +152,15 @@ type SSMGetOutput< /** * Options for the SSMProvider getMultiple method. * - * @interface SSMGetMultipleOptionsBase * @extends {GetMultipleOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {GetItemCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. - * @property {boolean} decrypt - If true, the parameter will be decrypted. - * @property {boolean} recursive - If true, the parameter will be fetched recursively. - * @property {boolean} throwOnTransformError - If true, the method will throw an error if the transform fails. + * + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property [sdkOptions] - Additional options to pass to the AWS SDK v3 client. + * @property transform - Transform to be applied, can be 'json' or 'binary'. + * @property decrypt - If true, the parameter will be decrypted. + * @property recursive - If true, the parameter will be fetched recursively. + * @property throwOnTransformError - If true, the method will throw an error if the transform fails. */ interface SSMGetMultipleOptionsBase extends GetMultipleOptionsInterface { /** @@ -230,11 +228,10 @@ type SSMGetMultipleOutput< /** * Options for the SSMProvider getParametersByName method. * - * @interface SSMGetParametersByNameOptions - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. - * @property {boolean} decrypt - If true, the parameter will be decrypted. - * @property {boolean} throwOnError - If true, the method will throw an error if one of the parameters cannot be fetched. Otherwise it will aggregate the errors under an _errors key in the response. + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property transform - Transform to be applied, can be 'json' or 'binary'. + * @property decrypt - If `true`, the parameter will be decrypted. + * @property throwOnError - If `true`, the method will throw an error if one of the parameters cannot be fetched. Otherwise it will aggregate the errors under an `_errors` key in the response. */ interface SSMGetParametersByNameOptions { maxAge?: number; diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index 0d31a66192..5db8d9ef7d 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -4,34 +4,32 @@ import type { SecretsManagerClient, SecretsManagerClientConfig, } from '@aws-sdk/client-secrets-manager'; +import type { SecretsProvider } from '../secrets/SecretsProvider.js'; import type { GetOptionsInterface, TransformOptions } from './BaseProvider.js'; /** - * Base interface for SecretsProviderOptions. + * Base interface for {@link SecretsProviderOptions | SecretsProviderOptions}. * - * @interface - * @property {SecretsManagerClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. - * @property {never} [awsSdkV3Client] - This property should never be passed. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. + * @property awsSdkV3Client - This property should never be passed. */ interface SecretsProviderOptionsWithClientConfig { /** - * Optional configuration to pass during client initialization, e.g. AWS region. It accepts the same configuration object as the AWS SDK v3 client (`SecretsManagerClient`). + * Optional configuration to pass during client initialization, e.g. AWS region. Accepts the same configuration object as the AWS SDK v3 client ({@link SecretsManagerClientConfig | `SecretsManagerClientConfig`}). */ clientConfig?: SecretsManagerClientConfig; awsSdkV3Client?: never; } /** - * Interface for SecretsProviderOptions with awsSdkV3Client property. + * Interface for {@link SecretsProviderOptions | SecretsProviderOptions} with `awsSdkV3Client` property. * - * @interface - * @extends SecretsProviderOptionsWithClientConfig - * @property {SecretsManagerClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SecretsProvider class instantiation - * @property {never} [clientConfig] - This property should never be passed. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during {@link SecretsProvider | `SecretsProvider`} class instantiation + * @property clientConfig - This property should never be passed. */ interface SecretsProviderOptionsWithClientInstance { /** - * Optional AWS SDK v3 client instance (`SecretsManagerClient`) to use for Secrets Manager operations. If not provided, we will create a new instance of `SecretsManagerClient`. + * Optional AWS SDK v3 client instance ({@link SecretsManagerClient | `SecretsManagerClient`}) to use for Secrets Manager operations. If not provided, we will create a new instance of the client. */ awsSdkV3Client?: SecretsManagerClient; clientConfig?: never; @@ -40,9 +38,8 @@ interface SecretsProviderOptionsWithClientInstance { /** * Options for the SecretsProvider class constructor. * - * @type SecretsProviderOptions - * @property {SecretsManagerClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. - * @property {SecretsManagerClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SecretsProvider class instantiation. Mutually exclusive with clientConfig. + * @property clientConfig - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with `awsSdkV3Client`. + * @property awsSdkV3Client - Optional AWS SDK v3 client to pass during {@link SecretsProvider | `SecretsProvider`} class instantiation. Mutually exclusive with `clientConfig`. */ type SecretsProviderOptions = | SecretsProviderOptionsWithClientConfig @@ -51,18 +48,19 @@ type SecretsProviderOptions = /** * Options to configure the retrieval of a secret. * - * @interface SecretsGetOptionsBase - * @extends {GetOptionsInterface} - * @property {number} maxAge - Maximum age of the value in the cache, in seconds. - * @property {boolean} forceFetch - Force fetch the value from the parameter store, ignoring the cache. - * @property {GetSecretValueCommandInput} sdkOptions - Options to pass to the underlying SDK. - * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. + * @property maxAge - Maximum age of the value in the cache, in seconds. + * @property forceFetch - Force fetch the value from the parameter store, ignoring the cache. + * @property sdkOptions - Options to pass to the underlying SDK, supports all options from {@link GetSecretValueCommandInput | `GetSecretValueCommandInput`} except `SecretId`. + * @property transform - Transform to be applied, can be 'json' or 'binary'. */ interface SecretsGetOptionsBase extends GetOptionsInterface { /** - * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetSecretValueCommandInput` except `SecretId`. + * Additional options to pass to the AWS SDK v3 client. Supports all options from {@link GetSecretValueCommandInput | `GetSecretValueCommandInput`} except `SecretId`. */ sdkOptions?: Omit, 'SecretId'>; + /** + * Transform to be applied, can be `json` or `binary`. + */ transform?: Exclude; } @@ -85,7 +83,7 @@ type SecretsGetOptions = | undefined; /** - * Generic output type for the SecretsProvider get method. + * Generic output type for the {@link SecretsProvider.get | `SecretsProvider.get()`} method. */ type SecretsGetOutput< ExplicitUserProvidedType = undefined, diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index af28e69110..938ae423e1 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -171,7 +171,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { describe('AppConfigProvider usage', () => { // Test 1 - get a single parameter as-is (no transformation - should return an Uint8Array) - it('should retrieve single parameter as-is', () => { + it('retrieves single parameter as-is', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[0]); @@ -182,7 +182,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { }); // Test 2 - get a free-form JSON and apply json transformation (should return an object) - it('should retrieve a free-form JSON parameter with JSON transformation', () => { + it('retrieves a free-form JSON parameter with JSON transformation', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[1]); @@ -194,7 +194,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { // Test 3 - get a free-form base64-encoded plain text and apply binary transformation // (should return a decoded string) - it('should retrieve a base64-encoded plain text parameter with binary transformation', () => { + it('retrieves a base64-encoded plain text parameter with binary transformation', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[2]); @@ -205,7 +205,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { }); // Test 4 - get a feature flag and apply json transformation (should return an object) - it('should retrieve a feature flag parameter with JSON transformation', () => { + it('retrieves a feature flag parameter with JSON transformation', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[3]); @@ -217,7 +217,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { // Test 5 - get parameter twice with middleware, which counts the number // of requests, we check later if we only called AppConfig API once - it('should retrieve single parameter cached', () => { + it('retrieves single parameter cached', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[4]); @@ -229,7 +229,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { // Test 6 - get parameter twice, but force fetch 2nd time, // we count number of SDK requests and check that we made two API calls - it('should retrieve single parameter twice without caching', async () => { + it('retrieves single parameter twice without caching', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[5]); @@ -242,7 +242,7 @@ describe('Parameters E2E tests, AppConfig provider', () => { // Test 7 - get parameter twice, using maxAge to avoid primary cache // we count number of SDK requests and check that we made two API calls // and check that the values match - it('should retrieve single parameter twice, with expiration between and matching values', async () => { + it('retrieves single parameter twice, with expiration between and matching values', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[6]); const result = freeFormPlainTextValue; diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index f4fa7c4ba4..9c3ac74cc6 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -263,7 +263,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { describe('DynamoDBProvider usage', () => { // Test 1 - get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') - it('should retrieve a single parameter', async () => { + it('retrieves a single parameter', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[0]); @@ -274,7 +274,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 2 - get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') - it('should retrieve multiple parameters', async () => { + it('retrieves multiple parameters', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[1]); @@ -285,7 +285,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 3 - get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') - it('should retrieve a single parameter', async () => { + it('retrieves a single parameter', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[2]); @@ -296,7 +296,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 4 - get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') - it('should retrieve multiple parameters', async () => { + it('retrieves multiple parameters', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[3]); @@ -307,7 +307,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 5 - get a single parameter with json transform - it('should retrieve a single parameter with json transform', async () => { + it('retrieves a single parameter with json transform', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[4]); @@ -318,7 +318,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 6 - get a single parameter with binary transform - it('should retrieve a single parameter with binary transform', async () => { + it('retrieves a single parameter with binary transform', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[5]); @@ -329,7 +329,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 7 - get multiple parameters with auto transforms (json and binary) - it('should retrieve multiple parameters with auto transforms', async () => { + it('retrieves multiple parameters with auto transforms', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[6]); @@ -343,7 +343,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 8 - Get a parameter twice and check that the value is cached. - it('should retrieve multiple parameters with auto transforms', async () => { + it('retrieves multiple parameters with auto transforms', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[7]); @@ -354,7 +354,7 @@ describe('Parameters E2E tests, dynamoDB provider', () => { }); // Test 9 - Get a cached parameter and force retrieval. - it('should retrieve multiple parameters with auto transforms', async () => { + it('retrieves multiple parameters with auto transforms', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[8]); diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.ts index b52481cbed..2899b1ac2d 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.ts @@ -136,7 +136,7 @@ describe('Parameters E2E tests, Secrets Manager provider', () => { }); describe('SecretsProvider usage', () => { - it('should retrieve a secret as plain string', async () => { + it('retrieves a secret as plain string', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[0]); @@ -146,7 +146,7 @@ describe('Parameters E2E tests, Secrets Manager provider', () => { }); }); - it('should retrieve a secret using transform json option', async () => { + it('retrieves a secret using transform json option', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[1]); @@ -156,7 +156,7 @@ describe('Parameters E2E tests, Secrets Manager provider', () => { }); }); - it('should retrieve a secret using transform binary option', async () => { + it('retrieves a secret using transform binary option', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[2]); @@ -167,7 +167,7 @@ describe('Parameters E2E tests, Secrets Manager provider', () => { }); }); - it('should retrieve a secret twice with cached value', async () => { + it('retrieves a secret twice with cached value', () => { const logs = invocationLogs.getFunctionLogs(); const testLogFirst = TestInvocationLogs.parseFunctionLog(logs[3]); @@ -178,7 +178,7 @@ describe('Parameters E2E tests, Secrets Manager provider', () => { }); }); - it('should retrieve a secret twice with forceFetch second time', async () => { + it('retrieves a secret twice with forceFetch second time', () => { const logs = invocationLogs.getFunctionLogs(); const testLogFirst = TestInvocationLogs.parseFunctionLog(logs[4]); diff --git a/packages/parameters/tests/e2e/ssmProvider.class.test.ts b/packages/parameters/tests/e2e/ssmProvider.class.test.ts index c5a28619fc..f65fb0c88f 100644 --- a/packages/parameters/tests/e2e/ssmProvider.class.test.ts +++ b/packages/parameters/tests/e2e/ssmProvider.class.test.ts @@ -188,7 +188,7 @@ describe('Parameters E2E tests, SSM provider', () => { describe('SSMProvider usage', () => { // Test 1 - get a single parameter by name with default options - it('should retrieve a single parameter', async () => { + it('retrieves a single parameter', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[0]); @@ -199,7 +199,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); // Test 2 - get a single parameter by name with decrypt - it('should retrieve a single parameter with decryption', async () => { + it('retrieves a single parameter with decryption', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[1]); @@ -210,7 +210,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); // Test 3 - get multiple parameters by path with default options - it('should retrieve multiple parameters', async () => { + it('retrieves multiple parameters', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[2]); const expectedParameterNameA = paramA.substring( @@ -232,7 +232,7 @@ describe('Parameters E2E tests, SSM provider', () => { // Test 4 - get multiple parameters by path recursively // (aka. get all parameters under a path recursively) i.e. // given /param, retrieve /param/get/a and /param/get/b (note path depth) - it('should retrieve multiple parameters recursively', async () => { + it('retrieves multiple parameters recursively', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[3]); const expectedParameterNameA = paramA.substring( @@ -251,7 +251,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); }); - it('should retrieve multiple parameters with decryption', async () => { + it('retrieves multiple parameters with decryption', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[4]); const expectedParameterNameA = paramEncryptedA.substring( @@ -271,7 +271,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); // Test 6 - get multiple parameters by name with default options - it('should retrieve multiple parameters by name', async () => { + it('retrieves multiple parameters by name', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[5]); @@ -285,7 +285,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); // Test 7 - get multiple parameters by name, some of them encrypted and some not - it('should retrieve multiple parameters by name with mixed decryption', async () => { + it('retrieves multiple parameters by name with mixed decryption', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[6]); @@ -301,7 +301,7 @@ describe('Parameters E2E tests, SSM provider', () => { // Test 8 - get parameter twice with middleware, which counts the number // of requests, we check later if we only called SSM API once - it('should retrieve single parameter cached', async () => { + it('retrieves single parameter cached', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[7]); @@ -313,7 +313,7 @@ describe('Parameters E2E tests, SSM provider', () => { // Test 9 - get parameter twice, but force fetch 2nd time, // we count number of SDK requests and check that we made two API calls - it('should retrieve single parameter twice without caching', async () => { + it('retrieves single parameter twice without caching', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[8]); @@ -324,7 +324,7 @@ describe('Parameters E2E tests, SSM provider', () => { }); // Test 10 - store and overwrite single parameter - it('should store and overwrite single parameter', async () => { + it('stores and overwrite single parameter', () => { const logs = invocationLogs.getFunctionLogs(); const testLog = TestInvocationLogs.parseFunctionLog(logs[9]); diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index 5bfb01ace3..2112bcdf45 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -9,7 +9,7 @@ import { mockClient } from 'aws-sdk-client-mock'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AppConfigProvider } from '../../src/appconfig/index.js'; import { ExpirableValue } from '../../src/base/ExpirableValue.js'; -import { APPCONFIG_TOKEN_EXPIRATION } from '../../src/constants'; +import { APPCONFIG_TOKEN_EXPIRATION } from '../../src/constants.js'; import type { AppConfigProviderOptions } from '../../src/types/AppConfigProvider.js'; vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ @@ -27,7 +27,7 @@ describe('Class: AppConfigProvider', () => { }); describe('Method: constructor', () => { - it('instantiates a new AWS SDK and adds a middleware to it', async () => { + it('instantiates a new AWS SDK and adds a middleware to it', () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -66,7 +66,7 @@ describe('Class: AppConfigProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - it('uses the provided AWS SDK client', async () => { + it('uses the provided AWS SDK client', () => { // Prepare const awsSdkV3Client = new AppConfigDataClient({ endpoint: 'http://localhost:8000', @@ -90,7 +90,7 @@ describe('Class: AppConfigProvider', () => { ); }); - it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { + it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', () => { // Prepare const awsSdkV3Client = {}; const options: AppConfigProviderOptions = { @@ -179,7 +179,7 @@ describe('Class: AppConfigProvider', () => { expect(result).toBe(mockData); }); - it('throws when no application is set', async () => { + it('throws when no application is set', () => { // Prepare process.env.POWERTOOLS_SERVICE_NAME = ''; const options = { diff --git a/packages/parameters/tests/unit/DynamoDBProvider.test.ts b/packages/parameters/tests/unit/DynamoDBProvider.test.ts index 195a766d3d..7b7018d889 100644 --- a/packages/parameters/tests/unit/DynamoDBProvider.test.ts +++ b/packages/parameters/tests/unit/DynamoDBProvider.test.ts @@ -27,7 +27,7 @@ describe('Class: DynamoDBProvider', () => { }); describe('Method: constructor', () => { - it('instantiates a new AWS SDK with default options', async () => { + it('instantiates a new AWS SDK with default options', () => { // Prepare const options: DynamoDBProviderOptions = { tableName: 'test-table', @@ -64,7 +64,7 @@ describe('Class: DynamoDBProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - it('uses the provided AWS SDK client', async () => { + it('uses the provided AWS SDK client', () => { // Prepare const awsSdkV3Client = new DynamoDBClient({ endpoint: 'http://localhost:8000', @@ -87,7 +87,7 @@ describe('Class: DynamoDBProvider', () => { ); }); - it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { + it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', () => { // Prepare const awsSdkV3Client = {}; const options: DynamoDBProviderOptions = { diff --git a/packages/parameters/tests/unit/SSMProvider.test.ts b/packages/parameters/tests/unit/SSMProvider.test.ts index 0b28f6863a..ab7da09989 100644 --- a/packages/parameters/tests/unit/SSMProvider.test.ts +++ b/packages/parameters/tests/unit/SSMProvider.test.ts @@ -36,7 +36,7 @@ describe('Class: SSMProvider', () => { }); describe('Method: constructor', () => { - it('adds the middleware to the created AWS SDK', async () => { + it('adds the middleware to the created AWS SDK', () => { // Prepare const options: SSMProviderOptions = {}; @@ -70,7 +70,7 @@ describe('Class: SSMProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - it('uses the provided AWS SDK client', async () => { + it('uses the provided AWS SDK client', () => { // Prepare const awsSdkV3Client = new SSMClient({ endpoint: 'http://localhost:3000', @@ -92,7 +92,7 @@ describe('Class: SSMProvider', () => { ); }); - it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { + it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', () => { // Prepare const awsSdkV3Client = {}; const options: SSMProviderOptions = { @@ -657,7 +657,7 @@ describe('Class: SSMProvider', () => { it('returns parameters from cache when present', async () => { // Prepare const provider = new SSMProviderMock(); - provider.getParametersByNameFromCache.mockResolvedValueOnce({ + provider.getParametersByNameFromCache.mockReturnValueOnce({ cached: { '/foo/bar': 'bar', '/foo/baz': 'baz', @@ -690,7 +690,7 @@ describe('Class: SSMProvider', () => { it('retrieves the parameters from remote when not present in the cache', async () => { // Prepare const provider = new SSMProviderMock(); - provider.getParametersByNameFromCache.mockResolvedValueOnce({ + provider.getParametersByNameFromCache.mockReturnValueOnce({ cached: {}, toFetch: { '/foo/bar': {}, @@ -738,7 +738,7 @@ describe('Class: SSMProvider', () => { it('retrieves the parameters not present in the cache and returns them, together with the cached ones', async () => { // Prepare const provider = new SSMProviderMock(); - provider.getParametersByNameFromCache.mockResolvedValueOnce({ + provider.getParametersByNameFromCache.mockReturnValueOnce({ cached: { '/foo/bar': 'bar', }, @@ -790,14 +790,14 @@ describe('Class: SSMProvider', () => { this.store.set(key, value); } - public async getParametersByNameFromCache( + public getParametersByNameFromCache( parameters: Record - ): Promise { + ): SSMGetParametersByNameFromCacheOutputType { return super.getParametersByNameFromCache(parameters); } } - it('returns an object with parameters split by cached and to fetch', async () => { + it('returns an object with parameters split by cached and to fetch', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -811,7 +811,7 @@ describe('Class: SSMProvider', () => { // Act const { cached, toFetch } = - await provider.getParametersByNameFromCache(parameters); + provider.getParametersByNameFromCache(parameters); // Assess expect(cached).toEqual({ @@ -828,7 +828,7 @@ describe('Class: SSMProvider', () => { public _getParametersByName = vi.fn(); public maxGetParametersItems = 1; - public async getParametersByNameInChunks( + public getParametersByNameInChunks( parameters: Record, throwOnError: boolean, decrypt: boolean @@ -933,7 +933,7 @@ describe('Class: SSMProvider', () => { class SSMProviderMock extends SSMProvider { public _get = vi.fn(); - public async getParametersByNameWithDecryptOption( + public getParametersByNameWithDecryptOption( parameters: Record, throwOnError: boolean ): Promise { diff --git a/packages/parameters/tests/unit/SecretsProvider.test.ts b/packages/parameters/tests/unit/SecretsProvider.test.ts index 7a9bc71a4f..b79c46b6af 100644 --- a/packages/parameters/tests/unit/SecretsProvider.test.ts +++ b/packages/parameters/tests/unit/SecretsProvider.test.ts @@ -23,7 +23,7 @@ describe('Class: SecretsProvider', () => { }); describe('Method: constructor', () => { - it('instantiates a new AWS SDK with default options', async () => { + it('instantiates a new AWS SDK with default options', () => { // Prepare const options: SecretsProviderOptions = {}; @@ -57,7 +57,7 @@ describe('Class: SecretsProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - it('uses the provided AWS SDK client', async () => { + it('uses the provided AWS SDK client', () => { // Prepare const awsSdkV3Client = new SecretsManagerClient({ endpoint: 'http://localhost:3000', @@ -79,7 +79,7 @@ describe('Class: SecretsProvider', () => { ); }); - it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { + it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', () => { // Prepare const awsSdkV3Client = {}; const options: SecretsProviderOptions = { diff --git a/packages/parameters/tests/unit/setParameter.test.ts b/packages/parameters/tests/unit/setParameter.test.ts index b9a0ebf01d..fa03b2ed58 100644 --- a/packages/parameters/tests/unit/setParameter.test.ts +++ b/packages/parameters/tests/unit/setParameter.test.ts @@ -49,7 +49,7 @@ describe('Function: setParameter', () => { expect(DEFAULT_PROVIDERS.ssm).toBe(provider); }); - it('rethrows the error thrown by the underlying sdk client', async () => { + it('rethrows the error thrown by the underlying sdk client', () => { // Prepare const options: SSMSetOptions = { value: 'my-value' }; client.on(PutParameterCommand).rejects(new Error('Could not send command')); diff --git a/packages/parameters/typedoc.json b/packages/parameters/typedoc.json index 90daedca0b..6f77a07a12 100644 --- a/packages/parameters/typedoc.json +++ b/packages/parameters/typedoc.json @@ -13,5 +13,22 @@ "./src/errors.ts", "./src/constants.ts" ], - "readme": "README.md" + "readme": "README.md", + "externalSymbolLinkMappings": { + "@aws-sdk/client-secrets-manager": { + "GetSecretValueCommandInput": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-secrets-manager/Interface/GetSecretValueCommandInput/" + }, + "@aws-sdk/client-dynamodb": { + "DynamoDBClient": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/", + "GetItemCommandInput": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-dynamodb/Interface/GetItemCommandInput/", + "QueryCommandInput": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-dynamodb/Interface/QueryCommandInput/" + }, + "@aws-sdk/client-appconfigdata": { + "AppConfigDataClient": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/appconfigdata/", + "StartConfigurationSessionCommandInput": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-appconfigdata/Interface/StartConfigurationSessionCommandInput/" + }, + "@aws-sdk/client-ssm": { + "GetParameterCommandInput": "https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-ssm/Interface/GetParameterCommandInput/" + } + } } diff --git a/packages/parser/package.json b/packages/parser/package.json index 5f4e93bce9..0c7f631c29 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/parser#readme", diff --git a/packages/parser/src/envelopes/sns-sqs.ts b/packages/parser/src/envelopes/sns-sqs.ts index 1aa17288e0..452930c0ed 100644 --- a/packages/parser/src/envelopes/sns-sqs.ts +++ b/packages/parser/src/envelopes/sns-sqs.ts @@ -150,11 +150,11 @@ export const SnsSqsEnvelope = { }>( (acc, record, index) => { const parsed = parseRecord(record, index); - if (!parsed.success) { + if (parsed.success) { + acc.records.push(parsed.data); + } else { acc.success = false; acc.errors[index] = parsed.error; - } else { - acc.records.push(parsed.data); } return acc; }, diff --git a/packages/parser/src/parserDecorator.ts b/packages/parser/src/parserDecorator.ts index 2089091894..d0166a4518 100644 --- a/packages/parser/src/parserDecorator.ts +++ b/packages/parser/src/parserDecorator.ts @@ -84,7 +84,7 @@ export const parser = < const { schema, envelope, safeParse } = options; - descriptor.value = async function ( + descriptor.value = function ( this: Handler, ...args: [ParserOutput, Context, Callback] ) { diff --git a/packages/parser/src/schemas/appsync-events.ts b/packages/parser/src/schemas/appsync-events.ts index 5c7b39f809..14bcdd1556 100644 --- a/packages/parser/src/schemas/appsync-events.ts +++ b/packages/parser/src/schemas/appsync-events.ts @@ -169,12 +169,14 @@ const AppSyncEventsSubscribeSchema = AppSyncEventsBaseSchema.extend({ export { AppSyncEventsBaseSchema, - AppSyncCognitoIdentity, - AppSyncIamIdentity, AppSyncLambdaAuthIdentity, - AppSyncOidcIdentity, AppSyncEventsRequestSchema, AppSyncEventsInfoSchema, AppSyncEventsPublishSchema, AppSyncEventsSubscribeSchema, }; +export { + AppSyncCognitoIdentity, + AppSyncIamIdentity, + AppSyncOidcIdentity, +} from './appsync-shared.js'; diff --git a/packages/parser/src/schemas/appsync.ts b/packages/parser/src/schemas/appsync.ts index ca2c554bec..dfdef7c29c 100644 --- a/packages/parser/src/schemas/appsync.ts +++ b/packages/parser/src/schemas/appsync.ts @@ -236,8 +236,10 @@ const AppSyncBatchResolverSchema = z.array(AppSyncResolverSchema); export { AppSyncResolverSchema, AppSyncBatchResolverSchema, + AppSyncLambdaIdentity, +}; +export { AppSyncCognitoIdentity, AppSyncIamIdentity, AppSyncOidcIdentity, - AppSyncLambdaIdentity, -}; +} from './appsync-shared.js'; diff --git a/packages/parser/src/schemas/cognito.ts b/packages/parser/src/schemas/cognito.ts index e7240cd0e8..08bcc5ac97 100644 --- a/packages/parser/src/schemas/cognito.ts +++ b/packages/parser/src/schemas/cognito.ts @@ -132,7 +132,7 @@ const PostConfirmationTriggerSchema = CognitoTriggerBaseSchema.extend({ * } * ``` * - * * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-authentication.html | Amazon Cognito Developer Guide} + * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-authentication.html | Amazon Cognito Developer Guide} */ const PreAuthenticationTriggerSchema = CognitoTriggerBaseSchema.extend({ triggerSource: z.literal('PreAuthentication_Authentication'), diff --git a/packages/parser/src/schemas/kafka.ts b/packages/parser/src/schemas/kafka.ts index 3747755a2d..28962752e0 100644 --- a/packages/parser/src/schemas/kafka.ts +++ b/packages/parser/src/schemas/kafka.ts @@ -23,7 +23,7 @@ const KafkaRecordSchema = z.object({ z.record( z.string(), z.array(z.number()).transform((value) => { - return String.fromCharCode(...value); + return String.fromCodePoint(...value); }) ) ), diff --git a/packages/parser/src/types/parser.ts b/packages/parser/src/types/parser.ts index 56f4f1d109..de6c4c982d 100644 --- a/packages/parser/src/types/parser.ts +++ b/packages/parser/src/types/parser.ts @@ -85,7 +85,7 @@ type ParserOutput< /** * The parser function that can parse the data using the provided schema and envelope * we use function overloads to provide the correct return type based on the provided envelope - **/ + */ type ParseFunction = { // No envelope, no safeParse ( diff --git a/packages/parser/tests/types/parser.test-d.ts b/packages/parser/tests/types/parser.test-d.ts index 6ac8f86cf5..3cc9ec3a95 100644 --- a/packages/parser/tests/types/parser.test-d.ts +++ b/packages/parser/tests/types/parser.test-d.ts @@ -73,7 +73,7 @@ describe('Parser types', () => { envelope: SqsEnvelope, }) ) - .handler(async (event) => { + .handler((event) => { expectTypeOf(event).toEqualTypeOf(); }); }); diff --git a/packages/parser/tests/unit/parser.decorator.test.ts b/packages/parser/tests/unit/parser.decorator.test.ts index 7955bca551..13ca3f32b9 100644 --- a/packages/parser/tests/unit/parser.decorator.test.ts +++ b/packages/parser/tests/unit/parser.decorator.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import type { Context } from 'aws-lambda'; import { describe, expect, it } from 'vitest'; @@ -30,6 +31,7 @@ describe('Decorator: parser', () => { class TestClass implements LambdaInterface { @parser({ schema: extendedSchema }) public async handler(event: event, _context: Context): Promise { + await setTimeout(1); // simulate some processing time return event; } @@ -38,6 +40,7 @@ describe('Decorator: parser', () => { event: z.infer, _context: Context ): Promise { + await setTimeout(1); // simulate some processing time return this.anotherMethod(event); } @@ -49,6 +52,7 @@ describe('Decorator: parser', () => { event: ParsedResult, _context: Context ): Promise> { + await setTimeout(1); // simulate some processing time return event; } @@ -61,10 +65,11 @@ describe('Decorator: parser', () => { event: ParsedResult, _context: Context ): Promise { + await setTimeout(1); // simulate some processing time return event; } - private async anotherMethod(event: unknown): Promise { + private anotherMethod(event: unknown): unknown { return event; } } diff --git a/packages/parser/tests/unit/parser.middy.test.ts b/packages/parser/tests/unit/parser.middy.test.ts index 34a3f1caf8..c138a3e5cf 100644 --- a/packages/parser/tests/unit/parser.middy.test.ts +++ b/packages/parser/tests/unit/parser.middy.test.ts @@ -54,7 +54,7 @@ describe('Middleware: parser', () => { const event = structuredClone(baseEventBridgeEvent); // Act & Assess - expect( + await expect( middy() .use(parser({ schema: z.string(), envelope: SqsEnvelope })) .handler((event) => event)(event as unknown as string[], {} as Context) @@ -68,7 +68,7 @@ describe('Middleware: parser', () => { event.Records[1].body = undefined; // Act & Assess - expect( + await expect( handlerWithSchemaAndEnvelope(event as unknown as string[], {} as Context) ).rejects.toThrow(); }); @@ -91,7 +91,7 @@ describe('Middleware: parser', () => { const event = structuredClone(JSONPayload); // Act & Assess - expect( + await expect( middy((event) => event).use(parser({ schema: z.number() }))( event as unknown as number, {} as Context diff --git a/packages/parser/tests/unit/parser.test.ts b/packages/parser/tests/unit/parser.test.ts index 374e41c245..a7fb4fee58 100644 --- a/packages/parser/tests/unit/parser.test.ts +++ b/packages/parser/tests/unit/parser.test.ts @@ -24,7 +24,7 @@ describe('Parser', () => { }); const JSONPayload = { name: 'John', age: 18 }; - it('parses an event with schema and envelope', async () => { + it('parses an event with schema and envelope', () => { // Prepare const event = structuredClone(baseSqsEvent); event.Records[1].body = 'bar'; @@ -37,7 +37,7 @@ describe('Parser', () => { expect(result).toStrictEqual(['Test message.', 'bar']); }); - it('throws when envelope does not match', async () => { + it('throws when envelope does not match', () => { // Prepare const event = structuredClone(baseEventBridgeEvent); @@ -45,7 +45,7 @@ describe('Parser', () => { expect(() => parse(event, SqsEnvelope, z.string())).toThrow(); }); - it('throws when schema does not match', async () => { + it('throws when schema does not match', () => { // Prepare const event = structuredClone(baseSqsEvent); // @ts-expect-error - setting an invalid body @@ -55,7 +55,7 @@ describe('Parser', () => { expect(() => parse(event, SqsEnvelope, z.string())).toThrow(); }); - it('parses the event successfully', async () => { + it('parses the event successfully', () => { // Prepare const event = 42; @@ -66,7 +66,7 @@ describe('Parser', () => { expect(result).toEqual(event); }); - it('throws when the event does not match the schema', async () => { + it('throws when the event does not match the schema', () => { // Prepare const event = structuredClone(JSONPayload); @@ -74,7 +74,7 @@ describe('Parser', () => { expect(() => parse(event, undefined, z.number())).toThrow(); }); - it('returns the payload when using safeParse', async () => { + it('returns the payload when using safeParse', () => { // Prepare const event = structuredClone(JSONPayload); @@ -88,7 +88,7 @@ describe('Parser', () => { }); }); - it('returns the error when using safeParse and the payload is invalid', async () => { + it('returns the error when using safeParse and the payload is invalid', () => { // Prepare const event = structuredClone(JSONPayload); @@ -103,7 +103,7 @@ describe('Parser', () => { }); }); - it('returns the payload when using safeParse with envelope', async () => { + it('returns the payload when using safeParse with envelope', () => { // Prepare const detail = structuredClone(JSONPayload); const event = structuredClone(baseEventBridgeEvent); @@ -119,7 +119,7 @@ describe('Parser', () => { }); }); - it('returns an error when using safeParse with envelope and the payload is invalid', async () => { + it('returns an error when using safeParse with envelope and the payload is invalid', () => { // Prepare const event = structuredClone(baseEventBridgeEvent); diff --git a/packages/testing/package.json b/packages/testing/package.json index 734dbe77a5..5357c036f8 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -16,6 +16,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "repository": { diff --git a/packages/testing/src/TestStack.ts b/packages/testing/src/TestStack.ts index 59bb975385..c42c30c7cd 100644 --- a/packages/testing/src/TestStack.ts +++ b/packages/testing/src/TestStack.ts @@ -80,6 +80,8 @@ class TestStack { * Otherwise, we log messages that are either warnings or errors as well as periodic * updates on the stack creation and destruction process. * + * biome-ignore lint/suspicious/useAwait: The CDK interface requires this to be async + * * @param msg - Message to log sent by the CDK CLI */ async notify(msg) { @@ -115,6 +117,7 @@ class TestStack { testConsole.log(msg); } }, + // biome-ignore lint/suspicious/useAwait: The CDK interface requires this to be async async requestResponse(msg) { if ( process.env.RUNNER_DEBUG === '1' || diff --git a/packages/testing/src/setupEnv.ts b/packages/testing/src/setupEnv.ts index 7363a80828..4960f75795 100644 --- a/packages/testing/src/setupEnv.ts +++ b/packages/testing/src/setupEnv.ts @@ -228,7 +228,7 @@ expect.addEqualityTesters([ // or this.equals(a.message, b.message) return this.equals(a.issues, b.issues); } - return aOk !== bOk ? false : undefined; + return aOk === bOk ? undefined : false; }, ]); diff --git a/packages/testing/src/xray-traces-utils.ts b/packages/testing/src/xray-traces-utils.ts index df1071e12d..b6115b3b88 100644 --- a/packages/testing/src/xray-traces-utils.ts +++ b/packages/testing/src/xray-traces-utils.ts @@ -16,7 +16,7 @@ import type { const retryOptions = { retries: 20, - minTimeout: 5_000, + minTimeout: 5000, maxTimeout: 10_000, factor: 1.25, }; diff --git a/packages/tracer/package.json b/packages/tracer/package.json index ac27c2e654..e36d2eed6b 100644 --- a/packages/tracer/package.json +++ b/packages/tracer/package.json @@ -23,6 +23,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/tracer#readme", diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 3c3cfb94e1..b498f7e74e 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -41,29 +41,24 @@ import type { const { Subsegment: XraySubsegment } = xraySdk; /** - * ## Intro * Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://github.com/aws/aws-xray-sdk-node). * * Tracing data can be visualized through AWS X-Ray Console. * - * ## Key features - * * Auto capture cold start as annotation, and responses or full exceptions as metadata - * * Auto-disable when not running in AWS Lambda environment - * * Automatically trace HTTP(s) clients and generate segments for each request - * * Support tracing functions via decorators, middleware, and manual instrumentation - * * Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js + * **Key features** + * - Auto capture cold start as annotation, and responses or full exceptions as metadata + * - Auto-disable when not running in AWS Lambda environment + * - Automatically trace HTTP(s) clients and generate segments for each request + * - Support tracing functions via decorators, middleware, and manual instrumentation + * - Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js * - * ## Usage - * - * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/tracer/). - * - * ### Functions usage with middleware + * **Functions usage with middleware** * * If you use function-based Lambda handlers you can use the {@link Tracer.captureLambdaHandler} middy middleware to automatically: - * * handle the subsegment lifecycle - * * add the `ServiceName` and `ColdStart` annotations - * * add the function response as metadata - * * add the function error as metadata (if any) + * - handle the subsegment lifecycle + * - add the `ServiceName` and `ColdStart` annotations + * - add the function response as metadata + * - add the function error as metadata (if any) * * @example * ```typescript @@ -80,13 +75,13 @@ const { Subsegment: XraySubsegment } = xraySdk; * export const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); * ``` * - * ### Object oriented usage with decorators + * **Object oriented usage with decorators** * * If instead you use TypeScript Classes to wrap your Lambda handler you can use the {@link Tracer.captureLambdaHandler} decorator to automatically: - * * handle the subsegment lifecycle - * * add the `ServiceName` and `ColdStart` annotations - * * add the function response as metadata - * * add the function error as metadata (if any) + * - handle the subsegment lifecycle + * - add the `ServiceName` and `ColdStart` annotations + * - add the function response as metadata + * - add the function error as metadata (if any) * * @example * ```typescript @@ -106,7 +101,7 @@ const { Subsegment: XraySubsegment } = xraySdk; * export const handler = handlerClass.handler.bind(handlerClass); * ``` * - * ### Functions usage with manual instrumentation + * **Functions usage with manual instrumentation** * * If you prefer to manually instrument your Lambda handler you can use the methods in the tracer class directly. * @@ -391,10 +386,10 @@ class Tracer extends Utility implements TracerInterface { * A decorator automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. * * Using this decorator on your handler function will automatically: - * * handle the subsegment lifecycle - * * add the `ColdStart` annotation - * * add the function response as metadata - * * add the function error as metadata (if any) + * - handle the subsegment lifecycle + * - add the `ColdStart` annotation + * - add the function response as metadata + * - add the function error as metadata (if any) * * Note: Currently TypeScript only supports decorators on classes and methods. If you are using the * function syntax, you should use the middleware instead. @@ -475,9 +470,9 @@ class Tracer extends Utility implements TracerInterface { * A decorator automating capture of metadata and annotations on segments or subsegments for an arbitrary function. * * Using this decorator on your function will automatically: - * * handle the subsegment lifecycle - * * add the function response as metadata - * * add the function error as metadata (if any) + * - handle the subsegment lifecycle + * - add the function response as metadata + * - add the function error as metadata (if any) * * Note: Currently TypeScript only supports decorators on classes and methods. If you are using the * function syntax, you should use the middleware instead. diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index 90207d7664..793bd94a93 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -11,10 +11,10 @@ import type { CaptureLambdaHandlerOptions } from '../types/Tracer.js'; * A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler. * * Using this middleware on your handler function will automatically: - * * handle the subsegment lifecycle - * * add the `ColdStart` annotation - * * add the function response as metadata - * * add the function error as metadata (if any) + * - handle the subsegment lifecycle + * - add the `ColdStart` annotation + * - add the function response as metadata + * - add the function error as metadata (if any) * * @example * ```typescript @@ -33,7 +33,6 @@ import type { CaptureLambdaHandlerOptions } from '../types/Tracer.js'; * * @param target - The Tracer instance to use for tracing * @param options - (_optional_) Options for the middleware - * @returns middleware - The middy middleware object */ const captureLambdaHandler = ( target: Tracer, @@ -83,9 +82,7 @@ const captureLambdaHandler = ( target.setSegment(lambdaSegment); }; - const captureLambdaHandlerBefore = async ( - request: MiddyLikeRequest - ): Promise => { + const before = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { open(); setCleanupFunction(request); @@ -94,9 +91,7 @@ const captureLambdaHandler = ( } }; - const captureLambdaHandlerAfter = async ( - request: MiddyLikeRequest - ): Promise => { + const after = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { if (options?.captureResponse ?? true) { target.addResponseAsMetadata(request.response, process.env._HANDLER); @@ -105,9 +100,7 @@ const captureLambdaHandler = ( } }; - const captureLambdaHandlerError = async ( - request: MiddyLikeRequest - ): Promise => { + const onError = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { target.addErrorAsMetadata(request.error as Error); close(); @@ -115,9 +108,9 @@ const captureLambdaHandler = ( }; return { - before: captureLambdaHandlerBefore, - after: captureLambdaHandlerAfter, - onError: captureLambdaHandlerError, + before, + after, + onError, }; }; diff --git a/packages/tracer/src/provider/ProviderService.ts b/packages/tracer/src/provider/ProviderService.ts index a17331e3f1..44a1488af5 100644 --- a/packages/tracer/src/provider/ProviderService.ts +++ b/packages/tracer/src/provider/ProviderService.ts @@ -27,8 +27,8 @@ import { subscribe } from 'node:diagnostics_channel'; import http from 'node:http'; import https from 'node:https'; import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; -import type { DiagnosticsChannel } from 'undici-types'; import { getXRayTraceIdFromEnv } from '@aws-lambda-powertools/commons/utils/env'; +import type { DiagnosticsChannel } from 'undici-types'; import { findHeaderAndDecode, getRequestURL, @@ -172,7 +172,7 @@ class ProviderService implements ProviderServiceInterface { response: { status, ...(contentLenght && { - content_length: Number.parseInt(contentLenght), + content_length: Number.parseInt(contentLenght, 10), }), }, }; diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 8978e0bd6a..3767f7b10c 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -32,10 +32,8 @@ type TracerOptions = { /** * Options for handler decorators and middleware. * - * Options supported: - * * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata + * **Middleware usage** * - * Middleware usage: * @example * ```typescript * import middy from '@middy/core'; @@ -48,7 +46,8 @@ type TracerOptions = { * .use(captureLambdaHandler(tracer, { captureResponse: false })); * ``` * - * Decorator usage: + * **Decorator usage** + * * @example * ```typescript * const tracer = new Tracer(); @@ -61,6 +60,8 @@ type TracerOptions = { * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` + * + * @property captureResponse - Whether to capture the Lambda handler response as subsegment metadata (default: true) */ type CaptureLambdaHandlerOptions = { captureResponse?: boolean; @@ -69,22 +70,18 @@ type CaptureLambdaHandlerOptions = { /** * Options for method decorators. * - * Options supported: - * * `subSegmentName` - (_optional_) - Set a custom name for the subsegment - * * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata - * * Usage: * @example * ```typescript * const tracer = new Tracer(); * * class Lambda implements LambdaInterface { - * @tracer.captureMethod({ subSegmentName: 'gettingChargeId', captureResponse: false }) + * ⁣@tracer.captureMethod({ subSegmentName: 'gettingChargeId', captureResponse: false }) * private getChargeId(): string { * return 'foo bar'; * } * - * @tracer.captureLambdaHandler({ captureResponse: false }) + * ⁣@tracer.captureLambdaHandler({ captureResponse: false }) * public async handler(_event: any, _context: any): Promise { * this.getChargeId(); * } @@ -93,6 +90,9 @@ type CaptureLambdaHandlerOptions = { * const handlerClass = new Lambda(); * export const handler = handlerClass.handler.bind(handlerClass); * ``` + * + * @property subSegmentName - (_optional_) - Set a custom name for the subsegment + * @property captureResponse - (_optional_) - Disable response serialization as subsegment metadata (default: true) */ type CaptureMethodOptions = { subSegmentName?: string; diff --git a/packages/tracer/tests/e2e/decorator.test.ts b/packages/tracer/tests/e2e/decorator.test.ts index 2c5b14a6af..64ee1cbf5c 100644 --- a/packages/tracer/tests/e2e/decorator.test.ts +++ b/packages/tracer/tests/e2e/decorator.test.ts @@ -88,7 +88,7 @@ describe('Tracer E2E tests, decorator instrumentation', () => { } }); - it('should generate all trace data correctly', async () => { + it('should generate all trace data correctly', () => { // Assess const mainSubsegment = traceData[0]; const { subsegments, annotations, metadata } = mainSubsegment; diff --git a/packages/tracer/tests/e2e/manual.test.ts b/packages/tracer/tests/e2e/manual.test.ts index c553a7e181..c2fdf8b12f 100644 --- a/packages/tracer/tests/e2e/manual.test.ts +++ b/packages/tracer/tests/e2e/manual.test.ts @@ -85,7 +85,7 @@ describe('Tracer E2E tests, manual instantiation', () => { } }); - it('should generate all trace data correctly', async () => { + it('should generate all trace data correctly', () => { // Assess const mainSubsegment = traceData[0]; const { subsegments, annotations, metadata } = mainSubsegment; diff --git a/packages/tracer/tests/helpers/mockRequests.ts b/packages/tracer/tests/helpers/mockRequests.ts index 4385d06be4..3568646f00 100644 --- a/packages/tracer/tests/helpers/mockRequests.ts +++ b/packages/tracer/tests/helpers/mockRequests.ts @@ -49,7 +49,7 @@ const mockFetch = ({ }); if (throwError) { - const error = new AggregateError('Mock fetch error'); + const error = new AggregateError([], 'Mock fetch error'); errorChannel.publish({ request, @@ -62,8 +62,7 @@ const mockFetch = ({ const encoder = new TextEncoder(); const encodedHeaders = []; for (const [key, value] of Object.entries(headers ?? {})) { - encodedHeaders.push(encoder.encode(key)); - encodedHeaders.push(encoder.encode(value)); + encodedHeaders.push(encoder.encode(key), encoder.encode(value)); } responseHeadersChannel.publish({ request, diff --git a/packages/tracer/tests/unit/ProviderService.test.ts b/packages/tracer/tests/unit/ProviderService.test.ts index d10ddd4eb1..9cea095984 100644 --- a/packages/tracer/tests/unit/ProviderService.test.ts +++ b/packages/tracer/tests/unit/ProviderService.test.ts @@ -306,7 +306,7 @@ describe('Class: ProviderService', () => { }); describe('Method: instrumentFetch', () => { - it('subscribes to the diagnostics channel', async () => { + it('subscribes to the diagnostics channel', () => { // Prepare const provider: ProviderService = new ProviderService(); @@ -319,7 +319,7 @@ describe('Class: ProviderService', () => { expect(channel('undici:request:error').hasSubscribers).toBe(true); }); - it('traces a successful request', async () => { + it('traces a successful request', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -367,7 +367,7 @@ describe('Class: ProviderService', () => { ); }); - it('excludes the content_length header when invalid or not found', async () => { + it('excludes the content_length header when invalid or not found', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -406,7 +406,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenLastCalledWith(segment); }); - it('adds a throttle flag to the segment when the status code is 429', async () => { + it('adds a throttle flag to the segment when the status code is 429', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -442,7 +442,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenLastCalledWith(segment); }); - it('adds an error flag to the segment when the status code is 4xx', async () => { + it('adds an error flag to the segment when the status code is 4xx', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -478,7 +478,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenLastCalledWith(segment); }); - it('adds a fault flag to the segment when the status code is 5xx', async () => { + it('adds a fault flag to the segment when the status code is 5xx', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -514,7 +514,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenLastCalledWith(segment); }); - it('skips the segment creation when the request has no origin', async () => { + it('skips the segment creation when the request has no origin', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -531,7 +531,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenCalledTimes(0); }); - it('does not add any path to the segment when the request has no path', async () => { + it('does not add any path to the segment when the request has no path', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -564,7 +564,7 @@ describe('Class: ProviderService', () => { }); }); - it('closes the segment and adds a fault flag when the connection fails', async () => { + it('closes the segment and adds a fault flag when the connection fails', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); @@ -595,7 +595,7 @@ describe('Class: ProviderService', () => { expect(provider.setSegment).toHaveBeenLastCalledWith(segment); }); - it('forwards the correct sampling decision in the request header', async () => { + it('forwards the correct sampling decision in the request header', () => { // Prepare const provider: ProviderService = new ProviderService(); const segment = new Subsegment('## dummySegment'); diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index a59a125a9c..8f9046a171 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; @@ -884,7 +885,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer, { shouldThrow: true }); // Act & Assess - expect(lambda.handler({}, context)).rejects.toThrow(Error); + await expect(lambda.handler({}, context)).rejects.toThrow(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); expect(addErrorSpy).toHaveBeenCalledTimes(0); @@ -905,7 +906,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer, { shouldThrow: true }); // Act & Assess - expect(lambda.handler({}, context)).rejects.toThrow(Error); + await expect(lambda.handler({}, context)).rejects.toThrow(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(addErrorAsMetadataSpy).toHaveBeenCalledTimes(1); expect(addErrorAsMetadataSpy).toHaveBeenCalledWith(expect.any(Error)); @@ -922,7 +923,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer); // Act - lambda.handler(event, context); + await lambda.handler(event, context); // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); @@ -978,6 +979,7 @@ describe('Class: Tracer', () => { } public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return this.memberVariable; } @@ -1057,6 +1059,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1087,6 +1090,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1126,6 +1130,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod({ captureResponse: false }) public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1161,6 +1166,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1197,6 +1203,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(_some: string): Promise { + await setTimeout(0); // Simulate some async operation throw new Error('Exception thrown!'); } @@ -1245,6 +1252,7 @@ describe('Class: Tracer', () => { @tracer.captureMethod() public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return `memberVariable:${this.memberVariable}`; } @@ -1279,7 +1287,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(): Promise { - return; + await setTimeout(0); // Simulate some async operation } public async handler( @@ -1288,12 +1296,10 @@ describe('Class: Tracer', () => { ): Promise { await this.dummyMethod(); this.otherDummyMethod(); - - return; } - public otherDummyMethod(): void { - return; + public otherDummyMethod(): boolean { + return false; } } @@ -1336,6 +1342,7 @@ describe('Class: Tracer', () => { @tracer.captureMethod() @passThrough() public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return 'foo'; } @@ -1374,6 +1381,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod({ subSegmentName: '#### myCustomMethod' }) public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1419,6 +1427,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index bb529a3af7..93e37b6309 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import { cleanupMiddlewares } from '@aws-lambda-powertools/commons'; import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; @@ -13,6 +14,10 @@ import { captureLambdaHandler } from '../../src/middleware/middy.js'; describe('Middy middleware', () => { const ENVIRONMENT_VARIABLES = process.env; + const lambdaHandler: Handler = async (_event: unknown, _context: Context) => { + await setTimeout(0); // Simulate some async operation + throw new Error('Exception thrown!'); + }; beforeEach(() => { vi.clearAllMocks(); @@ -64,12 +69,6 @@ describe('Middy middleware', () => { () => new Segment('facade', process.env._X_AMZN_TRACE_ID || null) ) .mockImplementationOnce(() => new Subsegment('## index.handler')); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess @@ -173,12 +172,6 @@ describe('Middy middleware', () => { setContextMissingStrategy(() => null); const addErrorSpy = vi.spyOn(newSubsegment, 'addError'); const addErrorFlagSpy = vi.spyOn(newSubsegment, 'addErrorFlag'); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess @@ -208,12 +201,6 @@ describe('Middy middleware', () => { ); setContextMissingStrategy(() => null); const addErrorSpy = vi.spyOn(newSubsegment, 'addError'); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess diff --git a/packages/tracer/vitest.config.ts b/packages/tracer/vitest.config.ts index baa5cf7463..e962bb6d1c 100644 --- a/packages/tracer/vitest.config.ts +++ b/packages/tracer/vitest.config.ts @@ -4,7 +4,7 @@ export default defineProject({ test: { environment: 'node', setupFiles: ['../testing/src/setupEnv.ts'], - hookTimeout: 1_000 * 60 * 10, // 10 minutes - testTimeout: 1_000 * 60 * 3, // 3 minutes + hookTimeout: 1000 * 60 * 10, // 10 minutes + testTimeout: 1000 * 60 * 3, // 3 minutes }, }); diff --git a/packages/validation/package.json b/packages/validation/package.json index 0aa16cd4b2..f254e48dc8 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -19,6 +19,7 @@ "build": "npm run build:esm & npm run build:cjs", "lint": "biome lint .", "lint:fix": "biome check --write .", + "lint:ci": "biome ci .", "prepack": "node ../../.github/scripts/release_patch_package_json.js ." }, "homepage": "https://github.com/aws-powertools/powertools-lambda-typescript#readme", diff --git a/packages/validation/src/middleware.ts b/packages/validation/src/middleware.ts index af6d953033..59a5093c62 100644 --- a/packages/validation/src/middleware.ts +++ b/packages/validation/src/middleware.ts @@ -105,7 +105,7 @@ import { validate } from './validate.js'; * @param options.ajv - Optional Ajv instance to use for validation, if not provided a new instance will be created. */ const validator = (options: ValidatorOptions) => { - const before: MiddlewareFn = async (request) => { + const before: MiddlewareFn = (request) => { if (options.inboundSchema) { const originalEvent = structuredClone(request.event); try { @@ -125,7 +125,7 @@ const validator = (options: ValidatorOptions) => { } }; - const after = async (handler: MiddyLikeRequest) => { + const after = (handler: MiddyLikeRequest) => { if (options.outboundSchema) { try { handler.response = validate({ diff --git a/packages/validation/tests/unit/decorator.test.ts b/packages/validation/tests/unit/decorator.test.ts index 28e6cd27fd..423ab34cc6 100644 --- a/packages/validation/tests/unit/decorator.test.ts +++ b/packages/validation/tests/unit/decorator.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import { describe, expect, it } from 'vitest'; import { validator } from '../../src/decorator.js'; import { SchemaValidationError } from '../../src/errors.js'; @@ -21,11 +22,12 @@ const outboundSchema = { }; describe('Decorator: validator', () => { - it('should validate inbound and outbound successfully', async () => { + it('validates both inbound and outbound successfully', async () => { // Prepare class TestClass { @validator({ inboundSchema, outboundSchema }) async multiply(input: { value: number }): Promise<{ result: number }> { + await setTimeout(1); // simulate some processing time return { result: input.value * 2 }; } } @@ -39,11 +41,12 @@ describe('Decorator: validator', () => { expect(output).toEqual({ result: 10 }); }); - it('should throw error on inbound validation failure', async () => { + it('throws an error on inbound validation failure', async () => { // Prepare class TestClass { @validator({ inboundSchema, outboundSchema }) async multiply(input: { value: number }): Promise<{ result: number }> { + await setTimeout(1); // simulate some processing time return { result: input.value * 2 }; } } @@ -58,11 +61,12 @@ describe('Decorator: validator', () => { ); }); - it('should throw error on outbound validation failure', async () => { + it('throws an error on outbound validation failure', async () => { // Prepare class TestClassInvalid { @validator({ inboundSchema, outboundSchema }) async multiply(_input: { value: number }) { + await setTimeout(1); // simulate some processing time return { result: 'invalid' }; } } @@ -74,11 +78,12 @@ describe('Decorator: validator', () => { ); }); - it('should no-op when no schemas are provided', async () => { + it('results in a no-op when no schemas are provided', async () => { // Prepare class TestClassNoOp { @validator({}) async echo(input: unknown): Promise { + await setTimeout(1); // simulate some processing time return input; } } @@ -92,11 +97,12 @@ describe('Decorator: validator', () => { expect(result).toEqual(data); }); - it('should validate inbound only', async () => { + it('validates the inbound schema only', async () => { // Prepare class TestClassInbound { @validator({ inboundSchema }) async process(input: { value: number }): Promise<{ data: string }> { + await setTimeout(1); // simulate some processing time return { data: JSON.stringify(input) }; } } @@ -110,11 +116,12 @@ describe('Decorator: validator', () => { expect(output).toEqual({ data: JSON.stringify(input) }); }); - it('should validate outbound only', async () => { + it('validates the outbound schema only', async () => { // Prepare class TestClassOutbound { @validator({ outboundSchema }) async process(_input: { text: string }): Promise<{ result: number }> { + await setTimeout(1); // simulate some processing time return { result: 42 }; } } diff --git a/packages/validation/tests/unit/middleware.test.ts b/packages/validation/tests/unit/middleware.test.ts index 1d2796638a..6b76d66d65 100644 --- a/packages/validation/tests/unit/middleware.test.ts +++ b/packages/validation/tests/unit/middleware.test.ts @@ -22,7 +22,7 @@ const outboundSchema = { additionalProperties: false, }; -const baseHandler = async (event: { inputValue: unknown }) => { +const baseHandler = (event: { inputValue: unknown }) => { return { outputValue: event.inputValue, };