"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.S2Client = exports.AppendRetryPolicy = void 0;
exports.genS2RequestToken = genS2RequestToken;
const accountDeleteBasin_1 = require("./funcs/accountDeleteBasin");
const errors_1 = require("./models/errors");
const stream_1 = require("./sdk/stream");
const basin_1 = require("./sdk/basin");
const account_1 = require("./sdk/account");
const uuid_1 = require("uuid");
const basinDeleteStream_1 = require("./funcs/basinDeleteStream");
const endpoints_1 = require("./endpoints");
const http_1 = require("./lib/http");
__exportStar(require("./endpoints"), exports);
/**
 * Retry policy for appends.
 */
var AppendRetryPolicy;
(function (AppendRetryPolicy) {
    /**
     * Retry all eligible failures encountered during an append.
     * This could result in append batches being duplicated on the stream.
     */
    AppendRetryPolicy[AppendRetryPolicy["All"] = 0] = "All";
    /**
     * Retry only failures with no side effects.
     * Will not attempt to retry failures where it cannot be concluded whether an append may become durable, in order to prevent duplicates.
     */
    AppendRetryPolicy[AppendRetryPolicy["NoSideEffects"] = 1] = "NoSideEffects";
})(AppendRetryPolicy || (exports.AppendRetryPolicy = AppendRetryPolicy = {}));
const defaultS2ClientConfig = {
    requestTimeout: 3000,
    endpoints: endpoints_1.S2Endpoints.forCloud(endpoints_1.S2Cloud.Aws),
    appedRetryPolicy: AppendRetryPolicy.All,
    httpClient: new http_1.HTTPClient({
        fetcher: async (request) => fetch(request),
    }),
};
class S2Client {
    get account() {
        return (this._account ?? (this._account = new S2Account(this.config)));
    }
    basin(basinName) {
        return (this._basin ?? (this._basin = new S2Basin(basinName, this.config)));
    }
    constructor(config) {
        this.config = { ...defaultS2ClientConfig, ...config };
    }
}
exports.S2Client = S2Client;
class S2Account {
    constructor(config) {
        this.accountURLSuffx = "/v1alpha";
        config.httpClient?.addHook("beforeRequest", (request) => {
            if (config.userAgent !== undefined) {
                request.headers.set("user-agent", config.userAgent ?? "s2-sdk-typescript");
            }
        });
        this._account = new account_1.Account({
            ...(config.authToken !== undefined && { bearerAuth: config.authToken }),
            ...(config.requestTimeout !== undefined && { timeoutMs: config.requestTimeout }),
            ...(config.httpClient !== undefined && { httpClient: config.httpClient }),
        });
        this.config = config;
    }
    get URL() {
        return `https://${endpoints_1.ClientKind.toAuthority({ kind: "Account" }, this.config.endpoints ?? endpoints_1.S2Endpoints.forCloud(endpoints_1.S2Cloud.Aws))}${this.accountURLSuffx}`;
    }
    basin(basinName) {
        return new S2Basin(basinName, this.config);
    }
    overrideConfig(config) {
        this.config.httpClient?.addHook("beforeRequest", (request) => {
            if (config?.authToken !== undefined) {
                request.headers.set("Authorization", `Bearer ${config.authToken}`);
            }
        });
    }
    async listBasins(request, opConfig) {
        this.overrideConfig(opConfig);
        return retryWithExponentialBackoff(() => this._account.listBasins(request ?? {}, {
            serverURL: this.URL,
        }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async getBasinConfig(basin, opConfig) {
        this.overrideConfig(opConfig);
        const _request = { basin };
        return retryWithExponentialBackoff(() => this._account.getBasinConfig(_request, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async createBasin(basin, request, opConfig) {
        this.overrideConfig(opConfig);
        const _request = {
            basin,
            s2RequestToken: genS2RequestToken(),
            createBasinRequest: request ?? {},
        };
        return retryWithExponentialBackoff(() => this._account.createBasin(_request, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async deleteBasin(basin, if_exists, opConfig) {
        this.overrideConfig(opConfig);
        const response = await retryWithExponentialBackoff(() => (0, accountDeleteBasin_1.accountDeleteBasin)(this._account, { basin }, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
        if (if_exists && response.error instanceof errors_1.NotFoundError)
            return;
        if (response.error)
            throw new Error(response.error.message);
        return;
    }
    async reconfigureBasin(basin, config, opConfig) {
        this.overrideConfig(opConfig);
        const _request = { basin, basinConfig: config };
        return retryWithExponentialBackoff(() => this._account.reconfigureBasin(_request, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
}
class S2Basin {
    get URL() {
        return `https://${endpoints_1.ClientKind.toAuthority(this.clientKind, this.config.endpoints ?? endpoints_1.S2Endpoints.forCloud(endpoints_1.S2Cloud.Aws))}${this.basinURLSuffx}`;
    }
    constructor(basinName, config) {
        this.basinURLSuffx = "/v1alpha";
        config.httpClient?.addHook("beforeRequest", (request) => {
            if (config.userAgent !== undefined) {
                request.headers.set("user-agent", config.userAgent ?? "s2-sdk-typescript");
            }
            if (config.endpoints?.basin?.kind === "Direct") {
                request.headers.set("s2-basin", basinName);
            }
        });
        this._basin = new basin_1.Basin({
            ...(config.authToken !== undefined && { bearerAuth: config.authToken }),
            ...(config.requestTimeout !== undefined && { timeoutMs: config.requestTimeout }),
            ...(config.httpClient !== undefined && { httpClient: config.httpClient }),
        });
        this.config = config;
        this.clientKind = { kind: "Basin", basin: basinName };
        this.basinName = basinName;
    }
    stream(streamName) {
        return (this._stream ?? (this._stream = new Stream(this.basinName, streamName, this.config)));
    }
    overrideConfig(config) {
        this.config.httpClient?.addHook("beforeRequest", (request) => {
            if (config?.authToken !== undefined) {
                request.headers.set("Authorization", `Bearer ${config.authToken}`);
            }
        });
    }
    async listStreams(request, opConfig) {
        this.overrideConfig(opConfig);
        return retryWithExponentialBackoff(() => this._basin.listStreams(request ?? {}, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async getStreamConfig(stream, opConfig) {
        this.overrideConfig(opConfig);
        return retryWithExponentialBackoff(() => this._basin.getStreamConfig({ stream }, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async createStream(stream, request, opConfig) {
        this.overrideConfig(opConfig);
        const _request = {
            stream,
            s2RequestToken: genS2RequestToken(),
            createStreamRequest: request ?? {},
        };
        return retryWithExponentialBackoff(() => this._basin.createStream(_request, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
    async deleteStream(stream, if_exists, opConfig) {
        this.overrideConfig(opConfig);
        const response = await retryWithExponentialBackoff(() => (0, basinDeleteStream_1.basinDeleteStream)(this._basin, { stream }, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
        if (if_exists && response instanceof errors_1.NotFoundError)
            return;
        if (response.error)
            throw new Error(response.error.message);
        return;
    }
    async reconfigureStream(stream, config, opConfig) {
        this.overrideConfig(opConfig);
        return retryWithExponentialBackoff(() => this._basin.reconfigureStream({ stream, streamConfig: config }, { serverURL: this.URL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
    }
}
class Stream {
    get basinURL() {
        return `https://${endpoints_1.ClientKind.toAuthority(this.clientKind, this.config.endpoints ?? endpoints_1.S2Endpoints.forCloud(endpoints_1.S2Cloud.Aws))}${this.basinURLSuffx}`;
    }
    overrideConfig(config) {
        this.config.httpClient?.addHook("beforeRequest", (request) => {
            if (config?.authToken !== undefined) {
                request.headers.set("Authorization", `Bearer ${config.authToken}`);
            }
        });
    }
    constructor(basinName, streamName, config) {
        this.basinURLSuffx = "/v1alpha";
        this.config = config;
        this.clientKind = { kind: "Basin", basin: basinName };
        this.streamName = streamName;
        config.httpClient?.addHook("beforeRequest", (request) => {
            if (config.userAgent !== undefined) {
                request.headers.set("user-agent", config.userAgent ?? "s2-sdk-typescript");
            }
            if (config.endpoints?.basin?.kind === "Direct") {
                request.headers.set("s2-basin", basinName);
            }
        });
        this._stream = new stream_1.Stream({
            ...(config.authToken !== undefined && { bearerAuth: config.authToken }),
            ...(config.requestTimeout !== undefined && { timeoutMs: config.requestTimeout }),
            ...(config.httpClient !== undefined && { httpClient: config.httpClient })
        });
    }
    async checkTail(opConfig) {
        this.overrideConfig(opConfig);
        return (await this._stream.checkTail({ stream: this.streamName }, { serverURL: this.basinURL }));
    }
    async append(request, opConfig) {
        this.overrideConfig(opConfig);
        switch (this.config.appedRetryPolicy) {
            case AppendRetryPolicy.All:
            case undefined:
                return retryWithExponentialBackoff(() => this._stream.append({ ...request, stream: this.streamName }, { serverURL: this.basinURL }), this.config.maxRetries, this.config.initialBackoffMs, this.config.maxBackoffMs);
            case AppendRetryPolicy.NoSideEffects:
                return (await this._stream.append({ ...request, stream: this.streamName }, {
                    serverURL: this.basinURL,
                }));
            default:
                throw new Error("Invalid AppendRetryPolicy");
        }
    }
    async read(request, opConfig) {
        this.overrideConfig(opConfig);
        return await this._stream.read({ ...request, stream: this.streamName }, { serverURL: this.basinURL });
    }
    async *readStream(request, opConfig) {
        this.overrideConfig(opConfig);
        let currentRequest = { ...request };
        let initialBackoffMs = this.config.initialBackoffMs ?? 100;
        const maxBackoffMs = 5000;
        const maxRetries = this.config.maxRetries ?? 5;
        let retryCount = 0;
        while (true) {
            let stream;
            try {
                const response = await this._stream.read({ ...currentRequest, stream: this.streamName }, {
                    serverURL: this.basinURL,
                    timeoutMs: -1, // disable only for streaming
                    acceptHeaderOverride: stream_1.ReadAcceptEnum.textEventStream
                });
                stream = response;
                if (!stream)
                    return;
                for await (const event of stream) {
                    yield event;
                    if (event.event === 'message') {
                        const output = event;
                        if ('batch' in output.data) {
                            const batch = output.data.batch;
                            if (batch.records && batch.records.length > 0) {
                                const lastRecord = batch.records[batch.records.length - 1];
                                if (lastRecord) {
                                    currentRequest = { ...currentRequest, startSeqNum: lastRecord.seqNum + 1 };
                                }
                            }
                            if (currentRequest.limit) {
                                if (currentRequest.limit.count != null) {
                                    currentRequest.limit.count = Math.max(0, currentRequest.limit.count - batch.records.length);
                                }
                                if (currentRequest.limit.bytes != null) {
                                    currentRequest.limit.bytes = Math.max(0, currentRequest.limit.bytes - meteredBatchSize(batch));
                                }
                            }
                        }
                    }
                }
                return;
            }
            catch (error) {
                if (error instanceof errors_1.RetryableError) {
                    if (retryCount >= maxRetries) {
                        throw error;
                    }
                    retryCount++;
                    await new Promise(resolve => setTimeout(resolve, initialBackoffMs));
                    initialBackoffMs = Math.min(initialBackoffMs * 2, maxBackoffMs);
                }
                else {
                    throw error;
                }
            }
        }
    }
}
function meteredRecordSize(batch) {
    const fixed = 8 + (2 * batch.headers.length);
    const headerSize = batch.headers.reduce((acc, header) => acc + header.name.length + header.value.length, 0);
    const bodySize = batch.body.length;
    return fixed + headerSize + bodySize;
}
function meteredBatchSize(batch) {
    return batch.records.reduce((acc, record) => acc + meteredRecordSize(record), 0);
}
function genS2RequestToken() {
    return (0, uuid_1.v4)().replace(/-/g, "");
}
async function retryWithExponentialBackoff(operation, maxAttempts = 5, initialBackoffMs = 100, maxBackoffMs = 5000) {
    let attempt = 0;
    while (attempt < maxAttempts) {
        try {
            return await operation();
        }
        catch (error) {
            if (error instanceof errors_1.RetryableError) {
                attempt++;
                if (attempt >= maxAttempts)
                    throw error;
                const jitter = Math.random() * 0.3 + 0.85;
                const delayMs = Math.min(initialBackoffMs * Math.pow(2, attempt) * jitter, maxBackoffMs);
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            else {
                throw error;
            }
        }
    }
    throw new Error("Max retry attempts reached");
}
//# sourceMappingURL=index.extras.js.map