ToolJet/plugins/packages/dynamodb/lib/index.ts
Midhun G S c8fe500a1d
Migrated aws-sdk v2 to v3 (#15023)
* chore: update bcrypt to version 6.0.0 in package.json

* chore: remove unused devDependencies from package.json

* Refactor code structure for improved readability and maintainability

* Refactor code structure for improved readability and maintainability
2026-02-04 17:27:17 +05:30

154 lines
5.4 KiB
TypeScript

import { ConnectionTestResult, QueryService, QueryResult, QueryError } from '@tooljet-plugins/common';
import {
deleteItem,
getItem,
listTables,
queryTable,
scanTable,
describeTable,
updateItem,
createTable,
putItem,
} from './operations';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { STSClient, AssumeRoleCommand, AssumeRoleCommandOutput } from '@aws-sdk/client-sts';
import { fromInstanceMetadata } from '@aws-sdk/credential-providers';
import { AssumeRoleCredentials, SourceOptions, QueryOptions } from './types';
export default class DynamodbQueryService implements QueryService {
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions): Promise<QueryResult> {
const operation = queryOptions.operation;
const client = await this.getConnection(sourceOptions, { operation });
let result = {};
try {
switch (operation) {
case 'list_tables':
result = await listTables(client);
break;
case 'get_item':
result = await getItem(client, queryOptions.table, JSON.parse(queryOptions.key));
break;
case 'delete_item':
result = await deleteItem(client, queryOptions.table, JSON.parse(queryOptions.key));
break;
case 'query_table':
result = await queryTable(client, JSON.parse(queryOptions.query_condition));
break;
case 'scan_table':
result = await scanTable(client, JSON.parse(queryOptions.scan_condition));
break;
case 'update_item':
result = await updateItem(client, JSON.parse(queryOptions.update_condition));
break;
case 'create_table':
result = await createTable(client, JSON.parse(queryOptions.table_parameters));
break;
case 'describe_table':
result = await describeTable(client, queryOptions.table);
break;
case 'put_item':
result = await putItem(client, JSON.parse(queryOptions.new_item_details));
break;
}
} catch (err) {
throw new QueryError('Query could not be completed', err.message, {});
}
return {
status: 'ok',
data: result,
};
}
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
const client = await this.getConnection(sourceOptions, { operation: 'list_tables' });
await listTables(client);
return {
status: 'ok',
};
}
async getAssumeRoleCredentials(roleArn: string, region: string): Promise<AssumeRoleCredentials> {
const stsClient = new STSClient({ region });
const timestamp = new Date().getTime();
const roleName = roleArn.split('/')[1];
const command = new AssumeRoleCommand({
RoleArn: roleArn,
RoleSessionName: `dynamodb-${roleName}-${timestamp}`,
});
try {
const data: AssumeRoleCommandOutput = await stsClient.send(command);
return {
accessKeyId: data.Credentials?.AccessKeyId,
secretAccessKey: data.Credentials?.SecretAccessKey,
sessionToken: data.Credentials?.SessionToken,
};
} catch (err) {
throw new Error(`Failed to assume role: ${err.message}`);
}
}
async getConnection(sourceOptions: SourceOptions, options?: { operation: string }): Promise<any> {
const useAWSInstanceProfile = sourceOptions['instance_metadata_credentials'] === 'aws_instance_credentials';
const region = sourceOptions['region'];
const useRoleArn = sourceOptions['instance_metadata_credentials'] === 'aws_arn_role';
let credentials;
if (useAWSInstanceProfile) {
// Use EC2 instance metadata credentials
credentials = fromInstanceMetadata({
timeout: 5000,
maxRetries: 1,
});
} else if (useRoleArn) {
// Assume role and use temporary credentials
const assumeRoleCredentials = await this.getAssumeRoleCredentials(sourceOptions['role_arn'], region);
credentials = {
accessKeyId: assumeRoleCredentials.accessKeyId,
secretAccessKey: assumeRoleCredentials.secretAccessKey,
sessionToken: assumeRoleCredentials.sessionToken,
};
} else {
// Use explicit access key and secret key
credentials = {
accessKeyId: sourceOptions['access_key'],
secretAccessKey: sourceOptions['secret_key'],
};
}
// Create base DynamoDB client
const dynamoDBClient = new DynamoDBClient({
region,
credentials,
});
// For operations that need the low-level client (create_table, list_tables, describe_table)
if (options?.operation && ['create_table', 'list_tables', 'describe_table'].includes(options.operation)) {
return dynamoDBClient;
} else {
// For document operations, wrap with DynamoDBDocumentClient
return DynamoDBDocumentClient.from(dynamoDBClient, {
marshallOptions: {
// Whether to automatically convert empty strings, blobs, and sets to `null`
convertEmptyValues: false,
// Whether to remove undefined values while marshalling
removeUndefinedValues: true,
// Whether to convert typeof object to map attribute
convertClassInstanceToMap: false,
},
unmarshallOptions: {
// Whether to return numbers as a string instead of converting them to native JavaScript numbers
wrapNumbers: false,
},
});
}
}
}