dyngoose
Version:
Elegant DynamoDB object modeling for Typescript
181 lines (153 loc) • 6.46 kB
text/typescript
/* eslint-disable @typescript-eslint/no-var-requires */
import type { Construct } from 'constructs'
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
import { readdir } from 'fs/promises'
import { isNil } from 'lodash'
import { join } from 'path'
import { type IThroughputAutoScalingCapacity } from '../interfaces'
import {
DEFAULT_AUTOSCALING_MAX_CAPACITY,
DEFAULT_AUTOSCALING_MIN_CAPACITY,
DEFAULT_AUTOSCALING_TARGET_UTILIZATION,
DEFAULT_READ_CAPACITY,
DEFAULT_WRITE_CAPACITY,
} from '../tables/defaults'
import { Table } from '../table'
import { type Schema } from '../tables/schema'
export interface CreateCDKTablesInput {
scope: Construct
tablesDirectory: string
// specify which files are the right files
tableFileSuffix: string // maybe .table.js or .model.js
// you can optionally override the log function called
log?: (message: string) => any
}
type MutableTableProps = {
-readonly [key in keyof dynamodb.TableProps]: dynamodb.TableProps[key]
}
export function createCDKTable(scope: Construct, schema: Schema, tableProps: Partial<MutableTableProps> = {}): dynamodb.Table {
if (tableProps.tableName == null) {
tableProps.tableName = schema.name
}
if (tableProps.partitionKey == null) {
tableProps.partitionKey = {
name: schema.primaryKey.hash.name,
type: schema.primaryKey.hash.type.type as any,
}
}
if (tableProps.sortKey == null && schema.primaryKey.range != null) {
tableProps.sortKey = {
name: schema.primaryKey.range.name,
type: schema.primaryKey.range.type.type as any,
}
}
if (tableProps.billingMode == null) {
if (schema.options.billingMode === 'PAY_PER_REQUEST') {
tableProps.billingMode = dynamodb.BillingMode.PAY_PER_REQUEST
} else if (schema.options.billingMode === 'PROVISIONED') {
tableProps.billingMode = dynamodb.BillingMode.PROVISIONED
tableProps.readCapacity = schema.throughput?.read ?? DEFAULT_READ_CAPACITY
tableProps.writeCapacity = schema.throughput?.write ?? DEFAULT_WRITE_CAPACITY
}
}
if (tableProps.pointInTimeRecovery == null && schema.options.backup === true) {
tableProps.pointInTimeRecovery = true
}
if (tableProps.encryption == null && schema.options.encrypted === true) {
tableProps.encryption = dynamodb.TableEncryption.AWS_MANAGED
}
if (tableProps.timeToLiveAttribute == null && schema.timeToLiveAttribute != null) {
tableProps.timeToLiveAttribute = schema.timeToLiveAttribute.name
}
if (tableProps.stream == null && schema.options.stream != null) {
if (typeof schema.options.stream === 'boolean') {
tableProps.stream = dynamodb.StreamViewType.NEW_AND_OLD_IMAGES
} else if (schema.options.stream.StreamViewType != null) {
tableProps.stream = schema.options.stream.StreamViewType as any
}
}
const table = new dynamodb.Table(scope, `${schema.name}Table`, tableProps as any)
if (schema.options.billingMode === 'PROVISIONED') {
let autoScaling = schema.throughput?.autoScaling
if (autoScaling === true) {
// default values will be applied automatically below
autoScaling = {}
}
if (autoScaling != null) {
const autoScalingAny = autoScaling as any
const read: IThroughputAutoScalingCapacity = isNil(autoScalingAny.read) ? autoScalingAny : autoScalingAny.read
const write: IThroughputAutoScalingCapacity = isNil(autoScalingAny.write) ? autoScalingAny : autoScalingAny.write
table.autoScaleReadCapacity({
minCapacity: read.minCapacity ?? DEFAULT_AUTOSCALING_MIN_CAPACITY,
maxCapacity: read.maxCapacity ?? DEFAULT_AUTOSCALING_MAX_CAPACITY,
}).scaleOnUtilization({
targetUtilizationPercent: read.targetUtilization ?? DEFAULT_AUTOSCALING_TARGET_UTILIZATION,
})
table.autoScaleWriteCapacity({
minCapacity: write.minCapacity ?? DEFAULT_AUTOSCALING_MIN_CAPACITY,
maxCapacity: write.maxCapacity ?? DEFAULT_AUTOSCALING_MIN_CAPACITY,
}).scaleOnUtilization({
targetUtilizationPercent: write.targetUtilization ?? DEFAULT_AUTOSCALING_TARGET_UTILIZATION,
})
}
}
if (schema.localSecondaryIndexes.length > 0) {
for (const indexMetadata of schema.localSecondaryIndexes) {
table.addLocalSecondaryIndex({
indexName: indexMetadata.name,
projectionType: indexMetadata.projection as any,
sortKey: {
name: indexMetadata.range.name,
type: indexMetadata.range.type.type as any,
},
nonKeyAttributes: indexMetadata.nonKeyAttributes,
})
}
}
if (schema.globalSecondaryIndexes.length > 0) {
for (const indexMetadata of schema.globalSecondaryIndexes) {
table.addGlobalSecondaryIndex({
indexName: indexMetadata.name,
partitionKey: {
name: indexMetadata.hash.name,
type: indexMetadata.hash.type.type as any,
},
sortKey: indexMetadata.range == null
? undefined
: { name: indexMetadata.range.name, type: indexMetadata.range.type.type as any },
nonKeyAttributes: indexMetadata.nonKeyAttributes,
projectionType: indexMetadata.projection as any,
readCapacity: indexMetadata.throughput == null ? tableProps.readCapacity : indexMetadata.throughput.read,
writeCapacity: indexMetadata.throughput == null ? tableProps.writeCapacity : indexMetadata.throughput.write,
})
}
}
return table
}
/**
* Convert a folder of tables into an array of CDK Table constructs.
*/
export async function createCDKTables(input: CreateCDKTablesInput): Promise<dynamodb.Table> {
const tableFiles = await readdir(input.tablesDirectory)
const tables: Array<typeof Table> = []
const resources: dynamodb.Table[] = []
const log = input.log == null ? console.log : input.log
log('Running Dyngoose CDK generation utility…')
for (const file of tableFiles) {
if (file.endsWith(input.tableFileSuffix)) {
const tableFile = join(input.tablesDirectory, file)
const tableFileExports = require(tableFile)
for (const exportedProperty of Object.values<any>(tableFileExports)) {
if (exportedProperty.prototype instanceof Table) {
tables.push(exportedProperty)
}
}
}
}
for (const SomeTable of tables) {
const table = createCDKTable(input.scope, SomeTable.schema)
log(`Generated ${table.tableName} CDK Table`)
resources.push(table)
}
return resources as any
}