@aws-amplify/graphql-model-transformer
Version:
Amplify graphql @model transformer
954 lines • 46.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTtlModified = exports.toCreateTableInput = exports.extractOldTableInputFromEvent = exports.extractTableInputFromEvent = exports.getPointInTimeRecoveryUpdate = exports.getTtlUpdate = exports.getDeletionProtectionUpdate = exports.requiresTagsUpdate = exports.getSseUpdate = exports.getStreamUpdate = exports.getNextAtomicUpdate = exports.processIsComplete = exports.getLambdaTags = exports.isComplete = exports.onEvent = void 0;
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
const client_lambda_1 = require("@aws-sdk/client-lambda");
const cfnResponse = __importStar(require("./cfn-response"));
const outbound_1 = require("./outbound");
const util_1 = require("./util");
const import_table_1 = require("./import-table");
const ddbClient = new client_dynamodb_1.DynamoDB();
const lambdaClient = new client_lambda_1.Lambda();
const finished = {
IsComplete: true,
};
const notFinished = {
IsComplete: false,
};
exports.onEvent = cfnResponse.safeHandler(onEventHandler);
exports.isComplete = cfnResponse.safeHandler(isCompleteHandler);
const getLambdaTags = async (functionArn) => {
var _a;
const command = new client_lambda_1.ListTagsCommand({ Resource: functionArn });
const tags = (_a = (await lambdaClient.send(command)).Tags) !== null && _a !== void 0 ? _a : {};
const result = [];
Object.keys(tags).forEach((key) => {
if (!key.startsWith('aws:cloudformation:')) {
result.push({
key: key,
value: tags[key],
});
}
});
return result;
};
exports.getLambdaTags = getLambdaTags;
const getTableTags = async (tableArn) => {
var _a;
const command = new client_dynamodb_1.ListTagsOfResourceCommand({ ResourceArn: tableArn });
const tags = (_a = (await ddbClient.send(command)).Tags) !== null && _a !== void 0 ? _a : [];
return tags;
};
async function onEventHandler(cfnRequest, context) {
const sanitizedRequest = { ...cfnRequest, ResponseURL: '[redacted]' };
(0, util_1.log)('onEventHandler', sanitizedRequest);
cfnRequest.ResourceProperties = cfnRequest.ResourceProperties || {};
const onEventResult = await processOnEvent(sanitizedRequest, context);
(0, util_1.log)('onEvent returned:', onEventResult);
const resourceEvent = createResponseEvent(cfnRequest, onEventResult);
if (cfnRequest.RequestType == 'Delete') {
(0, util_1.log)('Submitting response to CloudFormation');
await cfnResponse.submitResponse('SUCCESS', resourceEvent, { noEcho: resourceEvent.NoEcho });
}
else {
const waiter = {
stateMachineArn: (0, util_1.getEnv)('WAITER_STATE_MACHINE_ARN'),
name: resourceEvent.RequestId,
input: JSON.stringify(resourceEvent),
};
(0, util_1.log)('starting waiter', {
stateMachineArn: waiter.stateMachineArn,
name: resourceEvent.RequestId,
});
await (0, outbound_1.startExecution)(waiter);
}
}
async function isCompleteHandler(event, context) {
const sanitizedRequest = { ...event, ResponseURL: '[redacted]' };
(0, util_1.log)('isComplete', sanitizedRequest);
const isCompleteResult = await (0, exports.processIsComplete)(sanitizedRequest, context);
(0, util_1.log)('isComplete result', isCompleteResult);
if (!isCompleteResult.IsComplete) {
if (isCompleteResult.Data && Object.keys(isCompleteResult.Data).length > 0) {
throw new Error('"Data" is not allowed if "IsComplete" is "False"');
}
throw new cfnResponse.Retry(JSON.stringify(event));
}
const response = {
...event,
...isCompleteResult,
Data: {
...event.Data,
...isCompleteResult.Data,
},
};
await cfnResponse.submitResponse('SUCCESS', response, { noEcho: event.NoEcho });
}
const processOnEvent = async (event, context) => {
var _a, _b;
console.log({ ...event, ResponseURL: '[redacted]' });
const tableDef = await (0, exports.extractTableInputFromEvent)(event, context);
console.log('Input table state: ', tableDef);
let result;
switch (event.RequestType) {
case 'Create':
const createTableInput = (0, exports.toCreateTableInput)(tableDef);
if (tableDef.isImported) {
return (0, import_table_1.importTable)(createTableInput);
}
console.log('Initiating CREATE event');
console.log('Create Table Params: ', createTableInput);
const response = await createNewTable(createTableInput);
result = {
PhysicalResourceId: response.tableName,
Data: {
TableArn: response.tableArn,
TableStreamArn: response.streamArn,
TableName: response.tableName,
},
};
console.log('Returning result: ', result);
return result;
case 'Update':
if (!event.PhysicalResourceId) {
throw new Error(`Could not find the physical ID for the updated resource`);
}
const oldTableDef = (0, exports.extractOldTableInputFromEvent)(event);
console.log('Fetching current table state');
const describeTableResult = await ddbClient.describeTable({ TableName: event.PhysicalResourceId });
if (!describeTableResult.Table) {
throw new Error(`Could not find ${event.PhysicalResourceId} to update`);
}
(0, util_1.log)('Current table state: ', describeTableResult);
if (isKeySchemaModified(describeTableResult.Table.KeySchema, tableDef.keySchema)) {
console.log('Detected table key schema change. Update requires replacement');
if (tableDef.allowDestructiveGraphqlSchemaUpdates) {
return replaceTable(describeTableResult.Table, tableDef);
}
else {
throw new Error("Editing key schema of table requires replacement which will cause ALL EXISITING DATA TO BE LOST. If this is intended, set the 'allowDestructiveGraphqlSchemaUpdates' to true and deploy again.");
}
}
if (tableDef.replaceTableUponGsiUpdate && tableDef.allowDestructiveGraphqlSchemaUpdates) {
const nextUpdate = getNextGSIUpdate(describeTableResult.Table, tableDef);
if (nextUpdate !== undefined) {
console.log('Detected global secondary index changes in sandbox mode with destructive updates allowed. Update requires replacement');
return replaceTable(describeTableResult.Table, tableDef);
}
}
const currentTableTags = await getTableTags(describeTableResult.Table.TableArn);
const newTags = [];
Object.values((_a = tableDef.tags) !== null && _a !== void 0 ? _a : []).forEach((tag) => {
newTags.push({ Key: tag.key, Value: tag.value });
});
const removeTags = [];
currentTableTags.forEach((tag) => {
if (!newTags.some((newTag) => newTag.Key === tag.Key)) {
removeTags.push(tag.Key);
}
});
if (removeTags.length > 0) {
await ddbClient.untagResource({
ResourceArn: describeTableResult.Table.TableArn,
TagKeys: removeTags,
});
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the deletion of Tags.`);
}
if ((0, exports.requiresTagsUpdate)(currentTableTags, newTags)) {
console.log('Detected tag changes: ', tableDef.tags);
await ddbClient.tagResource({
ResourceArn: describeTableResult.Table.TableArn,
Tags: newTags,
});
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the update of Tags.`);
}
const describePointInTimeRecoveryResult = await ddbClient.describeContinuousBackups({ TableName: event.PhysicalResourceId });
console.log('Current point in time recovery: ', describePointInTimeRecoveryResult);
const pointInTimeUpdate = (0, exports.getPointInTimeRecoveryUpdate)(describePointInTimeRecoveryResult.ContinuousBackupsDescription, tableDef);
if (pointInTimeUpdate) {
(0, util_1.log)('Computed point in time recovery update', pointInTimeUpdate);
await ddbClient.updateContinuousBackups(pointInTimeUpdate);
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the update of PointInTimeRecovery.`);
}
const deletionProtectionUpdate = (0, exports.getDeletionProtectionUpdate)(describeTableResult.Table, tableDef);
if (deletionProtectionUpdate) {
(0, util_1.log)('Computed deletion protection update', deletionProtectionUpdate);
await ddbClient.updateTable(deletionProtectionUpdate);
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the update of deletion protection.`);
}
const sseUpdate = (0, exports.getSseUpdate)(describeTableResult.Table, tableDef);
if (sseUpdate) {
(0, util_1.log)('Computed server side encryption update', sseUpdate);
await ddbClient.updateTable(sseUpdate);
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the update of sever side encryption.`);
}
const streamUpdate = await (0, exports.getStreamUpdate)(describeTableResult.Table, tableDef);
if (streamUpdate) {
(0, util_1.log)('Computed stream specification update', streamUpdate);
await ddbClient.updateTable(streamUpdate);
await retry(async () => await isTableReady(event.PhysicalResourceId), (res) => res === true);
console.log(`Table '${event.PhysicalResourceId}' is ready after the update of stream specificaion.`);
}
if ((0, exports.isTtlModified)(oldTableDef.timeToLiveSpecification, tableDef.timeToLiveSpecification)) {
const describeTimeToLiveResult = await getTtlStatus(event.PhysicalResourceId);
console.log('Current TTL: ', describeTimeToLiveResult);
const ttlUpdate = (0, exports.getTtlUpdate)(describeTimeToLiveResult.TimeToLiveDescription, tableDef);
if (ttlUpdate) {
(0, util_1.log)('Computed time to live update', ttlUpdate);
console.log('Initiating TTL update');
await ddbClient.updateTimeToLive(ttlUpdate);
result = {
PhysicalResourceId: event.PhysicalResourceId,
Data: {
TableArn: describeTableResult.Table.TableArn,
TableStreamArn: describeTableResult.Table.LatestStreamArn,
TableName: describeTableResult.Table.TableName,
},
};
return result;
}
}
const nextGsiUpdate = (0, exports.getNextAtomicUpdate)(describeTableResult.Table, tableDef);
if (nextGsiUpdate) {
(0, util_1.log)('Computed next update', nextGsiUpdate);
console.log('Initiating table GSI update');
await ddbClient.updateTable(nextGsiUpdate);
}
result = {
PhysicalResourceId: event.PhysicalResourceId,
Data: {
TableArn: describeTableResult.Table.TableArn,
TableStreamArn: describeTableResult.Table.LatestStreamArn,
TableName: describeTableResult.Table.TableName,
},
};
return result;
case 'Delete':
if (!event.PhysicalResourceId) {
throw new Error(`Could not find the physical ID for the resource`);
}
result = {
PhysicalResourceId: event.PhysicalResourceId,
};
try {
console.log('Fetching current table state');
const describeTableResultBeforeDeletion = await ddbClient.describeTable({ TableName: event.PhysicalResourceId });
if ((_b = describeTableResultBeforeDeletion.Table) === null || _b === void 0 ? void 0 : _b.DeletionProtectionEnabled) {
return result;
}
console.log('Initiating table deletion');
await ddbClient.deleteTable({ TableName: event.PhysicalResourceId });
return result;
}
catch (err) {
if (err instanceof client_dynamodb_1.ResourceNotFoundException) {
console.log('Table to be deleted is not found. Deletion complete.');
return result;
}
throw err;
}
default:
throw new Error(`Event type ${event.RequestType} is not supported`);
}
};
const processIsComplete = async (event, context) => {
var _a, _b, _c;
(0, util_1.log)('got event', { ...event, ResponseURL: '[redacted]' });
if (event.RequestType === 'Delete') {
console.log('Delete is finished');
return finished;
}
if (!event.PhysicalResourceId) {
throw new Error('PhysicalResourceId not set in call to isComplete');
}
console.log('Fetching current table state');
const describeTableResult = await retry(async () => await ddbClient.describeTable({ TableName: event.PhysicalResourceId }), (result) => !!(result === null || result === void 0 ? void 0 : result.Table));
if (((_a = describeTableResult.Table) === null || _a === void 0 ? void 0 : _a.TableStatus) !== 'ACTIVE') {
console.log('Table not active yet');
return notFinished;
}
if ((_b = describeTableResult.Table.GlobalSecondaryIndexes) === null || _b === void 0 ? void 0 : _b.some((gsi) => gsi.IndexStatus !== 'ACTIVE' || gsi.Backfilling)) {
console.log('Some GSI is not active yet');
return notFinished;
}
const endState = await (0, exports.extractTableInputFromEvent)(event, context);
if (event.RequestType === 'Create' || ((_c = event.Data) === null || _c === void 0 ? void 0 : _c.IsTableReplaced) === true) {
const describePointInTimeRecoveryResult = await ddbClient.describeContinuousBackups({ TableName: event.PhysicalResourceId });
const pointInTimeUpdate = (0, exports.getPointInTimeRecoveryUpdate)(describePointInTimeRecoveryResult.ContinuousBackupsDescription, endState);
if (pointInTimeUpdate) {
console.log('Updating table with point in time recovery enabled');
try {
await ddbClient.updateContinuousBackups(pointInTimeUpdate);
}
catch (error) {
if (error instanceof client_dynamodb_1.ContinuousBackupsUnavailableException) {
console.log('Backups are being enabled for the table');
return notFinished;
}
else {
throw error;
}
}
return notFinished;
}
if (endState.timeToLiveSpecification && endState.timeToLiveSpecification.enabled) {
const describeTimeToLiveResult = await getTtlStatus(event.PhysicalResourceId);
const ttlUpdate = (0, exports.getTtlUpdate)(describeTimeToLiveResult.TimeToLiveDescription, endState);
if (ttlUpdate) {
console.log('Updating table with TTL enabled');
await ddbClient.updateTimeToLive(ttlUpdate);
return notFinished;
}
}
console.log('Create is finished');
return finished;
}
const nextUpdate = (0, exports.getNextAtomicUpdate)(describeTableResult.Table, endState);
(0, util_1.log)('Computed next update', nextUpdate);
if (!nextUpdate) {
console.log('No additional updates needed. Update finished');
return finished;
}
console.log('Initiating table update');
await ddbClient.updateTable(nextUpdate);
return notFinished;
};
exports.processIsComplete = processIsComplete;
const replaceTable = async (currentState, endState) => {
if (currentState.DeletionProtectionEnabled === true) {
throw new Error('Table cannot be replaced when the deletion protection is enabled.');
}
console.log('Deleting the old table');
await ddbClient.deleteTable({ TableName: currentState.TableName });
await retry(async () => await doesTableExist(currentState.TableName), (res) => res === false);
console.log(`Table '${currentState.TableName}' does not exist. Deletion is finished.`);
const createTableInput = (0, exports.toCreateTableInput)(endState);
console.log('Creating the new table');
const response = await createNewTable(createTableInput);
const result = {
PhysicalResourceId: response.tableName,
Data: {
TableArn: response.tableArn,
TableStreamArn: response.streamArn,
TableName: response.tableName,
IsTableReplaced: true,
},
};
(0, util_1.log)('Returning result', result);
return result;
};
const createResponseEvent = (cfnRequest, onEventResult) => {
onEventResult = onEventResult || {};
const physicalResourceId = onEventResult.PhysicalResourceId || defaultPhysicalResourceId(cfnRequest);
if (cfnRequest.RequestType === 'Delete' && physicalResourceId != cfnRequest.PhysicalResourceId) {
throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId} to "${onEventResult.PhysicalResourceId}" during deletion"`);
}
if (cfnRequest.RequestType === 'Update' && physicalResourceId !== cfnRequest.PhysicalResourceId) {
(0, util_1.log)(`UPDATE: changing physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${onEventResult.PhysicalResourceId}"`);
}
return {
...cfnRequest,
...onEventResult,
PhysicalResourceId: physicalResourceId,
};
};
const defaultPhysicalResourceId = (req) => {
switch (req.RequestType) {
case 'Create':
return req.RequestId;
case 'Update':
case 'Delete':
return req.PhysicalResourceId;
default:
throw new Error(`Invalid "RequestType" in request "${JSON.stringify(req)}"`);
}
};
const getNextAtomicUpdate = (currentState, endState) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
const currentStateGSIs = currentState.GlobalSecondaryIndexes || [];
const isTableBillingModeModified = (((_a = currentState.BillingModeSummary) === null || _a === void 0 ? void 0 : _a.BillingMode) !== undefined && ((_b = currentState.BillingModeSummary) === null || _b === void 0 ? void 0 : _b.BillingMode) !== endState.billingMode) ||
(((_c = currentState.BillingModeSummary) === null || _c === void 0 ? void 0 : _c.BillingMode) == undefined && endState.billingMode === 'PAY_PER_REQUEST');
const isTableProvisionThroughputModified = (((_d = endState.provisionedThroughput) === null || _d === void 0 ? void 0 : _d.readCapacityUnits) !== undefined &&
((_e = currentState.ProvisionedThroughput) === null || _e === void 0 ? void 0 : _e.ReadCapacityUnits) !== ((_f = endState.provisionedThroughput) === null || _f === void 0 ? void 0 : _f.readCapacityUnits)) ||
(((_g = endState.provisionedThroughput) === null || _g === void 0 ? void 0 : _g.writeCapacityUnits) !== undefined &&
((_h = currentState.ProvisionedThroughput) === null || _h === void 0 ? void 0 : _h.WriteCapacityUnits) !== ((_j = endState.provisionedThroughput) === null || _j === void 0 ? void 0 : _j.writeCapacityUnits));
if (isTableBillingModeModified || isTableProvisionThroughputModified) {
let updateInput = {
TableName: currentState.TableName,
BillingMode: isTableBillingModeModified ? endState.billingMode : undefined,
ProvisionedThroughput: isTableProvisionThroughputModified && endState.billingMode === 'PROVISIONED'
? {
ReadCapacityUnits: (_k = endState.provisionedThroughput) === null || _k === void 0 ? void 0 : _k.readCapacityUnits,
WriteCapacityUnits: (_l = endState.provisionedThroughput) === null || _l === void 0 ? void 0 : _l.writeCapacityUnits,
}
: undefined,
};
if (isTableBillingModeModified && endState.billingMode === 'PROVISIONED') {
const indexToBeUpdated = currentStateGSIs.map((gsiToUpdate) => {
var _a, _b;
return {
Update: {
IndexName: gsiToUpdate.IndexName,
ProvisionedThroughput: {
ReadCapacityUnits: (_a = endState.provisionedThroughput) === null || _a === void 0 ? void 0 : _a.readCapacityUnits,
WriteCapacityUnits: (_b = endState.provisionedThroughput) === null || _b === void 0 ? void 0 : _b.writeCapacityUnits,
},
},
};
});
updateInput = {
...updateInput,
GlobalSecondaryIndexUpdates: indexToBeUpdated.length > 0 ? indexToBeUpdated : undefined,
};
}
return parsePropertiesToDynamoDBInput(updateInput);
}
return getNextGSIUpdate(currentState, endState);
};
exports.getNextAtomicUpdate = getNextAtomicUpdate;
const getNextGSIUpdate = (currentState, endState) => {
var _a, _b, _c, _d, _e;
const endStateGSIs = endState.globalSecondaryIndexes || [];
const endStateGSINames = endStateGSIs.map((gsi) => gsi.indexName);
const modifiedAttributes = getTypeModifiedAttributes(currentState.AttributeDefinitions, endState.attributeDefinitions);
const indexesWithModifiedAttributeType = getIndexesContainingAttributes(currentState.GlobalSecondaryIndexes, modifiedAttributes);
const currentStateGSIs = currentState.GlobalSecondaryIndexes || [];
const currentStateGSINames = currentStateGSIs.map((gsi) => gsi.IndexName);
const gsiRequiresReplacementPredicate = (currentGSI) => {
if (!endStateGSINames.includes(currentGSI.IndexName))
return true;
if (indexesWithModifiedAttributeType.includes(currentGSI.IndexName))
return true;
const respectiveEndStateGSI = endStateGSIs.find((endStateGSI) => endStateGSI.indexName === currentGSI.IndexName);
if (isProjectionModified(currentGSI.Projection, respectiveEndStateGSI.projection))
return true;
if (isKeySchemaModified(currentGSI.KeySchema, respectiveEndStateGSI.keySchema))
return true;
return false;
};
const gsiToRemove = currentStateGSIs.find(gsiRequiresReplacementPredicate);
if (gsiToRemove) {
return {
TableName: currentState.TableName,
GlobalSecondaryIndexUpdates: [
{
Delete: {
IndexName: gsiToRemove.IndexName,
},
},
],
};
}
const gsiRequiresCreationPredicate = (endStateGSI) => !currentStateGSINames.includes(endStateGSI.indexName);
const gsiToAdd = endStateGSIs.find(gsiRequiresCreationPredicate);
if (gsiToAdd) {
let gsiProvisionThroughput = gsiToAdd.provisionedThroughput;
if (endState.billingMode === 'PROVISIONED' && gsiToAdd.provisionedThroughput === undefined) {
gsiProvisionThroughput = {
readCapacityUnits: (_a = endState.provisionedThroughput) === null || _a === void 0 ? void 0 : _a.readCapacityUnits,
writeCapacityUnits: (_b = endState.provisionedThroughput) === null || _b === void 0 ? void 0 : _b.writeCapacityUnits,
};
}
const attributeNamesToInclude = gsiToAdd.keySchema.map((schema) => schema.attributeName);
const gsiToAddAction = {
IndexName: gsiToAdd.indexName,
KeySchema: gsiToAdd.keySchema,
Projection: gsiToAdd.projection,
ProvisionedThroughput: gsiProvisionThroughput,
};
return {
TableName: currentState.TableName,
AttributeDefinitions: (_c = endState.attributeDefinitions) === null || _c === void 0 ? void 0 : _c.filter((def) => attributeNamesToInclude.includes(def.attributeName)).map((def) => usePascalCaseForObjectKeys(def)),
GlobalSecondaryIndexUpdates: [
{
Create: parsePropertiesToDynamoDBInput(gsiToAddAction),
},
],
};
}
const gsiRequiresUpdatePredicate = (endStateGSI) => {
var _a, _b, _c, _d;
if (endState.provisionedThroughput &&
endState.provisionedThroughput.readCapacityUnits &&
endState.provisionedThroughput.writeCapacityUnits &&
currentStateGSINames.includes(endStateGSI.indexName)) {
const currentStateGSI = currentStateGSIs.find((gsi) => gsi.IndexName === endStateGSI.indexName);
if (currentStateGSI) {
if (((_a = currentStateGSI.ProvisionedThroughput) === null || _a === void 0 ? void 0 : _a.ReadCapacityUnits) !== ((_b = endStateGSI.provisionedThroughput) === null || _b === void 0 ? void 0 : _b.readCapacityUnits) ||
((_c = currentStateGSI.ProvisionedThroughput) === null || _c === void 0 ? void 0 : _c.WriteCapacityUnits) !== ((_d = endStateGSI.provisionedThroughput) === null || _d === void 0 ? void 0 : _d.writeCapacityUnits)) {
return true;
}
}
}
return false;
};
const gsiToUpdate = endStateGSIs.find(gsiRequiresUpdatePredicate);
if (gsiToUpdate) {
return {
TableName: currentState.TableName,
GlobalSecondaryIndexUpdates: [
{
Update: {
IndexName: gsiToUpdate.indexName,
ProvisionedThroughput: {
ReadCapacityUnits: (_d = gsiToUpdate.provisionedThroughput) === null || _d === void 0 ? void 0 : _d.readCapacityUnits,
WriteCapacityUnits: (_e = gsiToUpdate.provisionedThroughput) === null || _e === void 0 ? void 0 : _e.writeCapacityUnits,
},
},
},
],
};
}
return undefined;
};
const getStreamUpdate = async (currentState, endState) => {
var _a, _b, _c, _d, _e, _f;
let streamUpdate;
if (((_a = endState.streamSpecification) === null || _a === void 0 ? void 0 : _a.streamViewType) !== undefined &&
(currentState.StreamSpecification === undefined || currentState.StreamSpecification.StreamEnabled === false)) {
streamUpdate = { StreamEnabled: true, StreamViewType: endState.streamSpecification.streamViewType };
}
else if (((_b = endState.streamSpecification) === null || _b === void 0 ? void 0 : _b.streamViewType) === undefined && ((_c = currentState.StreamSpecification) === null || _c === void 0 ? void 0 : _c.StreamEnabled) === true) {
streamUpdate = { StreamEnabled: false };
}
else if (((_d = currentState.StreamSpecification) === null || _d === void 0 ? void 0 : _d.StreamEnabled) === true &&
((_e = endState.streamSpecification) === null || _e === void 0 ? void 0 : _e.streamViewType) !== undefined &&
currentState.StreamSpecification.StreamViewType !== ((_f = endState.streamSpecification) === null || _f === void 0 ? void 0 : _f.streamViewType)) {
console.log('Detect stream view type is changed. Disabling stream before the type change.');
await ddbClient.updateTable({
TableName: currentState.TableName,
StreamSpecification: { StreamEnabled: false },
});
await retry(async () => await isTableReady(currentState.TableName), (res) => res === true);
streamUpdate = { StreamEnabled: true, StreamViewType: endState.streamSpecification.streamViewType };
}
if (streamUpdate) {
return {
TableName: currentState.TableName,
StreamSpecification: streamUpdate,
};
}
return undefined;
};
exports.getStreamUpdate = getStreamUpdate;
const getSseUpdate = (currentState, endState) => {
var _a, _b, _c;
let sseUpdate;
if (currentState.SSEDescription) {
if (!((_a = endState.sseSpecification) === null || _a === void 0 ? void 0 : _a.sseEnabled)) {
sseUpdate = {
Enabled: false,
};
}
else if (((_b = endState.sseSpecification) === null || _b === void 0 ? void 0 : _b.sseEnabled) === true &&
endState.sseSpecification.sseType !== undefined &&
endState.sseSpecification.sseType !== currentState.SSEDescription.SSEType) {
sseUpdate = {
Enabled: true,
SSEType: endState.sseSpecification.sseType,
KMSMasterKeyId: endState.sseSpecification.kmsMasterKeyId,
};
}
}
else {
if ((_c = endState.sseSpecification) === null || _c === void 0 ? void 0 : _c.sseEnabled) {
sseUpdate = {
Enabled: true,
SSEType: endState.sseSpecification.sseType,
KMSMasterKeyId: endState.sseSpecification.kmsMasterKeyId,
};
}
}
if (sseUpdate) {
return parsePropertiesToDynamoDBInput({
TableName: currentState.TableName,
SSESpecification: sseUpdate,
});
}
return undefined;
};
exports.getSseUpdate = getSseUpdate;
const requiresTagsUpdate = (currentTags, newTags) => {
if (!newTags || newTags.length === 0) {
return false;
}
if (currentTags.length !== newTags.length) {
return true;
}
for (const newTag of newTags) {
if (!currentTags.find((currentTag) => currentTag.Key === newTag.Key)) {
return true;
}
else {
const currentTag = currentTags.find((tag) => tag.Key === newTag.Key);
if ((currentTag === null || currentTag === void 0 ? void 0 : currentTag.Value) !== newTag.Value) {
return true;
}
}
}
return false;
};
exports.requiresTagsUpdate = requiresTagsUpdate;
const getDeletionProtectionUpdate = (currentState, endState) => {
if (endState.deletionProtectionEnabled !== undefined && currentState.DeletionProtectionEnabled !== endState.deletionProtectionEnabled) {
return {
TableName: currentState.TableName,
DeletionProtectionEnabled: endState.deletionProtectionEnabled,
};
}
else if (endState.deletionProtectionEnabled === undefined && currentState.DeletionProtectionEnabled === true) {
return {
TableName: currentState.TableName,
DeletionProtectionEnabled: false,
};
}
return undefined;
};
exports.getDeletionProtectionUpdate = getDeletionProtectionUpdate;
const getTtlUpdate = (currentTTL, endState) => {
const endTTL = endState.timeToLiveSpecification;
if (currentTTL && currentTTL.TimeToLiveStatus) {
if (currentTTL.TimeToLiveStatus === 'ENABLED' && currentTTL.AttributeName) {
if (!endTTL || !endTTL.enabled) {
return {
TableName: endState.tableName,
TimeToLiveSpecification: {
Enabled: false,
AttributeName: currentTTL.AttributeName,
},
};
}
else if (currentTTL.AttributeName !== endTTL.attributeName) {
return {
TableName: endState.tableName,
TimeToLiveSpecification: {
Enabled: true,
AttributeName: endTTL.attributeName,
},
};
}
}
else if (currentTTL.TimeToLiveStatus === 'DISABLED' && endTTL && endTTL.enabled) {
return {
TableName: endState.tableName,
TimeToLiveSpecification: {
Enabled: true,
AttributeName: endTTL.attributeName,
},
};
}
}
return undefined;
};
exports.getTtlUpdate = getTtlUpdate;
const getPointInTimeRecoveryUpdate = (currentPointInTime, endState) => {
var _a, _b;
if (!currentPointInTime) {
return undefined;
}
const currentStatus = (_a = currentPointInTime.PointInTimeRecoveryDescription) === null || _a === void 0 ? void 0 : _a.PointInTimeRecoveryStatus;
const endStatus = (_b = endState.pointInTimeRecoverySpecification) === null || _b === void 0 ? void 0 : _b.pointInTimeRecoveryEnabled;
if (endStatus === undefined || endStatus === false) {
if (currentStatus === 'ENABLED') {
return {
TableName: endState.tableName,
PointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: false,
},
};
}
}
else {
if (currentStatus === 'DISABLED') {
return {
TableName: endState.tableName,
PointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: true,
},
};
}
}
return undefined;
};
exports.getPointInTimeRecoveryUpdate = getPointInTimeRecoveryUpdate;
const extractTableInputFromEvent = async (event, context) => {
const tags = await (0, exports.getLambdaTags)(context.invokedFunctionArn);
const resourceProperties = {
...event.ResourceProperties,
...(tags.length > 0 && { tags }),
};
delete resourceProperties.ServiceToken;
const tableDef = convertStringToBooleanOrNumber(resourceProperties);
return tableDef;
};
exports.extractTableInputFromEvent = extractTableInputFromEvent;
const extractOldTableInputFromEvent = (event) => {
const resourceProperties = { ...event.OldResourceProperties };
delete resourceProperties.ServiceToken;
const tableDef = convertStringToBooleanOrNumber(resourceProperties);
return tableDef;
};
exports.extractOldTableInputFromEvent = extractOldTableInputFromEvent;
const parsePropertiesToDynamoDBInput = (obj) => {
return usePascalCaseForObjectKeys(removeUndefinedAttributes(obj));
};
const usePascalCaseForObjectKeys = (obj) => {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key) && key !== '') {
const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
const value = obj[key];
if (Array.isArray(value)) {
result[capitalizedKey] = value.map((v) => usePascalCaseForObjectKeys(v));
}
else if (typeof value === 'object' && value !== null) {
result[capitalizedKey] = usePascalCaseForObjectKeys(value);
}
else {
result[capitalizedKey] = value;
}
}
}
return result;
};
const convertStringToBooleanOrNumber = (obj) => {
const fieldsToBeConvertedToBoolean = [
'deletionProtectionEnabled',
'enabled',
'sseEnabled',
'pointInTimeRecoveryEnabled',
'allowDestructiveGraphqlSchemaUpdates',
'replaceTableUponGsiUpdate',
'isImported',
];
const fieldsToBeConvertedToNumber = ['readCapacityUnits', 'writeCapacityUnits'];
for (const key in obj) {
if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((o) => convertStringToBooleanOrNumber(o));
}
else if (typeof obj[key] === 'object') {
obj[key] = convertStringToBooleanOrNumber(obj[key]);
}
else if (typeof obj[key] === 'string') {
if ((obj[key] === 'true' || obj[key] === 'false') && fieldsToBeConvertedToBoolean.includes(key)) {
obj[key] = obj[key] === 'true';
}
else if (!isNaN(Number(obj[key])) && fieldsToBeConvertedToNumber.includes(key)) {
obj[key] = Number(obj[key]);
}
}
}
return obj;
};
const removeUndefinedAttributes = (obj) => {
for (const key in obj) {
if (Array.isArray(obj[key])) {
obj[key].map((o) => removeUndefinedAttributes(o));
}
else if (typeof obj[key] === 'object') {
removeUndefinedAttributes(obj[key]);
}
else if (obj[key] === undefined) {
delete obj[key];
}
}
return obj;
};
const toCreateTableInput = (props) => {
const createTableInput = {
TableName: props.tableName,
AttributeDefinitions: props.attributeDefinitions,
KeySchema: props.keySchema,
GlobalSecondaryIndexes: props.globalSecondaryIndexes,
BillingMode: props.billingMode,
StreamSpecification: props.streamSpecification
? {
StreamEnabled: true,
StreamViewType: props.streamSpecification.streamViewType,
}
: undefined,
ProvisionedThroughput: props.provisionedThroughput,
SSESpecification: props.sseSpecification ? { Enabled: props.sseSpecification.sseEnabled } : undefined,
DeletionProtectionEnabled: props.deletionProtectionEnabled,
Tags: props.tags,
};
return parsePropertiesToDynamoDBInput(createTableInput);
};
exports.toCreateTableInput = toCreateTableInput;
const createNewTable = async (input) => {
var _a;
const tableName = input.TableName;
const createTableInput = input;
const result = await ddbClient.createTable(createTableInput);
return { tableName: tableName, tableArn: result.TableDescription.TableArn, streamArn: (_a = result.TableDescription) === null || _a === void 0 ? void 0 : _a.LatestStreamArn };
};
const doesTableExist = async (tableName) => {
try {
await ddbClient.describeTable({ TableName: tableName });
return true;
}
catch (error) {
if (error instanceof client_dynamodb_1.ResourceNotFoundException) {
return false;
}
throw error;
}
};
const isTableReady = async (tableName) => {
var _a, _b;
const result = await ddbClient.describeTable({ TableName: tableName });
if (((_a = result.Table) === null || _a === void 0 ? void 0 : _a.TableStatus) !== 'ACTIVE') {
console.log('Table not active yet');
return false;
}
if ((_b = result.Table.GlobalSecondaryIndexes) === null || _b === void 0 ? void 0 : _b.some((gsi) => gsi.IndexStatus !== 'ACTIVE' || gsi.Backfilling)) {
console.log('Some GSI is not active yet');
return false;
}
return true;
};
const isProjectionModified = (currentProjection, endProjection) => {
if (currentProjection.ProjectionType !== endProjection.projectionType)
return true;
if (currentProjection.ProjectionType === 'ALL')
return false;
const currentNonKeyAttributes = currentProjection.NonKeyAttributes || [];
const endNonKeyAttributes = endProjection.nonKeyAttributes || [];
if (currentNonKeyAttributes.length !== endNonKeyAttributes.length)
return true;
if (currentNonKeyAttributes.some((currentNonKeyAttribute) => !endNonKeyAttributes.includes(currentNonKeyAttribute)))
return true;
return false;
};
const isKeySchemaModified = (currentSchema, endSchema) => {
const currentHashKey = currentSchema.find((schema) => schema.KeyType === 'HASH');
const endHashKey = endSchema.find((schema) => schema.keyType === 'HASH');
if ((currentHashKey === null || currentHashKey === void 0 ? void 0 : currentHashKey.AttributeName) !== (endHashKey === null || endHashKey === void 0 ? void 0 : endHashKey.attributeName))
return true;
const currentSortKey = currentSchema.find((schema) => schema.KeyType === 'RANGE');
const endSortKey = endSchema.find((schema) => schema.keyType === 'RANGE');
if (currentSortKey === undefined && endSortKey === undefined)
return false;
if ((currentSortKey === undefined && endSortKey !== undefined) || (currentSortKey !== undefined && endSortKey === undefined))
return true;
if ((currentSortKey === null || currentSortKey === void 0 ? void 0 : currentSortKey.AttributeName) !== (endSortKey === null || endSortKey === void 0 ? void 0 : endSortKey.attributeName))
return true;
return false;
};
const getTypeModifiedAttributes = (currentSchema, endSchema) => {
const result = [];
if (!currentSchema || !endSchema)
return result;
for (const attribute of currentSchema) {
const endAttribute = endSchema.find((endAttr) => endAttr.attributeName === attribute.AttributeName);
if (!endAttribute)
continue;
if (attribute.AttributeType !== endAttribute.attributeType) {
result.push(attribute.AttributeName);
}
}
return result;
};
const getIndexesContainingAttributes = (currentSchema, attributes) => {
if (!currentSchema)
return [];
const result = currentSchema
.filter((index) => index.IndexStatus === 'ACTIVE')
.filter((index) => { var _a; return (_a = index.KeySchema) === null || _a === void 0 ? void 0 : _a.some((key) => attributes.includes(key.AttributeName)); })
.map((index) => index.IndexName);
return result !== null && result !== void 0 ? result : [];
};
const isTtlModified = (oldTtl, endTtl) => {
if (oldTtl === undefined && endTtl === undefined) {
return false;
}
if (oldTtl === undefined || endTtl === undefined) {
return true;
}
return oldTtl.enabled !== endTtl.enabled || oldTtl.attributeName !== endTtl.attributeName;
};
exports.isTtlModified = isTtlModified;
const getTtlStatus = async (tableName) => {
const initialDelay = Math.floor(Math.random() * 5 * 1000);
console.log(`Waiting for ${initialDelay} ms`);
await sleep(initialDelay);
const describeTimeToLiveResult = retry(async () => await ddbClient.describeTimeToLive({ TableName: tableName }), () => true, {
times: 5,
delayMS: 1000,
exponentialBackoff: true,
});
return describeTimeToLiveResult;
};
const defaultSettings = {
times: Infinity,
delayMS: 1000 * 15,
timeoutMS: 1000 * 60 * 14,
stopOnError: false,
exponentialBackoff: false,
};
const retry = async (func, successPredicate, settings, failurePredicate) => {
const { times, delayMS, timeoutMS, stopOnError, exponentialBackoff } = {
...defaultSettings,
...settings,
};
let count = 0;
let result;
let terminate = false;
const startTime = Date.now();
do {
try {
result = await func();
if (successPredicate(result)) {
return result;
}
if (typeof failurePredicate === 'function' && failurePredicate(result)) {
throw new Error('Retry-able function execution result matched failure predicate. Stopping retries.');
}
console.warn(`Retry-able function execution did not match success predicate. Result was [${JSON.stringify(result)}]. Retrying...`);
}
catch (err) {
console.warn(`Retry-able function execution failed with [${err.message || err}]`);
if (stopOnError) {
console.log('Stopping retries on error.');
}
else {
console.log('Retrying...');
}
terminate = stopOnError;
}
count++;
const sleepTime = exponentialBackoff ? delayMS * Math.pow(2, count - 1) : delayMS;
await sleep(sleepTime);
} while (!terminate && count <= times && Date.now() - startTime < timeoutMS);
throw new Error('Retry-able function did not match predicate within the given retry constraints');
};
const sleep = async (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
//# sourceMappingURL=amplify-table-manager-handler.js.map