diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index ec26c92bc4..3914c0b48c 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -4,3 +4,4 @@ export * from './config'; export * as ContextExamples from './samples/resources/contexts'; export * as Events from './samples/resources/events'; export * from './types/middy'; +export * from './types/utils'; diff --git a/packages/commons/src/types/utils.ts b/packages/commons/src/types/utils.ts new file mode 100644 index 0000000000..64a322020d --- /dev/null +++ b/packages/commons/src/types/utils.ts @@ -0,0 +1,59 @@ +/** + * Returns true if the passed value is a record (object). + * + * @param value + */ +const isRecord = (value: unknown): value is Record => { + return ( + Object.prototype.toString.call(value) === '[object Object]' && + !Object.is(value, null) + ); +}; + +/** + * Returns true if the passed value is truthy. + * + * @param value + */ +const isTruthy = (value: unknown): boolean => { + if (typeof value === 'string') { + return value !== ''; + } else if (typeof value === 'number') { + return value !== 0; + } else if (typeof value === 'boolean') { + return value; + } else if (Array.isArray(value)) { + return value.length > 0; + } else if (isRecord(value)) { + return Object.keys(value).length > 0; + } else { + return false; + } +}; + +/** + * Returns true if the passed value is null or undefined. + * + * @param value + */ +const isNullOrUndefined = (value: unknown): value is null | undefined => { + return Object.is(value, null) || Object.is(value, undefined); +}; + +/** + * Returns true if the passed value is a string. + * @param value + * @returns + */ +const isString = (value: unknown): value is string => { + return typeof value === 'string'; +}; + +export { isRecord, isString, isTruthy, isNullOrUndefined }; + +type JSONPrimitive = string | number | boolean | null | undefined; +type JSONValue = JSONPrimitive | JSONObject | JSONArray; +type JSONObject = { [key: string]: JSONValue }; +type JSONArray = Array; + +export type { JSONPrimitive, JSONValue, JSONObject, JSONArray }; diff --git a/packages/commons/tests/unit/utils.test.ts b/packages/commons/tests/unit/utils.test.ts new file mode 100644 index 0000000000..464b05958f --- /dev/null +++ b/packages/commons/tests/unit/utils.test.ts @@ -0,0 +1,120 @@ +/** + * Test utils functions + * + * @group unit/commons/utils + */ +import { + isRecord, + isTruthy, + isNullOrUndefined, + isString, +} from '../../src/types/utils'; + +describe('Functions: utils', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + describe('Function: isRecord', () => { + it('returns true when the passed object is a Record', () => { + // Prepare + const obj = { a: 1, b: 2, c: 3 }; + + // Act + const result = isRecord(obj); + + // Assert + expect(result).toBe(true); + }); + + it('returns false when the passed object is not a Record', () => { + // Prepare + const obj = [1, 2, 3]; + + // Act + const result = isRecord(obj); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('Function: isTruthy', () => { + it.each(['hello', 1, true, [1], { foo: 1 }])( + 'returns true when the passed value is truthy', + (testValue) => { + // Prepare + const value = testValue; + + // Act + const result = isTruthy(value); + + // Assert + expect(result).toBe(true); + } + ); + + it.each(['', 0, false, [], {}, Symbol])( + 'returns true when the passed value is falsy', + (testValue) => { + // Prepare + const value = testValue; + + // Act + const result = isTruthy(value); + + // Assert + expect(result).toBe(false); + } + ); + }); + + describe('Function: isNullOrUndefined', () => { + it('returns true when the passed value is null or undefined', () => { + // Prepare + const value = undefined; + + // Act + const result = isNullOrUndefined(value); + + // Assert + expect(result).toBe(true); + }); + + it('returns false when the passed value is not null or undefined', () => { + // Prepare + const value = 'hello'; + + // Act + const result = isNullOrUndefined(value); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('Function: isString', () => { + it('returns true when the passed value is a string', () => { + // Prepare + const value = 'hello'; + + // Act + const result = isString(value); + + // Assert + expect(result).toBe(true); + }); + + it('returns false when the passed value is not a string', () => { + // Prepare + const value = 123; + + // Act + const result = isString(value); + + // Assert + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/parameters/package.json b/packages/parameters/package.json index eb1765f890..afd473bc50 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -34,57 +34,85 @@ "import": "./lib/index.js", "require": "./lib/index.js" }, + "./base/types": { + "import": "./lib/types/BaseProvider.d.ts", + "require": "./lib/types/BaseProvider.d.ts" + }, "./base": { "import": "./lib/base/index.js", "require": "./lib/base/index.js" }, + "./ssm/types": { + "import": "./lib/types/SSMProvider.d.ts", + "require": "./lib/types/SSMProvider.d.ts" + }, "./ssm": { "import": "./lib/ssm/index.js", "require": "./lib/ssm/index.js" }, + "./secrets/types": { + "import": "./lib/types/SecretsProvider.d.ts", + "require": "./lib/types/SecretsProvider.d.ts" + }, "./secrets": { "import": "./lib/secrets/index.js", "require": "./lib/secrets/index.js" }, + "./dynamodb/types": { + "import": "./lib/types/AppConfigProvider.d.ts", + "require": "./lib/types/AppConfigProvider.d.ts" + }, "./dynamodb": { "import": "./lib/dynamodb/index.js", "require": "./lib/dynamodb/index.js" }, + "./appconfig/types": { + "import": "./lib/appconfig/index.js", + "require": "./lib/appconfig/index.js" + }, "./appconfig": { "import": "./lib/appconfig/index.js", "require": "./lib/appconfig/index.js" }, "./errors": { - "import": "./lib/Exceptions.js", - "require": "./lib/Exceptions.js" - }, - "./types": { - "import": "./lib/types/index.d.ts", - "require": "./lib/types/index.d.ts" + "import": "./lib/errors.js", + "require": "./lib/errors.js" } }, "typesVersions": { "*": { + "base/types": [ + "lib/types/BaseProvider.d.ts" + ], "base": [ "lib/base/index.d.ts" ], + "ssm/types": [ + "lib/types/SSMProvider.d.ts" + ], "ssm": [ "lib/ssm/index.d.ts" ], + "secrets/types": [ + "lib/types/SecretsProvider.d.ts" + ], "secrets": [ "lib/secrets/index.d.ts" ], + "dynamodb/types": [ + "./lib/types/DynamoDBProvider.d.ts" + ], "dynamodb": [ "lib/dynamodb/index.d.ts" ], + "appconfig/types": [ + "./lib/types/AppConfigProvider.d.ts" + ], "appconfig": [ "lib/appconfig/index.d.ts" ], "errors": [ - "lib/Exceptions.d.ts" - ], - "types": [ - "lib/types/index.d.ts" + "lib/errors.d.ts" ] } }, diff --git a/packages/parameters/src/BaseProvider.ts b/packages/parameters/src/BaseProvider.ts deleted file mode 100644 index 3d58720724..0000000000 --- a/packages/parameters/src/BaseProvider.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { fromBase64 } from '@aws-sdk/util-base64-node'; -import { GetOptions } from './GetOptions'; -import { GetMultipleOptions } from './GetMultipleOptions'; -import { ExpirableValue } from './ExpirableValue'; -import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from './constants'; -import { GetParameterError, TransformParameterError } from './Exceptions'; -import { EnvironmentVariablesService } from './config/EnvironmentVariablesService'; -import type { - BaseProviderInterface, - GetMultipleOptionsInterface, - GetOptionsInterface, - TransformOptions, -} from './types'; - -// These providers are dinamycally intialized on first use of the helper functions -const DEFAULT_PROVIDERS: Record = {}; - -/** - * Base class for all providers. - * - * As an abstract class, it should not be used directly, but rather extended by other providers. - * - * It implements the common logic for all providers, such as caching, transformation, etc. - * Each provider that extends this class must implement the `_get` and `_getMultiple` abstract methods. - * - * These methods are responsible for retrieving the values from the underlying parameter store. They are - * called by the `get` and `getMultiple` methods, which are responsible for caching and transformation. - * - * If there are multiple calls to the same parameter but in a different transform, they will be stored multiple times. - * This allows us to optimize by transforming the data only once per retrieval, thus there is no need to transform cached values multiple times. - * - * However, this means that we need to make multiple calls to the underlying parameter store if we need to return it in different transforms. - * - * Since the number of supported transform is small and the probability that a given parameter will always be used in a specific transform, - * this should be an acceptable tradeoff. - */ -abstract class BaseProvider implements BaseProviderInterface { - public envVarsService: EnvironmentVariablesService; - protected store: Map; - - public constructor() { - this.store = new Map(); - this.envVarsService = new EnvironmentVariablesService(); - } - - /** - * Add a value to the cache. - * - * @param {string} key - Key of the cached value - * @param {string | Uint8Array | Record} value - Value to be cached - * @param {number} maxAge - Maximum age in seconds for the value to be cached - */ - public addToCache( - key: string, - value: string | Uint8Array | Record, - maxAge: number - ): void { - if (maxAge <= 0) return; - - this.store.set(key, new ExpirableValue(value, maxAge)); - } - - /** - * Clear the cache. - */ - public clearCache(): void { - this.store.clear(); - } - - /** - * Retrieve a parameter value or return the cached value. - * - * @param {string} name - Parameter name - * @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch - */ - public async get( - name: string, - options?: GetOptionsInterface - ): Promise> { - const configs = new GetOptions(options, this.envVarsService); - const key = [name, configs.transform].toString(); - - if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { - // If the code enters in this block, then the key must exist & not have been expired - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.store.get(key)!.value; - } - - let value; - try { - value = await this._get(name, options); - } catch (error) { - throw new GetParameterError((error as Error).message); - } - - if (value && configs.transform) { - value = transformValue(value, configs.transform, true, name); - } - - if (value) { - this.addToCache(key, value, configs.maxAge); - } - - return value; - } - - /** - * Retrieve multiple parameter values or return the cached values. - * - * @param {string} path - Parameters path - * @param {GetMultipleOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch - * @returns - */ - public async getMultiple( - path: string, - options?: GetMultipleOptionsInterface - ): Promise> { - const configs = new GetMultipleOptions(options, this.envVarsService); - const key = [path, configs.transform].toString(); - - if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { - // If the code enters in this block, then the key must exist & not have been expired - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.store.get(key)!.value as Record; - } - - let values = {}; - try { - values = await this._getMultiple(path, options); - } catch (error) { - throw new GetParameterError((error as Error).message); - } - - if (Object.keys(values) && configs.transform) { - values = transformValues( - values, - configs.transform, - configs.throwOnTransformError - ); - } - - if (Array.from(Object.keys(values)).length !== 0) { - this.addToCache(key, values, configs.maxAge); - } - - return values; - } - - /** - * Check whether a key has expired in the cache or not. - * - * It returns true if the key is expired or not present in the cache. - * - * @param {string} key - Stringified representation of the key to retrieve - */ - public hasKeyExpiredInCache(key: string): boolean { - const value = this.store.get(key); - if (value) return value.isExpired(); - - return true; - } - - /** - * Retrieve parameter value from the underlying parameter store. - * - * @param {string} name - Parameter name - * @param {unknown} options - Options to pass to the underlying implemented method - */ - protected abstract _get( - name: string, - options?: unknown - ): Promise; - - /** - * Retrieve multiple parameter values from the underlying parameter store. - * - * @param {string} path - Parameter name - * @param {unknown} options - Options to pass to the underlying implementated method - */ - protected abstract _getMultiple( - path: string, - options?: unknown - ): Promise>; -} - -/** - * Utility function to transform a value. - * - * It supports JSON and binary transformations, as well as an 'auto' mode that will try to transform the value based on the key. - * - * @param {string | Uint8Array | undefined} value - Value to be transformed - * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' - * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false - * @param {string} key - Key of the value to be transformed, used to determine the transformation method when using 'auto' - */ -const transformValue = ( - value: string | Uint8Array | undefined, - transform: TransformOptions, - throwOnTransformError: boolean, - key: string -): string | Record | undefined => { - try { - const normalizedTransform = transform.toLowerCase(); - - if (value instanceof Uint8Array) { - value = new TextDecoder('utf-8').decode(value); - } - - if ( - (normalizedTransform === TRANSFORM_METHOD_JSON || - (normalizedTransform === 'auto' && - key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) && - typeof value === 'string' - ) { - return JSON.parse(value) as Record; - } else if ( - (normalizedTransform === TRANSFORM_METHOD_BINARY || - (normalizedTransform === 'auto' && - key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`))) && - typeof value === 'string' - ) { - return new TextDecoder('utf-8').decode(fromBase64(value)); - } else { - return value; - } - } catch (error) { - if (throwOnTransformError) - throw new TransformParameterError(transform, (error as Error).message); - - return; - } -}; - -/** - * Utility function to transform multiple values. - * - * It iterates over the values and applies the transformation to each one by calling the `transformValue` function. - * - * @param {Record} value - Values to be transformed - * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' - * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false - */ -const transformValues = ( - value: Record, - transform: TransformOptions, - throwOnTransformError: boolean -): Record | undefined> => { - const transformedValues: Record< - string, - string | Record | undefined - > = {}; - for (const [entryKey, entryValue] of Object.entries(value)) { - try { - transformedValues[entryKey] = transformValue( - entryValue, - transform, - throwOnTransformError, - entryKey - ); - } catch (error) { - if (throwOnTransformError) - throw new TransformParameterError(transform, (error as Error).message); - } - } - - return transformedValues; -}; - -/** - * Utility function to clear all the caches of the default providers. - * - * This is useful when you want to clear the cache of all the providers at once, for example during testing. - */ -const clearCaches = (): void => { - for (const provider of Object.values(DEFAULT_PROVIDERS)) { - provider.clearCache(); - } -}; - -export { - BaseProvider, - ExpirableValue, - transformValue, - DEFAULT_PROVIDERS, - clearCaches, -}; diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index c3695d3dd8..637cc85723 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -1,4 +1,4 @@ -import { BaseProvider, DEFAULT_PROVIDERS } from '../BaseProvider'; +import { BaseProvider, DEFAULT_PROVIDERS } from '../base'; import { AppConfigDataClient, StartConfigurationSessionCommand, @@ -7,7 +7,8 @@ import { import type { StartConfigurationSessionCommandInput } from '@aws-sdk/client-appconfigdata'; import type { AppConfigProviderOptions, - AppConfigGetOptionsInterface, + AppConfigGetOptions, + AppConfigGetOutput, } from '../types/AppConfigProvider'; /** @@ -31,7 +32,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a configuration profile @@ -52,7 +56,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a configuration profile and cache it for 10 seconds @@ -67,7 +74,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a config and always fetch the latest value @@ -84,7 +94,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a JSON config or Feature Flag and parse it as JSON @@ -98,7 +111,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a base64-encoded string and decode it @@ -114,7 +130,10 @@ import type { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client @@ -140,6 +159,8 @@ import type { * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', * clientConfig: { region: 'eu-west-1' }, * }); * ``` @@ -155,6 +176,8 @@ import type { * * const client = new AppConfigDataClient({ region: 'eu-west-1' }); * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', * awsSdkV3Client: client, * }); * ``` @@ -204,7 +227,10 @@ class AppConfigProvider extends BaseProvider { * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * - * const configProvider = new AppConfigProvider(); + * const configProvider = new AppConfigProvider({ + * application: 'my-app', + * environment: 'prod', + * }); * * export const handler = async (): Promise => { * // Retrieve a configuration profile @@ -222,35 +248,43 @@ class AppConfigProvider extends BaseProvider { * For usage examples check {@link AppConfigProvider}. * * @param {string} name - The name of the configuration profile or its ID - * @param {GetAppConfigCombinedInterface} options - Options to configure the provider + * @param {AppConfigGetOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda-typescript/latest/utilities/parameters/ */ - public async get( + public async get< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends + | AppConfigGetOptions + | undefined = AppConfigGetOptions + >( name: string, - options?: AppConfigGetOptionsInterface - ): Promise> { - return super.get(name, options); + options?: InferredFromOptionsType & AppConfigGetOptions + ): Promise< + | AppConfigGetOutput + | undefined + > { + return super.get(name, options) as Promise< + | AppConfigGetOutput + | undefined + >; } /** * Retrieving multiple configurations is not supported by AWS AppConfig. */ - public async getMultiple( - path: string, - _options?: unknown - ): Promise> { - return super.getMultiple(path); + public async getMultiple(path: string, _options?: unknown): Promise { + await super.getMultiple(path); } /** * Retrieve a configuration from AWS AppConfig. * * @param {string} name - Name of the configuration or its ID - * @param {AppConfigGetOptionsInterface} options - SDK options to propagate to `StartConfigurationSession` API call + * @param {AppConfigGetOptions} options - SDK options to propagate to `StartConfigurationSession` API call */ protected async _get( name: string, - options?: AppConfigGetOptionsInterface + options?: AppConfigGetOptions ): Promise { /** * The new AppConfig APIs require two API calls to return the configuration @@ -321,7 +355,7 @@ class AppConfigProvider extends BaseProvider { protected async _getMultiple( _path: string, _sdkOptions?: unknown - ): Promise> { + ): Promise { throw new Error('Method not implemented.'); } } diff --git a/packages/parameters/src/appconfig/getAppConfig.ts b/packages/parameters/src/appconfig/getAppConfig.ts index cb38bad67a..41ab0f2d18 100644 --- a/packages/parameters/src/appconfig/getAppConfig.ts +++ b/packages/parameters/src/appconfig/getAppConfig.ts @@ -1,5 +1,8 @@ import { AppConfigProvider, DEFAULT_PROVIDERS } from './AppConfigProvider'; -import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; +import type { + AppConfigGetOutput, + GetAppConfigOptions, +} from '../types/AppConfigProvider'; /** * ## Intro @@ -24,7 +27,10 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * * export const handler = async (): Promise => { * // Retrieve a configuration profile - * const encodedConfig = await getAppConfig('my-config'); + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` @@ -42,7 +48,10 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * * export const handler = async (): Promise => { * // Retrieve a configuration profile and cache it for 10 seconds - * const encodedConfig = await getAppConfig('my-config'); + * const encodedConfig = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` @@ -55,7 +64,11 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * * export const handler = async (): Promise => { * // Retrieve a config and always fetch the latest value - * const config = await getAppConfig('my-config', { forceFetch: true }); + * const config = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * forceFetch: true, + * }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` @@ -70,7 +83,11 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * * export const handler = async (): Promise => { * // Retrieve a JSON config or Feature Flag and parse it as JSON - * const config = await getAppConfig('my-config', { transform: 'json' }); + * const config = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * transform: 'json' + * }); * }; * ``` * @@ -82,7 +99,11 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * * export const handler = async (): Promise => { * // Retrieve a base64-encoded string and decode it - * const config = await getAppConfig('my-config', { transform: 'binary' }); + * const config = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', + * transform: 'binary' + * }); * }; * ``` * @@ -97,6 +118,8 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * export const handler = async (): Promise => { * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client * const config = await getAppConfig('my-config', { + * application: 'my-app', + * environment: 'prod', * sdkOptions: { * RequiredMinimumPollIntervalInSeconds: 60, * }, @@ -114,18 +137,37 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda-typescript/latest/utilities/parameters/). * * @param {string} name - The name of the configuration profile or its ID - * @param {GetAppConfigCombinedInterface} options - Options to configure the provider + * @param {GetAppConfigOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda-typescript/latest/utilities/parameters/ */ -const getAppConfig = ( +const getAppConfig = < + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends + | GetAppConfigOptions + | undefined = GetAppConfigOptions +>( name: string, - options: GetAppConfigCombinedInterface -): Promise> => { + options: InferredFromOptionsType & GetAppConfigOptions +): Promise< + | AppConfigGetOutput + | undefined +> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('appconfig')) { - DEFAULT_PROVIDERS.appconfig = new AppConfigProvider(options); + DEFAULT_PROVIDERS.appconfig = new AppConfigProvider({ + application: options?.application, + environment: options.environment, + }); } - return DEFAULT_PROVIDERS.appconfig.get(name, options); + return (DEFAULT_PROVIDERS.appconfig as AppConfigProvider).get(name, { + maxAge: options?.maxAge, + transform: options?.transform, + forceFetch: options?.forceFetch, + sdkOptions: options?.sdkOptions, + }) as Promise< + | AppConfigGetOutput + | undefined + >; }; export { getAppConfig }; diff --git a/packages/parameters/src/base/BaseProvider.ts b/packages/parameters/src/base/BaseProvider.ts new file mode 100644 index 0000000000..a7ada6d5a6 --- /dev/null +++ b/packages/parameters/src/base/BaseProvider.ts @@ -0,0 +1,198 @@ +import { + isNullOrUndefined, + isRecord, + isString, +} from '@aws-lambda-powertools/commons'; +import { GetOptions } from './GetOptions'; +import { GetMultipleOptions } from './GetMultipleOptions'; +import { ExpirableValue } from './ExpirableValue'; +import { GetParameterError, TransformParameterError } from '../errors'; +import { EnvironmentVariablesService } from '../config/EnvironmentVariablesService'; +import { transformValue } from './transformValue'; +import type { + BaseProviderInterface, + GetMultipleOptionsInterface, + GetOptionsInterface, +} from '../types/BaseProvider'; + +/** + * Base class for all providers. + * + * As an abstract class, it should not be used directly, but rather extended by other providers. + * + * It implements the common logic for all providers, such as caching, transformation, etc. + * Each provider that extends this class must implement the `_get` and `_getMultiple` abstract methods. + * + * These methods are responsible for retrieving the values from the underlying parameter store. They are + * called by the `get` and `getMultiple` methods, which are responsible for caching and transformation. + * + * If there are multiple calls to the same parameter but in a different transform, they will be stored multiple times. + * This allows us to optimize by transforming the data only once per retrieval, thus there is no need to transform cached values multiple times. + * + * However, this means that we need to make multiple calls to the underlying parameter store if we need to return it in different transforms. + * + * Since the number of supported transform is small and the probability that a given parameter will always be used in a specific transform, + * this should be an acceptable tradeoff. + */ +abstract class BaseProvider implements BaseProviderInterface { + public envVarsService: EnvironmentVariablesService; + protected store: Map; + + public constructor() { + this.store = new Map(); + this.envVarsService = new EnvironmentVariablesService(); + } + + /** + * Add a value to the cache. + * + * @param {string} key - Key of the cached value + * @param {string | Uint8Array | Record} value - Value to be cached + * @param {number} maxAge - Maximum age in seconds for the value to be cached + */ + public addToCache(key: string, value: unknown, maxAge: number): void { + if (maxAge <= 0) return; + + this.store.set(key, new ExpirableValue(value, maxAge)); + } + + /** + * Clear the cache. + */ + public clearCache(): void { + this.store.clear(); + } + + /** + * Retrieve a parameter value or return the cached value. + * + * @param {string} name - Parameter name + * @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch + */ + public async get( + name: string, + options?: GetOptionsInterface + ): Promise { + const configs = new GetOptions(options, this.envVarsService); + const key = [name, configs.transform].toString(); + + if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { + return this.store.get(key)?.value; + } + + try { + let value = await this._get(name, options); + + if (isNullOrUndefined(value)) return undefined; + + if ( + configs.transform && + (isString(value) || value instanceof Uint8Array) + ) { + value = transformValue(value, configs.transform, true, name); + } + + this.addToCache(key, value, configs.maxAge); + + return value; + } catch (error) { + if (error instanceof TransformParameterError) throw error; + throw new GetParameterError((error as Error).message); + } + } + + /** + * Retrieve multiple parameter values or return the cached values. + * + * @param {string} path - Parameters path + * @param {GetMultipleOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch + * @returns + */ + public async getMultiple( + path: string, + options?: GetMultipleOptionsInterface + ): Promise { + const configs = new GetMultipleOptions(options, this.envVarsService); + const key = [path, configs.transform].toString(); + + if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { + // If the code enters in this block, then the key must exist & not have been expired + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.store.get(key)!.value as Record; + } + + let values; + try { + values = await this._getMultiple(path, options); + if (!isRecord(values)) { + throw new GetParameterError( + `Expected result to be a Record but got ${typeof values}` + ); + } + } catch (error) { + throw new GetParameterError((error as Error).message); + } + + if (configs.transform) { + for (const [entryKey, entryValue] of Object.entries(values)) { + if (!(isString(entryValue) || entryValue instanceof Uint8Array)) + continue; + try { + values[entryKey] = transformValue( + entryValue, + configs.transform, + configs.throwOnTransformError, + entryKey + ); + } catch (error) { + if (configs.throwOnTransformError) + throw new TransformParameterError( + configs.transform, + (error as Error).message + ); + } + } + } + + if (Object.keys(values).length !== 0) { + this.addToCache(key, values, configs.maxAge); + } + + return values; + } + + /** + * Check whether a key has expired in the cache or not. + * + * It returns true if the key is expired or not present in the cache. + * + * @param {string} key - Stringified representation of the key to retrieve + */ + public hasKeyExpiredInCache(key: string): boolean { + const value = this.store.get(key); + if (value) return value.isExpired(); + + return true; + } + + /** + * Retrieve parameter value from the underlying parameter store. + * + * @param {string} name - Parameter name + * @param {unknown} options - Options to pass to the underlying implemented method + */ + protected abstract _get(name: string, options?: unknown): Promise; + + /** + * Retrieve multiple parameter values from the underlying parameter store. + * + * @param {string} path - Parameter name + * @param {unknown} options - Options to pass to the underlying implementated method + */ + protected abstract _getMultiple( + path: string, + options?: unknown + ): Promise | void>; +} + +export { BaseProvider }; diff --git a/packages/parameters/src/base/DefaultProviders.ts b/packages/parameters/src/base/DefaultProviders.ts new file mode 100644 index 0000000000..692dacdcc3 --- /dev/null +++ b/packages/parameters/src/base/DefaultProviders.ts @@ -0,0 +1,19 @@ +import type { BaseProviderInterface } from '../types/BaseProvider'; + +// These providers are dinamycally intialized on first use of the helper functions +const DEFAULT_PROVIDERS: Record = {}; + +/** + * Utility function to clear all the caches of the default providers. + * + * This is useful when you want to clear the cache of all the providers at once, for example during testing. + */ +const clearCaches = (): void => { + for (const provider of Object.values(DEFAULT_PROVIDERS)) { + if (provider.clearCache) { + provider.clearCache(); + } + } +}; + +export { DEFAULT_PROVIDERS, clearCaches }; diff --git a/packages/parameters/src/ExpirableValue.ts b/packages/parameters/src/base/ExpirableValue.ts similarity index 77% rename from packages/parameters/src/ExpirableValue.ts rename to packages/parameters/src/base/ExpirableValue.ts index 08ed63de5e..7793319e59 100644 --- a/packages/parameters/src/ExpirableValue.ts +++ b/packages/parameters/src/base/ExpirableValue.ts @@ -1,4 +1,4 @@ -import type { ExpirableValueInterface } from './types'; +import type { ExpirableValueInterface } from '../types/BaseProvider'; /** * Class to represent a value that can expire. @@ -8,17 +8,14 @@ import type { ExpirableValueInterface } from './types'; */ class ExpirableValue implements ExpirableValueInterface { public ttl: number; - public value: string | Uint8Array | Record; + public value: unknown; /** * * @param value - Value to be cached * @param maxAge - Maximum age in seconds for the value to be cached */ - public constructor( - value: string | Uint8Array | Record, - maxAge: number - ) { + public constructor(value: unknown, maxAge: number) { this.value = value; const timeNow = new Date(); this.ttl = timeNow.setSeconds(timeNow.getSeconds() + maxAge); diff --git a/packages/parameters/src/GetMultipleOptions.ts b/packages/parameters/src/base/GetMultipleOptions.ts similarity index 79% rename from packages/parameters/src/GetMultipleOptions.ts rename to packages/parameters/src/base/GetMultipleOptions.ts index 9b8c5ded5e..d452062e43 100644 --- a/packages/parameters/src/GetMultipleOptions.ts +++ b/packages/parameters/src/base/GetMultipleOptions.ts @@ -1,6 +1,6 @@ import { GetOptions } from './GetOptions'; -import { EnvironmentVariablesService } from './config/EnvironmentVariablesService'; -import type { GetMultipleOptionsInterface } from './types'; +import { EnvironmentVariablesService } from '../config/EnvironmentVariablesService'; +import type { GetMultipleOptionsInterface } from '../types/BaseProvider'; /** * Options for the `getMultiple` method. diff --git a/packages/parameters/src/GetOptions.ts b/packages/parameters/src/base/GetOptions.ts similarity index 72% rename from packages/parameters/src/GetOptions.ts rename to packages/parameters/src/base/GetOptions.ts index 23f77e5760..e83d0a622f 100644 --- a/packages/parameters/src/GetOptions.ts +++ b/packages/parameters/src/base/GetOptions.ts @@ -1,6 +1,9 @@ -import { DEFAULT_MAX_AGE_SECS } from './constants'; -import { EnvironmentVariablesService } from './config/EnvironmentVariablesService'; -import type { GetOptionsInterface, TransformOptions } from './types'; +import { EnvironmentVariablesService } from '../config/EnvironmentVariablesService'; +import { DEFAULT_MAX_AGE_SECS } from '../constants'; +import type { + GetOptionsInterface, + TransformOptions, +} from '../types/BaseProvider'; /** * Options for the `get` method. diff --git a/packages/parameters/src/base/index.ts b/packages/parameters/src/base/index.ts new file mode 100644 index 0000000000..2833869ad7 --- /dev/null +++ b/packages/parameters/src/base/index.ts @@ -0,0 +1,4 @@ +export * from './DefaultProviders'; +export * from './BaseProvider'; +export * from './GetOptions'; +export * from './GetMultipleOptions'; diff --git a/packages/parameters/src/base/transformValue.ts b/packages/parameters/src/base/transformValue.ts new file mode 100644 index 0000000000..a3b767ddbd --- /dev/null +++ b/packages/parameters/src/base/transformValue.ts @@ -0,0 +1,77 @@ +import type { JSONValue } from '@aws-lambda-powertools/commons'; +import { isString } from '@aws-lambda-powertools/commons'; +import { fromBase64 } from '@aws-sdk/util-base64-node'; +import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from '../constants'; +import { TransformParameterError } from '../errors'; +import type { TransformOptions } from '../types/BaseProvider'; + +/** + * Utility function to transform a value. + * + * It supports JSON and binary transformations, as well as an `auto` mode that will try to transform the value based on the key. + * + * The function supports both `string` and `Uint8Array` values as input. Other types will be returned as-is. + * + * If the value is a `Uint8Array`, it will be decoded to a string first. Then, when the transform is `json` or `auto` and the key ends with `.json`, + * the value will be parsed as JSON using the `JSON.parse` function. + * + * When the transform is `binary` or `auto` and the key ends with `.binary`, the value will be decoded from base64 using the `fromBase64` function + * from the `@aws-sdk/util-base64-node` package. + * + * If the transformation fails, the function will return the value as-is unless `throwOnTransformError` is set to `true`. + * + * @note When using `auto` mode, the key must end with either `.json` or `.binary` to be transformed. Automatic transformation is supported only for + * `getMultiple` calls. + * + * @param {string | Uint8Array} value - Value to be transformed + * @param {TransformOptions} transform - Transform to be applied, can be `json`, `binary`, or `auto` + * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false + * @param {string} key - Key of the value to be transformed, used to determine the transformation method when using 'auto' + */ +const transformValue = ( + value: string | Uint8Array, + transform: TransformOptions, + throwOnTransformError: boolean, + key: string +): string | JSONValue | Uint8Array | undefined => { + const normalizedTransform = transform.toLowerCase(); + const isAutoTransform = normalizedTransform === 'auto'; + const isAutoJsonTransform = + isAutoTransform && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`); + const isAutoBinaryTransform = + isAutoTransform && + key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`); + const isJsonTransform = normalizedTransform === TRANSFORM_METHOD_JSON; + const isBinaryTransform = normalizedTransform === TRANSFORM_METHOD_BINARY; + + // If the value is not a string or Uint8Array, or if the transform is `auto` + // and the key does not end with `.json` or `.binary`, return the value as-is + if ( + !(value instanceof Uint8Array || isString(value)) || + (isAutoTransform && !isAutoJsonTransform && !isAutoBinaryTransform) + ) { + return value; + } + + try { + // If the value is a Uint8Array, decode it to a string first + if (value instanceof Uint8Array) { + value = new TextDecoder('utf-8').decode(value); + } + + // If the transform is `json` or `auto` and the key ends with `.json`, parse the value as JSON + if (isJsonTransform || isAutoJsonTransform) { + return JSON.parse(value) as JSONValue; + // If the transform is `binary` or `auto` and the key ends with `.binary`, decode the value from base64 + } else if (isBinaryTransform || isAutoBinaryTransform) { + return new TextDecoder('utf-8').decode(fromBase64(value)); + } + } catch (error) { + if (throwOnTransformError) + throw new TransformParameterError(transform, (error as Error).message); + + return; + } +}; + +export { transformValue }; diff --git a/packages/parameters/src/constants.ts b/packages/parameters/src/constants.ts index 7edd7ad9d4..dcb53165c7 100644 --- a/packages/parameters/src/constants.ts +++ b/packages/parameters/src/constants.ts @@ -1,3 +1,30 @@ -export const DEFAULT_MAX_AGE_SECS = 5; -export const TRANSFORM_METHOD_JSON = 'json'; -export const TRANSFORM_METHOD_BINARY = 'binary'; +const DEFAULT_MAX_AGE_SECS = 5; +const TRANSFORM_METHOD_JSON = 'json'; +const TRANSFORM_METHOD_BINARY = 'binary'; +const TRANSFORM_METHOD_AUTO = 'auto'; + +/** + * Transform methods for values retrieved by parameter providers. + */ +const Transform = { + /** + * Transform the retrieved value using `JSON.parse`. + */ + JSON: TRANSFORM_METHOD_JSON, + /** + * Transform a base64-encoded value from `Uint8Array` to `string`. + */ + BINARY: TRANSFORM_METHOD_BINARY, + /** + * Automatically detect the transform method based on the parameter' name suffix. + */ + AUTO: TRANSFORM_METHOD_AUTO, +} as const; + +export { + DEFAULT_MAX_AGE_SECS, + TRANSFORM_METHOD_JSON, + TRANSFORM_METHOD_BINARY, + TRANSFORM_METHOD_AUTO, + Transform, +}; diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index 8bbedb0b23..3aa9f51424 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -1,20 +1,23 @@ -import { BaseProvider } from '../BaseProvider'; +import { BaseProvider } from '../base'; import { DynamoDBClient, GetItemCommand, paginateQuery, } from '@aws-sdk/client-dynamodb'; +import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'; import type { DynamoDBProviderOptions, - DynamoDBGetOptionsInterface, - DynamoDBGetMultipleOptionsInterface, + DynamoDBGetOptions, + DynamoDBGetMultipleOptions, + DynamoDBGetOutput, + DynamoDBGetMultipleOutput, } from '../types/DynamoDBProvider'; import type { GetItemCommandInput, QueryCommandInput, } from '@aws-sdk/client-dynamodb'; -import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'; import type { PaginationConfiguration } from '@aws-sdk/types'; +import type { JSONValue } from '@aws-lambda-powertools/commons'; /** * ## Intro @@ -63,6 +66,7 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * // Retrieve multiple values from DynamoDB * const values = await tableProvider.getMultiple('my-values-path'); * }; + * ``` * * ## Advanced usage * @@ -297,12 +301,21 @@ class DynamoDBProvider extends BaseProvider { * @param {DynamoDBGetOptionsInterface} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda-typescript/latest/utilities/parameters/ */ - public async get( + public async get< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends + | DynamoDBGetOptions + | undefined = DynamoDBGetOptions + >( name: string, - options?: DynamoDBGetOptionsInterface - ): Promise> { + options?: InferredFromOptionsType & DynamoDBGetOptions + ): Promise< + | DynamoDBGetOutput + | undefined + > { return super.get(name, options) as Promise< - undefined | string | Record + | DynamoDBGetOutput + | undefined >; } @@ -333,26 +346,43 @@ class DynamoDBProvider extends BaseProvider { * For usage examples check {@link DynamoDBProvider}. * * @param {string} path - The path of the values to retrieve (i.e. the partition key) - * @param {DynamoDBGetMultipleOptionsInterface} options - Options to configure the provider + * @param {DynamoDBGetMultipleOptions} options - Options to configure the provider * @see https://docs.powertools.aws.dev/lambda-typescript/latest/utilities/parameters/ */ - public async getMultiple( + public async getMultiple< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends + | DynamoDBGetMultipleOptions + | undefined = DynamoDBGetMultipleOptions + >( path: string, - options?: DynamoDBGetMultipleOptionsInterface - ): Promise> { - return super.getMultiple(path, options); + options?: InferredFromOptionsType & DynamoDBGetMultipleOptions + ): Promise< + | DynamoDBGetMultipleOutput< + ExplicitUserProvidedType, + InferredFromOptionsType + > + | undefined + > { + return super.getMultiple(path, options) as Promise< + | DynamoDBGetMultipleOutput< + ExplicitUserProvidedType, + InferredFromOptionsType + > + | undefined + >; } /** * Retrieve an item from Amazon DynamoDB. * * @param {string} name - Key of the item to retrieve (i.e. the partition key) - * @param {DynamoDBGetOptionsInterface} options - Options to customize the retrieval + * @param {DynamoDBGetOptions} options - Options to customize the retrieval */ protected async _get( name: string, - options?: DynamoDBGetOptionsInterface - ): Promise { + options?: DynamoDBGetOptions + ): Promise { const sdkOptions: GetItemCommandInput = { ...(options?.sdkOptions || {}), TableName: this.tableName, @@ -371,12 +401,12 @@ 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 {DynamoDBGetMultipleOptionsInterface} options - Options to customize the retrieval + * @param {DynamoDBGetMultipleOptions} options - Options to customize the retrieval */ protected async _getMultiple( path: string, - options?: DynamoDBGetMultipleOptionsInterface - ): Promise> { + options?: DynamoDBGetMultipleOptions + ): Promise> { const sdkOptions: QueryCommandInput = { ...(options?.sdkOptions || {}), TableName: this.tableName, @@ -394,7 +424,7 @@ class DynamoDBProvider extends BaseProvider { pageSize: options?.sdkOptions?.Limit, }; - const parameters: Record = {}; + const parameters: Record = {}; for await (const page of paginateQuery(paginationOptions, sdkOptions)) { for (const item of page.Items || []) { const unmarshalledItem = unmarshall(item); diff --git a/packages/parameters/src/Exceptions.ts b/packages/parameters/src/errors.ts similarity index 56% rename from packages/parameters/src/Exceptions.ts rename to packages/parameters/src/errors.ts index 21b557e13d..fab94a7702 100644 --- a/packages/parameters/src/Exceptions.ts +++ b/packages/parameters/src/errors.ts @@ -1,4 +1,12 @@ -class GetParameterError extends Error {} +/** + * Error thrown when a parameter cannot be retrieved. + */ +class GetParameterError extends Error { + public constructor(message?: string) { + super(message); + this.name = 'GetParameterError'; + } +} /** * Error thrown when a transform fails. @@ -6,7 +14,7 @@ class GetParameterError extends Error {} class TransformParameterError extends Error { public constructor(transform: string, message: string) { super(message); - + this.name = 'TransformParameterError'; this.message = `Unable to transform value using '${transform}' transform: ${message}`; } } diff --git a/packages/parameters/src/index.ts b/packages/parameters/src/index.ts index 4fdc288aba..13cad811c8 100644 --- a/packages/parameters/src/index.ts +++ b/packages/parameters/src/index.ts @@ -1,2 +1,2 @@ -export * from './BaseProvider'; -export * from './Exceptions'; +export * from './errors'; +export * from './constants'; diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 79694f7409..defc552664 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -1,4 +1,4 @@ -import { BaseProvider } from '../BaseProvider'; +import { BaseProvider } from '../base'; import { SecretsManagerClient, GetSecretValueCommand, @@ -8,7 +8,6 @@ import type { SecretsProviderOptions, SecretsGetOptions, SecretsGetOutput, - SecretsGetOptionsUnion, } from '../types/SecretsProvider'; /** @@ -204,8 +203,8 @@ class SecretsProvider extends BaseProvider { public async get< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends - | SecretsGetOptionsUnion - | undefined = SecretsGetOptionsUnion + | SecretsGetOptions + | undefined = SecretsGetOptions >( name: string, options?: InferredFromOptionsType & SecretsGetOptions @@ -222,15 +221,12 @@ class SecretsProvider extends BaseProvider { /** * Retrieving multiple parameter values is not supported with AWS Secrets Manager. */ - public async getMultiple( - path: string, - _options?: unknown - ): Promise> { - return super.getMultiple(path); + public async getMultiple(path: string, _options?: unknown): Promise { + await super.getMultiple(path); } /** - * Retrieve a configuration from AWS AppConfig. + * Retrieve a configuration 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 @@ -261,7 +257,7 @@ class SecretsProvider extends BaseProvider { protected async _getMultiple( _path: string, _options?: unknown - ): Promise> { + ): Promise { throw new Error('Method not implemented.'); } } diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index 4da74decd8..c4a304008e 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -1,9 +1,8 @@ -import { DEFAULT_PROVIDERS } from '../BaseProvider'; +import { DEFAULT_PROVIDERS } from '../base'; import { SecretsProvider } from './SecretsProvider'; import type { SecretsGetOptions, SecretsGetOutput, - SecretsGetOptionsUnion, } from '../types/SecretsProvider'; /** @@ -110,8 +109,8 @@ import type { const getSecret = async < ExplicitUserProvidedType = undefined, InferredFromOptionsType extends - | SecretsGetOptionsUnion - | undefined = SecretsGetOptionsUnion + | SecretsGetOptions + | undefined = SecretsGetOptions >( name: string, options?: InferredFromOptionsType & SecretsGetOptions @@ -123,13 +122,7 @@ const getSecret = async < DEFAULT_PROVIDERS.secrets = new SecretsProvider(); } - return (DEFAULT_PROVIDERS.secrets as SecretsProvider).get( - name, - options - ) as Promise< - | SecretsGetOutput - | undefined - >; + return (DEFAULT_PROVIDERS.secrets as SecretsProvider).get(name, options); }; export { getSecret }; diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index abfc6670e2..4ffbb1b6ee 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -1,9 +1,6 @@ -import { - BaseProvider, - DEFAULT_PROVIDERS, - transformValue, -} from '../BaseProvider'; -import { GetParameterError } from '../Exceptions'; +import { BaseProvider, DEFAULT_PROVIDERS } from '../base'; +import { transformValue } from '../base/transformValue'; +import { GetParameterError } from '../errors'; import { DEFAULT_MAX_AGE_SECS } from '../constants'; import { SSMClient, @@ -22,7 +19,6 @@ import type { SSMGetOptions, SSMGetOutput, SSMGetMultipleOptions, - SSMGetMultipleOptionsUnion, SSMGetMultipleOutput, SSMGetParametersByNameOutput, SSMGetParametersByNameOutputInterface, @@ -77,6 +73,7 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * // Retrieve multiple parameters by path from SSM * const parameters = await parametersProvider.getMultiple('/my-parameters-path'); * }; + * ``` * * If you don't need to customize the provider, you can also use the {@link getParameters} function instead. * @@ -371,7 +368,7 @@ class SSMProvider extends BaseProvider { public async getMultiple< ExplicitUserProvidedType = undefined, InferredFromOptionsType extends - | SSMGetMultipleOptionsUnion + | SSMGetMultipleOptions | undefined = undefined >( path: string, diff --git a/packages/parameters/src/ssm/getParameter.ts b/packages/parameters/src/ssm/getParameter.ts index 0360add2af..adb3916730 100644 --- a/packages/parameters/src/ssm/getParameter.ts +++ b/packages/parameters/src/ssm/getParameter.ts @@ -1,9 +1,5 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; -import type { - SSMGetOptions, - SSMGetOutput, - SSMGetOptionsUnion, -} from '../types/SSMProvider'; +import type { SSMGetOptions, SSMGetOutput } from '../types/SSMProvider'; /** * ## Intro @@ -142,9 +138,7 @@ import type { */ const getParameter = async < ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends - | SSMGetOptionsUnion - | undefined = SSMGetOptionsUnion + InferredFromOptionsType extends SSMGetOptions | undefined = SSMGetOptions >( name: string, options?: InferredFromOptionsType & SSMGetOptions diff --git a/packages/parameters/src/ssm/getParameters.ts b/packages/parameters/src/ssm/getParameters.ts index db28d4d889..ac6c3c3824 100644 --- a/packages/parameters/src/ssm/getParameters.ts +++ b/packages/parameters/src/ssm/getParameters.ts @@ -1,7 +1,6 @@ import { SSMProvider, DEFAULT_PROVIDERS } from './SSMProvider'; import type { SSMGetMultipleOptions, - SSMGetMultipleOptionsUnion, SSMGetMultipleOutput, } from '../types/SSMProvider'; @@ -144,8 +143,8 @@ import type { const getParameters = async < ExplicitUserProvidedType = undefined, InferredFromOptionsType extends - | SSMGetMultipleOptionsUnion - | undefined = SSMGetMultipleOptionsUnion + | SSMGetMultipleOptions + | undefined = SSMGetMultipleOptions >( path: string, options?: InferredFromOptionsType & SSMGetMultipleOptions diff --git a/packages/parameters/src/types/AppConfigProvider.ts b/packages/parameters/src/types/AppConfigProvider.ts index 61e3ee03cf..6bfc26ecb9 100644 --- a/packages/parameters/src/types/AppConfigProvider.ts +++ b/packages/parameters/src/types/AppConfigProvider.ts @@ -1,3 +1,4 @@ +import type { JSONValue } from '@aws-lambda-powertools/commons'; import type { AppConfigDataClient, AppConfigDataClientConfig, @@ -61,34 +62,71 @@ type AppConfigProviderOptions = /** * Options for the AppConfigProvider get method. * - * @interface AppConfigGetOptionsInterface + * @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'. */ -interface AppConfigGetOptionsInterface - extends Omit { +interface AppConfigGetOptionsBase extends GetOptionsInterface { + /** + * Additional options to pass to the AWS SDK v3 client. Supports all options from `StartConfigurationSessionCommandInput` except `ApplicationIdentifier`, `EnvironmentIdentifier`, and `ConfigurationProfileIdentifier`. + */ sdkOptions?: Omit< Partial, | 'ApplicationIdentifier' - | 'EnvironmentIdentifier | ConfigurationProfileIdentifier' + | 'EnvironmentIdentifier' + | 'ConfigurationProfileIdentifier' >; } +interface AppConfigGetOptionsTransformJson extends AppConfigGetOptionsBase { + transform: 'json'; +} + +interface AppConfigGetOptionsTransformBinary extends AppConfigGetOptionsBase { + transform: 'binary'; +} + +interface AppConfigGetOptionsTransformNone extends AppConfigGetOptionsBase { + transform?: never; +} + +type AppConfigGetOptions = + | AppConfigGetOptionsTransformNone + | AppConfigGetOptionsTransformJson + | AppConfigGetOptionsTransformBinary + | undefined; + +/** + * Generic output type for the AppConfigProvider get method. + */ +type AppConfigGetOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType | AppConfigGetOptionsTransformNone + ? Uint8Array + : InferredFromOptionsType extends AppConfigGetOptionsTransformBinary + ? string + : InferredFromOptionsType extends AppConfigGetOptionsTransformJson + ? JSONValue + : never + : ExplicitUserProvidedType; + /** * Combined options for the getAppConfig utility function. - * - * @interface getAppConfigCombinedInterface - * @extends {AppConfigProviderOptions, AppConfigGetOptionsInterface} */ -interface GetAppConfigCombinedInterface - extends Omit, - AppConfigGetOptionsInterface {} +type GetAppConfigOptions = Omit< + AppConfigProviderOptions, + 'clientConfig' | 'awsSdkV3Client' +> & + AppConfigGetOptions; export type { AppConfigProviderOptions, - AppConfigGetOptionsInterface, - GetAppConfigCombinedInterface, + AppConfigGetOptions, + AppConfigGetOutput, + GetAppConfigOptions, }; diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index a743b4e375..5a82970460 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -1,7 +1,9 @@ +import { Transform } from '../constants'; + /** * Type for the transform option. */ -type TransformOptions = 'auto' | 'binary' | 'json'; +type TransformOptions = (typeof Transform)[keyof typeof Transform]; /** * Options for the `get` method. @@ -57,7 +59,7 @@ interface ExpirableValueInterface { /** * Value of the parameter. */ - value: string | Uint8Array | Record; + value: unknown; /** * Expiration timestamp of the value. */ @@ -68,14 +70,12 @@ interface ExpirableValueInterface { * Interface for a parameter store provider. */ interface BaseProviderInterface { - get( - name: string, - options?: GetOptionsInterface - ): Promise>; + get(name: string, options?: GetOptionsInterface): Promise; getMultiple( path: string, options?: GetMultipleOptionsInterface - ): Promise>; + ): Promise; + clearCache?(): void; } export type { diff --git a/packages/parameters/src/types/DynamoDBProvider.ts b/packages/parameters/src/types/DynamoDBProvider.ts index 24914ccf58..2ddcd4e819 100644 --- a/packages/parameters/src/types/DynamoDBProvider.ts +++ b/packages/parameters/src/types/DynamoDBProvider.ts @@ -1,13 +1,14 @@ -import type { - GetOptionsInterface, - GetMultipleOptionsInterface, -} from './BaseProvider'; +import type { JSONValue } from '@aws-lambda-powertools/commons'; import type { DynamoDBClient, + DynamoDBClientConfig, GetItemCommandInput, QueryCommandInput, - DynamoDBClientConfig, } from '@aws-sdk/client-dynamodb'; +import type { + GetMultipleOptionsInterface, + GetOptionsInterface, +} from './BaseProvider'; /** * Base interface for DynamoDBProviderOptions. @@ -18,7 +19,7 @@ import type { * @property {string} [sortAttr] - The DynamoDB table sort attribute name. Defaults to 'sk'. * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. */ -interface DynamoDBProviderOptionsBaseInterface { +interface DynamoDBProviderOptionsBase { tableName: string; keyAttr?: string; sortAttr?: string; @@ -29,12 +30,12 @@ interface DynamoDBProviderOptionsBaseInterface { * Interface for DynamoDBProviderOptions with clientConfig property. * * @interface - * @extends DynamoDBProviderOptionsBaseInterface - * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. + * @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. */ interface DynamoDBProviderOptionsWithClientConfig - extends DynamoDBProviderOptionsBaseInterface { + extends DynamoDBProviderOptionsBase { clientConfig?: DynamoDBClientConfig; awsSdkV3Client?: never; } @@ -43,26 +44,26 @@ interface DynamoDBProviderOptionsWithClientConfig * Interface for DynamoDBProviderOptions with awsSdkV3Client property. * * @interface - * @extends DynamoDBProviderOptionsBaseInterface - * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation + * @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. */ interface DynamoDBProviderOptionsWithClientInstance - extends DynamoDBProviderOptionsBaseInterface { + extends DynamoDBProviderOptionsBase { awsSdkV3Client?: DynamoDBClient; clientConfig?: never; } /** - * Options for the AppConfigProvider class constructor. + * Options for the DynamoDBProvider class constructor. * - * @type AppConfigProviderOptions + * @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 {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 DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. + * @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. */ type DynamoDBProviderOptions = | DynamoDBProviderOptionsWithClientConfig @@ -71,24 +72,60 @@ type DynamoDBProviderOptions = /** * Options for the DynamoDBProvider get method. * - * @interface DynamoDBGetOptionsInterface + * @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'. */ -interface DynamoDBGetOptionsInterface extends GetOptionsInterface { +interface DynamoDBGetOptionsBase extends GetOptionsInterface { sdkOptions?: Omit< Partial, 'Key' | 'TableName' | 'ProjectionExpression' >; } +interface DynamoDBGetOptionsTransformJson extends DynamoDBGetOptionsBase { + transform: 'json'; +} + +interface DynamoDBGetOptionsTransformBinary extends DynamoDBGetOptionsBase { + transform: 'binary'; +} + +interface DynamoDBGetOptionsTransformNone extends DynamoDBGetOptionsBase { + transform?: never; +} + +type DynamoDBGetOptions = + | DynamoDBGetOptionsTransformNone + | DynamoDBGetOptionsTransformJson + | DynamoDBGetOptionsTransformBinary + | undefined; + +/** + * Generic output type for DynamoDBProvider get method. + */ +type DynamoDBGetOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType + ? JSONValue + : InferredFromOptionsType extends + | DynamoDBGetOptionsTransformNone + | DynamoDBGetOptionsTransformJson + ? JSONValue + : InferredFromOptionsType extends DynamoDBGetOptionsTransformBinary + ? string + : never + : ExplicitUserProvidedType; + /** * Options for the DynamoDBProvider getMultiple method. * - * @interface DynamoDBGetMultipleOptionsInterface + * @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. @@ -96,13 +133,59 @@ interface DynamoDBGetOptionsInterface extends GetOptionsInterface { * @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`) */ -interface DynamoDBGetMultipleOptionsInterface - extends GetMultipleOptionsInterface { +interface DynamoDBGetMultipleOptionsBase extends GetMultipleOptionsInterface { sdkOptions?: Partial; } +interface DynamoDBGetMultipleOptionsTransformJson + extends DynamoDBGetMultipleOptionsBase { + transform: 'json'; +} + +interface DynamoDBGetMultipleOptionsTransformBinary + extends DynamoDBGetMultipleOptionsBase { + transform: 'binary'; +} + +interface DynamoDBGetMultipleOptionsTransformAuto + extends DynamoDBGetMultipleOptionsBase { + transform: 'auto'; +} + +interface DynamoDBGetMultipleOptionsTransformNone + extends DynamoDBGetMultipleOptionsBase { + transform?: never; +} + +type DynamoDBGetMultipleOptions = + | DynamoDBGetMultipleOptionsTransformJson + | DynamoDBGetMultipleOptionsTransformBinary + | DynamoDBGetMultipleOptionsTransformAuto + | DynamoDBGetMultipleOptionsTransformNone; + +/** + * Generic output type for DynamoDBProvider getMultiple method. + */ +type DynamoDBGetMultipleOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType + ? JSONValue + : InferredFromOptionsType extends + | DynamoDBGetMultipleOptionsTransformNone + | DynamoDBGetMultipleOptionsTransformAuto + | DynamoDBGetMultipleOptionsTransformJson + ? JSONValue + : InferredFromOptionsType extends DynamoDBGetOptionsTransformBinary + ? string + : never + : ExplicitUserProvidedType; + export type { DynamoDBProviderOptions, - DynamoDBGetOptionsInterface, - DynamoDBGetMultipleOptionsInterface, + DynamoDBGetOptions, + DynamoDBGetOutput, + DynamoDBGetMultipleOptions, + DynamoDBGetMultipleOutput, }; diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index dd505a6019..e677ff821f 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -1,12 +1,13 @@ +import type { JSONValue } from '@aws-lambda-powertools/commons'; import type { - SSMClient, - SSMClientConfig, GetParameterCommandInput, GetParametersByPathCommandInput, + SSMClient, + SSMClientConfig, } from '@aws-sdk/client-ssm'; import type { - GetOptionsInterface, GetMultipleOptionsInterface, + GetOptionsInterface, TransformOptions, } from './BaseProvider'; @@ -48,7 +49,7 @@ type SSMProviderOptions = /** * Options for the SSMProvider getMultiple method. * - * @interface SSMGetOptionsInterface + * @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. @@ -56,7 +57,7 @@ type SSMProviderOptions = * @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`. */ -interface SSMGetOptions extends GetOptionsInterface { +interface SSMGetOptionsBase extends GetOptionsInterface { /** * If true, the parameter will be decrypted. Defaults to `false`. */ @@ -69,19 +70,19 @@ interface SSMGetOptions extends GetOptionsInterface { transform?: Exclude; } -interface SSMGetOptionsTransformJson extends SSMGetOptions { +interface SSMGetOptionsTransformJson extends SSMGetOptionsBase { transform: 'json'; } -interface SSMGetOptionsTransformBinary extends SSMGetOptions { +interface SSMGetOptionsTransformBinary extends SSMGetOptionsBase { transform: 'binary'; } -interface SSMGetOptionsTransformNone extends SSMGetOptions { +interface SSMGetOptionsTransformNone extends SSMGetOptionsBase { transform?: never; } -type SSMGetOptionsUnion = +type SSMGetOptions = | SSMGetOptionsTransformJson | SSMGetOptionsTransformBinary | SSMGetOptionsTransformNone @@ -101,14 +102,14 @@ type SSMGetOutput< | SSMGetOptionsTransformBinary ? string : InferredFromOptionsType extends SSMGetOptionsTransformJson - ? Record + ? JSONValue : never : ExplicitUserProvidedType; /** * Options for the SSMProvider getMultiple method. * - * @interface SSMGetMultipleOptionsInterface + * @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. @@ -118,7 +119,7 @@ type SSMGetOutput< * @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. */ -interface SSMGetMultipleOptions extends GetMultipleOptionsInterface { +interface SSMGetMultipleOptionsBase extends GetMultipleOptionsInterface { /** * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParametersByPathCommandInput`. */ @@ -137,23 +138,24 @@ interface SSMGetMultipleOptions extends GetMultipleOptionsInterface { throwOnTransformError?: boolean; } -interface SSMGetMultipleOptionsTransformJson extends SSMGetMultipleOptions { +interface SSMGetMultipleOptionsTransformJson extends SSMGetMultipleOptionsBase { transform: 'json'; } -interface SSMGetMultipleOptionsTransformBinary extends SSMGetMultipleOptions { +interface SSMGetMultipleOptionsTransformBinary + extends SSMGetMultipleOptionsBase { transform: 'binary'; } -interface SSMGetMultipleOptionsTransformAuto extends SSMGetMultipleOptions { +interface SSMGetMultipleOptionsTransformAuto extends SSMGetMultipleOptionsBase { transform: 'auto'; } -interface SSMGetMultipleOptionsTransformNone extends SSMGetMultipleOptions { +interface SSMGetMultipleOptionsTransformNone extends SSMGetMultipleOptionsBase { transform?: never; } -type SSMGetMultipleOptionsUnion = +type SSMGetMultipleOptions = | SSMGetMultipleOptionsTransformJson | SSMGetMultipleOptionsTransformBinary | SSMGetMultipleOptionsTransformAuto @@ -174,9 +176,9 @@ type SSMGetMultipleOutput< | SSMGetMultipleOptionsTransformBinary ? Record : InferredFromOptionsType extends SSMGetMultipleOptionsTransformAuto - ? Record + ? Record : InferredFromOptionsType extends SSMGetMultipleOptionsTransformJson - ? Record> + ? Record : never : Record; @@ -231,10 +233,8 @@ type SSMGetParametersByNameOutput = export type { SSMProviderOptions, SSMGetOptions, - SSMGetOptionsUnion, SSMGetOutput, SSMGetMultipleOptions, - SSMGetMultipleOptionsUnion, SSMGetMultipleOutput, SSMGetParametersByNameOptions, SSMSplitBatchAndDecryptParametersOutputType, diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index 1105d016c1..94af356f3a 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -1,9 +1,10 @@ -import type { GetOptionsInterface, TransformOptions } from './BaseProvider'; +import type { JSONValue } from '@aws-lambda-powertools/commons'; import type { + GetSecretValueCommandInput, SecretsManagerClient, SecretsManagerClientConfig, - GetSecretValueCommandInput, } from '@aws-sdk/client-secrets-manager'; +import type { GetOptionsInterface, TransformOptions } from './BaseProvider'; /** * Base interface for SecretsProviderOptions. @@ -34,8 +35,8 @@ interface SecretsProviderOptionsWithClientInstance { * Options for the SecretsProvider class constructor. * * @type SecretsProviderOptions - * @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 SecretsProvider class instantiation. Mutually exclusive with clientConfig. + * @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. */ type SecretsProviderOptions = | SecretsProviderOptionsWithClientConfig @@ -51,27 +52,27 @@ type SecretsProviderOptions = * @property {GetSecretValueCommandInput} sdkOptions - Options to pass to the underlying SDK. * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ -interface SecretsGetOptions extends GetOptionsInterface { +interface SecretsGetOptionsBase extends GetOptionsInterface { /** - * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetSecretValueCommandInput`. + * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetSecretValueCommandInput` except `SecretId`. */ sdkOptions?: Omit, 'SecretId'>; transform?: Exclude; } -interface SecretsGetOptionsTransformJson extends SecretsGetOptions { +interface SecretsGetOptionsTransformJson extends SecretsGetOptionsBase { transform: 'json'; } -interface SecretsGetOptionsTransformBinary extends SecretsGetOptions { +interface SecretsGetOptionsTransformBinary extends SecretsGetOptionsBase { transform: 'binary'; } -interface SecretsGetOptionsTransformNone extends SecretsGetOptions { +interface SecretsGetOptionsTransformNone extends SecretsGetOptionsBase { transform?: never; } -type SecretsGetOptionsUnion = +type SecretsGetOptions = | SecretsGetOptionsTransformNone | SecretsGetOptionsTransformJson | SecretsGetOptionsTransformBinary @@ -84,20 +85,13 @@ type SecretsGetOutput< ExplicitUserProvidedType = undefined, InferredFromOptionsType = undefined > = undefined extends ExplicitUserProvidedType - ? undefined extends InferredFromOptionsType - ? string | Uint8Array - : InferredFromOptionsType extends SecretsGetOptionsTransformNone + ? undefined extends InferredFromOptionsType | SecretsGetOptionsTransformNone ? string | Uint8Array : InferredFromOptionsType extends SecretsGetOptionsTransformBinary ? string : InferredFromOptionsType extends SecretsGetOptionsTransformJson - ? Record + ? JSONValue : never : ExplicitUserProvidedType; -export type { - SecretsProviderOptions, - SecretsGetOptions, - SecretsGetOutput, - SecretsGetOptionsUnion, -}; +export type { SecretsProviderOptions, SecretsGetOptions, SecretsGetOutput }; diff --git a/packages/parameters/src/types/index.ts b/packages/parameters/src/types/index.ts deleted file mode 100644 index 18f961f63c..0000000000 --- a/packages/parameters/src/types/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './BaseProvider'; -export * from './AppConfigProvider'; -export * from './SSMProvider'; -export * from './SecretsProvider'; -export * from './DynamoDBProvider'; diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts index 8fe93a77ee..ece202a259 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts @@ -1,6 +1,7 @@ import { Context } from 'aws-lambda'; +import { Transform } from '../../src'; import { AppConfigProvider } from '../../src/appconfig'; -import { AppConfigGetOptionsInterface } from '../../src/types'; +import { AppConfigGetOptions } from '../../src/types/AppConfigProvider'; import { TinyLogger } from '../helpers/tinyLogger'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; @@ -41,7 +42,7 @@ const resolveProvider = (provider?: AppConfigProvider): AppConfigProvider => { const _call_get = async ( paramName: string, testName: string, - options?: AppConfigGetOptionsInterface, + options?: AppConfigGetOptions, provider?: AppConfigProvider ): Promise => { try { @@ -69,19 +70,19 @@ export const handler = async ( // Test 2 - get a free-form JSON and apply json transformation (should return an object) await _call_get(freeFormJsonName, 'get-freeform-json-binary', { - transform: 'json', + transform: Transform.JSON, }); // Test 3 - get a free-form base64-encoded plain text and apply binary transformation (should return a decoded string) await _call_get( freeFormBase64encodedPlainText, 'get-freeform-base64-plaintext-binary', - { transform: 'binary' } + { transform: Transform.BINARY } ); // Test 4 - get a feature flag and apply json transformation (should return an object) await _call_get(featureFlagName, 'get-feature-flag-binary', { - transform: 'json', + transform: Transform.JSON, }); // Test 5 @@ -130,14 +131,14 @@ export const handler = async ( freeFormBase64encodedPlainText, { maxAge: 0, - transform: 'base64', + transform: Transform.BINARY, } ); const expiredResult2 = await providerWithMiddleware.get( freeFormBase64encodedPlainText, { maxAge: 0, - transform: 'base64', + transform: Transform.BINARY, } ); logger.log({ diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index de99890c58..2e75f8cc6b 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -371,7 +371,7 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = async () => { const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[6]); - const result = freeFormBase64PlainTextValue; + const result = freeFormPlainTextValue; expect(testLog).toStrictEqual({ test: 'get-expired', diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts index 7755d4b819..18df7bb21e 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts @@ -1,9 +1,10 @@ import { Context } from 'aws-lambda'; +import { Transform } from '../../src'; import { DynamoDBProvider } from '../../src/dynamodb'; import { - DynamoDBGetOptionsInterface, - DynamoDBGetMultipleOptionsInterface, -} from '../../src/types'; + DynamoDBGetOptions, + DynamoDBGetMultipleOptions, +} from '../../src/types/DynamoDBProvider'; import { TinyLogger } from '../helpers/tinyLogger'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; @@ -55,7 +56,7 @@ const _call_get = async ( paramName: string, testName: string, provider: DynamoDBProvider, - options?: DynamoDBGetOptionsInterface + options?: DynamoDBGetOptions ): Promise => { try { const parameterValue = await provider.get(paramName, options); @@ -76,7 +77,7 @@ const _call_get_multiple = async ( paramPath: string, testName: string, provider: DynamoDBProvider, - options?: DynamoDBGetMultipleOptionsInterface + options?: DynamoDBGetMultipleOptions ): Promise => { try { const parameterValues = await provider.getMultiple(paramPath, options); @@ -114,12 +115,12 @@ export const handler = async ( // Test 5 - get a single parameter with json transform await _call_get('my-param-json', 'get-json-transform', providerGet, { - transform: 'json', + transform: Transform.JSON, }); // Test 6 - get a single parameter with binary transform await _call_get('my-param-binary', 'get-binary-transform', providerGet, { - transform: 'binary', + transform: Transform.BINARY, }); // Test 7 - get multiple parameters with auto transform @@ -128,7 +129,7 @@ export const handler = async ( 'get-multiple-auto-transform', providerGetMultiple, { - transform: 'auto', + transform: Transform.AUTO, } ); diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts index a446bd0c1f..a52eeea586 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts @@ -2,8 +2,9 @@ import { Context } from 'aws-lambda'; import { TinyLogger } from '../helpers/tinyLogger'; import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; +import { Transform } from '../../src'; import { SecretsProvider } from '../../src/secrets'; -import { SecretsGetOptionsInterface } from '../../src/types'; +import { SecretsGetOptions } from '../../src/types/SecretsProvider'; const logger = new TinyLogger(); const defaultProvider = new SecretsProvider(); @@ -11,10 +12,6 @@ const defaultProvider = new SecretsProvider(); const secretNamePlain = process.env.SECRET_NAME_PLAIN || ''; const secretNameObject = process.env.SECRET_NAME_OBJECT || ''; const secretNameBinary = process.env.SECRET_NAME_BINARY || ''; -const secretNameObjectWithSuffix = - process.env.SECRET_NAME_OBJECT_WITH_SUFFIX || ''; -const secretNameBinaryWithSuffix = - process.env.SECRET_NAME_BINARY_WITH_SUFFIX || ''; const secretNamePlainChached = process.env.SECRET_NAME_PLAIN_CACHED || ''; const secretNamePlainForceFetch = process.env.SECRET_NAME_PLAIN_FORCE_FETCH || ''; @@ -29,7 +26,7 @@ const providerWithMiddleware = new SecretsProvider({ const _call_get = async ( paramName: string, testName: string, - options?: SecretsGetOptionsInterface, + options?: SecretsGetOptions, provider?: SecretsProvider ): Promise => { try { @@ -58,25 +55,15 @@ export const handler = async ( // Test 2 get single secret with transform json await _call_get(secretNameObject, 'get-transform-json', { - transform: 'json', + transform: Transform.JSON, }); // Test 3 get single secret with transform binary await _call_get(secretNameBinary, 'get-transform-binary', { - transform: 'binary', + transform: Transform.BINARY, }); - // Test 4 get single secret with transform auto json - await _call_get(secretNameObjectWithSuffix, 'get-transform-auto-json', { - transform: 'auto', - }); - - // Test 5 get single secret with transform auto binary - await _call_get(secretNameBinaryWithSuffix, 'get-transform-auto-binary', { - transform: 'auto', - }); - - // Test 6 + // Test 4 // get secret twice with middleware, which counts number of SDK requests, we check later if we only called SecretManager API once try { middleware.counter = 0; @@ -92,7 +79,7 @@ export const handler = async ( error: err.message, }); } - // Test 7 + // Test 5 // get secret twice, but force fetch 2nd time, we count number of SDK requests and check that we made two API calls try { middleware.counter = 0; diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.ts index 3d42577cba..d656b4a3c3 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.ts @@ -38,12 +38,10 @@ if (!isValidRuntimeKey(runtime)) { * Test 1: create a secret with plain text value, fetch it with no additional options * Test 2: create a secret with json value, fetch it using `transform: 'json'` option * Test 3: create a secret with base64 encoded value (technicaly string), fetch it using `transform: 'binary'` option - * Test 4: create a secret with json value and secret name ends with .json, fetch it using `transform: 'auto'` option - * Test 5: create a secret with base64 encoded value (technicaly string) and secert name ends with .binary, fetch it using `transform: 'auto'` option - * Test 6: create a secret with plain text value, fetch it twice, check that value was cached, the number of SDK calls should be 1 - * Test 7: create a secret with plain text value, fetch it twice, second time with `forceFetch: true` option, check that value was not cached, the number of SDK calls should be 2 + * Test 4: create a secret with plain text value, fetch it twice, check that value was cached, the number of SDK calls should be 1 + * Test 5: create a secret with plain text value, fetch it twice, second time with `forceFetch: true` option, check that value was not cached, the number of SDK calls should be 2 * - * For tests 6 and 7 we use our own AWS SDK custom middleware plugin `sdkMiddlewareRequestCounter.ts` + * For tests 4 and 5 we use our own AWS SDK custom middleware plugin `sdkMiddlewareRequestCounter.ts` * * Adding new test: * Please keep the state clean, and create dedicated resource for your test, don't reuse resources from other tests. @@ -93,18 +91,6 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => runtime, 'testSecretBinary' ); - const secretNameObjectWithSuffix = generateUniqueName( - RESOURCE_NAME_PREFIX, - uuid, - runtime, - 'testSecretObject.json' - ); - const secretNameBinaryWithSuffix = generateUniqueName( - RESOURCE_NAME_PREFIX, - uuid, - runtime, - 'testSecretObject.binary' - ); const secretNamePlainCached = generateUniqueName( RESOURCE_NAME_PREFIX, uuid, @@ -131,8 +117,6 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => SECRET_NAME_PLAIN: secretNamePlain, SECRET_NAME_OBJECT: secretNameObject, SECRET_NAME_BINARY: secretNameBinary, - SECRET_NAME_OBJECT_WITH_SUFFIX: secretNameObjectWithSuffix, - SECRET_NAME_BINARY_WITH_SUFFIX: secretNameBinaryWithSuffix, SECRET_NAME_PLAIN_CACHED: secretNamePlainCached, SECRET_NAME_PLAIN_FORCE_FETCH: secretNamePlainForceFetch, }, @@ -156,26 +140,6 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => secretStringValue: SecretValue.unsafePlainText('Zm9v'), // 'foo' encoded in base64 }); - const secretObjectWithSuffix = new Secret( - stack, - 'testSecretObjectWithSuffix', - { - secretName: secretNameObjectWithSuffix, - secretObjectValue: { - foo: SecretValue.unsafePlainText('bar'), - }, - } - ); - - const secretBinaryWithSuffix = new Secret( - stack, - 'testSecretBinaryWithSuffix', - { - secretName: secretNameBinaryWithSuffix, - secretStringValue: SecretValue.unsafePlainText('Zm9v'), // 'foo' encoded in base64 - } - ); - const secretStringCached = new Secret(stack, 'testSecretStringCached', { secretName: secretNamePlainCached, secretStringValue: SecretValue.unsafePlainText('foo'), @@ -196,8 +160,6 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => secretString, secretObject, secretBinary, - secretObjectWithSuffix, - secretBinaryWithSuffix, secretStringCached, secretStringForceFetch, ]) @@ -256,34 +218,9 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => ); }); - it( - 'should retrieve a secret using transform auto option with implicit json', - async () => { - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[3]); - - // result should be a json object - expect(testLog).toStrictEqual({ - test: 'get-transform-auto-json', - value: { foo: 'bar' }, - }); - }, - TEST_CASE_TIMEOUT - ); - - it('should retrieve a secret using transform auto option with implicit binary', async () => { - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[4]); - - expect(testLog).toStrictEqual({ - test: 'get-transform-auto-binary', - value: 'foo', - }); - }); - it('should retrieve a secret twice with cached value', async () => { const logs = invocationLogs[0].getFunctionLogs(); - const testLogFirst = InvocationLogs.parseFunctionLog(logs[5]); + const testLogFirst = InvocationLogs.parseFunctionLog(logs[3]); // we fetch twice, but we expect to make an API call only once expect(testLogFirst).toStrictEqual({ @@ -294,7 +231,7 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => it('should retrieve a secret twice with forceFetch second time', async () => { const logs = invocationLogs[0].getFunctionLogs(); - const testLogFirst = InvocationLogs.parseFunctionLog(logs[6]); + const testLogFirst = InvocationLogs.parseFunctionLog(logs[4]); // we fetch twice, 2nd time with forceFetch: true flag, we expect two api calls expect(testLogFirst).toStrictEqual({ diff --git a/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts index e97ebca434..49aefa90bd 100644 --- a/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts @@ -1,10 +1,10 @@ import { Context } from 'aws-lambda'; import { SSMProvider } from '../../src/ssm'; import { - SSMGetOptionsInterface, - SSMGetMultipleOptionsInterface, - SSMGetParametersByNameOptionsInterface, -} from '../../src/types'; + SSMGetOptions, + SSMGetMultipleOptions, + SSMGetParametersByNameOptions, +} from '../../src/types/SSMProvider'; import { TinyLogger } from '../helpers/tinyLogger'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; import { SSMClient } from '@aws-sdk/client-ssm'; @@ -37,7 +37,7 @@ const resolveProvider = (provider?: SSMProvider): SSMProvider => { const _call_get = async ( paramName: string, testName: string, - options?: SSMGetOptionsInterface, + options?: SSMGetOptions, provider?: SSMProvider ): Promise => { try { @@ -60,7 +60,7 @@ const _call_get = async ( const _call_get_multiple = async ( paramPath: string, testName: string, - options?: SSMGetMultipleOptionsInterface, + options?: SSMGetMultipleOptions, provider?: SSMProvider ): Promise => { try { @@ -84,9 +84,9 @@ const _call_get_multiple = async ( // Helper function to call getParametersByName() and log the result const _call_get_parameters_by_name = async ( - params: Record, + params: Record, testName: string, - options?: SSMGetParametersByNameOptionsInterface, + options?: SSMGetParametersByNameOptions, provider?: SSMProvider ): Promise => { try { diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index 0fa4af63d2..affe3c28fd 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -4,7 +4,7 @@ * @group unit/parameters/AppConfigProvider/class */ import { AppConfigProvider } from '../../src/appconfig/index'; -import { ExpirableValue } from '../../src/ExpirableValue'; +import { ExpirableValue } from '../../src/base/ExpirableValue'; import { AppConfigProviderOptions } from '../../src/types/AppConfigProvider'; import { AppConfigDataClient, diff --git a/packages/parameters/tests/unit/BaseProvider.test.ts b/packages/parameters/tests/unit/BaseProvider.test.ts index 6fb45c2010..773c31b23d 100644 --- a/packages/parameters/tests/unit/BaseProvider.test.ts +++ b/packages/parameters/tests/unit/BaseProvider.test.ts @@ -3,14 +3,9 @@ * * @group unit/parameters/baseProvider/class */ -import { - BaseProvider, - ExpirableValue, - GetParameterError, - TransformParameterError, - clearCaches, - DEFAULT_PROVIDERS, -} from '../../src'; +import { BaseProvider, clearCaches, DEFAULT_PROVIDERS } from '../../src/base'; +import { ExpirableValue } from '../../src/base/ExpirableValue'; +import { GetParameterError, TransformParameterError } from '../../src/errors'; import { toBase64 } from '@aws-sdk/util-base64-node'; const encoder = new TextEncoder(); @@ -286,6 +281,23 @@ describe('Class: BaseProvider', () => { ); }); + test('when the underlying _getMultiple does not return an object, it throws a GetParameterError', async () => { + // Prepare + const provider = new TestProvider(); + jest.spyOn(provider, '_getMultiple').mockImplementation( + () => + new Promise((resolve, _reject) => + // need to type cast to force the error + resolve('not an object' as unknown as Record) + ) + ); + + // Act & Assess + await expect(provider.getMultiple('my-parameter')).rejects.toThrowError( + GetParameterError + ); + }); + test('when called with a json transform, and all the values are a valid string representation of a JSON, it returns an object with all the values', async () => { // Prepare const mockData = { A: JSON.stringify({ foo: 'bar' }) }; diff --git a/packages/parameters/tests/unit/SSMProvider.test.ts b/packages/parameters/tests/unit/SSMProvider.test.ts index 1d58232fbe..8cec3e4de2 100644 --- a/packages/parameters/tests/unit/SSMProvider.test.ts +++ b/packages/parameters/tests/unit/SSMProvider.test.ts @@ -20,7 +20,7 @@ import type { SSMSplitBatchAndDecryptParametersOutputType, SSMGetParametersByNameOutputInterface, } from '../../src/types/SSMProvider'; -import { ExpirableValue } from '../../src/BaseProvider'; +import { ExpirableValue } from '../../src/base/ExpirableValue'; import { toBase64 } from '@aws-sdk/util-base64'; const encoder = new TextEncoder(); diff --git a/packages/parameters/tests/unit/getAppConfig.test.ts b/packages/parameters/tests/unit/getAppConfig.test.ts index 20e9bfe8d9..4d1a3b23c2 100644 --- a/packages/parameters/tests/unit/getAppConfig.test.ts +++ b/packages/parameters/tests/unit/getAppConfig.test.ts @@ -8,6 +8,7 @@ import { getAppConfig, DEFAULT_PROVIDERS, } from '../../src/appconfig'; +import { Transform } from '../../src'; import { AppConfigDataClient, StartConfigurationSessionCommand, @@ -15,8 +16,8 @@ import { } from '@aws-sdk/client-appconfigdata'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; -import type { GetAppConfigCombinedInterface } from '../../src/types/AppConfigProvider'; import { toBase64 } from '@aws-sdk/util-base64-node'; +import { JSONValue } from '@aws-lambda-powertools/commons'; describe('Function: getAppConfig', () => { const client = mockClient(AppConfigDataClient); @@ -28,30 +29,23 @@ describe('Function: getAppConfig', () => { test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare - const options: GetAppConfigCombinedInterface = { - application: 'MyApp', - environment: 'MyAppProdEnv', - }; - const name = 'MyAppFeatureFlag'; - const mockInitialToken = - 'AYADeNgfsRxdKiJ37A12OZ9vN2cAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF1RzlLMTg1Tkx2Wjk4OGV2UXkyQ1'; - const mockNextToken = - 'ImRmyljpZnxt7FfxeEOE5H8xQF1SfOlWZFnHujbzJmIvNeSAAA8/qA9ivK0ElRMwpvx96damGxt125XtMkmYf6a0OWSqnBw=='; const mockData = encoder.encode('myAppConfiguration'); - client .on(StartConfigurationSessionCommand) .resolves({ - InitialConfigurationToken: mockInitialToken, + InitialConfigurationToken: 'abcdefg', }) .on(GetLatestConfigurationCommand) .resolves({ Configuration: mockData, - NextPollConfigurationToken: mockNextToken, + NextPollConfigurationToken: 'hijklmn', }); // Act - const result = await getAppConfig(name, options); + const result: Uint8Array | undefined = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + }); // Assess expect(result).toBe(mockData); @@ -59,32 +53,28 @@ describe('Function: getAppConfig', () => { test('when called and a default provider exists, it uses it and returns the value', async () => { // Prepare - const options: GetAppConfigCombinedInterface = { - application: 'MyApp', - environment: 'MyAppProdEnv', - }; - const provider = new AppConfigProvider(options); + const provider = new AppConfigProvider({ + application: 'my-app', + environment: 'prod', + }); DEFAULT_PROVIDERS.appconfig = provider; - const name = 'MyAppFeatureFlag'; - const mockInitialToken = - 'AYADeNgfsRxdKiJ37A12OZ9vN2cAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF1RzlLMTg1Tkx2Wjk4OGV2UXkyQ1'; - const mockNextToken = - 'ImRmyljpZnxt7FfxeEOE5H8xQF1SfOlWZFnHujbzJmIvNeSAAA8/qA9ivK0ElRMwpvx96damGxt125XtMkmYf6a0OWSqnBw=='; const mockData = encoder.encode('myAppConfiguration'); - client .on(StartConfigurationSessionCommand) .resolves({ - InitialConfigurationToken: mockInitialToken, + InitialConfigurationToken: 'abcdefg', }) .on(GetLatestConfigurationCommand) .resolves({ Configuration: mockData, - NextPollConfigurationToken: mockNextToken, + NextPollConfigurationToken: 'hijklmn', }); // Act - const result = await getAppConfig(name, options); + const result: Uint8Array | undefined = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + }); // Assess expect(result).toBe(mockData); @@ -92,35 +82,53 @@ describe('Function: getAppConfig', () => { test('when called with transform: `binary` option, it returns string value', async () => { // Prepare - const options: GetAppConfigCombinedInterface = { - application: 'MyApp', - environment: 'MyAppProdEnv', - transform: 'binary', - }; - - const name = 'MyAppFeatureFlag'; - const mockInitialToken = - 'AYADeNgfsRxdKiJ37A12OZ9vN2cAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF1RzlLMTg1Tkx2Wjk4OGV2UXkyQ1'; - const mockNextToken = - 'ImRmyljpZnxt7FfxeEOE5H8xQF1SfOlWZFnHujbzJmIvNeSAAA8/qA9ivK0ElRMwpvx96damGxt125XtMkmYf6a0OWSqnBw=='; const expectedValue = 'my-value'; const mockData = encoder.encode(toBase64(encoder.encode(expectedValue))); - client .on(StartConfigurationSessionCommand) .resolves({ - InitialConfigurationToken: mockInitialToken, + InitialConfigurationToken: 'abcdefg', }) .on(GetLatestConfigurationCommand) .resolves({ Configuration: mockData, - NextPollConfigurationToken: mockNextToken, + NextPollConfigurationToken: 'hijklmn', }); // Act - const result = await getAppConfig(name, options); + const result: string | undefined = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + transform: Transform.BINARY, + }); // Assess expect(result).toBe(expectedValue); }); + + test('when called with transform: `json` option, it returns a JSON value', async () => { + // Prepare + const expectedValue = { foo: 'my-value' }; + const mockData = encoder.encode(JSON.stringify(expectedValue)); + client + .on(StartConfigurationSessionCommand) + .resolves({ + InitialConfigurationToken: 'abcdefg', + }) + .on(GetLatestConfigurationCommand) + .resolves({ + Configuration: mockData, + NextPollConfigurationToken: 'hijklmn', + }); + + // Act + const result: JSONValue = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + transform: Transform.JSON, + }); + + // Assess + expect(result).toStrictEqual(expectedValue); + }); }); diff --git a/packages/parameters/tests/unit/getParameter.test.ts b/packages/parameters/tests/unit/getParameter.test.ts index f6e5d50320..175f5e5f4a 100644 --- a/packages/parameters/tests/unit/getParameter.test.ts +++ b/packages/parameters/tests/unit/getParameter.test.ts @@ -3,7 +3,7 @@ * * @group unit/parameters/ssm/getParameter/function */ -import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; +import { DEFAULT_PROVIDERS } from '../../src/base'; import { SSMProvider, getParameter } from '../../src/ssm'; import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; diff --git a/packages/parameters/tests/unit/getParameters.test.ts b/packages/parameters/tests/unit/getParameters.test.ts index 7ec9680dc7..b8afe00bc6 100644 --- a/packages/parameters/tests/unit/getParameters.test.ts +++ b/packages/parameters/tests/unit/getParameters.test.ts @@ -3,7 +3,7 @@ * * @group unit/parameters/ssm/getParameters/function */ -import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; +import { DEFAULT_PROVIDERS } from '../../src/base'; import { SSMProvider, getParameters } from '../../src/ssm'; import { SSMClient, GetParametersByPathCommand } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; diff --git a/packages/parameters/tests/unit/getParametersByName.test.ts b/packages/parameters/tests/unit/getParametersByName.test.ts index 3e2dcfc5f3..e0c6201d22 100644 --- a/packages/parameters/tests/unit/getParametersByName.test.ts +++ b/packages/parameters/tests/unit/getParametersByName.test.ts @@ -3,7 +3,7 @@ * * @group unit/parameters/ssm/getParametersByName/function */ -import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; +import { DEFAULT_PROVIDERS } from '../../src/base'; import { SSMProvider, getParametersByName } from '../../src/ssm'; import type { SSMGetParametersByNameOptions } from '../../src/types/SSMProvider'; diff --git a/packages/parameters/tests/unit/getSecret.test.ts b/packages/parameters/tests/unit/getSecret.test.ts index 966f6456e7..e1cf3b82ee 100644 --- a/packages/parameters/tests/unit/getSecret.test.ts +++ b/packages/parameters/tests/unit/getSecret.test.ts @@ -3,7 +3,7 @@ * * @group unit/parameters/SecretsProvider/getSecret/function */ -import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; +import { DEFAULT_PROVIDERS } from '../../src/base'; import { SecretsProvider, getSecret } from '../../src/secrets'; import { SecretsManagerClient, diff --git a/packages/parameters/typedoc.json b/packages/parameters/typedoc.json index 2464c677ef..9a2e53d7f6 100644 --- a/packages/parameters/typedoc.json +++ b/packages/parameters/typedoc.json @@ -1,10 +1,19 @@ { - "extends": ["../../typedoc.base.json"], + "extends": [ + "../../typedoc.base.json" + ], "entryPoints": [ "./src/appconfig/index.ts", + "./src/types/AppConfigProvider.ts", "./src/ssm/index.ts", + "./src/types/SSMProvider.ts", "./src/dynamodb/index.ts", + "./src/types/DynamoDBProvider.ts", "./src/secrets/index.ts", - "./src/types/index.ts"], + "./src/types/SecretsProvider.ts", + "./src/base/index.ts", + "./src/errors.ts", + "./src/constants.ts", + ], "readme": "README.md" } \ No newline at end of file