Skip to content

Commit

Permalink
Use bare-bones AWS clients/commands to make tree-shaking more effective
Browse files Browse the repository at this point in the history
  • Loading branch information
komiya-atsushi committed Oct 18, 2024
1 parent ef0ad96 commit 4c951c7
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 97 deletions.
6 changes: 2 additions & 4 deletions packages/bolt-dynamodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ You also need to install `@aws-sdk/client-dynamodb` package to create a DynamoDB
```typescript
import {App, ExpressReceiver, LogLevel} from '@slack/bolt';
import serverlessExpress from '@codegenie/serverless-express';
import {DynamoDB} from '@aws-sdk/client-dynamodb';
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {
BinaryInstallationCodec,
DynamoDbInstallationStore,
Expand All @@ -38,8 +38,6 @@ function ensureNotUndefined(envName: string): string {

const clientId = ensureNotUndefined('SLACK_CLIENT_ID');

const dynamoDb = new DynamoDB({region: process.env.AWS_REGION});

// You can compress and encrypt Installation using BinaryInstallationCodec.
const installationCodec = BinaryInstallationCodec.createDefault(
ensureNotUndefined('INSTALLATION_STORE_ENCRYPTION_PASSWORD'),
Expand All @@ -48,7 +46,7 @@ const installationCodec = BinaryInstallationCodec.createDefault(

const installationStore = DynamoDbInstallationStore.create({
clientId,
dynamoDb,
dynamoDb: new DynamoDBClient(),
tableName: ensureNotUndefined('DYNAMODB_TABLE_NAME'),
// Specify the attribute name of the partition key.
// In the default implementation, the combined string of Slack client ID,
Expand Down
121 changes: 64 additions & 57 deletions packages/bolt-dynamodb/src/DynamoDbInstallationStore.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {Logger} from '@slack/logger';
import {
AttributeValue,
BatchGetItemCommand,
BatchGetItemCommandInput,
BatchWriteItemCommandInput,
DynamoDB,
GetItemCommandInput,
BatchWriteItemCommand,
DynamoDBClient,
GetItemCommand,
QueryCommand,
QueryCommandInput,
UpdateItemCommandInput,
UpdateItemCommand,
} from '@aws-sdk/client-dynamodb';
import {InstallationCodec, JsonInstallationCodec} from './InstallationCodec';
import {
Expand All @@ -31,7 +33,9 @@ type DeletionOption = 'DELETE_ITEM' | 'DELETE_ATTRIBUTE';
export interface DynamoDbKeyGenerator
extends KeyGenerator<DynamoDbKey, DynamoDbDeletionKey> {
readonly keyAttributeNames: string[];

extractKeyFrom(item: Record<string, AttributeValue>): DynamoDbKey;

equals(key1: DynamoDbKey, key2: DynamoDbKey): boolean;
}

Expand Down Expand Up @@ -137,7 +141,7 @@ export class SimpleKeyGenerator implements DynamoDbKeyGenerator {

class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
constructor(
private readonly client: DynamoDB,
private readonly client: DynamoDBClient,
private readonly tableName: string,
private readonly keyGenerator: DynamoDbKeyGenerator,
private readonly attributeName: string,
Expand All @@ -147,7 +151,7 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
}

static async create(
client: DynamoDB | Promise<DynamoDB>,
client: DynamoDBClient | Promise<DynamoDBClient>,
tableName: string,
keyGenerator: DynamoDbKeyGenerator,
attributeName: string,
Expand All @@ -169,20 +173,21 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
isBotToken: boolean,
logger?: Logger
): Promise<void> {
const input: UpdateItemCommandInput = {
TableName: this.tableName,
Key: key,
UpdateExpression: 'SET #attrName = :d',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ExpressionAttributeValues: {
':d': {B: data},
},
ReturnConsumedCapacity: 'TOTAL',
};
const response = await this.client.send(
new UpdateItemCommand({
TableName: this.tableName,
Key: key,
UpdateExpression: 'SET #attrName = :d',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ExpressionAttributeValues: {
':d': {B: data},
},
ReturnConsumedCapacity: 'TOTAL',
})
);

const response = await this.client.updateItem(input);
logger?.debug(
'[store] UpdateItem consumed capacity',
response.ConsumedCapacity
Expand All @@ -192,17 +197,18 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
// ---

async fetch(key: DynamoDbKey, logger?: Logger): Promise<Buffer | undefined> {
const input: GetItemCommandInput = {
TableName: this.tableName,
Key: key,
ProjectionExpression: '#attrName',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ReturnConsumedCapacity: 'TOTAL',
};
const response = await this.client.send(
new GetItemCommand({
TableName: this.tableName,
Key: key,
ProjectionExpression: '#attrName',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ReturnConsumedCapacity: 'TOTAL',
})
);

const response = await this.client.getItem(input);
logger?.debug(
'[fetch] GetItem consumed capacity',
response.ConsumedCapacity
Expand Down Expand Up @@ -258,7 +264,7 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
ReturnConsumedCapacity: 'TOTAL',
};

const response = await this.client.batchGetItem(input);
const response = await this.client.send(new BatchGetItemCommand(input));
logger?.debug(
'[fetchMultiple] BatchGetItem consumed capacity',
response.ConsumedCapacity
Expand Down Expand Up @@ -319,14 +325,15 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
): Promise<DynamoDbKey[]> {
const keyAttributeNames = Object.values(key.ExpressionAttributeNames);

const input: QueryCommandInput = {
TableName: this.tableName,
ProjectionExpression: keyAttributeNames.join(','),
...key,
ReturnConsumedCapacity: 'TOTAL',
};
const response = await this.client.send(
new QueryCommand({
TableName: this.tableName,
ProjectionExpression: keyAttributeNames.join(','),
...key,
ReturnConsumedCapacity: 'TOTAL',
})
);

const response = await this.client.query(input);
logger?.debug(
'[delete] Query consumed capacity',
response.ConsumedCapacity
Expand All @@ -351,15 +358,15 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
for (let i = 0; i < keys.length; i += BATCH_WRITE_ITEM_MAX_ITEMS) {
const chunk = keys.slice(i, i + BATCH_WRITE_ITEM_MAX_ITEMS);

const input: BatchWriteItemCommandInput = {
RequestItems: Object.fromEntries([
[this.tableName, chunk.map(key => ({DeleteRequest: {Key: key}}))],
]),
ReturnConsumedCapacity: 'TOTAL',
};

const promise = this.client
.batchWriteItem(input)
.send(
new BatchWriteItemCommand({
RequestItems: Object.fromEntries([
[this.tableName, chunk.map(key => ({DeleteRequest: {Key: key}}))],
]),
ReturnConsumedCapacity: 'TOTAL',
})
)
.then(
res =>
logger?.debug(
Expand All @@ -380,18 +387,18 @@ class DynamoDbStorage extends StorageBase<DynamoDbKey, DynamoDbDeletionKey> {
): Promise<void> {
const promises = [];
for (const key of keys) {
const input: UpdateItemCommandInput = {
TableName: this.tableName,
Key: key,
UpdateExpression: 'REMOVE #attrName',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ReturnConsumedCapacity: 'TOTAL',
};

const promise = this.client
.updateItem(input)
.send(
new UpdateItemCommand({
TableName: this.tableName,
Key: key,
UpdateExpression: 'REMOVE #attrName',
ExpressionAttributeNames: {
'#attrName': this.attributeName,
},
ReturnConsumedCapacity: 'TOTAL',
})
)
.then(
res =>
logger?.debug(
Expand Down Expand Up @@ -431,7 +438,7 @@ export class DynamoDbInstallationStore extends InstallationStoreBase<

static create(args: {
clientId: string;
dynamoDb: Promise<DynamoDB> | DynamoDB;
dynamoDb: Promise<DynamoDBClient> | DynamoDBClient;
tableName: string;
partitionKeyName: string;
sortKeyName: string;
Expand Down
29 changes: 17 additions & 12 deletions packages/bolt-dynamodb/test/DynamoDbInstallationStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {ConsoleLogger, LogLevel} from '@slack/logger';
import {Installation, InstallationQuery} from '@slack/oauth';
import {
AttributeValue,
BatchGetItemCommandInput,
BatchGetItemCommand,
BatchGetItemCommandOutput,
DynamoDB,
DynamoDBClient,
PutItemCommandInput,
ScanCommandOutput,
} from '@aws-sdk/client-dynamodb';
Expand Down Expand Up @@ -671,19 +672,23 @@ describe('DynamoDbInstallationStore', () => {
function setUp(
...items: Record<string, AttributeValue>[]
): DynamoDbInstallationStore {
const mockedBatchGetItem: (
args: BatchGetItemCommandInput
) => Promise<BatchGetItemCommandOutput> = jest.fn(() => {
return new Promise(resolve =>
resolve({
Responses: Object.fromEntries([[tableName, items]]),
} as BatchGetItemCommandOutput)
);
});
const mockedSend: (command: unknown) => Promise<unknown> = jest.fn(
command => {
if (!(command instanceof BatchGetItemCommand)) {
throw new Error('Unexpected command');
}

return new Promise(resolve =>
resolve({
Responses: Object.fromEntries([[tableName, items]]),
} as BatchGetItemCommandOutput)
);
}
);

const mockedDynamoDbClient = {
batchGetItem: mockedBatchGetItem,
} as DynamoDB;
send: mockedSend,
} as DynamoDBClient;

return DynamoDbInstallationStore.create({
clientId: 'bolt-dynamodb-test',
Expand Down
6 changes: 2 additions & 4 deletions packages/bolt-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ You also need to install `@aws-sdk/client-s3` package to create an S3 client.

```typescript
import {App} from '@slack/bolt';
import {S3} from '@aws-sdk/client-s3';
import {S3Client} from '@aws-sdk/client-s3';
import {S3InstallationStore} from '@k11i/bolt-s3';

const s3 = new S3({ region: 'us-east-2' });

const installationStore = S3InstallationStore.create({
clientId,
s3,
s3: new S3Client(),
// An S3 bucket needs to be created.
bucketName: 'bucket-name-where-installations-are-stored',
options: {
Expand Down
Loading

0 comments on commit 4c951c7

Please sign in to comment.