@worktif/purews
v0.2.20
Published
Work TIF TypeScript-based AWS infrastructure toolkit featuring DynamoDB integration, AppSync support, SES functionality, and GraphQL capabilities with comprehensive audit logging and AWS Signature V4 authentication.
Maintainers
Readme
@worktif/purews
Enterprise-grade TypeScript utilities for AWS-centric serverless systems. Provides a composable DI-powered bundle with batteries-included services for DynamoDB, AppSync (GraphQL), SES, S3, Lambda middy middlewares, Zod validation, and environment configuration via Zod.
Overview
@worktif/purews is a production-ready utility toolkit designed for critical workloads (financial, cloud-native, distributed systems). It standardizes how you wire AWS services, validate configuration, instrument Lambdas, and execute GraphQL/DynamoDB/S3/SES operations with consistent logging and error semantics.
It emphasizes:
- Strong typing with TypeScript
- Deterministic environment/config validation (Zod)
- Inversion of control and factory-based DI
- Reliable logging + structured middlewares
- Low-overhead, testable service classes
Key Features
- Dependency Injection Bundle
- Pure container with explicit bindings for env/config and AWS services
- Configuration
- Zod-validated env schema for AWS AppSync and SES; dotenv support
- AWS Services
DynamoDbService (abstract): typed CRUD helpers, update expression generatorAppSyncService: typed GraphQL query/mutation with serializer hooksSesService: verify and send emails, verified-address checksS3Service: safe object read to string
- Lambda Utilities
- middy-based handlers for API Gateway and SQS
- Request parsing, logger injection, request-id correlation, Zod middleware
- Decorators: API injection, validation injector
- GraphQL Utils
- Types for internal GraphQL shapes; helpers for enum/case transformations
- Serialization
- Composable serializer for API and GraphQL payloads
- Observability
- Consistent logging via AWS Powertools logger with contextual metadata
Installation
# npm
npm install @worktif/purews
# yarn
yarn add @worktif/purewsPeer assumptions:
- Node.js >= 20
- TypeScript 5.8.x recommended
- AWS credentials available via environment or IAM role
Usage
Basic bootstrapping
import 'reflect-metadata';
import { bundlePurews } from '@worktif/purews';
// Access DI-provisioned services
const { appSync, ses, dynamoDb } = bundlePurews.aws.services;
// Access validated environment
const env = bundlePurews.env;
console.log(env.aws.credentials.region);AppSync GraphQL query
import { AppSyncService } from '@worktif/purews';
const appSync = bundlePurews.aws.services.appSync as AppSyncService;
const query = `
query GetItem($id: ID!) {
getItem(id: $id) {
id
name
updatedAt
}
}
`;
const result = await appSync.query<{ id: string; name: string; updatedAt: string }>({
query,
payload: { id: 'abc-123' }, // Variables
});
console.log(result.data); // { id, name, updatedAt }AppSync GraphQL mutation with serializer
import { identityToNull, identityToVoid } from '@worktif/utils';
const mutation = `
mutation UpdateItem($input: UpdateItemInput!, $condition: ModelItemConditionInput) {
updateItem(input: $input, condition: $condition) {
id
name
updatedAt
}
}
`;
const updated = await appSync.mutate<{ id: string; name: string }>({
mutation,
payload: { id: 'abc-123', name: 'New Name' },
}, {
serializer: identityToNull,
deserializer: identityToVoid,
});
console.log(updated.data.updateItem);SES: send email
import { SesService } from '@worktif/purews';
const ses = bundlePurews.aws.services.ses as SesService;
await ses.sendEmail('<b>Hello</b> from PureWS', '[email protected]', 'Subject');S3: read object
import { S3Service } from '@worktif/purews';
const s3 = new S3Service(bundlePurews.env);
const content = await s3.getS3ObjectContent({ Bucket: 'my-bucket', Key: 'path/to/file.txt' });
console.log(content);Lambda API handler with middy
import { lambdaApi } from '@worktif/purews';
type Payload = { id: string };
export const handler = lambdaApi<Payload, 'post'>(
async (event, context, internal) => {
internal?.log?.now({ body: event.body }, { message: 'Handling POST' });
return {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ ok: true, id: event.body.id }),
};
},
{
logger: { service: { entity: 'Example', name: 'CreateItem' } },
}
);Validation decorator and middleware
import { z } from 'zod';
import { injectApi, injectValidation } from '@worktif/purews';
import { lambdaApi } from '@worktif/purews';
const BodySchema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
const withValidation = injectValidation({ body: BodySchema });
export const handler = lambdaApi<{ email: string; name: string }, 'post'>(
async (event) => {
// event.body is typed and validated
return {
statusCode: 201,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ created: true }),
};
},
{ logger: { service: { entity: 'Users', name: 'CreateUser' } } }
).before(withValidation);API Reference
- Bundle and DI
class BundlePurewsaws.services: { dynamoDb: DynamoDbService, appSync: AppSyncService, ses: SesService }env: EnvConfigPurews
- Environment Config
class EnvConfigPurews extends EnvConfigDefaultaws.appSync: { appId?: string; apiKey?: string; appUrl?: string; appRealTimeUrl?: string }aws.ses: { notificationEmail?: string }- Validates from process.env:
AWS_APP_SYNC_APP_ID,AWS_APP_SYNC_API_KEY,AWS_APP_SYNC_APP_URL,AWS_APP_SYNC_APP_REAL_TIME_URL,SES_NOTIFICATION_EMAIL
DynamoDbService (abstract)– Service is developmenttableName: stringput<T>(payload: Partial<DynamoEntity<T>>): Promise<T | undefined>get<T>(id: string): Promise<T | undefined>update<T extends DynamoEntity<object>>(payload: Partial<DynamoEntity<T>>): Promise<T | undefined>delete<T extends DynamoEntity<object>>(payload: Partial<T>): Promise<Partial<T> | undefined>queryByAttribute<T>(options: QueryByAttrOptions): Promise<T[] | undefined>scanFinder<T>(): Promise<T[] | undefined>static generateExpressionForUpdate<T>(payload, listAttributes?): { updateExpression: string; expressionAttributeValues: Record<string, unknown> }- Types:
DynamoEntity<T> = T & { id: string; createdAt?: string | null; updatedAt?: string | null }
AppSyncServicequery<TResult, TVariables>(args: { query: string; payload: TVariables }, serializer?: GraphQlSerializer): Promise<GqlQueryResult<TResult>>mutate<TResult, TVariables>(args: { mutation: string; payload: TVariables; conditions?: ModelConditionInput<TResult> }, serializer?: GraphQlSerializer): Promise<GqlMutationResult<TResult>>GraphQlSerializer: { serializer?: (variables) => any; deserializer?: (result) => any }- Internals:
- parses input variable names and output key from DocumentNode
SesServicesendEmail(messageHtml: string, toAddress: string, subject?: string): Promise<SendEmailCommandOutput>verifyEmailAddress(email: string): Promise<void>isEmailAddressVerified(email: string): Promise<boolean>
S3ServicegetS3ObjectContent({ Bucket, Key }: { Bucket: string; Key: string }): Promise<string | undefined>
- Lambda utilities
lambdaApi<T, TMethod>(handler, internalContext?, schema?): Middy handler for API GatewaylambdaSqs<T>(handler, internalContext?): Middy handler for SQSinjectApi(injectFunction, injectCatchFunction?): MethodDecorator to pre-parse body and handle catchesinjectValidation(schemas, service?): returns function(event, context) to validate body/path with ZodzodValidationMiddleware(schema): middy before middleware that validates event.body- Types:
LambdaEventApi<T, TMethod>,LambdaEventMethod,LambdaEventSqs<T>,ValidationService
Use Cases
- Payments/Financial Services
- Deterministic GraphQL mutations to AppSync with safe serializers; strict audit fields on Dynamo entities
- Multi-tenant SaaS
- Composable DI container to wire per-tenant configuration sources; consistent SES notifications and verification
- Data Pipelines
- SQS consumer lambdas using lambdaSqs with uniform logging, Zod validation per record schema
- API Gateways at scale
- lambdaApi with standard parsing, logging context injection, error wrapping; easy adoption of Zod validation middleware
- Regulated environments
- Zod-validated env eliminates misconfiguration class; serializer boundaries for IO control; consistent logger metadata
Design Principles
- Type-first: all public APIs are strongly typed, including GraphQL and Dynamo helpers
- Composability: DI container + service factories to swap/test components
- Functional boundaries: pure serializers, explicit side-effect services
- Predictable IO: Zod validation at boundaries; identity serializers as defaults
- Low-latency: AWS SDK v3 usage, disabled AppSync offline cache, lightweight middlewares
- Observability: structured logs with contextual service names and stages
Best Practices
- Export concrete DynamoDB services by extending DynamoDbService and setting tableName
- Always validate env in CI: boot bundle in a smoke test to fail-fast on missing vars
- Keep GraphQL operations typed with TResult and TVariables; use serializer to normalize cases
- Use lambdaApi for all API Gateway handlers to standardize parsing/logging
- Keep SES notificationEmail verified; call verifyEmailAddress in provisioning workflows
Compatibility and Performance
- Node.js: 18+ recommended
- AppSync: AUTH_TYPE.API_KEY supported; pluggable if you extend for Cognito/JWT
- DynamoDB: AWS SDK v3 (lib-dynamodb); uses DocumentClient marshalling with removeUndefinedValues
- Performance notes:
- Query/mutate configured with no cache thrash and typenames disabled
- Dynamo batch/get/update paths minimize marshalling overhead
- Middy handlers inject logger once, avoiding repeated construction
Contributing
This section is intended for external publishers responsible for releasing the package to npm. Follow the sequence precisely to ensure auditability, semantic versioning integrity, and a clean release trail.
- Authenticate to the scoped registry
npm login --scope=@worktif- If you encounter a TLS/registry error, set the registry explicitly:
npm config set registry https://registry.npmjs.org/
- Complete your enhancement
- Implement and locally validate your changes (types, build, docs as applicable).
- Open a Pull Request (PR)
- Submit your changes for review.
- Await approval before proceeding.
- Merge the PR
- After approval, merge into main using your standard merge policy.
- Synchronize your local main
git checkout maingit pullto ensure you’re up to date.
- Prepare a release branch
- Create a branch using the release template:
releases/v[your.semantic.version-[pre+[meta]]]-next-release-description
- Create a branch using the release template:
- Bump the version
- Update the package version according to SemVer (major/minor/patch).
- Commit the version bump to the release branch
- Commit only the version change (and any generated artifacts if required by your policy).
- Push the release branch
- Push the branch to the remote to trigger any CI gates.
- Open a Release PR
- Create a PR from the release branch to main.
- Await approval and required checks.
- Merge the Release PR
- Merge into main after approvals and passing checks.
- Final synchronization
- Pull the latest changes from main locally.
- Validate the version in package.json
- Ensure the version reflects the intended release.
- Publish
- If the version was not increased (npm will reject):
- Bump the version, commit, and then run yarn run publish:npm.
- If the version has been increased and publishing fails unexpectedly:
- Contact the maintainer at [email protected] with context (command output, Node/npm versions, CI logs).
- If the version was not increased (npm will reject):
Successful publish output resembles:
+ @worktif/purews@[your.semantic.version-[pre+[meta]]]
✨ Done in 28.81s.Security and responsible disclosure
- Do not commit secrets
- Do not include secrets in tests or examples
- Report vulnerabilities privately to the maintainers contact below
- Use .npmrc with correct registry config for scoped publish
- New services must include error translation via CustomException and structured logs
License
This project is licensed under the Elastic License 2.0.
- See
LICENSEfor the full license text. - See
NOTICEfor attribution and relicensing details (re-licensed from BUSL-1.1 on 2025-09-15). - See
THIRD_PARTY_LICENSES.txtfor third-party attributions and license texts.
Rationale:
- Suitable for commercial use with restrictions on offering as a managed service; fit for enterprise deployments requiring source access with guardrails.
Maintainers / Contact
- Maintainer: Raman Marozau, [email protected]
- Documentation and support:
docs/generated via TypeDoc
For security reports, please contact [email protected].
