import { of } from 'rxjs';
import { delay, map, mergeMap, tap } from 'rxjs/operators';
import { randomExponentialBackoffTimer } from '../../../helper';
import { createLogger } from '../../../logger/logger';
import { createToKeyFn, toDb } from '../../../mapper';
const MAX_BATCH_WRITE_ITEMS = 25;
export class BatchWriteSingleTableRequest {
    constructor(dynamoRx, modelClazz, tableName) {
        this.logger = createLogger('dynamo.request.BatchWriteSingleTableRequest', modelClazz);
        this.dynamoRx = dynamoRx;
        if (modelClazz === null || modelClazz === undefined) {
            throw new Error("please provide the model clazz for the request, won't work otherwise");
        }
        this.modelClazz = modelClazz;
        this.tableName = tableName;
        this.itemsToProcess = [];
    }
    get toKey() {
        if (!this.keyFn) {
            this.keyFn = createToKeyFn(this.modelClazz);
        }
        return this.keyFn;
    }
    delete(items) {
        this.itemsToProcess.push(...items.map(item => ({ DeleteRequest: { Key: this.toKey(item) } })));
        this.logger.debug(`${items.length} items added for DeleteRequest`);
        return this;
    }
    put(items) {
        this.itemsToProcess.push(...items.map(item => ({ PutRequest: { Item: toDb(item, this.modelClazz) } })));
        this.logger.debug(`${items.length} items added for PutRequest`);
        return this;
    }
    execNextBatch() {
        const batch = this.itemsToProcess.splice(0, MAX_BATCH_WRITE_ITEMS);
        const batchWriteItemInput = {
            RequestItems: {
                [this.tableName]: batch,
            },
        };
        this.logger.debug('request', batchWriteItemInput);
        return this.dynamoRx.batchWriteItem(batchWriteItemInput).pipe(tap(response => this.logger.debug('response', response)), tap((batchWriteManyResponse) => {
            if (batchWriteManyResponse.UnprocessedItems && batchWriteManyResponse.UnprocessedItems[this.tableName]) {
                this.itemsToProcess.unshift(...batchWriteManyResponse.UnprocessedItems[this.tableName]);
            }
        }), map((batchWriteManyResponse) => ({
            remainingItems: this.itemsToProcess.length,
            capacityExceeded: !!(batchWriteManyResponse.UnprocessedItems && batchWriteManyResponse.UnprocessedItems[this.tableName]),
            consumedCapacity: batchWriteManyResponse.ConsumedCapacity,
        })), tap(response => {
            if (response.capacityExceeded) {
                this.logger.info('capacity exceeded', response.consumedCapacity);
            }
        }));
    }
    /**
     *
     * @param backoffTimer generator for how much timeSlots should be waited before requesting next batch. only used when capacity was exceeded. default randomExponentialBackoffTimer
     * @param throttleTimeSlot defines how long one timeSlot is for throttling, default 1 second
     */
    exec(backoffTimer = randomExponentialBackoffTimer, throttleTimeSlot = 1000) {
        this.logger.debug('starting batchWriteItem');
        let rBoT = backoffTimer();
        let backoffTime = 0;
        return this.execNextBatch().pipe(mergeMap((r) => {
            if (!r.capacityExceeded) {
                rBoT = backoffTimer();
                backoffTime = 0;
            }
            else {
                backoffTime = rBoT.next().value * throttleTimeSlot;
                this.logger.info(`wait ${backoffTime} ms until next request`, { backoffTime });
            }
            return of(r).pipe(delay(backoffTime));
        }), mergeMap((r) => {
            if (r.remainingItems > 0) {
                return this.exec();
            }
            else {
                return of();
            }
        }));
    }
}
//# sourceMappingURL=batch-write-single-table.request.js.map