// Generated by nautilus-codegen. Do not edit manually.

import { NautilusClient } from '../_internal/_client.js';
import { NotFoundError } from '../_internal/_errors.js';
import {
  EventPhase,
  createCrudEventContext,
  createModelEvents,
  defaultCrudResult,
  resolveStopResult,
  runCrudEvent,
} from '../_internal/_events.js';
import { PROTOCOL_VERSION } from '../_internal/_protocol.js';
{%- if has_enums %}
import { {% for e in enum_imports %}{{ e }}{% if not loop.last %}, {% endif %}{% endfor %} } from '../enums.js';
{%- endif %}
{%- if has_extension_types %}
{%- for ext in extension_imports %}
import { {% for t in ext.types %}{{ t }}{% if not loop.last %}, {% endif %}{% endfor %} } from '../extensions/{{ ext.module }}/types.js';
{%- endfor %}
{%- endif %}
{%- if has_includes %}
{%- for field in include_fields %}
{%- if field.target_model != model_name %}
import { _coerce{{ field.target_model }} as _coerce{{ field.target_model }}_for_{{ field.name }} } from './{{ field.target_snake }}.js';
{%- endif %}
{%- endfor %}
{%- endif %}

export const {{ model_name }} = createModelEvents('{{ model_name }}');

function _{{ model_name }}EventContext(client, operation, phase, args, payload, state, result, error) {
  return createCrudEventContext({
    db: client,
    model: {{ model_name }},
    modelName: '{{ model_name }}',
    operation,
    phase,
    args,
    payload,
    state,
    result,
    error,
  });
}

/** Maps `table__dbColumn` keys returned by the engine to JS field names. */
const _{{ table_name }}RowMap = {
{%- for f in scalar_fields %}
  '{{ table_name }}__{{ f.db_name }}': '{{ f.name }}',
{%- endfor %}
};

const _{{ table_name }}Fields = new Set([
{%- for f in scalar_fields %}
  '{{ f.name }}',
{%- endfor %}
]);

const _{{ table_name }}ArrayFields = new Set([
{%- for f in scalar_fields %}
{%- if f.is_array %}
  '{{ f.name }}',
{%- endif %}
{%- endfor %}
]);

const _{{ table_name }}BooleanFields = new Set([
{%- for f in scalar_fields %}
{%- if f.base_type == "boolean" %}
  '{{ f.name }}',
{%- endif %}
{%- endfor %}
]);

/** Maps JS / logical field names to DB column names (handles @map renames). */
const _{{ table_name }}FieldToDb = {
{%- for f in scalar_fields %}
  '{{ f.logical_name }}': '{{ f.db_name }}',
{%- if f.name != f.logical_name %}
  '{{ f.name }}': '{{ f.db_name }}',
{%- endif %}
{%- endfor %}
};

const _{{ table_name }}FieldExtensionToWire = {
{%- for f in scalar_fields %}
{%- if f.extension_input_serializer %}
  '{{ f.logical_name }}': {{ f.extension_input_serializer }},
{%- if f.name != f.logical_name %}
  '{{ f.name }}': {{ f.extension_input_serializer }},
{%- endif %}
{%- endif %}
{%- endfor %}
};

const _{{ table_name }}ObjectValueDbFields = new Set([
{%- for db_name in object_value_db_fields %}
  '{{ db_name }}',
{%- endfor %}
]);

const _FILTER_OPERATOR_KEYS = new Set([
  'contains',
  'endsWith',
  'eq',
  'equals',
  'gt',
  'gte',
  'in',
  'isNull',
  'isNotNull',
  'like',
  'lt',
  'lte',
  'not',
  'notIn',
  'startsWith',
]);

const _MISSING = Symbol('missing');

function _getWireValue(row, ...keys) {
  for (const key of keys) {
    if (Object.prototype.hasOwnProperty.call(row, key)) return row[key];
  }
  return _MISSING;
}

function _hydrate{{ model_name }}Relation(field, value) {
{%- for field in include_fields %}
  if (field === '{{ field.name }}') {
    const coerceTarget = {% if field.target_model == model_name %}_coerce{{ field.target_model }}{% else %}_coerce{{ field.target_model }}_for_{{ field.name }}{% endif %};
{%- if field.is_array %}
    if (value == null) return [];
    if (Array.isArray(value)) {
      return value
        .filter(item => item != null && typeof item === 'object' && !Array.isArray(item))
        .map(item => coerceTarget(item));
    }
    return [];
{%- else %}
    if (value == null) return null;
    if (typeof value === 'object' && !Array.isArray(value)) return coerceTarget(value);
    return null;
{%- endif %}
  }
{%- endfor %}
  return value;
}

function _coerceBooleanValue(value) {
  if (typeof value === 'boolean') return value;
  if (typeof value === 'number' && (value === 0 || value === 1)) return value === 1;
  if (typeof value === 'string') {
    const normalized = value.toLowerCase();
    if (normalized === 'true' || normalized === '1') return true;
    if (normalized === 'false' || normalized === '0') return false;
  }
  return value;
}

function _looksLikeFilterOperatorObject(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
  const keys = Object.keys(value);
  return keys.length > 0 && keys.every(key => _FILTER_OPERATOR_KEYS.has(key));
}

function _objectEqualityRequiresExplicitEquals(fieldName) {
  throw new TypeError(
    `Field '${fieldName}' stores object-like JSON/HSTORE values. Use { equals: ... } for object equality filters.`,
  );
}

function _serializeScalarInput(fieldName, value) {
  const serializer = _{{ table_name }}FieldExtensionToWire[fieldName];
  return _toWireValue(serializer ? serializer(value) : value);
}

function _serializeFilterInput(fieldName, operator, value) {
  const serializer = _{{ table_name }}FieldExtensionToWire[fieldName];
  if (!serializer) return _toWireValue(value);
  if ((operator === 'in' || operator === 'notIn') && Array.isArray(value)) {
    return value.map(item => _toWireValue(serializer(item)));
  }
  return _toWireValue(serializer(value));
}

function _serializeNearestInput(nearest) {
  if (!nearest || typeof nearest !== 'object' || Array.isArray(nearest)) return nearest;
  const result = { ...nearest };
  if (typeof result.field === 'string' && Object.prototype.hasOwnProperty.call(result, 'query')) {
    result.query = _serializeScalarInput(result.field, result.query);
  }
  return result;
}

function _toWireValue(value) {
  if (value == null) return value;
  if (value instanceof Date) return value.toISOString();
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return value;
  if (typeof value.toWire === 'function') return _toWireValue(value.toWire());
  if (Array.isArray(value)) return value.map(item => _toWireValue(item));
  if (typeof value === 'object') {
    return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, _toWireValue(item)]));
  }
  return value;
}

export function _coerce{{ model_name }}(row) {
  const out = {};
{%- for f in scalar_fields %}
  {
    const value = _getWireValue(row, '{{ table_name }}__{{ f.db_name }}', '{{ f.logical_name }}');
    if (value !== _MISSING) {
      if (value == null) {
        out['{{ f.name }}'] = _{{ table_name }}ArrayFields.has('{{ f.name }}') ? [] : null;
      } else {
        let coerced = value;
        if (_{{ table_name }}BooleanFields.has('{{ f.name }}')) coerced = _coerceBooleanValue(value);
{%- if f.base_type == "Date" %}
        if (typeof value === 'string') coerced = new Date(value);
{%- elif f.base_type == "Buffer" %}
        if (typeof value === 'string') coerced = Buffer.from(value, 'base64');
{%- elif f.extension_coercer %}
        coerced = {{ f.extension_coercer }}(value);
{%- endif %}
        out['{{ f.name }}'] = coerced;
      }
    }
  }
{%- endfor %}
{%- for field in include_fields %}
  {
    const relationValue = _getWireValue(row, '{{ field.name }}_json');
    if (relationValue !== _MISSING) {
      out['{{ field.name }}'] = _hydrate{{ model_name }}Relation('{{ field.name }}', relationValue);
    }
  }
{%- endfor %}
  return out;
}

function _processWhereFilters(where, fieldToDb) {
  const out = {};
  for (const [k, v] of Object.entries(where)) {
    if (k === 'AND' || k === 'OR' || k === 'NOT') {
      if (Array.isArray(v)) {
        out[k] = v.map(item => _processWhereFilters(item, fieldToDb));
      } else if (v && typeof v === 'object') {
        out[k] = _processWhereFilters(v, fieldToDb);
      } else {
        out[k] = v;
      }
    } else {
      const dbKey = fieldToDb[k] ?? k;
      const hasExtensionSerializer = Object.prototype.hasOwnProperty.call(_{{ table_name }}FieldExtensionToWire, k);
      if (
        v instanceof Date
        || (typeof Buffer !== 'undefined' && Buffer.isBuffer(v))
        || (v && typeof v.toWire === 'function')
      ) {
        out[dbKey] = _toWireValue(v);
      } else if (v && typeof v === 'object' && !Array.isArray(v)) {
        if (
          hasExtensionSerializer
          && !_{{ table_name }}ObjectValueDbFields.has(dbKey)
          && !_looksLikeFilterOperatorObject(v)
        ) {
          out[dbKey] = _serializeScalarInput(k, v);
          continue;
        }
        if (_{{ table_name }}ObjectValueDbFields.has(dbKey) && !_looksLikeFilterOperatorObject(v)) {
          _objectEqualityRequiresExplicitEquals(k);
        }
        const ops = {};
        for (const [op, opVal] of Object.entries(v)) {
          const actualOp = op === 'equals' ? 'eq' : op;
          ops[actualOp] = _serializeFilterInput(k, actualOp, opVal);
        }
        if (Object.keys(ops).length > 0) out[dbKey] = ops;
      } else {
        out[dbKey] = _serializeScalarInput(k, v);
      }
    }
  }
  return out;
}

function _processCreateData(data, fieldToDb) {
  const out = {};
  for (const [k, v] of Object.entries(data)) {
    const dbKey = fieldToDb[k] ?? k;
    out[dbKey] = _serializeScalarInput(k, v);
  }
  return out;
}

function _serializeInclude(include) {
  if (include == null || typeof include === 'boolean') return include;
  if (typeof include !== 'object' || Array.isArray(include)) return include;
  const result = {};
  for (const [field, spec] of Object.entries(include)) {
    if (typeof spec === 'boolean') {
      result[field] = spec;
    } else if (spec && typeof spec === 'object') {
      result[field] = _serializeInclude(spec);
    } else {
      result[field] = spec;
    }
  }
  return result;
}

function _firstDataRow(result) {
  const data = result?.data;
  if (!Array.isArray(data)) return null;
  const row = data[0];
  if (row == null || typeof row !== 'object' || Array.isArray(row)) return null;
  return row;
}

export class {{ delegate_name }} {
  constructor(client) {
    this.client = client;
  }

  /**
   * Fetch every matching row and resolve a final in-memory array.
   *
   * Prefer this for small or medium result sets, eager-loaded reads, and any
   * downstream path that already wants the whole collection before continuing.
   */
  async findMany(args) {
    const rpcArgs = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    if (args?.take     != null) rpcArgs['take']    = args.take;
    if (args?.skip     != null) rpcArgs['skip']    = args.skip;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    if (args?.cursor   != null) rpcArgs['cursor']  = args.cursor;
    if (args?.distinct != null) rpcArgs['distinct'] = args.distinct;
    const request = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
    };
    if (args?.chunkSize != null) request['chunkSize'] = args.chunkSize;
    const result = await this.client._rpc('query.findMany', request);
    return ((result['data']) ?? []).map(r => _coerce{{ model_name }}(r));
  }

  /**
   * Yield matching rows incrementally as an async iterable.
   *
   * Prefer this for exports, scans, and `for await ... of` pipelines where
   * keeping client memory flatter matters more than receiving one final array.
   * The underlying request keeps a pooled connection busy until iteration
   * finishes or the iterable is closed.
   */
  async *streamMany(args) {
    const chunkSize = args?.chunkSize ?? 128;
    if (!Number.isInteger(chunkSize) || chunkSize < 1) {
      throw new RangeError('streamMany chunkSize must be a positive integer');
    }

    const rpcArgs = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    if (args?.take     != null) rpcArgs['take']    = args.take;
    if (args?.skip     != null) rpcArgs['skip']    = args.skip;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    if (args?.cursor   != null) rpcArgs['cursor']  = args.cursor;
    if (args?.distinct != null) rpcArgs['distinct'] = args.distinct;

    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
      chunkSize,
    };

    for await (const chunk of this.client._streamRpc('query.findMany', payload)) {
      const rows = chunk?.['data'];
      if (!Array.isArray(rows)) continue;
      for (const row of rows) {
        yield _coerce{{ model_name }}(row);
      }
    }
  }

  async findFirst(args) {
    const rpcArgs = {};
    if (args?.where    != null) rpcArgs['where']   = _processWhereFilters(args.where, _{{ table_name }}FieldToDb);
    if (args?.orderBy  != null) {
      const ob = args.orderBy;
      rpcArgs['orderBy'] = Array.isArray(ob) ? ob : [ob];
    }
{%- if has_vector_fields %}
    if (args?.nearest  != null) rpcArgs['nearest'] = _serializeNearestInput(args.nearest);
{%- endif %}
    rpcArgs['take'] = 1;
    if (args?.select   != null) rpcArgs['select']  = args.select;
    if (args?.include  != null) rpcArgs['include'] = _serializeInclude(args.include);
    const request = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args: rpcArgs,
    };
    const result = await this.client._rpc('query.findFirst', request);
    const row = _firstDataRow(result);
    return row == null ? null : _coerce{{ model_name }}(row);
  }

  async findUnique(args) {
    if (args.select != null || args.include != null) {
      return this.findFirst({ where: args.where, select: args.select, include: args.include });
    }
    const request = {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      filter: _processWhereFilters(args.where, _{{ table_name }}FieldToDb),
    };
    const result = await this.client._rpc('query.findUnique', request);
    const row = _firstDataRow(result);
    return row == null ? null : _coerce{{ model_name }}(row);
  }

  async findUniqueOrThrow(args) {
    const record = await this.findUnique(args);
    if (record == null) throw new NotFoundError('findUniqueOrThrow: no {{ model_name }} record found matching the given filter');
    return record;
  }

  async findFirstOrThrow(args) {
    const record = await this.findFirst(args);
    if (record == null) throw new NotFoundError('findFirstOrThrow: no {{ model_name }} record found matching the given filter');
    return record;
  }

  async create(args) {
    const returnData = args.returnData ?? true;
    const eventArgs = { data: args.data, returnData };
    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      data:       _processCreateData(args.data, _{{ table_name }}FieldToDb),
      returnData,
    };
    const state = {};
    try {
      let stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'create', EventPhase.Before, eventArgs, payload, state,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('create', returnData));

      const result = await this.client._rpc('query.create', payload);
      let decoded = null;
      if (returnData) {
        const rows = result['data'];
        if (!rows?.length) throw new Error('create: engine returned no data');
        decoded = _coerce{{ model_name }}(rows[0]);
      }

      stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'create', EventPhase.After, eventArgs, payload, state, decoded,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('create', returnData));
      return decoded;
    } catch (error) {
      await runCrudEvent(
        _{{ model_name }}EventContext(
          this.client, 'create', EventPhase.Error, eventArgs, payload, state, undefined, error,
        ),
        { handleStopPropagation: false },
      );
      throw error;
    }
  }

  async createMany(args) {
    const returnData = args.returnData ?? true;
    const eventArgs = { data: args.data, returnData };
    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      data:       args.data.map(d => _processCreateData(d, _{{ table_name }}FieldToDb)),
      returnData,
    };
    const state = {};
    try {
      let stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'createMany', EventPhase.Before, eventArgs, payload, state,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('createMany', returnData));

      const result = await this.client._rpc('query.createMany', payload);
      const decoded = returnData
        ? ((result['data']) ?? []).map(r => _coerce{{ model_name }}(r))
        : [];

      stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'createMany', EventPhase.After, eventArgs, payload, state, decoded,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('createMany', returnData));
      return decoded;
    } catch (error) {
      await runCrudEvent(
        _{{ model_name }}EventContext(
          this.client, 'createMany', EventPhase.Error, eventArgs, payload, state, undefined, error,
        ),
        { handleStopPropagation: false },
      );
      throw error;
    }
  }

  async update(args) {
    const returnData = args.returnData ?? true;
    const eventArgs = { where: args.where, data: args.data, returnData };
    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters((args.where ?? {}), _{{ table_name }}FieldToDb),
      data:       _processCreateData(args.data, _{{ table_name }}FieldToDb),
      returnData,
    };
    const state = {};
    try {
      let stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'update', EventPhase.Before, eventArgs, payload, state,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('update', returnData));

      const result = await this.client._rpc('query.update', payload);
      const decoded = returnData
        ? ((result['data']) ?? []).map(r => _coerce{{ model_name }}(r))
        : (result['count'] ?? 0);

      stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'update', EventPhase.After, eventArgs, payload, state, decoded,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('update', returnData));
      return decoded;
    } catch (error) {
      await runCrudEvent(
        _{{ model_name }}EventContext(
          this.client, 'update', EventPhase.Error, eventArgs, payload, state, undefined, error,
        ),
        { handleStopPropagation: false },
      );
      throw error;
    }
  }

  async delete(args) {
    const record = await this.findFirst({ where: args.where });
    if (record == null) throw new NotFoundError('delete: no {{ model_name }} record found matching the given filter');
    const pkFilter = {
{%- for pk in primary_key_fields %}
      '{{ pk }}': record['{{ pk }}'],
{%- endfor %}
    };
    const returnData = args.returnData ?? true;
    const eventArgs = { where: args.where, returnData };
    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters(pkFilter, _{{ table_name }}FieldToDb),
      returnData,
    };
    const state = {};
    try {
      let stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'delete', EventPhase.Before, eventArgs, payload, state,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('delete', returnData));

      const result = await this.client._rpc('query.delete', payload);
      let decoded = null;
      if (returnData) {
        const rows = result['data'];
        decoded = rows?.length ? _coerce{{ model_name }}(rows[0]) : record;
      }

      stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'delete', EventPhase.After, eventArgs, payload, state, decoded,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('delete', returnData));
      return decoded;
    } catch (error) {
      await runCrudEvent(
        _{{ model_name }}EventContext(
          this.client, 'delete', EventPhase.Error, eventArgs, payload, state, undefined, error,
        ),
        { handleStopPropagation: false },
      );
      throw error;
    }
  }

  async deleteMany(args) {
    const returnData = args?.returnData ?? false;
    const eventArgs = { where: args?.where, returnData };
    const payload = {
      protocolVersion: PROTOCOL_VERSION,
      model:      '{{ model_name }}',
      filter:     _processWhereFilters((args?.where ?? {}), _{{ table_name }}FieldToDb),
      returnData,
    };
    const state = {};
    try {
      let stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'deleteMany', EventPhase.Before, eventArgs, payload, state,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('deleteMany', returnData));

      const result = await this.client._rpc('query.delete', payload);
      const decoded = returnData
        ? ((result['data']) ?? []).map(r => _coerce{{ model_name }}(r))
        : (result['count'] ?? 0);

      stop = await runCrudEvent(_{{ model_name }}EventContext(
        this.client, 'deleteMany', EventPhase.After, eventArgs, payload, state, decoded,
      ));
      if (stop) return resolveStopResult(stop, defaultCrudResult('deleteMany', returnData));
      return decoded;
    } catch (error) {
      await runCrudEvent(
        _{{ model_name }}EventContext(
          this.client, 'deleteMany', EventPhase.Error, eventArgs, payload, state, undefined, error,
        ),
        { handleStopPropagation: false },
      );
      throw error;
    }
  }

  async count(args) {
    const rpcArgs = {};
    if (args?.where  != null) rpcArgs['where']  = _processWhereFilters(args.where, _{{ table_name }}FieldToDb);
    if (args?.take   != null) rpcArgs['take']   = args.take;
    if (args?.skip   != null) rpcArgs['skip']   = args.skip;
    if (args?.cursor != null) rpcArgs['cursor'] = args.cursor;
    const result = await this.client._rpc('query.count', {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args:  Object.keys(rpcArgs).length ? rpcArgs : null,
    });
    return result['count'] ?? 0;
  }

  /** Execute a raw SQL string and return result rows as generic objects. */
  async rawQuery(sql) {
    const result = await this.client._rpc('query.rawQuery', {
      protocolVersion: PROTOCOL_VERSION,
      sql,
    });
    return result['data'] ?? [];
  }

  /** Execute a raw prepared-statement query with bound parameters. */
  async rawStmtQuery(sql, params) {
    const result = await this.client._rpc('query.rawStmtQuery', {
      protocolVersion: PROTOCOL_VERSION,
      sql,
      params: params ?? [],
    });
    return result['data'] ?? [];
  }

  async upsert(args) {
    const existing = await this.findFirst({ where: args.where });
    if (existing != null) {
      const updated = await this.update({ where: args.where, data: args.data.update, returnData: args.returnData ?? true });
      if (!(args.returnData ?? true)) return null;
      if (args.include) {
        const rows = await this.findMany({ where: args.where, take: 1, include: args.include });
        return rows[0] ?? (Array.isArray(updated) ? updated[0] : null) ?? existing;
      }
      return Array.isArray(updated) ? (updated[0] ?? existing) : existing;
    }
    const created = await this.create({ data: args.data.create, returnData: args.returnData ?? true });
    if (!(args.returnData ?? true)) return null;
    if (created == null) throw new Error('upsert: create returned no data');
    if (args.include) {
      const rows = await this.findMany({ where: args.where, take: 1, include: args.include });
      return rows[0] ?? created;
    }
    return created;
  }

  async groupBy(args) {
    const rpcArgs = { by: [...args.by] };
    if (args.where  != null) rpcArgs['where']  = _processWhereFilters(args.where, _{{ table_name }}FieldToDb);
    if (args.having != null) rpcArgs['having'] = args.having;
    if (args.take   != null) rpcArgs['take']   = args.take;
    if (args.skip   != null) rpcArgs['skip']   = args.skip;
    if (args.count  != null) rpcArgs['count']  = args.count;
{%- if has_numeric_fields %}
    if (args.avg    != null) rpcArgs['avg']    = args.avg;
    if (args.sum    != null) rpcArgs['sum']    = args.sum;
{%- endif %}
    if (args.min    != null) rpcArgs['min']    = args.min;
    if (args.max    != null) rpcArgs['max']    = args.max;
    if (args.order  != null) {
      const o = args.order;
      rpcArgs['orderBy'] = Array.isArray(o) ? o : [o];
    }
    const result = await this.client._rpc('query.groupBy', {
      protocolVersion: PROTOCOL_VERSION,
      model: '{{ model_name }}',
      args:  rpcArgs,
    });
    return result['data'] ?? [];
  }
}
