@aws-amplify/graphql-model-transformer
Version:
Amplify graphql @model transformer
260 lines • 12.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AmplifyDynamoDBTable = exports.CUSTOM_IMPORTED_DDB_CFN_TYPE = exports.CUSTOM_DDB_CFN_TYPE = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_dynamodb_1 = require("aws-cdk-lib/aws-dynamodb");
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
const HASH_KEY_TYPE = 'HASH';
const RANGE_KEY_TYPE = 'RANGE';
const MAX_LOCAL_SECONDARY_INDEX_COUNT = 5;
exports.CUSTOM_DDB_CFN_TYPE = 'Custom::AmplifyDynamoDBTable';
exports.CUSTOM_IMPORTED_DDB_CFN_TYPE = 'Custom::ImportedAmplifyDynamoDBTable';
class AmplifyDynamoDBTable extends aws_cdk_lib_1.Resource {
constructor(scope, id, props) {
var _a, _b, _c;
super(scope, id, {
physicalName: props.tableName,
});
this.keySchema = new Array();
this.attributeDefinitions = new Array();
this.globalSecondaryIndexes = new Array();
this.localSecondaryIndexes = new Array();
this.secondaryIndexSchemas = new Map();
this.nonKeyAttributes = new Set();
this.customResourceServiceToken = props.customResourceServiceToken;
this.tableName = this.physicalName;
const { sseSpecification, encryptionKey } = this.parseEncryption(props);
let streamSpecification;
this.billingMode = (_a = props.billingMode) !== null && _a !== void 0 ? _a : aws_dynamodb_1.BillingMode.PROVISIONED;
if (props.stream) {
streamSpecification = { streamViewType: props.stream };
}
this.validateProvisioning(props);
this.table = new aws_cdk_lib_1.CustomResource(this, 'Default', {
serviceToken: this.customResourceServiceToken,
resourceType: props.isImported ? exports.CUSTOM_IMPORTED_DDB_CFN_TYPE : exports.CUSTOM_DDB_CFN_TYPE,
properties: {
tableName: this.tableName,
attributeDefinitions: this.attributeDefinitions,
keySchema: this.keySchema,
globalSecondaryIndexes: aws_cdk_lib_1.Lazy.any({ produce: () => this.globalSecondaryIndexes }, { omitEmptyArray: true }),
localSecondaryIndexes: aws_cdk_lib_1.Lazy.any({ produce: () => this.localSecondaryIndexes }, { omitEmptyArray: true }),
pointInTimeRecoverySpecification: props.pointInTimeRecovery != null ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } : undefined,
billingMode: this.billingMode === aws_dynamodb_1.BillingMode.PAY_PER_REQUEST ? this.billingMode : undefined,
provisionedThroughput: this.billingMode === aws_dynamodb_1.BillingMode.PAY_PER_REQUEST
? undefined
: {
readCapacityUnits: props.readCapacity || 5,
writeCapacityUnits: props.writeCapacity || 5,
},
sseSpecification: sseSpecification,
streamSpecification: streamSpecification,
tableClass: props.tableClass,
timeToLiveSpecification: props.timeToLiveAttribute ? { attributeName: props.timeToLiveAttribute, enabled: true } : undefined,
deletionProtectionEnabled: props.deletionProtection,
allowDestructiveGraphqlSchemaUpdates: (_b = props.allowDestructiveGraphqlSchemaUpdates) !== null && _b !== void 0 ? _b : false,
replaceTableUponGsiUpdate: (_c = props.replaceTableUponGsiUpdate) !== null && _c !== void 0 ? _c : false,
isImported: props.isImported,
},
removalPolicy: props.removalPolicy,
});
this.encryptionKey = encryptionKey;
this.tableArn = this.table.getAttString('TableArn');
this.tableStreamArn = streamSpecification ? this.table.getAttString('TableStreamArn') : undefined;
this.tableFromAttr = aws_dynamodb_1.Table.fromTableAttributes(scope, `CustomTable${id}`, {
tableArn: this.tableArn,
tableStreamArn: this.tableStreamArn,
});
this.addKey(props.partitionKey, HASH_KEY_TYPE);
this.tablePartitionKey = props.partitionKey;
if (props.sortKey) {
this.addKey(props.sortKey, RANGE_KEY_TYPE);
this.tableSortKey = props.sortKey;
}
this.node.addValidation({ validate: () => this.validateTable() });
}
addGlobalSecondaryIndex(props) {
this.validateProvisioning(props);
this.validateIndexName(props.indexName);
const gsiKeySchema = this.buildIndexKeySchema(props.partitionKey, props.sortKey);
const gsiProjection = this.buildIndexProjection(props);
this.globalSecondaryIndexes.push({
indexName: props.indexName,
keySchema: gsiKeySchema,
projection: gsiProjection,
provisionedThroughput: this.billingMode === aws_dynamodb_1.BillingMode.PAY_PER_REQUEST
? undefined
: {
readCapacityUnits: props.readCapacity || 5,
writeCapacityUnits: props.writeCapacity || 5,
},
});
this.secondaryIndexSchemas.set(props.indexName, {
partitionKey: props.partitionKey,
sortKey: props.sortKey,
});
}
addLocalSecondaryIndex(props) {
if (this.localSecondaryIndexes.length >= MAX_LOCAL_SECONDARY_INDEX_COUNT) {
throw new RangeError(`a maximum number of local secondary index per table is ${MAX_LOCAL_SECONDARY_INDEX_COUNT}`);
}
this.validateIndexName(props.indexName);
const lsiKeySchema = this.buildIndexKeySchema(this.tablePartitionKey, props.sortKey);
const lsiProjection = this.buildIndexProjection(props);
this.localSecondaryIndexes.push({
indexName: props.indexName,
keySchema: lsiKeySchema,
projection: lsiProjection,
});
this.secondaryIndexSchemas.set(props.indexName, {
partitionKey: this.tablePartitionKey,
sortKey: props.sortKey,
});
}
schema(indexName) {
if (!indexName) {
return {
partitionKey: this.tablePartitionKey,
sortKey: this.tableSortKey,
};
}
let schema = this.secondaryIndexSchemas.get(indexName);
if (!schema) {
throw new Error(`Cannot find schema for index: ${indexName}. Use 'addGlobalSecondaryIndex' or 'addLocalSecondaryIndex' to add index`);
}
return schema;
}
grantStreamRead(grantee) {
if (!this.tableStreamArn) {
throw new Error(`No stream ARNs found on the table ${this.node.path}`);
}
if (this.encryptionKey) {
this.encryptionKey.grant(grantee, 'kms:Decrypt', 'kms:DescribeKey');
}
return aws_iam_1.Grant.addToPrincipal({
grantee,
actions: ['dynamodb:ListStreams', 'dynamodb:DescribeStream', 'dynamodb:GetRecords', 'dynamodb:GetShardIterator'],
resourceArns: [this.tableStreamArn],
});
}
addKey(attribute, keyType) {
const existingProp = this.findKey(keyType);
if (existingProp) {
throw new Error(`Unable to set ${attribute.name} as a ${keyType} key, because ${existingProp.attributeName} is a ${keyType} key`);
}
this.registerAttribute(attribute);
this.keySchema.push({
attributeName: attribute.name,
keyType,
});
return this;
}
findKey(keyType) {
return this.keySchema.find((prop) => prop.keyType === keyType);
}
registerAttribute(attribute) {
const { name, type } = attribute;
const existingDef = this.attributeDefinitions.find((def) => def.attributeName === name);
if (existingDef && existingDef.attributeType !== type) {
throw new Error(`Unable to specify ${name} as ${type} because it was already defined as ${existingDef.attributeType}`);
}
if (!existingDef) {
this.attributeDefinitions.push({
attributeName: name,
attributeType: type,
});
}
}
validateTable() {
const errors = new Array();
if (!this.tablePartitionKey) {
errors.push('a partition key must be specified');
}
if (this.localSecondaryIndexes.length > 0 && !this.tableSortKey) {
errors.push('a sort key of the table must be specified to add local secondary indexes');
}
return errors;
}
parseEncryption(props) {
var _a;
let encryptionType = props.encryption;
if (encryptionType === undefined) {
encryptionType =
props.encryptionKey != null
?
aws_dynamodb_1.TableEncryption.CUSTOMER_MANAGED
:
aws_dynamodb_1.TableEncryption.AWS_MANAGED;
}
if (encryptionType !== aws_dynamodb_1.TableEncryption.CUSTOMER_MANAGED && props.encryptionKey) {
throw new Error('`encryptionKey cannot be specified unless encryption is set to TableEncryption.CUSTOMER_MANAGED (it was set to ${encryptionType})`');
}
if (encryptionType === aws_dynamodb_1.TableEncryption.CUSTOMER_MANAGED && props.replicationRegions) {
throw new Error('TableEncryption.CUSTOMER_MANAGED is not supported by DynamoDB Global Tables (where replicationRegions was set)');
}
switch (encryptionType) {
case aws_dynamodb_1.TableEncryption.CUSTOMER_MANAGED:
const encryptionKey = (_a = props.encryptionKey) !== null && _a !== void 0 ? _a : new aws_cdk_lib_1.aws_kms.Key(this, 'Key', {
description: `Customer-managed key auto-created for encrypting DynamoDB table at ${this.node.path}`,
enableKeyRotation: true,
});
return {
sseSpecification: { sseEnabled: true, kmsMasterKeyId: encryptionKey.keyArn, sseType: 'KMS' },
encryptionKey,
};
case aws_dynamodb_1.TableEncryption.AWS_MANAGED:
return { sseSpecification: { sseEnabled: true } };
case aws_dynamodb_1.TableEncryption.DEFAULT:
return { sseSpecification: { sseEnabled: false } };
case undefined:
return { sseSpecification: undefined };
default:
throw new Error(`Unexpected 'encryptionType': ${encryptionType}`);
}
}
validateProvisioning(props) {
if (this.billingMode === aws_dynamodb_1.BillingMode.PAY_PER_REQUEST) {
if (props.readCapacity !== undefined || props.writeCapacity !== undefined) {
throw new Error('you cannot provision read and write capacity for a table with PAY_PER_REQUEST billing mode');
}
}
}
validateIndexName(indexName) {
if (this.secondaryIndexSchemas.has(indexName)) {
throw new Error(`a duplicate index name, ${indexName}, is not allowed`);
}
}
validateNonKeyAttributes(nonKeyAttributes) {
if (this.nonKeyAttributes.size + nonKeyAttributes.length > 100) {
throw new RangeError('a maximum number of nonKeyAttributes across all of secondary indexes is 100');
}
nonKeyAttributes.forEach((att) => this.nonKeyAttributes.add(att));
}
buildIndexKeySchema(partitionKey, sortKey) {
this.registerAttribute(partitionKey);
const indexKeySchema = [{ attributeName: partitionKey.name, keyType: HASH_KEY_TYPE }];
if (sortKey) {
this.registerAttribute(sortKey);
indexKeySchema.push({ attributeName: sortKey.name, keyType: RANGE_KEY_TYPE });
}
return indexKeySchema;
}
buildIndexProjection(props) {
var _a, _b;
if (props.projectionType === aws_dynamodb_1.ProjectionType.INCLUDE && !props.nonKeyAttributes) {
throw new Error(`non-key attributes should be specified when using ${aws_dynamodb_1.ProjectionType.INCLUDE} projection type`);
}
if (props.projectionType !== aws_dynamodb_1.ProjectionType.INCLUDE && props.nonKeyAttributes) {
throw new Error(`non-key attributes should not be specified when not using ${aws_dynamodb_1.ProjectionType.INCLUDE} projection type`);
}
if (props.nonKeyAttributes) {
this.validateNonKeyAttributes(props.nonKeyAttributes);
}
return {
projectionType: (_a = props.projectionType) !== null && _a !== void 0 ? _a : aws_dynamodb_1.ProjectionType.ALL,
nonKeyAttributes: (_b = props.nonKeyAttributes) !== null && _b !== void 0 ? _b : undefined,
};
}
}
exports.AmplifyDynamoDBTable = AmplifyDynamoDBTable;
//# sourceMappingURL=index.js.map