import Debug from '@prisma/debug';
import { NodeEngine, } from '@prisma/engine-core/dist/NodeEngine';
import { tryLoadEnvs } from '@prisma/sdk';
import { mapPreviewFeatures } from '@prisma/sdk/dist/utils/mapPreviewFeatures';
import { AsyncResource } from 'async_hooks';
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import * as sqlTemplateTag from 'sql-template-tag';
import stripAnsi from 'strip-ansi';
import { PrismaClientInitializationError, PrismaClientKnownRequestError, PrismaClientRustPanicError, PrismaClientUnknownRequestError, } from '.';
import { Dataloader } from './Dataloader';
import { DMMFClass } from './dmmf';
import { DMMF } from './dmmf-types';
import { getLogLevel } from './getLogLevel';
import { mergeBy } from './mergeBy';
import { Args, makeDocument, transformDocument, unpack, } from './query';
import { clientVersion } from './utils/clientVersion';
import { getOutputTypeName, lowerCase } from './utils/common';
import { deepSet } from './utils/deep-set';
import { mssqlPreparedStatement } from './utils/mssqlPreparedStatement';
import { printJsonWithErrors } from './utils/printJsonErrors';
import { printStack } from './utils/printStack';
import { serializeRawParameters } from './utils/serializeRawParameters';
import { validatePrismaClientOptions } from './utils/validatePrismaClientOptions';
const debug = Debug('prisma-client');
function isReadonlyArray(arg) {
    return Array.isArray(arg);
}
const actionOperationMap = {
    findOne: 'query',
    findUnique: 'query',
    findFirst: 'query',
    findMany: 'query',
    count: 'query',
    create: 'mutation',
    update: 'mutation',
    updateMany: 'mutation',
    upsert: 'mutation',
    delete: 'mutation',
    deleteMany: 'mutation',
    executeRaw: 'mutation',
    queryRaw: 'mutation',
    aggregate: 'query',
    groupBy: 'query',
};
const aggregateKeys = {
    avg: true,
    count: true,
    sum: true,
    min: true,
    max: true,
};
// TODO: We **may** be able to get real types. However, we have both a bootstrapping
// problem here, that we want to return a type that's not yet defined
// and we're typecasting this anyway later
export function getPrismaClient(config) {
    class NewPrismaClient {
        constructor(optionsArg) {
            this._middlewares = [];
            this._engineMiddlewares = [];
            this._transactionId = 1;
            if (optionsArg) {
                validatePrismaClientOptions(optionsArg, config.datasourceNames);
            }
            this._clientVersion = config.clientVersion ?? clientVersion;
            this._activeProvider = config.activeProvider;
            const envPaths = {
                rootEnvPath: config.relativeEnvPaths.rootEnvPath &&
                    path.resolve(config.dirname, config.relativeEnvPaths.rootEnvPath),
                schemaEnvPath: config.relativeEnvPaths.schemaEnvPath &&
                    path.resolve(config.dirname, config.relativeEnvPaths.schemaEnvPath),
            };
            const loadedEnv = tryLoadEnvs(envPaths, { conflictCheck: 'none' });
            try {
                const options = optionsArg ?? {};
                const internal = options.__internal ?? {};
                const useDebug = internal.debug === true;
                if (useDebug) {
                    Debug.enable('prisma-client');
                }
                if (internal.hooks) {
                    this._hooks = internal.hooks;
                }
                let predefinedDatasources = config.sqliteDatasourceOverrides ?? [];
                predefinedDatasources = predefinedDatasources.map((d) => ({
                    name: d.name,
                    url: 'file:' + path.resolve(config.dirname, d.url),
                }));
                const thedatasources = options.datasources || {};
                const inputDatasources = Object.entries(thedatasources)
                    /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
                    .filter(([_, source]) => {
                    return source && source.url;
                })
                    .map(([name, { url }]) => ({
                    name,
                    url,
                }));
                const datasources = mergeBy(predefinedDatasources, inputDatasources, (source) => source.name);
                const engineConfig = internal.engine || {};
                if (options.errorFormat) {
                    this._errorFormat = options.errorFormat;
                }
                else if (process.env.NODE_ENV === 'production') {
                    this._errorFormat = 'minimal';
                }
                else if (process.env.NO_COLOR) {
                    this._errorFormat = 'colorless';
                }
                else {
                    this._errorFormat = 'colorless'; // default errorFormat
                }
                this._dmmf = new DMMFClass(config.document);
                let cwd = path.resolve(config.dirname, config.relativePath);
                if (!fs.existsSync(cwd)) {
                    cwd = config.dirname;
                }
                this._previewFeatures = config.generator?.previewFeatures ?? [];
                this._engineConfig = {
                    cwd,
                    dirname: config.dirname,
                    enableDebugLogs: useDebug,
                    enableEngineDebugMode: engineConfig.enableEngineDebugMode,
                    datamodelPath: path.join(config.dirname, 'schema.prisma'),
                    prismaPath: engineConfig.binaryPath ?? undefined,
                    engineEndpoint: engineConfig.endpoint,
                    datasources,
                    generator: config.generator,
                    showColors: this._errorFormat === 'pretty',
                    logLevel: options.log && getLogLevel(options.log),
                    logQueries: options.log &&
                        Boolean(typeof options.log === 'string'
                            ? options.log === 'query'
                            : options.log.find((o) => typeof o === 'string' ? o === 'query' : o.level === 'query')),
                    env: loadedEnv ? loadedEnv.parsed : {},
                    flags: [],
                    clientVersion: config.clientVersion,
                    enableExperimental: mapPreviewFeatures(this._previewFeatures),
                    useUds: internal.useUds,
                    activeProvider: config.activeProvider,
                };
                debug({ clientVersion: config.clientVersion });
                this._engine = new NodeEngine(this._engineConfig);
                this._fetcher = new PrismaClientFetcher(this, false, this._hooks);
                if (options.log) {
                    for (const log of options.log) {
                        const level = typeof log === 'string'
                            ? log
                            : log.emit === 'stdout'
                                ? log.level
                                : null;
                        if (level) {
                            this.$on(level, (event) => {
                                const colorMap = {
                                    query: 'blue',
                                    info: 'cyan',
                                    warn: 'yellow',
                                    error: 'red',
                                };
                                console.error(chalk[colorMap[level]](`prisma:${level}`.padEnd(13)) +
                                    (event.message || event.query));
                            });
                        }
                    }
                }
                this._bootstrapClient();
                void this._getActiveProvider();
            }
            catch (e) {
                e.clientVersion = this._clientVersion;
                throw e;
            }
        }
        get [Symbol.toStringTag]() {
            return 'PrismaClient';
        }
        $use(namespace, cb) {
            if (typeof namespace === 'function') {
                this._middlewares.push(namespace);
            }
            else if (typeof namespace === 'string') {
                if (namespace === 'all') {
                    this._middlewares.push(cb);
                }
                else if (namespace === 'engine') {
                    this._engineMiddlewares.push(cb);
                }
                else {
                    throw new Error(`Unknown middleware hook ${namespace}`);
                }
            }
            else {
                throw new Error(`Invalid middleware ${namespace}`);
            }
        }
        $on(eventType, callback) {
            if (eventType === 'beforeExit') {
                this._engine.on('beforeExit', callback);
            }
            else {
                this._engine.on(eventType, (event) => {
                    const fields = event.fields;
                    if (eventType === 'query') {
                        return callback({
                            timestamp: event.timestamp,
                            query: fields.query,
                            params: fields.params,
                            duration: fields.duration_ms,
                            target: event.target,
                        });
                    }
                    else {
                        // warn, info, or error events
                        return callback({
                            timestamp: event.timestamp,
                            message: fields.message,
                            target: event.target,
                        });
                    }
                });
            }
        }
        $connect() {
            try {
                return this._engine.start();
            }
            catch (e) {
                e.clientVersion = this._clientVersion;
                throw e;
            }
        }
        /**
         * @private
         */
        async _runDisconnect() {
            await this._engine.stop();
            delete this._connectionPromise;
            this._engine = new NodeEngine(this._engineConfig);
            delete this._disconnectionPromise;
            delete this._getConfigPromise;
        }
        /**
         * Disconnect from the database
         */
        $disconnect() {
            try {
                return this._engine.stop();
            }
            catch (e) {
                e.clientVersion = this._clientVersion;
                throw e;
            }
        }
        async _getActiveProvider() {
            try {
                const configResult = await this._engine.getConfig();
                this._activeProvider = configResult.datasources[0].activeProvider;
            }
            catch (e) {
                // it's ok to silently fail
            }
        }
        /**
         * Executes a raw query. Always returns a number
         */
        $executeRawInternal(runInTransaction, transactionId, stringOrTemplateStringsArray, ...values) {
            // TODO Clean up types
            let query = '';
            let parameters = undefined;
            if (typeof stringOrTemplateStringsArray === 'string') {
                // If this was called as prisma.$executeRaw(<SQL>, [...values]), assume it is a pre-prepared SQL statement, and forward it without any changes
                query = stringOrTemplateStringsArray;
                parameters = {
                    values: serializeRawParameters(values || []),
                    __prismaRawParamaters__: true,
                };
            }
            else if (isReadonlyArray(stringOrTemplateStringsArray)) {
                // If this was called as prisma.$executeRaw`<SQL>`, try to generate a SQL prepared statement
                switch (this._activeProvider) {
                    case 'sqlite':
                    case 'mysql': {
                        const queryInstance = sqlTemplateTag.sqltag(stringOrTemplateStringsArray, ...values);
                        query = queryInstance.sql;
                        parameters = {
                            values: serializeRawParameters(queryInstance.values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                    case 'postgresql': {
                        const queryInstance = sqlTemplateTag.sqltag(stringOrTemplateStringsArray, ...values);
                        query = queryInstance.text;
                        parameters = {
                            values: serializeRawParameters(queryInstance.values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                    case 'sqlserver': {
                        query = mssqlPreparedStatement(stringOrTemplateStringsArray);
                        parameters = {
                            values: serializeRawParameters(values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                }
            }
            else {
                // If this was called as prisma.raw(sql`<SQL>`), use prepared statements from sql-template-tag
                switch (this._activeProvider) {
                    case 'sqlite':
                    case 'mysql':
                        query = stringOrTemplateStringsArray.sql;
                        break;
                    case 'postgresql':
                        query = stringOrTemplateStringsArray.text;
                        break;
                    case 'sqlserver':
                        query = mssqlPreparedStatement(stringOrTemplateStringsArray.strings);
                        break;
                }
                parameters = {
                    values: serializeRawParameters(stringOrTemplateStringsArray.values),
                    __prismaRawParamaters__: true,
                };
            }
            if (parameters?.values) {
                debug(`prisma.$executeRaw(${query}, ${parameters.values})`);
            }
            else {
                debug(`prisma.$executeRaw(${query})`);
            }
            const args = { query, parameters };
            debug(`Prisma Client call:`);
            return this._request({
                args,
                clientMethod: 'executeRaw',
                dataPath: [],
                action: 'executeRaw',
                callsite: this._getCallsite(),
                runInTransaction,
                transactionId: transactionId ?? undefined,
            });
        }
        /**
         * Executes a raw query. Always returns a number
         */
        $executeRaw(stringOrTemplateStringsArray, ...values) {
            const doRequest = (runInTransaction = false, transactionId) => {
                try {
                    const promise = this.$executeRawInternal(runInTransaction, transactionId ?? null, stringOrTemplateStringsArray, ...values);
                    promise.isExecuteRaw = true;
                    return promise;
                }
                catch (e) {
                    e.clientVersion = this._clientVersion;
                    throw e;
                }
            };
            return {
                then(onfulfilled, onrejected) {
                    return doRequest().then(onfulfilled, onrejected);
                },
                requestTransaction(transactionId) {
                    return doRequest(true, transactionId);
                },
                catch(onrejected) {
                    return doRequest().catch(onrejected);
                },
                finally(onfinally) {
                    return doRequest().finally(onfinally);
                },
            };
        }
        _getCallsite() {
            if (this._errorFormat !== 'minimal') {
                return new Error().stack;
            }
            return undefined;
        }
        /**
         * Executes a raw query. Always returns a number
         */
        $queryRawInternal(runInTransaction, transactionId, stringOrTemplateStringsArray, ...values) {
            let query = '';
            let parameters = undefined;
            if (typeof stringOrTemplateStringsArray === 'string') {
                // If this was called as prisma.$queryRaw(<SQL>, [...values]), assume it is a pre-prepared SQL statement, and forward it without any changes
                query = stringOrTemplateStringsArray;
                parameters = {
                    values: serializeRawParameters(values || []),
                    __prismaRawParamaters__: true,
                };
            }
            else if (isReadonlyArray(stringOrTemplateStringsArray)) {
                // If this was called as prisma.$queryRaw`<SQL>`, try to generate a SQL prepared statement
                switch (this._activeProvider) {
                    case 'sqlite':
                    case 'mysql': {
                        const queryInstance = sqlTemplateTag.sqltag(stringOrTemplateStringsArray, ...values);
                        query = queryInstance.sql;
                        parameters = {
                            values: serializeRawParameters(queryInstance.values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                    case 'postgresql': {
                        const queryInstance = sqlTemplateTag.sqltag(stringOrTemplateStringsArray, ...values);
                        query = queryInstance.text;
                        parameters = {
                            values: serializeRawParameters(queryInstance.values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                    case 'sqlserver': {
                        query = mssqlPreparedStatement(stringOrTemplateStringsArray);
                        parameters = {
                            values: serializeRawParameters(values),
                            __prismaRawParamaters__: true,
                        };
                        break;
                    }
                }
            }
            else {
                // If this was called as prisma.raw(sql`<SQL>`), use prepared statements from sql-template-tag
                switch (this._activeProvider) {
                    case 'sqlite':
                    case 'mysql':
                        query = stringOrTemplateStringsArray.sql;
                        break;
                    case 'postgresql':
                        query = stringOrTemplateStringsArray.text;
                        break;
                    case 'sqlserver':
                        query = mssqlPreparedStatement(stringOrTemplateStringsArray.strings);
                        break;
                }
                parameters = {
                    values: serializeRawParameters(stringOrTemplateStringsArray.values),
                    __prismaRawParamaters__: true,
                };
            }
            if (parameters?.values) {
                debug(`prisma.queryRaw(${query}, ${parameters.values})`);
            }
            else {
                debug(`prisma.queryRaw(${query})`);
            }
            const args = { query, parameters };
            debug(`Prisma Client call:`);
            // const doRequest = (runInTransaction = false) => {
            return this._request({
                args,
                clientMethod: 'queryRaw',
                dataPath: [],
                action: 'queryRaw',
                callsite: this._getCallsite(),
                runInTransaction,
                transactionId: transactionId ?? undefined,
            });
        }
        /**
         * Executes a raw query. Always returns a number
         */
        $queryRaw(stringOrTemplateStringsArray, ...values) {
            const doRequest = (runInTransaction = false, transactionId) => {
                try {
                    const promise = this.$queryRawInternal(runInTransaction, transactionId ?? null, stringOrTemplateStringsArray, ...values);
                    promise.isQueryRaw = true;
                    return promise;
                }
                catch (e) {
                    e.clientVersion = this._clientVersion;
                    throw e;
                }
            };
            return {
                then(onfulfilled, onrejected) {
                    return doRequest().then(onfulfilled, onrejected);
                },
                requestTransaction(transactionId) {
                    return doRequest(true, transactionId);
                },
                catch(onrejected) {
                    return doRequest().catch(onrejected);
                },
                finally(onfinally) {
                    return doRequest().finally(onfinally);
                },
            };
        }
        __internal_triggerPanic(fatal) {
            if (!this._engineConfig.enableEngineDebugMode) {
                throw new Error(`In order to use .__internal_triggerPanic(), please enable the debug mode like so:
new PrismaClient({
  __internal: {
    engine: {
      enableEngineDebugMode: true
    }
  }
})`);
            }
            const query = 'SELECT 1';
            const headers = fatal
                ? { 'X-DEBUG-FATAL': '1' }
                : { 'X-DEBUG-NON-FATAL': '1' };
            return this._request({
                action: 'queryRaw',
                args: {
                    query,
                    parameters: undefined,
                },
                clientMethod: 'queryRaw',
                dataPath: [],
                runInTransaction: false,
                headers,
                callsite: this._getCallsite(),
            });
        }
        getTransactionId() {
            return this._transactionId++;
        }
        async $transactionInternal(promises) {
            for (const p of promises) {
                if (!p) {
                    throw new Error(`All elements of the array need to be Prisma Client promises. Hint: Please make sure you are not awaiting the Prisma client calls you intended to pass in the $transaction function.`);
                }
                if ((!p.requestTransaction ||
                    typeof p.requestTransaction !== 'function') &&
                    !p?.isQueryRaw &&
                    !p?.isExecuteRaw) {
                    throw new Error(`All elements of the array need to be Prisma Client promises. Hint: Please make sure you are not awaiting the Prisma client calls you intended to pass in the $transaction function.`);
                }
            }
            const transactionId = this.getTransactionId();
            return Promise.all(promises.map((p) => {
                if (p.requestTransaction) {
                    return p.requestTransaction(transactionId);
                }
                else {
                }
                return p;
            }));
        }
        async $transaction(promises) {
            try {
                return this.$transactionInternal(promises);
            }
            catch (e) {
                e.clientVersion = this._clientVersion;
                throw e;
            }
        }
        _request(internalParams) {
            try {
                const resource = new AsyncResource('prisma-client-request');
                if (this._middlewares.length > 0) {
                    // https://perf.link/# %3D
                    const params = {
                        args: internalParams.args,
                        dataPath: internalParams.dataPath,
                        runInTransaction: internalParams.runInTransaction,
                        action: internalParams.action,
                        model: internalParams.model,
                    };
                    return resource.runInAsyncScope(() => this._requestWithMiddlewares(params, this._middlewares.slice(), internalParams.clientMethod, internalParams.callsite, internalParams.headers));
                }
                return resource.runInAsyncScope(() => this._executeRequest(internalParams));
            }
            catch (e) {
                e.clientVersion = this._clientVersion;
                throw e;
            }
        }
        _requestWithMiddlewares(params, middlewares, clientMethod, callsite, headers) {
            const middleware = middlewares.shift();
            if (middleware) {
                return middleware(params, (params2) => this._requestWithMiddlewares(params2, middlewares, clientMethod, callsite));
            }
            // No, we won't copy the whole object here just to make it easier to do TypeScript
            // as it would be much slower
            ;
            params.clientMethod = clientMethod;
            params.callsite = callsite;
            params.headers = headers;
            return this._executeRequest(params);
        }
        _executeRequest({ args, clientMethod, dataPath, callsite, runInTransaction, action, model, headers, transactionId, }) {
            if (action !== 'executeRaw' && action !== 'queryRaw' && !model) {
                throw new Error(`Model missing for action ${action}`);
            }
            if ((action === 'executeRaw' || action === 'queryRaw') && model) {
                throw new Error(`executeRaw and queryRaw can't be executed on a model basis. The model ${model} has been provided`);
            }
            if (action === 'findOne')
                action = 'findUnique';
            let rootField;
            const operation = actionOperationMap[action];
            if (action === 'executeRaw' || action === 'queryRaw') {
                rootField = action;
            }
            // TODO: Replace with lookup map for speedup
            let mapping;
            if (model) {
                mapping = this._dmmf.mappingsMap[model];
                if (!mapping) {
                    throw new Error(`Could not find mapping for model ${model}`);
                }
                rootField = mapping[action];
            }
            if (operation !== 'query' && operation !== 'mutation') {
                throw new Error(`Invalid operation ${operation} for action ${action}`);
            }
            const field = this._dmmf.rootFieldMap[rootField];
            if (!field) {
                throw new Error(`Could not find rootField ${rootField} for action ${action} for model ${model} on rootType ${operation}`);
            }
            const { isList } = field.outputType;
            const typeName = getOutputTypeName(field.outputType.type);
            let document = makeDocument({
                dmmf: this._dmmf,
                rootField: rootField,
                rootTypeName: operation,
                select: args,
            });
            document.validate(args, false, clientMethod, this._errorFormat, callsite);
            document = transformDocument(document);
            // as printJsonWithErrors takes a bit of compute
            // we only want to do it, if debug is enabled for 'prisma-client'
            if (Debug.enabled('prisma-client')) {
                const query = String(document);
                debug(`Prisma Client call:`);
                debug(`prisma.${clientMethod}(${printJsonWithErrors({
                    ast: args,
                    keyPaths: [],
                    valuePaths: [],
                    missingItems: [],
                })})`);
                debug(`Generated request:`);
                debug(query + '\n');
            }
            return this._fetcher.request({
                document,
                clientMethod,
                typeName,
                dataPath,
                isList,
                rootField: rootField,
                callsite,
                showColors: this._errorFormat === 'pretty',
                args,
                engineHook: this._engineMiddlewares[0],
                runInTransaction,
                headers,
                transactionId,
            });
        }
        _bootstrapClient() {
            const clients = this._dmmf.mappings.modelOperations.reduce((acc, mapping) => {
                const lowerCaseModel = lowerCase(mapping.model);
                const model = this._dmmf.modelMap[mapping.model];
                if (!model) {
                    throw new Error(`Invalid mapping ${mapping.model}, can't find model`);
                }
                const prismaClient = ({ operation, actionName, args, dataPath, modelName, }) => {
                    if (actionName === 'findOne') {
                        console.warn(`${chalk.yellow('warn(prisma) ')} findOne is deprecated. Please use findUnique instead.`);
                    }
                    dataPath = dataPath ?? [];
                    const clientMethod = `${lowerCaseModel}.${actionName}`;
                    let requestPromise;
                    const callsite = this._getCallsite();
                    const requestModelName = modelName ?? model.name;
                    const clientImplementation = {
                        then: (onfulfilled, onrejected) => {
                            if (!requestPromise) {
                                requestPromise = this._request({
                                    args,
                                    dataPath,
                                    action: actionName,
                                    model: requestModelName,
                                    clientMethod,
                                    callsite,
                                    runInTransaction: false,
                                });
                            }
                            return requestPromise.then(onfulfilled, onrejected);
                        },
                        requestTransaction: (transactionId) => {
                            if (!requestPromise) {
                                requestPromise = this._request({
                                    args,
                                    dataPath,
                                    action: actionName,
                                    model: requestModelName,
                                    clientMethod,
                                    callsite,
                                    runInTransaction: true,
                                    transactionId,
                                });
                            }
                            return requestPromise;
                        },
                        catch: (onrejected) => {
                            if (!requestPromise) {
                                requestPromise = this._request({
                                    args,
                                    dataPath,
                                    action: actionName,
                                    model: requestModelName,
                                    clientMethod,
                                    callsite,
                                    runInTransaction: false,
                                });
                            }
                            return requestPromise.catch(onrejected);
                        },
                        finally: (onfinally) => {
                            if (!requestPromise) {
                                requestPromise = this._request({
                                    args,
                                    dataPath,
                                    action: actionName,
                                    model: requestModelName,
                                    clientMethod,
                                    callsite,
                                    runInTransaction: false,
                                });
                            }
                            return requestPromise.finally(onfinally);
                        },
                    };
                    // add relation fields
                    for (const field of model.fields.filter((f) => f.kind === 'object')) {
                        clientImplementation[field.name] = (fieldArgs) => {
                            const prefix = dataPath.includes('select')
                                ? 'select'
                                : dataPath.includes('include')
                                    ? 'include'
                                    : 'select';
                            const newDataPath = [...dataPath, prefix, field.name];
                            const newArgs = deepSet(args, newDataPath, fieldArgs || true);
                            return clients[field.type]({
                                operation,
                                actionName,
                                args: newArgs,
                                dataPath: newDataPath,
                                isList: field.isList,
                                /*
                                 * necessary for user.posts() calls -> the original model name needs to be preserved
                                 */
                                modelName: modelName || model.name,
                            });
                        };
                    }
                    return clientImplementation;
                };
                acc[model.name] = prismaClient;
                return acc;
            }, {});
            for (const mapping of this._dmmf.mappings.modelOperations) {
                const lowerCaseModel = lowerCase(mapping.model);
                const filteredActionsList = {
                    model: true,
                    plural: true,
                    aggregate: true,
                    groupBy: true,
                };
                const newMapping = {
                    ...mapping,
                    findOne: mapping.findUnique,
                };
                const delegate = Object.keys(newMapping).reduce((acc, actionName) => {
                    if (!filteredActionsList[actionName]) {
                        const operation = getOperation(actionName);
                        acc[actionName] = (args) => clients[mapping.model]({
                            operation,
                            actionName,
                            args,
                        });
                    }
                    return acc;
                }, {});
                delegate.count = (args) => {
                    return clients[mapping.model]({
                        operation: 'query',
                        actionName: `aggregate`,
                        args: args
                            ? {
                                ...args,
                                select: { count: { select: { _all: true } } },
                            }
                            : {
                                select: { count: { select: { _all: true } } },
                            },
                        dataPath: ['count'],
                    });
                };
                delegate.aggregate = (args) => {
                    /**
                     * avg, count, sum, min, max need to go into select
                     * For speed reasons we can go with "for in "
                     */
                    const select = Object.entries(args).reduce((acc, [key, value]) => {
                        // if it is an aggregate like "avg", wrap it with "select"
                        if (aggregateKeys[key]) {
                            if (!acc.select) {
                                acc.select = {};
                            }
                            // `count` doesn't have a sub-selection
                            if (key === 'count') {
                                acc.select[key] = { select: { _all: value } };
                            }
                            else {
                                acc.select[key] = { select: value };
                            }
                        }
                        else {
                            acc[key] = value;
                        }
                        return acc;
                    }, {});
                    return clients[mapping.model]({
                        operation: 'query',
                        actionName: 'aggregate',
                        rootField: mapping.aggregate,
                        args: select,
                        dataPath: [],
                    });
                };
                delegate.groupBy = (args) => {
                    if (!this._previewFeatures.includes('groupBy')) {
                        throw new Error(`To use "groupBy", please add "groupBy" to the previewFeatures attribute in the generator block:
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["groupBy"]
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
`);
                    }
                    /**
                     * avg, count, sum, min, max need to go into select
                     * For speed reasons we can go with "for in "
                     */
                    const select = Object.entries(args).reduce((acc, [key, value]) => {
                        // if it is an aggregate like "avg", wrap it with "select"
                        if (aggregateKeys[key]) {
                            if (!acc.select) {
                                acc.select = {};
                            }
                            acc.select[key] = { select: value };
                            // otherwise leave it alone
                        }
                        else {
                            acc[key] = value;
                        }
                        return acc;
                    }, {});
                    return clients[mapping.model]({
                        operation: 'query',
                        actionName: 'groupBy',
                        rootField: mapping.groupBy,
                        args: select,
                        dataPath: [],
                    });
                };
                this[lowerCaseModel] = delegate;
            }
        }
    }
    return NewPrismaClient;
}
export class PrismaClientFetcher {
    constructor(prisma, enableDebug = false, hooks) {
        this.prisma = prisma;
        this.debug = enableDebug;
        this.hooks = hooks;
        this.dataloader = new Dataloader({
            batchLoader: (requests) => {
                const queries = requests.map((r) => String(r.document));
                const runTransaction = requests[0].runInTransaction;
                return this.prisma._engine.requestBatch(queries, runTransaction);
            },
            singleLoader: (request) => {
                const query = String(request.document);
                return this.prisma._engine.request(query, request.headers);
            },
            batchBy: (request) => {
                if (request.runInTransaction) {
                    if (request.transactionId) {
                        return `transaction-batch-${request.transactionId}`;
                    }
                    return 'transaction-batch';
                }
                if (!(request.document.children[0].name.startsWith('findOne') ||
                    request.document.children[0].name.startsWith('findUnique'))) {
                    return null;
                }
                const selectionSet = request.document.children[0].children.join(',');
                const args = request.document.children[0].args?.args
                    .map((a) => {
                    if (a.value instanceof Args) {
                        return a.key + '-' + a.value.args.map((a) => a.key).join(',');
                    }
                    return a.key;
                })
                    .join(',');
                return `${request.document.children[0].name}|${args}|${selectionSet}`;
            },
        });
    }
    get [Symbol.toStringTag]() {
        return 'PrismaClientFetcher';
    }
    async request({ document, dataPath = [], rootField, typeName, isList, callsite, clientMethod, runInTransaction, showColors, engineHook, args, headers, transactionId, }) {
        if (this.hooks && this.hooks.beforeRequest) {
            const query = String(document);
            this.hooks.beforeRequest({
                query,
                path: dataPath,
                rootField,
                typeName,
                document,
                isList,
                clientMethod,
                args,
            });
        }
        try {
            /**
             * If there's an engine hook, use it here
             */
            let data, elapsed;
            if (engineHook) {
                const result = await engineHook({
                    document,
                    runInTransaction,
                }, (params) => this.dataloader.request(params));
                data = result.data;
                elapsed = result.elapsed;
            }
            else {
                const result = await this.dataloader.request({
                    document,
                    runInTransaction,
                    headers,
                    transactionId,
                });
                data = result.data;
                elapsed = result.elapsed;
            }
            /**
             * Unpack
             */
            const unpackResult = this.unpack(document, data, dataPath, rootField);
            if (process.env.PRISMA_CLIENT_GET_TIME) {
                return { data: unpackResult, elapsed };
            }
            return unpackResult;
        }
        catch (e) {
            debug(e);
            let message = e.message;
            if (callsite) {
                const { stack } = printStack({
                    callsite,
                    originalMethod: clientMethod,
                    onUs: e.isPanic,
                    showColors,
                });
                message = stack + '\n  ' + e.message;
            }
            message = this.sanitizeMessage(message);
            // TODO: Do request with callsite instead, so we don't need to rethrow
            if (e.code) {
                throw new PrismaClientKnownRequestError(message, e.code, this.prisma._clientVersion, e.meta);
            }
            else if (e.isPanic) {
                throw new PrismaClientRustPanicError(message, this.prisma._clientVersion);
            }
            else if (e instanceof PrismaClientUnknownRequestError) {
                throw new PrismaClientUnknownRequestError(message, this.prisma._clientVersion);
            }
            else if (e instanceof PrismaClientInitializationError) {
                throw new PrismaClientInitializationError(message, this.prisma._clientVersion);
            }
            else if (e instanceof PrismaClientRustPanicError) {
                throw new PrismaClientRustPanicError(message, this.prisma._clientVersion);
            }
            e.clientVersion = this.prisma._clientVersion;
            throw e;
        }
    }
    sanitizeMessage(message) {
        if (this.prisma._errorFormat && this.prisma._errorFormat !== 'pretty') {
            return stripAnsi(message);
        }
        return message;
    }
    unpack(document, data, path, rootField) {
        if (data.data) {
            data = data.data;
        }
        // to lift up _all in count
        if (rootField.startsWith('aggregate') && data[rootField].count) {
            data[rootField].count = data[rootField].count._all;
        }
        const getPath = [];
        if (rootField) {
            getPath.push(rootField);
        }
        getPath.push(...path.filter((p) => p !== 'select' && p !== 'include'));
        return unpack({ document, data, path: getPath });
    }
}
export function getOperation(action) {
    if (action === DMMF.ModelAction.findMany ||
        action === DMMF.ModelAction.findUnique ||
        action === 'findOne' ||
        action === DMMF.ModelAction.findFirst) {
        return 'query';
    }
    return 'mutation';
}
//# sourceMappingURL=getPrismaClient.js.map