// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as crypto from 'node:crypto';
import { InternalError, NewlineKind, Sort } from '@rushstack/node-core-library';
import { CollatedTerminal } from '@rushstack/stream-collator';
import { DiscardStdoutTransform, TextRewriterTransform, SplitterTransform, Terminal } from '@rushstack/terminal';
import { CollatedTerminalProvider } from '../../utilities/CollatedTerminalProvider';
import { OperationStatus } from './OperationStatus';
import { CobuildLock } from '../cobuild/CobuildLock';
import { OperationBuildCache } from '../buildCache/OperationBuildCache';
import { RushConstants } from '../RushConstants';
import { initializeProjectLogFilesAsync, getProjectLogFilePaths } from './ProjectLogWritable';
import { DisjointSet } from '../cobuild/DisjointSet';
import { PeriodicCallback } from './PeriodicCallback';
import { NullTerminalProvider } from '../../utilities/NullTerminalProvider';
const PLUGIN_NAME = 'CacheablePhasedOperationPlugin';
const PERIODIC_CALLBACK_INTERVAL_IN_SECONDS = 10;
export class CacheableOperationPlugin {
    constructor(options) {
        this._buildCacheContextByOperation = new Map();
        this._options = options;
    }
    apply(hooks) {
        const { allowWarningsInSuccessfulBuild, buildCacheConfiguration, cobuildConfiguration } = this._options;
        hooks.beforeExecuteOperations.tap(PLUGIN_NAME, (recordByOperation, context) => {
            var _a;
            const { isIncrementalBuildAllowed, inputsSnapshot, projectConfigurations, isInitial } = context;
            if (!inputsSnapshot) {
                throw new Error(`Build cache is only supported if running in a Git repository. Either disable the build cache or run Rush in a Git repository.`);
            }
            const disjointSet = (cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildFeatureEnabled)
                ? new DisjointSet()
                : undefined;
            for (const [operation, record] of recordByOperation) {
                const { associatedProject, associatedPhase, runner, settings: operationSettings } = operation;
                if (!runner) {
                    return;
                }
                const { name: phaseName } = associatedPhase;
                const projectConfiguration = projectConfigurations.get(associatedProject);
                // This value can *currently* be cached per-project, but in the future the list of files will vary
                // depending on the selected phase.
                const fileHashes = inputsSnapshot.getTrackedFileHashesForOperation(associatedProject, phaseName);
                const cacheDisabledReason = projectConfiguration
                    ? projectConfiguration.getCacheDisabledReason(fileHashes.keys(), phaseName, operation.isNoOp)
                    : `Project does not have a ${RushConstants.rushProjectConfigFilename} configuration file, ` +
                        'or one provided by a rig, so it does not support caching.';
                const metadataFolderPath = record.metadataFolderPath;
                const outputFolderNames = metadataFolderPath ? [metadataFolderPath] : [];
                const configuredOutputFolderNames = operationSettings === null || operationSettings === void 0 ? void 0 : operationSettings.outputFolderNames;
                if (configuredOutputFolderNames) {
                    for (const folderName of configuredOutputFolderNames) {
                        outputFolderNames.push(folderName);
                    }
                }
                disjointSet === null || disjointSet === void 0 ? void 0 : disjointSet.add(operation);
                const buildCacheContext = {
                    // Supports cache writes by default for initial operations.
                    // Don't write during watch runs for performance reasons (and to avoid flooding the cache)
                    isCacheWriteAllowed: isInitial,
                    isCacheReadAllowed: isIncrementalBuildAllowed,
                    operationBuildCache: undefined,
                    outputFolderNames,
                    cacheDisabledReason,
                    cobuildLock: undefined,
                    cobuildClusterId: undefined,
                    buildCacheTerminal: undefined,
                    buildCacheTerminalWritable: undefined,
                    periodicCallback: new PeriodicCallback({
                        interval: PERIODIC_CALLBACK_INTERVAL_IN_SECONDS * 1000
                    }),
                    cacheRestored: false,
                    isCacheReadAttempted: false
                };
                // Upstream runners may mutate the property of build cache context for downstream runners
                this._buildCacheContextByOperation.set(operation, buildCacheContext);
            }
            if (disjointSet) {
                clusterOperations(disjointSet, this._buildCacheContextByOperation);
                for (const operationSet of disjointSet.getAllSets()) {
                    if ((cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildFeatureEnabled) && cobuildConfiguration.cobuildContextId) {
                        // Get a deterministic ordered array of operations, which is important to get a deterministic cluster id.
                        const groupedOperations = Array.from(operationSet);
                        Sort.sortBy(groupedOperations, (operation) => {
                            return operation.name;
                        });
                        // Generates cluster id, cluster id comes from the project folder and operation name of all operations in the same cluster.
                        const hash = crypto.createHash('sha1');
                        for (const operation of groupedOperations) {
                            const { associatedPhase: phase, associatedProject: project } = operation;
                            hash.update(project.projectRelativeFolder);
                            hash.update(RushConstants.hashDelimiter);
                            hash.update((_a = operation.name) !== null && _a !== void 0 ? _a : phase.name);
                            hash.update(RushConstants.hashDelimiter);
                        }
                        const cobuildClusterId = hash.digest('hex');
                        // Assign same cluster id to all operations in the same cluster.
                        for (const record of groupedOperations) {
                            const buildCacheContext = this._getBuildCacheContextByOperationOrThrow(record);
                            buildCacheContext.cobuildClusterId = cobuildClusterId;
                        }
                    }
                }
            }
        });
        hooks.beforeExecuteOperation.tapPromise(PLUGIN_NAME, async (runnerContext) => {
            if (this._buildCacheContextByOperation.size === 0) {
                return;
            }
            const buildCacheContext = this._getBuildCacheContextByOperation(runnerContext.operation);
            if (!buildCacheContext) {
                return;
            }
            const record = runnerContext;
            const { associatedProject: project, associatedPhase: phase, runner, _operationMetadataManager: operationMetadataManager, operation } = record;
            if (!operation.enabled || !(runner === null || runner === void 0 ? void 0 : runner.cacheable)) {
                return;
            }
            const runBeforeExecute = async () => {
                var _a, _b;
                if (!buildCacheContext.buildCacheTerminal ||
                    ((_a = buildCacheContext.buildCacheTerminalWritable) === null || _a === void 0 ? void 0 : _a.isOpen) === false) {
                    // The writable does not exist or has been closed, re-create one
                    // eslint-disable-next-line require-atomic-updates
                    buildCacheContext.buildCacheTerminal = await this._createBuildCacheTerminalAsync({
                        record,
                        buildCacheContext,
                        buildCacheEnabled: buildCacheConfiguration === null || buildCacheConfiguration === void 0 ? void 0 : buildCacheConfiguration.buildCacheEnabled,
                        rushProject: project,
                        logFilenameIdentifier: operation.logFilenameIdentifier,
                        quietMode: record.quietMode,
                        debugMode: record.debugMode
                    });
                }
                const buildCacheTerminal = buildCacheContext.buildCacheTerminal;
                let operationBuildCache = this._tryGetOperationBuildCache({
                    buildCacheContext,
                    buildCacheConfiguration,
                    terminal: buildCacheTerminal,
                    record
                });
                // Try to acquire the cobuild lock
                let cobuildLock;
                if (cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildFeatureEnabled) {
                    if ((cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildLeafProjectLogOnlyAllowed) &&
                        operation.consumers.size === 0 &&
                        !operationBuildCache) {
                        // When the leaf project log only is allowed and the leaf project is build cache "disabled", try to get
                        // a log files only project build cache
                        operationBuildCache = await this._tryGetLogOnlyOperationBuildCacheAsync({
                            buildCacheConfiguration,
                            cobuildConfiguration,
                            buildCacheContext,
                            record,
                            terminal: buildCacheTerminal
                        });
                        if (operationBuildCache) {
                            buildCacheTerminal.writeVerboseLine(`Log files only build cache is enabled for the project "${project.packageName}" because the cobuild leaf project log only is allowed`);
                        }
                        else {
                            buildCacheTerminal.writeWarningLine(`Failed to get log files only build cache for the project "${project.packageName}"`);
                        }
                    }
                    cobuildLock = await this._tryGetCobuildLockAsync({
                        buildCacheContext,
                        operationBuildCache,
                        cobuildConfiguration,
                        packageName: project.packageName,
                        phaseName: phase.name
                    });
                }
                // eslint-disable-next-line require-atomic-updates -- we are mutating the build cache context intentionally
                buildCacheContext.cobuildLock = cobuildLock;
                // If possible, we want to skip this operation -- either by restoring it from the
                // cache, if caching is enabled, or determining that the project
                // is unchanged (using the older incremental execution logic). These two approaches,
                // "caching" and "skipping", are incompatible, so only one applies.
                //
                // Note that "caching" and "skipping" take two different approaches
                // to tracking dependents:
                //
                //   - For caching, "isCacheReadAllowed" is set if a project supports
                //     incremental builds, and determining whether this project or a dependent
                //     has changed happens inside the hashing logic.
                //
                const { error: errorLogPath } = getProjectLogFilePaths({
                    project,
                    logFilenameIdentifier: operation.logFilenameIdentifier
                });
                const restoreCacheAsync = async (
                // TODO: Investigate if `operationBuildCacheForRestore` is always the same instance as `operationBuildCache`
                // above, and if it is, remove this parameter
                operationBuildCacheForRestore, specifiedCacheId) => {
                    buildCacheContext.isCacheReadAttempted = true;
                    const restoreFromCacheSuccess = await (operationBuildCacheForRestore === null || operationBuildCacheForRestore === void 0 ? void 0 : operationBuildCacheForRestore.tryRestoreFromCacheAsync(buildCacheTerminal, specifiedCacheId));
                    if (restoreFromCacheSuccess) {
                        buildCacheContext.cacheRestored = true;
                        await runnerContext.runWithTerminalAsync(async (taskTerminal, terminalProvider) => {
                            // Restore the original state of the operation without cache
                            await (operationMetadataManager === null || operationMetadataManager === void 0 ? void 0 : operationMetadataManager.tryRestoreAsync({
                                terminalProvider,
                                terminal: buildCacheTerminal,
                                errorLogPath,
                                cobuildContextId: cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildContextId,
                                cobuildRunnerId: cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildRunnerId
                            }));
                        }, { createLogFile: false });
                    }
                    return !!restoreFromCacheSuccess;
                };
                if (cobuildLock) {
                    // handling rebuilds. "rush rebuild" or "rush retest" command will save operations to
                    // the build cache once completed, but does not retrieve them (since the "incremental"
                    // flag is disabled). However, we still need a cobuild to be able to retrieve a finished
                    // build from another cobuild in this case.
                    const cobuildCompletedState = await cobuildLock.getCompletedStateAsync();
                    if (cobuildCompletedState) {
                        const { status, cacheId } = cobuildCompletedState;
                        if ((_b = record.operation.settings) === null || _b === void 0 ? void 0 : _b.allowCobuildWithoutCache) {
                            // This should only be enabled if the experiment for cobuild orchestration is enabled.
                            return status;
                        }
                        const restoreFromCacheSuccess = await restoreCacheAsync(cobuildLock.operationBuildCache, cacheId);
                        if (restoreFromCacheSuccess) {
                            return status;
                        }
                    }
                    else if (!buildCacheContext.isCacheReadAttempted && buildCacheContext.isCacheReadAllowed) {
                        const restoreFromCacheSuccess = await restoreCacheAsync(operationBuildCache);
                        if (restoreFromCacheSuccess) {
                            return OperationStatus.FromCache;
                        }
                    }
                }
                else if (buildCacheContext.isCacheReadAllowed) {
                    const restoreFromCacheSuccess = await restoreCacheAsync(operationBuildCache);
                    if (restoreFromCacheSuccess) {
                        return OperationStatus.FromCache;
                    }
                }
                if (buildCacheContext.isCacheWriteAllowed && cobuildLock) {
                    const acquireSuccess = await cobuildLock.tryAcquireLockAsync();
                    if (acquireSuccess) {
                        const { periodicCallback } = buildCacheContext;
                        periodicCallback.addCallback(async () => {
                            await (cobuildLock === null || cobuildLock === void 0 ? void 0 : cobuildLock.renewLockAsync());
                        });
                        periodicCallback.start();
                    }
                    else {
                        setTimeout(() => {
                            record.status = OperationStatus.Ready;
                        }, 500);
                        return OperationStatus.Executing;
                    }
                }
            };
            return await runBeforeExecute();
        });
        hooks.afterExecuteOperation.tapPromise(PLUGIN_NAME, async (runnerContext) => {
            var _a;
            const record = runnerContext;
            const { status, stopwatch, _operationMetadataManager: operationMetadataManager, operation } = record;
            const { associatedProject: project, runner, enabled } = operation;
            if (!enabled || !(runner === null || runner === void 0 ? void 0 : runner.cacheable)) {
                return;
            }
            const buildCacheContext = this._getBuildCacheContextByOperation(operation);
            if (!buildCacheContext) {
                return;
            }
            // No need to run for the following operation status
            if (!record.isTerminal || record.status === OperationStatus.NoOp) {
                return;
            }
            const { cobuildLock, operationBuildCache, isCacheWriteAllowed, buildCacheTerminal, cacheRestored } = buildCacheContext;
            try {
                if (!cacheRestored) {
                    // Save the metadata to disk
                    const { logFilenameIdentifier } = operationMetadataManager;
                    const { duration: durationInSeconds } = stopwatch;
                    const { text: logPath, error: errorLogPath, jsonl: logChunksPath } = getProjectLogFilePaths({
                        project,
                        logFilenameIdentifier
                    });
                    await operationMetadataManager.saveAsync({
                        durationInSeconds,
                        cobuildContextId: cobuildLock === null || cobuildLock === void 0 ? void 0 : cobuildLock.cobuildConfiguration.cobuildContextId,
                        cobuildRunnerId: cobuildLock === null || cobuildLock === void 0 ? void 0 : cobuildLock.cobuildConfiguration.cobuildRunnerId,
                        logPath,
                        errorLogPath,
                        logChunksPath
                    });
                }
                if (!buildCacheTerminal) {
                    // This should not happen
                    throw new InternalError(`Build Cache Terminal is not created`);
                }
                let setCompletedStatePromiseFunction;
                let setCacheEntryPromise;
                if (cobuildLock && isCacheWriteAllowed) {
                    const { cacheId, contextId } = cobuildLock.cobuildContext;
                    let finalCacheId = cacheId;
                    if (status === OperationStatus.Failure) {
                        finalCacheId = `${cacheId}-${contextId}-failed`;
                    }
                    else if (status === OperationStatus.SuccessWithWarning && !record.runner.warningsAreAllowed) {
                        finalCacheId = `${cacheId}-${contextId}-warnings`;
                    }
                    switch (status) {
                        case OperationStatus.SuccessWithWarning:
                        case OperationStatus.Success:
                        case OperationStatus.Failure: {
                            const currentStatus = status;
                            setCompletedStatePromiseFunction = () => {
                                return cobuildLock === null || cobuildLock === void 0 ? void 0 : cobuildLock.setCompletedStateAsync({
                                    status: currentStatus,
                                    cacheId: finalCacheId
                                });
                            };
                            setCacheEntryPromise = () => cobuildLock.operationBuildCache.trySetCacheEntryAsync(buildCacheTerminal, finalCacheId);
                        }
                    }
                }
                const taskIsSuccessful = status === OperationStatus.Success ||
                    (status === OperationStatus.SuccessWithWarning &&
                        record.runner.warningsAreAllowed &&
                        allowWarningsInSuccessfulBuild);
                // If the command is successful, we can calculate project hash, and no dependencies were skipped,
                // write a new cache entry.
                if (!setCacheEntryPromise && taskIsSuccessful && isCacheWriteAllowed && operationBuildCache) {
                    setCacheEntryPromise = () => operationBuildCache.trySetCacheEntryAsync(buildCacheTerminal);
                }
                if (!cacheRestored) {
                    const cacheWriteSuccess = await (setCacheEntryPromise === null || setCacheEntryPromise === void 0 ? void 0 : setCacheEntryPromise());
                    await (setCompletedStatePromiseFunction === null || setCompletedStatePromiseFunction === void 0 ? void 0 : setCompletedStatePromiseFunction());
                    if (cacheWriteSuccess === false && status === OperationStatus.Success) {
                        record.status = OperationStatus.SuccessWithWarning;
                    }
                }
            }
            finally {
                (_a = buildCacheContext.buildCacheTerminalWritable) === null || _a === void 0 ? void 0 : _a.close();
                buildCacheContext.periodicCallback.stop();
            }
        });
        hooks.afterExecuteOperation.tap(PLUGIN_NAME, (record) => {
            const { operation } = record;
            const buildCacheContext = this._buildCacheContextByOperation.get(operation);
            // Status changes to direct dependents
            let blockCacheWrite = !(buildCacheContext === null || buildCacheContext === void 0 ? void 0 : buildCacheContext.isCacheWriteAllowed);
            switch (record.status) {
                case OperationStatus.Skipped: {
                    // Skipping means cannot guarantee integrity, so prevent cache writes in dependents.
                    blockCacheWrite = true;
                    break;
                }
            }
            // Apply status changes to direct dependents
            if (blockCacheWrite) {
                for (const consumer of operation.consumers) {
                    const consumerBuildCacheContext = this._getBuildCacheContextByOperation(consumer);
                    if (consumerBuildCacheContext) {
                        consumerBuildCacheContext.isCacheWriteAllowed = false;
                    }
                }
            }
        });
        hooks.afterExecuteOperations.tapPromise(PLUGIN_NAME, async () => {
            this._buildCacheContextByOperation.clear();
        });
    }
    _getBuildCacheContextByOperation(operation) {
        const buildCacheContext = this._buildCacheContextByOperation.get(operation);
        return buildCacheContext;
    }
    _getBuildCacheContextByOperationOrThrow(operation) {
        const buildCacheContext = this._getBuildCacheContextByOperation(operation);
        if (!buildCacheContext) {
            // This should not happen
            throw new InternalError(`Build cache context for operation ${operation.name} should be defined`);
        }
        return buildCacheContext;
    }
    _tryGetOperationBuildCache({ buildCacheConfiguration, buildCacheContext, terminal, record }) {
        var _a;
        if (!buildCacheContext.operationBuildCache) {
            const { cacheDisabledReason } = buildCacheContext;
            if (cacheDisabledReason && !((_a = record.operation.settings) === null || _a === void 0 ? void 0 : _a.allowCobuildWithoutCache)) {
                terminal.writeVerboseLine(cacheDisabledReason);
                return;
            }
            if (!buildCacheConfiguration) {
                // Unreachable, since this will have set `cacheDisabledReason`.
                return;
            }
            buildCacheContext.operationBuildCache = OperationBuildCache.forOperation(record, {
                buildCacheConfiguration,
                terminal
            });
        }
        return buildCacheContext.operationBuildCache;
    }
    // Get an OperationBuildCache only cache/restore log files
    async _tryGetLogOnlyOperationBuildCacheAsync(options) {
        const { buildCacheContext, buildCacheConfiguration, cobuildConfiguration, record, terminal } = options;
        if (!(buildCacheConfiguration === null || buildCacheConfiguration === void 0 ? void 0 : buildCacheConfiguration.buildCacheEnabled)) {
            return;
        }
        const { outputFolderNames } = buildCacheContext;
        const hasher = crypto.createHash('sha1');
        hasher.update(record.getStateHash());
        if (cobuildConfiguration.cobuildContextId) {
            hasher.update(`${RushConstants.hashDelimiter}cobuildContextId=${cobuildConfiguration.cobuildContextId}`);
        }
        hasher.update(`${RushConstants.hashDelimiter}logFilesOnly=1`);
        const operationStateHash = hasher.digest('hex');
        const { associatedPhase, associatedProject } = record.operation;
        const operationBuildCache = OperationBuildCache.getOperationBuildCache({
            project: associatedProject,
            projectOutputFolderNames: outputFolderNames,
            buildCacheConfiguration,
            terminal,
            operationStateHash,
            phaseName: associatedPhase.name
        });
        buildCacheContext.operationBuildCache = operationBuildCache;
        return operationBuildCache;
    }
    async _tryGetCobuildLockAsync({ cobuildConfiguration, buildCacheContext, operationBuildCache, packageName, phaseName }) {
        if (!buildCacheContext.cobuildLock) {
            if (operationBuildCache && (cobuildConfiguration === null || cobuildConfiguration === void 0 ? void 0 : cobuildConfiguration.cobuildFeatureEnabled)) {
                if (!buildCacheContext.cobuildClusterId) {
                    // This should not happen
                    throw new InternalError('Cobuild cluster id is not defined');
                }
                buildCacheContext.cobuildLock = new CobuildLock({
                    cobuildConfiguration,
                    operationBuildCache,
                    cobuildClusterId: buildCacheContext.cobuildClusterId,
                    lockExpireTimeInSeconds: PERIODIC_CALLBACK_INTERVAL_IN_SECONDS * 3,
                    packageName,
                    phaseName
                });
            }
        }
        return buildCacheContext.cobuildLock;
    }
    async _createBuildCacheTerminalAsync({ record, buildCacheContext, buildCacheEnabled, rushProject, logFilenameIdentifier, quietMode, debugMode }) {
        const silent = record.silent;
        if (silent) {
            const nullTerminalProvider = new NullTerminalProvider();
            return new Terminal(nullTerminalProvider);
        }
        let cacheConsoleWritable;
        // This creates the writer, only do this if necessary.
        const collatedWriter = record.collatedWriter;
        const cacheProjectLogWritable = await this._tryGetBuildCacheTerminalWritableAsync({
            buildCacheContext,
            buildCacheEnabled,
            rushProject,
            logFilenameIdentifier
        });
        if (quietMode) {
            const discardTransform = new DiscardStdoutTransform({
                destination: collatedWriter
            });
            const normalizeNewlineTransform = new TextRewriterTransform({
                destination: discardTransform,
                normalizeNewlines: NewlineKind.Lf,
                ensureNewlineAtEnd: true
            });
            cacheConsoleWritable = normalizeNewlineTransform;
        }
        else {
            cacheConsoleWritable = collatedWriter;
        }
        let cacheCollatedTerminal;
        if (cacheProjectLogWritable) {
            const cacheSplitterTransform = new SplitterTransform({
                destinations: [cacheConsoleWritable, cacheProjectLogWritable]
            });
            cacheCollatedTerminal = new CollatedTerminal(cacheSplitterTransform);
        }
        else {
            cacheCollatedTerminal = new CollatedTerminal(cacheConsoleWritable);
        }
        const buildCacheTerminalProvider = new CollatedTerminalProvider(cacheCollatedTerminal, {
            debugEnabled: debugMode
        });
        return new Terminal(buildCacheTerminalProvider);
    }
    async _tryGetBuildCacheTerminalWritableAsync({ buildCacheEnabled, rushProject, buildCacheContext, logFilenameIdentifier }) {
        // Only open the *.cache.log file(s) if the cache is enabled.
        if (!buildCacheEnabled) {
            return;
        }
        const logFilePaths = getProjectLogFilePaths({
            project: rushProject,
            logFilenameIdentifier: `${logFilenameIdentifier}.cache`
        });
        buildCacheContext.buildCacheTerminalWritable = await initializeProjectLogFilesAsync({
            logFilePaths
        });
        return buildCacheContext.buildCacheTerminalWritable;
    }
}
export function clusterOperations(initialClusters, operationBuildCacheMap) {
    var _a;
    // If disjoint set exists, connect build cache disabled project with its consumers
    for (const [operation, { cacheDisabledReason }] of operationBuildCacheMap) {
        if (cacheDisabledReason && !((_a = operation.settings) === null || _a === void 0 ? void 0 : _a.allowCobuildWithoutCache)) {
            /**
             * Group the project build cache disabled with its consumers. This won't affect too much in
             * a monorepo with high build cache coverage.
             *
             * The mental model is that if X disables the cache, and Y depends on X, then:
             *   1. Y must be built by the same VM that build X;
             *   2. OR, Y must be rebuilt on each VM that needs it.
             * Approach 1 is probably the better choice.
             */
            for (const consumer of operation.consumers) {
                initialClusters === null || initialClusters === void 0 ? void 0 : initialClusters.union(operation, consumer);
            }
        }
    }
}
//# sourceMappingURL=CacheableOperationPlugin.js.map