Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions packages/common-aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import { S3SecureStore, downloadObject } from '@paradoxical-io/common-aws';

const secureStore = new S3SecureStore({
s3Bucket: 'my-bucket',
kmsKeyID: 'arn:aws:kms:...'
kmsKeyID: 'arn:aws:kms:...',
});

await secureStore.set('sensitive-data', Buffer.from('secret'));
Expand Down Expand Up @@ -128,7 +128,7 @@ import { CloudfrontPrivateAccess } from '@paradoxical-io/common-aws';
const cloudfront = new CloudfrontPrivateAccess({
distributionUrl: 'https://d123.cloudfront.net',
privateKey: '...',
publicKeyId: 'KEY123'
publicKeyId: 'KEY123',
});

const cookies = await cloudfront.generateCookies(expiresAt);
Expand All @@ -150,7 +150,7 @@ const taskId = await cwManager.createExportTask({
s3Bucket: 'logs-bucket',
logGroupName: '/aws/lambda/my-function',
from: startTime,
to: endTime
to: endTime,
});
```

Expand All @@ -165,9 +165,7 @@ WebSocket connection management for API Gateway:
```typescript
import { ApiGatewayWebsocket } from '@paradoxical-io/common-aws';

const websocket = ApiGatewayWebsocket.createEndpoint(
'wss://abc123.execute-api.us-east-1.amazonaws.com/prod'
);
const websocket = ApiGatewayWebsocket.createEndpoint('wss://abc123.execute-api.us-east-1.amazonaws.com/prod');

await websocket.publish(connectionId, { message: 'Hello!' });
```
Expand All @@ -186,7 +184,7 @@ import { defaultValueProvider } from '@paradoxical-io/common-aws';
const valueProvider = await defaultValueProvider();
const dbPassword = await valueProvider.get({
type: 'ssm',
key: '/prod/database/password'
key: '/prod/database/password',
});
```

Expand Down Expand Up @@ -243,8 +241,8 @@ const s3 = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: '...',
secretAccessKey: '...'
}
secretAccessKey: '...',
},
});
```

Expand Down Expand Up @@ -276,15 +274,39 @@ Metrics integrate with Datadog by default but can be customized.

## Dependencies

This package uses AWS SDK v3 and requires the following peer dependencies:
All AWS SDK clients, `@paradoxical-io/common-server`, `aws-lambda`, and `datadog-lambda-js` are **peer dependencies**. You must install only the packages required by the subpath you import. This avoids pulling in the entire AWS SDK or heavy dependencies when you only need a single module.

For example, if you only use `@paradoxical-io/common-aws/dynamo/keys`:

```bash
yarn add @paradoxical-io/common-aws @aws-sdk/client-dynamodb @aws/dynamodb-data-mapper-annotations
```

If you use `@paradoxical-io/common-aws/sqs`, you also need `@paradoxical-io/common-server`:

- Node.js 14 or higher
- TypeScript 4.5 or higher (for development)
```bash
yarn add @paradoxical-io/common-aws @aws-sdk/client-sqs @paradoxical-io/common-server
```

Core dependencies include:
- `@aws-sdk/client-*` - AWS SDK v3 clients
- `@paradoxical-io/types` - Shared type definitions
- `@paradoxical-io/common-server` - Server utilities
### Subpath Exports

Import only the module you need to keep your dependency footprint minimal:

| Subpath | Required Peer Dependencies |
| -------------------------------------------- | ---------------------------------------------------------------------------- |
| `@paradoxical-io/common-aws/dynamo/keys` | `@aws-sdk/client-dynamodb`, `@aws/dynamodb-data-mapper-annotations` |
| `@paradoxical-io/common-aws/dynamo` | Above + `@paradoxical-io/common-server` |
| `@paradoxical-io/common-aws/monitoring` | (none beyond core) |
| `@paradoxical-io/common-aws/cloudfront` | `@aws-sdk/cloudfront-signer` |
| `@paradoxical-io/common-aws/cloudwatch` | `@aws-sdk/client-cloudwatch-logs` |
| `@paradoxical-io/common-aws/sns` | `@aws-sdk/client-sns` |
| `@paradoxical-io/common-aws/sqs` | `@aws-sdk/client-sqs`, `@paradoxical-io/common-server` |
| `@paradoxical-io/common-aws/s3` | `@aws-sdk/client-s3`, `@aws-sdk/client-kms`, `@paradoxical-io/common-server` |
| `@paradoxical-io/common-aws/parameter-store` | `@aws-sdk/client-ssm`, `@paradoxical-io/common-server` |
| `@paradoxical-io/common-aws/lambda` | `aws-lambda`, `datadog-lambda-js` |
| `@paradoxical-io/common-aws/gateway` | `@aws-sdk/client-apigatewaymanagementapi` |

The root import (`@paradoxical-io/common-aws`) re-exports everything and requires all peer dependencies.

## Configuration

Expand Down
181 changes: 162 additions & 19 deletions packages/common-aws/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,71 @@
{
"name": "@paradoxical-io/common-aws",
"version": "2.9.0",
"description": "Common aws for paradox services",
"version": "3.0.0",
"description": "Common aws utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js"
},
"./dynamo/keys": {
"types": "./dist/dynamo/keys/index.d.ts",
"import": "./dist/dynamo/keys/index.js",
"require": "./dist/dynamo/keys/index.js"
},
"./dynamo": {
"types": "./dist/dynamo/index.d.ts",
"import": "./dist/dynamo/index.js",
"require": "./dist/dynamo/index.js"
},
"./monitoring": {
"types": "./dist/monitoring/index.d.ts",
"import": "./dist/monitoring/index.js",
"require": "./dist/monitoring/index.js"
},
"./cloudfront": {
"types": "./dist/cloudfront/index.d.ts",
"import": "./dist/cloudfront/index.js",
"require": "./dist/cloudfront/index.js"
},
"./cloudwatch": {
"types": "./dist/cloudwatch/index.d.ts",
"import": "./dist/cloudwatch/index.js",
"require": "./dist/cloudwatch/index.js"
},
"./sns": {
"types": "./dist/sns/index.d.ts",
"import": "./dist/sns/index.js",
"require": "./dist/sns/index.js"
},
"./sqs": {
"types": "./dist/sqs/index.d.ts",
"import": "./dist/sqs/index.js",
"require": "./dist/sqs/index.js"
},
"./s3": {
"types": "./dist/s3/index.d.ts",
"import": "./dist/s3/index.js",
"require": "./dist/s3/index.js"
},
"./parameter-store": {
"types": "./dist/parameter-store/index.d.ts",
"import": "./dist/parameter-store/index.js",
"require": "./dist/parameter-store/index.js"
},
"./lambda": {
"types": "./dist/lambda/metrics.d.ts",
"import": "./dist/lambda/metrics.js",
"require": "./dist/lambda/metrics.js"
},
"./gateway": {
"types": "./dist/gateway/apiGatewayWebsocket.d.ts",
"import": "./dist/gateway/apiGatewayWebsocket.js",
"require": "./dist/gateway/apiGatewayWebsocket.js"
}
},
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
Expand All @@ -20,35 +82,116 @@
"author": "Anton Kropp",
"license": "MIT",
"devDependencies": {
"@aws-sdk/client-apigatewaymanagementapi": "^3.1002.0",
"@aws-sdk/client-cloudwatch-logs": "^3.1002.0",
"@aws-sdk/client-dynamodb": "^3.1002.0",
"@aws-sdk/client-kms": "^3.1002.0",
"@aws-sdk/client-s3": "^3.1002.0",
"@aws-sdk/client-sns": "^3.1002.0",
"@aws-sdk/client-sqs": "^3.1002.0",
"@aws-sdk/client-ssm": "^3.1002.0",
"@aws-sdk/client-sts": "^3.1002.0",
"@aws-sdk/cloudfront-signer": "^3.1002.0",
"@aws-sdk/credential-provider-node": "^3.972.0",
"@aws-sdk/credential-providers": "^3.1002.0",
"@aws-sdk/s3-request-presigner": "^3.1002.0",
"@aws-sdk/util-retry": "^3.374.0",
"@aws/dynamodb-data-mapper": "^0.7.3",
"@aws/dynamodb-data-mapper-annotations": "^0.7.3",
"@paradoxical-io/common-server": "workspace:packages/common-server",
"@types/aws-lambda": "^8.10.150",
"@types/aws4": "^1.11.6",
"@types/lodash": "^4.17.18",
"@types/nodemailer": "^6.4.17"
"@types/nodemailer": "^6.4.17",
"aws-lambda": "^1.0.7",
"datadog-lambda-js": "^3.59.0"
},
"optionalDependencies": {
"@paradoxical-io/common-test": "workspace:packages/common-test"
},
"dependencies": {
"@aws-sdk/client-apigatewaymanagementapi": "^3.835.0",
"@aws-sdk/client-cloudwatch-logs": "^3.835.0",
"@aws-sdk/client-dynamodb": "^3.835.0",
"@aws-sdk/client-kms": "^3.835.0",
"@aws-sdk/client-s3": "^3.837.0",
"@aws-sdk/client-sns": "^3.835.0",
"@aws-sdk/client-sqs": "^3.835.0",
"@aws-sdk/client-ssm": "^3.835.0",
"@aws-sdk/client-sts": "^3.835.0",
"@aws-sdk/cloudfront-signer": "^3.821.0",
"@aws-sdk/credential-provider-node": "^3.835.0",
"@aws-sdk/credential-providers": "^3.835.0",
"@aws-sdk/s3-request-presigner": "^3.837.0",
"peerDependencies": {
"@aws-sdk/client-apigatewaymanagementapi": "^3.1002.0",
"@aws-sdk/client-cloudwatch-logs": "^3.1002.0",
"@aws-sdk/client-dynamodb": "^3.1002.0",
"@aws-sdk/client-kms": "^3.1002.0",
"@aws-sdk/client-s3": "^3.1002.0",
"@aws-sdk/client-sns": "^3.1002.0",
"@aws-sdk/client-sqs": "^3.1002.0",
"@aws-sdk/client-ssm": "^3.1002.0",
"@aws-sdk/client-sts": "^3.1002.0",
"@aws-sdk/cloudfront-signer": "^3.1002.0",
"@aws-sdk/credential-provider-node": "^3.972.0",
"@aws-sdk/credential-providers": "^3.1002.0",
"@aws-sdk/s3-request-presigner": "^3.1002.0",
"@aws-sdk/util-retry": "^3.374.0",
"@aws/dynamodb-data-mapper": "^0.7.3",
"@aws/dynamodb-data-mapper-annotations": "^0.7.3",
"@paradoxical-io/common-server": "workspace:packages/common-server",
"@paradoxical-io/types": "workspace:packages/types",
"aws-lambda": "^1.0.7",
"datadog-lambda-js": "^3.59.0",
"datadog-lambda-js": "^3.59.0"
},
"peerDependenciesMeta": {
"@aws-sdk/client-apigatewaymanagementapi": {
"optional": true
},
"@aws-sdk/client-cloudwatch-logs": {
"optional": true
},
"@aws-sdk/client-dynamodb": {
"optional": true
},
"@aws-sdk/client-kms": {
"optional": true
},
"@aws-sdk/client-s3": {
"optional": true
},
"@aws-sdk/client-sns": {
"optional": true
},
"@aws-sdk/client-sqs": {
"optional": true
},
"@aws-sdk/client-ssm": {
"optional": true
},
"@aws-sdk/client-sts": {
"optional": true
},
"@aws-sdk/cloudfront-signer": {
"optional": true
},
"@aws-sdk/credential-provider-node": {
"optional": true
},
"@aws-sdk/credential-providers": {
"optional": true
},
"@aws-sdk/s3-request-presigner": {
"optional": true
},
"@aws-sdk/util-retry": {
"optional": true
},
"@aws/dynamodb-data-mapper": {
"optional": true
},
"@aws/dynamodb-data-mapper-annotations": {
"optional": true
},
"@paradoxical-io/common-server": {
"optional": true
},
"aws-lambda": {
"optional": true
},
"datadog-lambda-js": {
"optional": true
}
},
"dependencies": {
"@paradoxical-io/common": "workspace:packages/common",
"@paradoxical-io/types": "workspace:packages/types",
"joi": "^17.13.3",
"lodash": "^4.17.21"
},
Expand Down
45 changes: 13 additions & 32 deletions packages/common-aws/src/dynamo/docker.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,23 @@
import { getSchema } from '@aws/dynamodb-data-mapper';
import { keysFromSchema } from '@aws/dynamodb-data-marshaller';
import { CreateTableCommand, DynamoDBClient, waitUntilTableExists } from '@aws-sdk/client-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry';
import { Docker, newDocker } from '@paradoxical-io/common-server/dist/test/docker';

import { DynamoDao } from './mapper';
import { DynamoUtil } from './util';

export class DynamoDocker {
constructor(public container: Docker, public dynamo: DynamoDBClient) {}
readonly util: DynamoUtil;

constructor(public container: Docker, public dynamo: DynamoDBClient) {
this.util = new DynamoUtil(dynamo);
}

async createTable<T extends DynamoDao>(descriptor: new () => T, tableName?: string): Promise<void> {
const schema = getSchema(descriptor.prototype);
const { attributes, tableKeys } = keysFromSchema(schema);

const attributeDefs = Object.keys(attributes).map(name => ({
AttributeName: name,
AttributeType: attributes[name],
}));

const keySchema = Object.keys(tableKeys).map(name => ({
AttributeName: name,
KeyType: tableKeys[name],
}));

const command = new CreateTableCommand({
TableName: tableName,
AttributeDefinitions: attributeDefs,
KeySchema: keySchema,
ProvisionedThroughput: {
ReadCapacityUnits: 1,
WriteCapacityUnits: 1,
},
});

const result = await this.dynamo.send(command);

if (result.TableDescription?.TableStatus !== 'ACTIVE') {
await waitUntilTableExists({ client: this.dynamo, maxWaitTime: 30 }, { TableName: tableName });
}
return this.util.createTable(descriptor, tableName);
}

async removeTable(tableName: string): Promise<void> {
return this.util.removeTable(tableName);
}
}

Expand All @@ -54,6 +34,7 @@ export async function newDynamoDocker(): Promise<DynamoDocker> {
const dynamo = new DynamoDBClient({
endpoint: base,
region: 'us-west-2',
credentials: { accessKeyId: 'local', secretAccessKey: 'local' },
// added retry logic as there appears to be intermittent timeout errors with dynamodb-local
retryStrategy: new ConfiguredRetryStrategy(4, (attempt: number) => 100 + attempt * 1000),
});
Expand Down
Loading
Loading