Reference for writing code and tests in this repo. Rules are grouped by concern; apply every rule in the section that matches your task.
- The codebase is TypeScript, ESM. Each utility lives in
packages/<package-name>withsrcfor source andtestfor tests. - Import across packages by package name (
import { myFunction } from '@aws-lambda-powertools/commons'), with the dependency declared in the importing package'spackage.json. Relative paths stay within a package and always carry the.jsextension (from './utils.js'). - Utilities and types shared by two or more packages belong in
@aws-lambda-powertools/commons. - Sibling-package dependencies (including peerDependencies) are exact pins matching the current lockstep version (
"@aws-lambda-powertools/commons": "2.35.0"), no range specifiers.
Conventions inside each package:
- Errors live in a per-package
errors.ts; classes carry theErrorsuffix (IdempotencyKeyError) and extend the package's base error. - Constants live in a per-package
constants.tsasas constobjects, in place of TS enums. - Environment variables are read through the commons helpers —
getStringFromEnv,getNumberFromEnv,getBooleanFromEnvfrom@aws-lambda-powertools/commons/utils/env— rather thanprocess.env. - AWS SDK clients get the Powertools user agent at construction:
addUserAgentMiddleware(client, '<feature>')from commons. - New public entry points (packages build dual ESM/CJS) require entries in both the
exportsmap andtypesVersionsin the package'spackage.json.
Match the existing style of the surrounding code. House conventions:
constby default;letonly for reassignment.async/awaitfor asynchronous code.for...offor array iteration;Object.entries/Object.keys/Object.valuesfor object iteration.- Specific types first,
unknownwhen the type is genuinely unknown,anyonly when unavoidable. - Fix type errors at their source; a cast (
as Type, and especiallyas unknown as Type) is a last resort for genuine boundaries, paired with a comment explaining why it's safe. import type/export typefor type-only imports and exports.nullmeans the absence of a value;undefinedmeans a value not yet set or initialized. Pick the one that matches the meaning.- Descriptive, spelled-out names; readability over cleverness.
- To suppress a type error, use
@ts-expect-errorwith a reason (@ts-ignoreis a lint error). - Biome organizes imports on save/commit; leave import ordering to it.
Document functions, classes, and types with a single JSDoc block per symbol — public APIs always, internal ones too.
- Open with one active-voice sentence ending in a period (
Gets the user by ID.), then longer detail as needed. @param name - the name of the user: active voice, no type annotation. For option objects:@param {Object} optionsthen@param {string} options.name - the name of the user.@exampleto show usage.- Link symbols as
{@link functionName |functionName}— backticks mandatory for docs rendering. @internalmarks symbols outside the public API;@deprecatedalways carries a reason and an alternative.- Skip
@returnsand@throws.
Run from the repo root with -w <workspace>, or from the package directory:
npm run lintto check;npm run lint:fixto auto-fix (review its changes).npm run build:teststo type-check source and tests without emitting — CI compiles them separately from running them.npm run buildadditionally compiles the CommonJS target.
Tests use vitest and live in each package's test directory. Run with npm run test:unit -w packages/<name> (or npm run test:unit from the package directory). Write unit tests only — end-to-end tests happen when the user asks for them.
Coverage: CI enforces 100% coverage on src/** (types files excluded) via npm run test:unit:coverage — the plain test run skips coverage, so verify with the :coverage variant before finishing. Every new source line needs a covering test.
Structure:
- One behavior per test case. Use
it.eachto cover input variations of the same behavior. - Nest
describeblocks at most one level deep. - Name tests in active voice, no conditional:
it('throws an error when input is invalid'). - Delimit each test into phases with comments:
// Prepare(setup),// Act(execute),// Assess(verify). - Commit every test enabled:
it.onlyand.skipare lint errors.
Assertions:
- Verify results with
expectassertions on observable behavior. - Each assertion tests something meaningful; test behavior, and reach private methods only by extending the class in the test to expose them.
- Custom matchers from
packages/testing/src/setupEnv.ts:toHaveLogged,toHaveLoggedNth,toHaveEmittedEMFWith,toHaveEmittedNthEMFWith,toHaveEmittedMetricWith,toHaveEmittedNthMetricWith, plustoReceiveCommandWithfor mocked AWS SDK clients.
Environment:
- Keep test cases isolated from each other.
vi.mocksparingly, for external dependencies only.consoleis pre-mocked: use it freely in code under test and in assertions.- Set env vars with
vi.stubEnv()and restore withvi.unstubAllEnvs()inbeforeEach/afterEach; setupEnv pre-sets the standard Lambda env vars.
When unsure, copy the pattern of an existing test in the same package.
Feature work updates the MkDocs site in docs/features/<utility>.md. Code examples are real TypeScript files in examples/snippets/<utility>/, included via --8<-- snippet syntax — examples/snippets is a workspace that CI lints and type-checks, so every doc example must compile.