UNPKG

@goldstack/template-dynamodb-cli

Version:

Utilities for building modules for DynamoDB access.

138 lines 6.44 kB
"use strict"; 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