Skip to content

Commit

Permalink
Merge pull request #288 from DerDackel/aws-sdk-v3
Browse files Browse the repository at this point in the history
feat(aws-sdk): Updated client libraries to AWS SDK v3

BREAKING CHANGE: move to v3
  • Loading branch information
hoegertn authored Mar 13, 2023
2 parents 3905dc2 + 3a66efa commit 55d2053
Show file tree
Hide file tree
Showing 9 changed files with 1,558 additions and 136 deletions.
38 changes: 37 additions & 1 deletion .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ const { TaimosTypescriptLibrary } = require('@taimos/projen');
const project = new TaimosTypescriptLibrary({
name: '@taimos/lambda-toolbox',
deps: [
'aws-sdk',
'jsonwebtoken',
'jwk-to-pem',
'axios',
'uuid',
'lambda-log',
'@aws-crypto/sha256-js',
'@aws-sdk/client-appsync',
'@aws-sdk/client-dynamodb',
'@aws-sdk/credential-providers',
'@aws-sdk/lib-dynamodb',
'@aws-sdk/node-http-handler',
'@aws-sdk/protocol-http',
'@aws-sdk/signature-v4',
],
docgen: false,
defaultReleaseBranch: 'main',
Expand All @@ -21,6 +28,8 @@ const project = new TaimosTypescriptLibrary({
'@types/uuid',
'@taimos/projen',
'@hapi/boom',
'aws-sdk-client-mock',
'aws-sdk-client-mock-jest',
],
keywords: [
'aws',
Expand Down
11 changes: 10 additions & 1 deletion package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 25 additions & 12 deletions src/client/appsync.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { parse, UrlWithStringQuery } from 'url';
import * as AWS from 'aws-sdk';
import { URL } from 'url';
import { Sha256 } from '@aws-crypto/sha256-js';
import * as credentials from '@aws-sdk/credential-providers';
import { HttpRequest } from '@aws-sdk/protocol-http';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import * as axios from 'axios';

export class AppSyncClient {

protected readonly graphQlServerUri: UrlWithStringQuery;
protected readonly graphQlServerUri: URL;

constructor(protected readonly graphQlServerUrl: string, protected readonly awsRegion: string) {
this.graphQlServerUri = parse(this.graphQlServerUrl);
this.graphQlServerUri = new URL(this.graphQlServerUrl);
if (!this.graphQlServerUri.href) {
throw new Error('Invalid GraphQL server URL');
}
Expand All @@ -20,22 +23,32 @@ export class AppSyncClient {
variables,
};

const httpRequest = new AWS.HttpRequest(new AWS.Endpoint(this.graphQlServerUri.href!), this.awsRegion);
const httpRequest = new HttpRequest({
headers: {
'host': this.graphQlServerUri.host!,
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify(post_body),
});
httpRequest.headers.host = this.graphQlServerUri.host!;
httpRequest.headers['Content-Type'] = 'application/json';
httpRequest.method = 'POST';
httpRequest.body = JSON.stringify(post_body);

await ((AWS.config.credentials as AWS.Credentials)?.getPromise());
// There's now an official signature library - yay!
const signer = new SignatureV4({
credentials: credentials.fromEnv(),
service: 'appsync',
region: this.awsRegion,
sha256: Sha256,
});

// Signers is an internal API
const signer = new (AWS as any).Signers.V4(httpRequest, 'appsync', true);
signer.addAuthorization(AWS.config.credentials, (AWS as any).util.date.getDate());
const signedRequest = await signer.sign(httpRequest, { signingDate: new Date() });

const res = await axios.default.post(this.graphQlServerUri.href!, httpRequest.body, {
headers: httpRequest.headers,
return axios.default.post(this.graphQlServerUri.href!, signedRequest.body, {
headers: signedRequest.headers,
});
return res;
}

}
47 changes: 26 additions & 21 deletions src/dynamodb/client.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
import * as https from 'https';
import { env } from 'process';
import { DynamoDB } from 'aws-sdk';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import * as dynamodb from '@aws-sdk/lib-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { NodeHttpHandler } from '@aws-sdk/node-http-handler';
import { PrimaryEntity } from './model';

const agent = new https.Agent({
keepAlive: true,
});
export const dynamoClient: DynamoDB.DocumentClient = new DynamoDB.DocumentClient({ httpOptions: { agent } });
export const dynamoClient: DynamoDBDocumentClient = DynamoDBDocumentClient.from(new DynamoDBClient({
requestHandler: new NodeHttpHandler({ httpsAgent: agent }),
}));

export const TABLE_NAME: string = env.TABLE!;

export async function getItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<DynamoDB.DocumentClient.GetItemInput, 'TableName' | 'Key'>): Promise<E | undefined> {
const res = await dynamoClient.get({
export async function getItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<dynamodb.GetCommandInput, 'TableName' | 'Key'>): Promise<E | undefined> {
const res = await dynamoClient.send(new dynamodb.GetCommand({
TableName: TABLE_NAME,
Key: {
PK: pk,
SK: sk,
},
...options,
}).promise();
}));
return res.Item ? res.Item as E : undefined;
}

export async function deleteItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<DynamoDB.DocumentClient.DeleteItemInput, 'TableName' | 'Key'>): Promise<void> {
await dynamoClient.delete({
export async function deleteItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], options?: Omit<dynamodb.DeleteCommandInput, 'TableName' | 'Key'>): Promise<void> {
await dynamoClient.send(new dynamodb.DeleteCommand({
TableName: TABLE_NAME,
Key: {
PK: pk,
SK: sk,
},
...options,
}).promise();
}));
}

export async function putNewItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], item: Omit<E, 'PK' | 'SK'>): Promise<E> {
Expand All @@ -39,35 +44,35 @@ export async function putNewItem<E extends PrimaryEntity<any, any>>(pk: E['PK'],
SK: sk,
...item,
};
await dynamoClient.put({
await dynamoClient.send(new dynamodb.PutCommand({
TableName: TABLE_NAME,
Item,
ConditionExpression: 'attribute_not_exists(PK) and attribute_not_exists(SK)',
}).promise();
}));
return Item as E;
}

export async function updateExistingItem<E extends PrimaryEntity<any, any>>(pk: E['PK'], sk: E['SK'], item: Partial<E>): Promise<E | undefined> {
const res = await dynamoClient.update(createUpdate<E>({
const res = await dynamoClient.send(createUpdate<E>({
Key: {
PK: pk,
SK: sk,
},
ConditionExpression: 'attribute_exists(PK) and attribute_exists(SK)',
ReturnValues: 'ALL_NEW',
}, item)).promise();
}, item));
return res.Attributes ? res.Attributes as E : undefined;
}

export async function pagedQuery<T>(query: Omit<DynamoDB.DocumentClient.QueryInput, 'TableName'>): Promise<T[]> {
export async function pagedQuery<T>(query: Omit<dynamodb.QueryCommandInput, 'TableName'>): Promise<T[]> {
let startKey;
const result: T[] = [];
do {
const res: DynamoDB.DocumentClient.QueryOutput = await dynamoClient.query({
const res: dynamodb.QueryCommandOutput = await dynamoClient.send(new dynamodb.QueryCommand({
...query,
TableName: TABLE_NAME,
ExclusiveStartKey: startKey,
}).promise();
}));
if (res.Items) {
result.push(...res.Items as T[]);
}
Expand All @@ -76,15 +81,15 @@ export async function pagedQuery<T>(query: Omit<DynamoDB.DocumentClient.QueryInp
return result;
}

export async function pagedScan<T>(query: Omit<DynamoDB.DocumentClient.ScanInput, 'TableName'>): Promise<T[]> {
export async function pagedScan<T>(query: Omit<dynamodb.ScanCommandInput, 'TableName'>): Promise<T[]> {
let startKey;
const result: T[] = [];
do {
const res: DynamoDB.DocumentClient.ScanOutput = await dynamoClient.scan({
const res: dynamodb.ScanCommandOutput = await dynamoClient.send(new dynamodb.ScanCommand({
...query,
TableName: TABLE_NAME,
ExclusiveStartKey: startKey,
}).promise();
}));
if (res.Items) {
result.push(...res.Items as T[]);
}
Expand All @@ -101,7 +106,7 @@ export function padLeftZeros(val: number | string | undefined) {
return ('00' + val).slice(-2);
}

export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItemInput, 'TableName'>, data: Partial<T>): DynamoDB.DocumentClient.UpdateItemInput {
export function createUpdate<T>(request: Omit<dynamodb.UpdateCommandInput, 'TableName'>, data: Partial<T>): dynamodb.UpdateCommand {
const fieldsToSet = [];
const fieldsToRemove = [];
const expressionNames: any = {};
Expand Down Expand Up @@ -129,7 +134,7 @@ export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItem
if (request.UpdateExpression) {
update += request.UpdateExpression;
}
return {
return new dynamodb.UpdateCommand({
...request,
TableName: TABLE_NAME,
UpdateExpression: update,
Expand All @@ -147,5 +152,5 @@ export function createUpdate<T>(request: Omit<DynamoDB.DocumentClient.UpdateItem
...expressionValues,
},
},
};
});
}
3 changes: 1 addition & 2 deletions src/http/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { env } from 'process';
import Axios from 'axios';
import { verify, JwtHeader, SigningKeyCallback } from 'jsonwebtoken';
// import jwkToPem = require('jwk-to-pem');
import { JwtHeader, SigningKeyCallback, verify } from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';
import logger from 'lambda-log';
import { ForbiddenError, UnauthenticatedError } from '../types/errors';
Expand Down
Loading

0 comments on commit 55d2053

Please sign in to comment.