UNPKG

@aws-amplify/graphql-model-transformer

Version:
260 lines • 12.9 kB
"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