@goldstack/template-dynamodb-cli
Version:
Utilities for building modules for DynamoDB access.
138 lines • 6.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGsi = createGsi;
exports.deleteGsi = deleteGsi;
exports.gsiExists = gsiExists;
exports.getExistingAttributes = getExistingAttributes;
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
const utils_log_1 = require("@goldstack/utils-log");
/**
* Creates a single GSI and waits for it to become active
*/
async function createGsi(context, indexName, existingAttributes) {
var _a, _b;
(0, utils_log_1.info)(`Starting GSI creation: ${indexName}`);
// Prepare attribute definitions for this GSI
const gsiAttributes = [
{
AttributeName: `${indexName}_pk`,
AttributeType: 'S',
},
{
AttributeName: `${indexName}_sk`,
AttributeType: 'S',
},
];
// Filter to only attributes not already defined
const existingAttributeNames = new Set(existingAttributes.map((attr) => attr.AttributeName).filter(Boolean));
const attributesToAdd = gsiAttributes.filter((attr) => !existingAttributeNames.has(attr.AttributeName));
const newAttributes = [...existingAttributes, ...attributesToAdd];
(0, utils_log_1.info)(`Creating GSI ${indexName} with ${attributesToAdd.length} new attributes`);
await context.client.send(new client_dynamodb_1.UpdateTableCommand({
TableName: context.tableName,
AttributeDefinitions: newAttributes,
GlobalSecondaryIndexUpdates: [
{
Create: {
IndexName: indexName,
KeySchema: [
{
AttributeName: `${indexName}_pk`,
KeyType: client_dynamodb_1.KeyType.HASH,
},
{
AttributeName: `${indexName}_sk`,
KeyType: client_dynamodb_1.KeyType.RANGE,
},
],
Projection: {
ProjectionType: client_dynamodb_1.ProjectionType.ALL,
},
},
},
],
}));
(0, utils_log_1.info)(`GSI ${indexName} creation initiated, waiting for activation...`);
// Wait for GSI to become active (30 minute timeout with 520 retries, progressive delay)
let active = false;
let retriesLeft = 520;
let delayIndex = 0;
while (!active && retriesLeft > 0) {
const describe = await context.client.send(new client_dynamodb_1.DescribeTableCommand({ TableName: context.tableName }));
const gsi = (_b = (_a = describe.Table) === null || _a === void 0 ? void 0 : _a.GlobalSecondaryIndexes) === null || _b === void 0 ? void 0 : _b.find((idx) => idx.IndexName === indexName);
(0, utils_log_1.info)(`GSI ${indexName} status: ${gsi === null || gsi === void 0 ? void 0 : gsi.IndexStatus} (${retriesLeft} retries remaining)`);
active = (gsi === null || gsi === void 0 ? void 0 : gsi.IndexStatus) === 'ACTIVE';
if (!active) {
// Progressive delay: starts at 1s, increases by 10ms each retry
const delayMs = 1000 + delayIndex * 10;
(0, utils_log_1.info)(`Waiting ${delayMs}ms before next check (delay index: ${delayIndex})`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayIndex++;
}
retriesLeft--;
}
if (!active) {
throw new Error(`GSI ${indexName} failed to become active within 30 minutes`);
}
(0, utils_log_1.info)(`GSI ${indexName} is now active`);
}
/**
* Deletes a single GSI and waits for the table to become active
*/
async function deleteGsi(context, indexName) {
var _a, _b;
(0, utils_log_1.info)(`Starting GSI deletion: ${indexName}`);
await context.client.send(new client_dynamodb_1.UpdateTableCommand({
TableName: context.tableName,
GlobalSecondaryIndexUpdates: [
{
Delete: {
IndexName: indexName,
},
},
],
}));
(0, utils_log_1.info)(`GSI ${indexName} deletion initiated, waiting for table to be active...`);
// Wait for table to be active after GSI deletion (30 minute timeout with 520 retries, progressive delay)
let tableActive = false;
let retriesLeft = 520;
let delayIndex = 0;
while (!tableActive && retriesLeft > 0) {
const describe = await context.client.send(new client_dynamodb_1.DescribeTableCommand({ TableName: context.tableName }));
tableActive = ((_a = describe.Table) === null || _a === void 0 ? void 0 : _a.TableStatus) === 'ACTIVE';
(0, utils_log_1.info)(`Table status after deleting ${indexName}: ${(_b = describe.Table) === null || _b === void 0 ? void 0 : _b.TableStatus} (${retriesLeft} retries remaining)`);
if (!tableActive) {
// Progressive delay: starts at 1s, increases by 10ms each retry
const delayMs = 1000 + delayIndex * 10;
(0, utils_log_1.info)(`Waiting ${delayMs}ms before next check (delay index: ${delayIndex})`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayIndex++;
}
retriesLeft--;
}
if (!tableActive) {
throw new Error(`Table failed to become active after deleting GSI ${indexName} within 30 minutes`);
}
(0, utils_log_1.info)(`Table is now active after deleting GSI ${indexName}`);
}
/**
* Checks if a GSI exists on the table
*/
async function gsiExists(context, indexName) {
var _a, _b, _c;
const tableDescription = await context.client.send(new client_dynamodb_1.DescribeTableCommand({
TableName: context.tableName,
}));
return ((_c = (_b = (_a = tableDescription.Table) === null || _a === void 0 ? void 0 : _a.GlobalSecondaryIndexes) === null || _b === void 0 ? void 0 : _b.some((index) => index.IndexName === indexName)) !== null && _c !== void 0 ? _c : false);
}
/**
* Gets existing attribute definitions from the table
*/
async function getExistingAttributes(context) {
var _a;
const tableDescription = await context.client.send(new client_dynamodb_1.DescribeTableCommand({
TableName: context.tableName,
}));
return ((_a = tableDescription.Table) === null || _a === void 0 ? void 0 : _a.AttributeDefinitions) || [];
}
//# sourceMappingURL=gsiMigrationUtils.js.map