"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const errors_1 = tslib_1.__importDefault(require("@tradle/errors"));
const errors_2 = require("./errors");
const isTTLEnabled = (ttl) => {
    return ttl.TimeToLiveStatus === 'ENABLING' || ttl.TimeToLiveStatus === 'ENABLED';
};
const canRetryAwaitExists = (err) => {
    return /max attempts exceeded/i.test(err.message) || errors_1.default.matches(err, { code: 'ResourceNotReady' });
};
class DynamoDB {
    constructor(client) {
        this.client = client;
        this.isPointInTimeRecoveryEnabled = (tableName) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { ContinuousBackupsDescription } = yield this.client.describeContinuousBackups({ TableName: tableName }).promise();
            return ContinuousBackupsDescription.ContinuousBackupsStatus === 'ENABLED';
        });
        this.copyPointInTimeRecoverySettings = ({ sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const enabled = yield this.isPointInTimeRecoveryEnabled(sourceName);
            yield this.setPointInTimeRecovery({ tableName: destName, enabled });
        });
        this.setPointInTimeRecovery = ({ tableName, enabled }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield this.client.updateContinuousBackups({
                TableName: tableName,
                PointInTimeRecoverySpecification: {
                    PointInTimeRecoveryEnabled: enabled
                }
            }).promise();
        });
        this.ensureStreamMatchesTable = ({ tableArn, streamArn }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { Table } = yield this.client.describeTable({ TableName: tableArn }).promise();
            if (Table.LatestStreamArn !== streamArn) {
                throw new errors_2.Errors.InvalidInput(`stream ${streamArn} does not match table ${tableArn}`);
            }
        });
        this.assertCanRestoreTable = ({ date, sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield Promise.all([
                this.assertTableExists(sourceName),
                this.assertTableDoesNotExist(destName),
                this.assertTableHasBackupForDate({ tableName: sourceName, date }),
            ]);
        });
        this.assertTableHasBackupForDate = ({ tableName, date }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { ContinuousBackupsDescription, } = yield this.client.describeContinuousBackups({ TableName: tableName }).promise();
            if (ContinuousBackupsDescription.ContinuousBackupsStatus !== 'ENABLED') {
                throw new errors_2.Errors.InvalidInput(`table ${tableName} does not have continuous backups enabled`);
            }
            const { EarliestRestorableDateTime, LatestRestorableDateTime, } = ContinuousBackupsDescription.PointInTimeRecoveryDescription;
            const target = new Date(date);
            const min = new Date(EarliestRestorableDateTime);
            const max = new Date(LatestRestorableDateTime);
            if (target < min || target > max) {
                throw new errors_2.Errors.InvalidInput(`table ${tableName} can be restored only to a point within the following range: ${min.toISOString()} - ${max.toISOString()}`);
            }
        });
        this.restoreTable = ({ date, sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const params = {
                RestoreDateTime: new Date(date),
                SourceTableName: sourceName,
                TargetTableName: destName,
            };
            yield this.client.restoreTableToPointInTime(params).promise();
            yield this.awaitExists(destName);
            yield this.copyTableSettings({ sourceName, destName });
            const { Table } = yield this.client.describeTable({ TableName: destName }).promise();
            return {
                table: Table.TableArn,
                stream: Table.LatestStreamArn,
            };
        });
        this.awaitExists = (tableName) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                yield this.client.waitFor('tableExists', { TableName: tableName }).promise();
            }
            catch (err) {
                if (canRetryAwaitExists(err)) {
                    return this.awaitExists(tableName);
                }
                throw err;
            }
        });
        this.copyTableSettings = ({ sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield this.copyPointInTimeRecoverySettings({ sourceName, destName });
            yield this.copyTTLSettings({ sourceName, destName });
            yield this.copyStreamSettings({ sourceName, destName });
        });
        this.getTTLSettings = (tableName) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { TimeToLiveDescription } = yield this.client.describeTimeToLive({ TableName: tableName }).promise();
            return {
                Enabled: isTTLEnabled(TimeToLiveDescription),
                AttributeName: TimeToLiveDescription.AttributeName
            };
        });
        this.setTTLSettings = ({ tableName, ttl }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const params = {
                TableName: tableName,
                TimeToLiveSpecification: ttl
            };
            yield this.client.updateTimeToLive(params).promise();
        });
        this.copyTTLSettings = ({ sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const ttl = yield this.getTTLSettings(sourceName);
            if (ttl.Enabled) {
                yield this.setTTLSettings({ tableName: destName, ttl });
            }
        });
        this.getStreamSettings = (tableName) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { Table } = yield this.client.describeTable({ TableName: tableName }).promise();
            return Table.StreamSpecification;
        });
        this.setStreamSettings = ({ tableName, settings }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const params = {
                TableName: tableName,
                StreamSpecification: settings,
            };
            yield this.client.updateTable(params).promise();
        });
        this.copyStreamSettings = ({ sourceName, destName }) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const settings = yield this.getStreamSettings(sourceName);
            if (settings && settings.StreamEnabled) {
                yield this.setStreamSettings({ tableName: destName, settings });
            }
        });
        this.doesTableExist = (tableName) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                yield this.client.describeTable({ TableName: tableName }).promise();
            }
            catch (err) {
                errors_1.default.ignore(err, { code: 'ResourceNotFoundException' });
                return false;
            }
            return true;
        });
        this.assertTableExists = (tableName, errMessage) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const exists = yield this.doesTableExist(tableName);
            if (!exists) {
                throw new errors_2.Errors.InvalidInput(errMessage || `table does not exist: ${tableName}`);
            }
        });
        this.assertTableDoesNotExist = (tableName, errMessage) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const exists = yield this.doesTableExist(tableName);
            if (exists) {
                throw new errors_2.Errors.InvalidInput(errMessage || `table already exists: ${tableName}`);
            }
        });
    }
}
exports.create = (client) => new DynamoDB(client);
//# sourceMappingURL=dynamodb.js.map