vsrepo 1.3.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1556 @@
1
+ # VSRepository
2
+
3
+ ![npm](https://img.shields.io/npm/v/vsrepo?style=flat-square)
4
+ ![NPM License](https://img.shields.io/npm/l/vsrepo?style=flat-square)
5
+ ![NPM Downloads](https://img.shields.io/npm/dt/vsrepo?style=flat-square)
6
+
7
+ 🇧🇷 Você está lendo a versão em português. [🇺🇸 Read in English](./README.md)
8
+
9
+ Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **inferência de tipos** automática.
10
+
11
+ O VSRepository permite criar repositórios fortemente tipados com:
12
+
13
+ - **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
14
+ - **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
15
+ - **Métodos dinâmicos** inferidos pelo nome: `findOneByEmail`, `findManyPaginated`, `updateById`, `deleteManyByNameStartsWith`
16
+ - **Select models** reutilizáveis para diferentes projeções de dados
17
+ - **Type safety** em 100% das operações
18
+ - **Transações** nativas do Prisma (automáticas em `saveList` e `patchList`)
19
+ - **Extensibilidade** com métodos customizados
20
+
21
+ > 💡 Quer ver tudo isso na prática? A pasta [`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples) do repositório tem exemplos comentados e executáveis para cada funcionalidade — veja a seção [Exemplos práticos](#exemplos-práticos) mais abaixo.
22
+
23
+ ---
24
+
25
+ ## Sumário
26
+
27
+ - [Instalação](#instalação)
28
+ - [Gerando os tipos](#gerando-os-tipos)
29
+ - [Uso básico](#uso-básico)
30
+ - [Abordagem baseada em classes (DynamicRepository)](#abordagem-baseada-em-classes-dynamicrepository)
31
+ - [Integração com NestJS](#integração-com-nestjs)
32
+ - [Métodos base](#métodos-base)
33
+ - [Soft-delete](#soft-delete)
34
+ - [Operações em lote](#operações-em-lote)
35
+ - [Merge](#merge)
36
+ - [Configurando os métodos base](#configurando-os-métodos-base)
37
+ - [Select Models](#select-models)
38
+ - [Include Models](#include-models)
39
+ - [Include bruto (options.include)](#include-bruto-optionsinclude)
40
+ - [Required Where](#required-where)
41
+ - [Ordenação padrão (Default Ordenation)](#ordenação-padrão-default-ordenation)
42
+ - [Opção `see`](#opção-see)
43
+ - [Métodos dinâmicos](#métodos-dinâmicos)
44
+ - [Prefixos disponíveis](#prefixos-disponíveis)
45
+ - [Filtros de campo](#filtros-de-campo)
46
+ - [Operadores lógicos](#operadores-lógicos)
47
+ - [Filtros de relação](#filtros-de-relação)
48
+ - [Sufixos de paginação e ordenação](#sufixos-de-paginação-e-ordenação)
49
+ - [Distinct](#distinct)
50
+ - [Configuração de método](#configuração-de-método)
51
+ - [Aggregate e GroupBy](#aggregate-e-groupby)
52
+ - [Query Methods](#query-methods)
53
+ - [Relações no save](#relações-no-save)
54
+ - [Transações](#transações)
55
+ - [Estendendo um repositório](#estendendo-um-repositório)
56
+ - [Tratamento de erros](#tratamento-de-erros)
57
+ - [Tipos utilitários](#tipos-utilitários)
58
+ - [Referência da API](#referência-da-api)
59
+ - [Exemplos práticos](#exemplos-práticos)
60
+ - [Contribuindo](#contribuindo)
61
+ - [Requisitos](#requisitos)
62
+ - [Solução de problemas](#solução-de-problemas)
63
+
64
+ ---
65
+
66
+ ## Instalação
67
+
68
+ ```bash
69
+ npm i vsrepo @prisma/client
70
+ ```
71
+
72
+ Gere o Prisma Client:
73
+
74
+ ```bash
75
+ npx prisma generate
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Gerando os tipos
81
+
82
+ O VSRepository precisa saber o caminho real do seu Prisma Client para gerar as tipagens corretamente.
83
+
84
+ ```bash
85
+ npx vsrepo generate
86
+ ```
87
+
88
+ Equivalente a:
89
+
90
+ ```bash
91
+ npx vsrepo generate \
92
+ --output generated/vsrepo \
93
+ --prisma generated/prisma
94
+ ```
95
+
96
+ **Flags disponíveis:**
97
+
98
+ | Flag | Atalho | Padrão |
99
+ | ---------- | ------ | -------------------- |
100
+ | `--output` | `-o` | `generated/vsrepo` |
101
+ | `--prisma` | `-p` | `generated/prisma` |
102
+
103
+ **Arquivos gerados:**
104
+
105
+ ```
106
+ generated/vsrepo/
107
+ ├── VSRepoError.ts
108
+ ├── VSRepoError.types.d.ts
109
+ ├── VSRepository.ts
110
+ ├── VSRepository.types.d.ts
111
+ └── index.ts
112
+ ```
113
+
114
+ Depois de gerar, sempre importe a partir da pasta gerada:
115
+
116
+ ```ts
117
+ // CORRETO ✅
118
+ import { setupVSRepo } from "../../generated/vsrepo";
119
+
120
+ // ERRADO ❌
121
+ import { setupVSRepo } from "vsrepo";
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Uso básico
127
+
128
+ ### Configurando o Prisma Client
129
+
130
+ ```ts
131
+ // src/configs/db.ts
132
+ import { PrismaClient } from '../../generated/prisma/client';
133
+ import { PrismaPg } from '@prisma/adapter-pg';
134
+ import 'dotenv/config';
135
+
136
+ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
137
+ const prisma = new PrismaClient({ adapter });
138
+
139
+ export default prisma;
140
+ ```
141
+
142
+ ### Criando um repositório
143
+
144
+ ```ts
145
+ // src/repositories/userRepository.ts
146
+ import prisma from "../configs/db";
147
+ import { setupVSRepo } from "../../generated/vsrepo";
148
+ import type { User } from "../../generated/prisma/client";
149
+
150
+ const userRepository = setupVSRepo<User, "User">()(({
151
+ tableName: "user",
152
+ pkName: "id",
153
+ selectModels: {
154
+ public: { id: true, name: true, email: true },
155
+ },
156
+ defaultSelectModel: "public",
157
+ }).build(prisma);
158
+
159
+ export default userRepository;
160
+ ```
161
+
162
+ ### Usando o repositório
163
+
164
+ ```ts
165
+ import userRepository from "./repositories/userRepository";
166
+
167
+ const user = await userRepository.save({
168
+ name: "John",
169
+ email: "john@email.com",
170
+ password: "password",
171
+ });
172
+
173
+ const found = await userRepository.get(user.id);
174
+ const all = await userRepository.getAll();
175
+
176
+ user.name = "John Smith";
177
+
178
+ await userRepository.save(user);
179
+ await userRepository.remove(user.id);
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Abordagem baseada em classes (DynamicRepository)
185
+
186
+ Se você prefere um estilo OOP com decorators em vez da abordagem funcional `setupVSRepo`, o VSRepository também oferece `DynamicRepository` — uma classe que você pode estender com decorators `@DynamicMethod()` para definir seus métodos dinâmicos.
187
+
188
+ Veja **[README-DynamicRepo.pt-BR.md](./README-DynamicRepo.pt-BR.md)** para a documentação completa da abordagem baseada em classes, incluindo exemplos de integração com NestJS, configuração de decorators e uma comparação com o `setupVSRepo`.
189
+
190
+ ---
191
+
192
+ ## Integração com NestJS
193
+
194
+ O VSRepository pode ser facilmente integrado a projetos NestJS através de providers. Abaixo está um exemplo completo usando o padrão de injeção de dependência do NestJS.
195
+
196
+ ### Configurando o repositório como provider
197
+
198
+ ```ts
199
+ // src/modules/user/user.repository.ts
200
+ import { Provider } from "@nestjs/common";
201
+ import { PrismaService } from "../../database/prisma.service";
202
+ import { UserGetPayload } from "../../../generated/prisma/models";
203
+ import { setupVSRepo } from "../../../generated/vsrepo";
204
+
205
+ const userVSRepo = setupVSRepo<
206
+ UserGetPayload<{ include: { profile: true } }>,
207
+ "User"
208
+ >()(({
209
+ tableName: "user",
210
+ pkName: "id",
211
+ selectModels: {
212
+ public: {
213
+ id: true,
214
+ email: true,
215
+ createdAt: true,
216
+ updatedAt: true,
217
+ },
218
+ auth: {
219
+ id: true,
220
+ email: true,
221
+ password: true,
222
+ },
223
+ },
224
+ defaultSelectModel: "public",
225
+ requiredWhere: {
226
+ deletedAt: null,
227
+ },
228
+ relations: {
229
+ profile: {
230
+ mode: "oto",
231
+ pk: "id",
232
+ restriction: "add",
233
+ },
234
+ },
235
+ methods: {
236
+ findAuthByEmail: {
237
+ map: true,
238
+ proxyTo: "findUniqueByEmail",
239
+ selectModel: "auth",
240
+ },
241
+ findByEmailEndsWith: {
242
+ map: true,
243
+ }
244
+ },
245
+ });
246
+
247
+ const setupUserRepository = (prisma: PrismaService) => {
248
+ return userVSRepo.build(prisma);
249
+ };
250
+
251
+ export type UserRepository = ReturnType<typeof setupUserRepository>;
252
+ /*
253
+ O tipo também pode ser inferido usando o `RepositoryOf` do VSRepository, passando o tipo de `userVSRepo`:
254
+
255
+ export type UserRepository = RepositoryOf<typeof userVSRepo>;
256
+
257
+ OBS: Se você usar `.extend` para estender o repositório ou configurar os métodos base,
258
+ usar `ReturnType` é recomendado por ser mais simples de inferir o tipo
259
+ */
260
+
261
+ export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
262
+
263
+ export const UserRepositoryProvider: Provider = {
264
+ provide: USER_REPOSITORY,
265
+ inject: [PrismaService],
266
+ useFactory: setupUserRepository,
267
+ };
268
+ ```
269
+
270
+ ### Registrando o provider no módulo
271
+
272
+ ```ts
273
+ // src/modules/user/user.module.ts
274
+ import { Module } from "@nestjs/common";
275
+ import { UserRepositoryProvider } from "./user.repository";
276
+ import { UserService } from "./user.service";
277
+ import { UserController } from "./user.controller";
278
+
279
+ @Module({
280
+ imports: [DatabaseModule],
281
+ providers: [UserRepositoryProvider, UserService],
282
+ controllers: [UserController],
283
+ exports: [UserService],
284
+ })
285
+ export class UserModule {}
286
+ ```
287
+
288
+ ### Usando o repositório em um service
289
+
290
+ ```ts
291
+ // src/modules/user/user.service.ts
292
+ import { Injectable, Inject } from "@nestjs/common";
293
+ import { USER_REPOSITORY, type UserRepository } from "./user.repository";
294
+
295
+ @Injectable()
296
+ export class UserService {
297
+ constructor(
298
+ @Inject(USER_REPOSITORY)
299
+ private readonly userRepository: UserRepository,
300
+ ) {}
301
+
302
+ async getUserById(id: string) {
303
+ return this.userRepository.get(id);
304
+ }
305
+
306
+ async getUserAuthByEmail(email: string) {
307
+ return this.userRepository.findAuthByEmail(email);
308
+ }
309
+
310
+ async createUser(data: { email: string; password: string; name: string }) {
311
+ return this.userRepository.save({
312
+ email: data.email,
313
+ password: data.password,
314
+ name: data.name,
315
+ });
316
+ }
317
+ }
318
+ ```
319
+
320
+ **Benefícios dessa abordagem:**
321
+
322
+ - ✅ Repositórios type-safe com injeção de dependência
323
+ - ✅ Fácil de testar (mock do `USER_REPOSITORY`)
324
+ - ✅ Isolamento da lógica de persistência
325
+ - ✅ Reuso do repositório em múltiplos services
326
+ - ✅ Suporte a transações via `PrismaService`
327
+
328
+ ---
329
+
330
+ ## Métodos base
331
+
332
+ Ao chamar `.build(prisma)`, os métodos base abaixo ficam automaticamente disponíveis:
333
+
334
+ | Método | Descrição |
335
+ | -------------------------- | -------------------------------------------------------------------------------------------------------------|
336
+ | `get(pk)` | Busca um registro pela sua chave primária |
337
+ | `getOrThrow(pk)` | Busca um registro pela sua chave primária; lança `VSRepoRuntimeError` (código `"20727"`) se não for encontrado |
338
+ | `getList(pks)` | Busca múltiplos registros a partir de uma lista de chaves primárias |
339
+ | `save(obj)` | Cria ou atualiza — se o objeto tiver uma `pk`, realiza um `upsert`; caso contrário, um `create` |
340
+ | `saveList(objs)` | Salva um array de objetos em uma única transação automática |
341
+ | `patch(pk, obj)` | Atualiza parcialmente um registro pela sua chave primária |
342
+ | `patchList(tuples)` | Atualiza parcialmente múltiplos registros via um array de tuplas `[pk, obj]` em uma transação automática |
343
+ | `merge(pk, obj)` | Busca um registro e faz um deep merge em memória — **não persiste**, retorna o objeto mesclado |
344
+ | `remove(pk)` | Remove um registro pela sua chave primária |
345
+ | `removeList(pks)` | Remove vários registros a partir de uma lista de chaves primárias — retorna `{ count }` |
346
+ | `getAll()` | Retorna todos os registros (aceita `pagination` e `order` em `options`) |
347
+ | `total()` | Retorna o número total de registros |
348
+ | `has(pk)` | Verifica se um registro existe pela sua chave primária — retorna `boolean` |
349
+
350
+ Todos eles aceitam `options` como último argumento.
351
+
352
+ ### Soft-delete
353
+
354
+ Quando `softRemovekName` está configurado no repositório, os métodos adicionais abaixo ficam disponíveis:
355
+
356
+ | Método | Descrição |
357
+ | ---------------------------- | ---------------------------------------------------------------------------------------|
358
+ | `softRemove(pk)` | Marca um registro como removido, preenchendo `softRemovekName` com a data atual |
359
+ | `softRemoveList(pks)` | Marca múltiplos registros como removidos em lote — retorna `{ count }` |
360
+ | `restore(pk)` | Restaura um registro com soft-delete, limpando o campo `softRemovekName` |
361
+ | `restoreList(pks)` | Restaura múltiplos registros com soft-delete em lote — retorna `{ count }` |
362
+
363
+ ```ts
364
+ const userRepository = setupVSRepo<User, "user">()(({
365
+ tableName: "user",
366
+ pkName: "id",
367
+ softRemovekName: "deletedAt", // deve ser um campo DateTime no schema do Prisma
368
+ }).build(prisma);
369
+
370
+ await userRepository.softRemove(1);
371
+ await userRepository.restore(1);
372
+ ```
373
+
374
+ > O campo informado em `softRemovekName` **deve** ser do tipo `DateTime` no schema do Prisma. O VSRepository valida isso no momento do `build` e lança `VSRepoBuildError` se o tipo estiver incorreto.
375
+
376
+ ### Operações em lote
377
+
378
+ `saveList` e `patchList` executam automaticamente todas as operações dentro de uma única transação do Prisma. Se alguma operação falhar, todas as anteriores são desfeitas (rollback).
379
+
380
+ ```ts
381
+ // saveList — cria ou atualiza múltiplos objetos em uma transação automática
382
+ const users = await userRepository.saveList([
383
+ { name: "Mary", email: "mary@email.com" },
384
+ { id: 2, name: "John Updated", email: "john@email.com" },
385
+ ]);
386
+
387
+ // patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
388
+ const updated = await userRepository.patchList([
389
+ [1, { active: false }],
390
+ [2, { name: "New Name" }],
391
+ ]);
392
+ ```
393
+
394
+ Quando você já está dentro de uma transação existente, passe-a em `options.db`. Nesse caso, `db` deve ser um `DbTransaction` (não o client principal):
395
+
396
+ ```ts
397
+ await prisma.$transaction(async (tx) => {
398
+ await userRepository.saveList([{ name: "Mary" }, { name: "Gus" }], { db: tx });
399
+ await userRepository.patchList([[1, { active: false }], [2, { active: true }]], { db: tx });
400
+ });
401
+ ```
402
+
403
+ ### Merge
404
+
405
+ O método `merge` busca um registro pela PK e faz um deep merge (`deepmerge`) do objeto informado com os dados existentes **em memória**. Ele **não persiste** as alterações — retorna o resultado mesclado para que você decida o que fazer com ele.
406
+
407
+ ```ts
408
+ const existing = await userRepository.get(1);
409
+ // existing: { id: 1, name: "Mary", profile: { bio: "Hi", age: 25 } }
410
+
411
+ const merged = await userRepository.merge(1, {
412
+ profile: { bio: "Updated bio" },
413
+ });
414
+ // merged: { id: 1, name: "Mary", profile: { bio: "Updated bio", age: 25 } }
415
+
416
+ // Para persistir, passe para save ou patch:
417
+ await userRepository.save(merged);
418
+ ```
419
+
420
+ Retorna `null` se o registro não for encontrado.
421
+
422
+ **O merge de relações to-many (`otm`/`mtm`) é feito por PK, não por simples concatenação.** Para relações to-one (`oto`/`mto`), o `merge` faz um deep merge comum do objeto. Para relações to-many, cada item do array enviado é comparado com o item existente que tem a mesma PK (definida em `relations[key].pk`): se a PK bater, os dois objetos são mesclados; se não bater (um item novo, sem correspondente), ele é simplesmente adicionado à lista. Itens existentes que não aparecem no array enviado são mantidos.
423
+
424
+ ```ts
425
+ const existing = await userRepository.get(1);
426
+ // existing: {
427
+ // id: 1,
428
+ // posts: [
429
+ // { id: 10, title: "Post A", published: false },
430
+ // { id: 11, title: "Post B", published: true },
431
+ // ],
432
+ // }
433
+
434
+ const merged = await userRepository.merge(1, {
435
+ posts: [
436
+ { id: 10, published: true }, // mesma PK (id: 10) → mescla com o item existente
437
+ { title: "Post C" }, // sem PK → adicionado como novo item
438
+ ],
439
+ });
440
+ // merged: {
441
+ // id: 1,
442
+ // posts: [
443
+ // { id: 10, title: "Post A", published: true }, // mesclado
444
+ // { id: 11, title: "Post B", published: true }, // mantido, não estava no array enviado
445
+ // { title: "Post C" }, // adicionado
446
+ // ],
447
+ // }
448
+ ```
449
+
450
+ > Observe que o `merge` nunca remove itens de uma relação to-many — ele apenas mescla os que batem por PK e adiciona os que não batem. Para remover itens de uma relação, use `save`/`patch` com `restriction: "set"` na configuração da relação.
451
+
452
+ ### Configurando os métodos base
453
+
454
+ O segundo argumento de `.build(prisma, config)` permite ajustar o comportamento global do repositório e customizar cada método base individualmente através de `baseMethods`.
455
+
456
+ ```ts
457
+ userVSRepo.build(prisma, {
458
+ // Exibe os logs internos do VSRepository no console (queries montadas, prefixo
459
+ // detectado, filtros aplicados, etc). Ótimo para debugar métodos dinâmicos. Padrão = false.
460
+ showWorking: true,
461
+
462
+ baseMethods: {
463
+ get: {
464
+ // Habilita/desabilita o método no repositório final. Se `false`, o método
465
+ // nem aparece no tipo do repositório (não é apenas um erro em runtime). Padrão = true.
466
+ active: true,
467
+
468
+ // Select model aplicado por padrão quando o método é chamado sem `options.selectModel`.
469
+ // Sobrescreve o `defaultSelectModel` de setupVSRepo apenas para este método.
470
+ defaultSelect: "public",
471
+ },
472
+ remove: {
473
+ active: true,
474
+ defaultSelect: "minimal",
475
+
476
+ // Quando `true`, ignora o `requiredWhere` configurado em setupVSRepo para
477
+ // este método específico — útil quando um método precisa "furar" um
478
+ // filtro global (ex: multi-tenancy) em um caso específico. Padrão = false.
479
+ ignoreRequiredWhere: false,
480
+ },
481
+ save: {
482
+ // Aqui apenas `ignoreRequiredWhere` é definido — `active` e `defaultSelect`
483
+ // mantêm seus padrões (true e o `defaultSelectModel` global).
484
+ ignoreRequiredWhere: true,
485
+ },
486
+ patch: {
487
+ // Apenas o select é sobrescrito; o método continua ativo normalmente.
488
+ defaultSelect: "minimal",
489
+ },
490
+ has: {
491
+ active: false, // Desabilita 'has' (padrão = true) — o método desaparece do repositório
492
+ },
493
+ softRemove: {
494
+ // Métodos de soft-delete seguem as mesmas opções (`active`, `defaultSelect`,
495
+ // `ignoreRequiredWhere`). Só estão disponíveis se `softRemovekName` estiver configurado.
496
+ active: true,
497
+ defaultSelect: "minimal",
498
+ },
499
+ },
500
+ });
501
+ ```
502
+
503
+ > Métodos de lote/agregação como `removeList`, `softRemoveList`, `restoreList`, `total` e `has` **não** aceitam `defaultSelect` (eles não retornam um registro selecionável — retornam `{ count }` ou `boolean`). Nesses casos, `BaseMethodConfig` fica restrito a `active` e `ignoreRequiredWhere`.
504
+
505
+ ---
506
+
507
+ ## Select Models
508
+
509
+ `selectModels` define projeções de dados nomeadas e reutilizáveis.
510
+
511
+ ```ts
512
+ selectModels: {
513
+ public: { id: true, name: true, email: true },
514
+ internal: { id: true, name: true, email: true, password: true },
515
+ minimal: { id: true },
516
+ },
517
+ defaultSelectModel: "public",
518
+ ```
519
+
520
+ `defaultSelectModel` define qual select é usado automaticamente quando nenhum é especificado na chamada. É recomendado sempre defini-lo junto com `selectModels`.
521
+
522
+ **Usando um select específico na chamada:**
523
+
524
+ ```ts
525
+ const user = await userRepository.get(id, { selectModel: "minimal" });
526
+ ```
527
+
528
+ **Retornando o payload padrão do Prisma (sem select):**
529
+
530
+ ```ts
531
+ const fullUser = await userRepository.get(id, { selectModel: false });
532
+ ```
533
+
534
+ ---
535
+
536
+ ## Include Models
537
+
538
+ `includeModels` funciona de forma parecida com `selectModels`, mas em vez de receber um `select`, recebe um `include` válido do Prisma.
539
+
540
+ ```ts
541
+ const userRepository = setupVSRepo<User, "user">()(({
542
+ tableName: "user",
543
+ pkName: "id",
544
+ selectModels: {
545
+ public: { id: true, name: true, email: true },
546
+ },
547
+ defaultSelectModel: "public",
548
+ includeModels: {
549
+ withPosts: { posts: true },
550
+ withPostsAndProfile: { posts: true, profile: true },
551
+ },
552
+ }).build(prisma);
553
+ ```
554
+
555
+ **Usando um `includeModel` na chamada:**
556
+
557
+ ```ts
558
+ const user = await userRepository.get(id, { includeModel: "withPosts" });
559
+ ```
560
+
561
+ Nesse caso, o `select` padrão (`selectModels`/`defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
562
+
563
+ ### Diferenças em relação a `selectModels`
564
+
565
+ - **Só pode ser passado na chamada do método**, via `options.includeModel`. Não existe `defaultIncludeModel` ou `defaultInclude` — não há como configurar um `includeModel` padrão no repositório, ao contrário do que acontece com `defaultSelectModel`.
566
+ - **`includeModel` e `selectModel` não podem ser passados juntos** na mesma chamada. Se um `includeModel` for informado, qualquer `selectModel` (incluindo o padrão) é ignorado.
567
+
568
+ ```ts
569
+ // CORRETO ✅ — apenas includeModel
570
+ await userRepository.get(id, { includeModel: "withPosts" });
571
+
572
+ // CORRETO ✅ — apenas selectModel
573
+ await userRepository.get(id, { selectModel: "public" });
574
+
575
+ // ERRADO ❌ — combinar os dois não é permitido
576
+ await userRepository.get(id, { selectModel: "public", includeModel: "withPosts" });
577
+ ```
578
+
579
+ ### Include bruto (`options.include`)
580
+
581
+ Além do `includeModel` (nomeado, pré-configurado em `includeModels`), você pode passar um `include` bruto do Prisma diretamente na chamada, sem precisar registrá-lo antes no repositório.
582
+
583
+ ```ts
584
+ const user = await userRepository.get(id, {
585
+ include: { posts: true, profile: true },
586
+ });
587
+ ```
588
+
589
+ `options.include` aceita qualquer `include` válido para o modelo Prisma do repositório — é totalmente tipado e oferece o mesmo autocomplete/validação de chamar `prisma.user.findMany({ include: ... })` diretamente.
590
+
591
+ **Regras e comportamento:**
592
+
593
+ - **Mutuamente exclusivo com `selectModel` e `includeModel`.** Apenas um dos três pode ser informado por chamada; os tipos garantem isso — passar mais de um é um erro em tempo de compilação.
594
+ - **Ad hoc, não reutilizável.** Diferente do `includeModel`, não precisa ser declarado em `includeModels`. Use para includes pontuais que não justificam um model nomeado.
595
+ - **Nenhum `selectModel` padrão é aplicado.** Assim como com `includeModel`, quando `include` é informado, o select (incluindo `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
596
+
597
+ ```ts
598
+ // CORRETO ✅ — apenas include bruto
599
+ await userRepository.get(id, { include: { posts: true } });
600
+
601
+ // ERRADO ❌ — combinar include com selectModel/includeModel não é permitido
602
+ await userRepository.get(id, { selectModel: "public", include: { posts: true } });
603
+ await userRepository.get(id, { includeModel: "withPosts", include: { posts: true } });
604
+ ```
605
+
606
+ > **Quando usar `includeModel` vs. `include`:** prefira `includeModel` para includes reutilizados em várias chamadas (definidos uma vez em `includeModels`); use `include` para includes específicos e pontuais que não precisam de um nome.
607
+
608
+ ---
609
+
610
+ ## Required Where
611
+
612
+ `requiredWhere` define filtros que são aplicados automaticamente em toda query do repositório.
613
+
614
+ ```ts
615
+ requiredWhere: { active: true },
616
+ ```
617
+
618
+ Agora toda query vai incluir automaticamente `active: true`:
619
+
620
+ ```ts
621
+ // Internamente: WHERE active = true
622
+ const users = await userRepository.findMany();
623
+
624
+ // Internamente: WHERE email = 'john@email.com' AND active = true
625
+ const user = await userRepository.findByEmail("john@email.com");
626
+ ```
627
+
628
+ Útil para soft-deletes manuais, multi-tenancy e filtros globais de qualquer tipo.
629
+
630
+ ---
631
+
632
+ ## Ordenação padrão (Default Ordenation)
633
+
634
+ `defaultOrdenation` define uma ordenação padrão que é aplicada automaticamente em toda query que aceita `orderBy`, sem precisar repetir o argumento `order` em cada chamada.
635
+
636
+ ```ts
637
+ const userRepository = setupVSRepo<User, "user">()(({
638
+ tableName: "user",
639
+ pkName: "id",
640
+ defaultOrdenation: { createdAt: "desc" },
641
+ }).build(prisma);
642
+ ```
643
+
644
+ Com isso, toda query de listagem já virá ordenada por `createdAt` decrescente:
645
+
646
+ ```ts
647
+ // Internamente: ORDER BY createdAt DESC
648
+ const users = await userRepository.getAll();
649
+
650
+ // Também se aplica ao getAll com paginação
651
+ const paginated = await userRepository.getAll({ pagination: { take: 10 } });
652
+ ```
653
+
654
+ **`defaultOrdenation` é ignorado quando:**
655
+
656
+ - O método usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered` — nesses casos, o argumento `order` passado na chamada tem prioridade.
657
+ - O método dinâmico tem `injectOrdenation` configurado — a ordenação fixa do método tem precedência.
658
+
659
+ ```ts
660
+ methods: {
661
+ findManyPaginatedAndOrdered: { map: true }, // order vem do argumento → defaultOrdenation ignorado
662
+ findManyByActive: { map: true }, // sem Ordered → defaultOrdenation aplicado
663
+ findManyByStatus: {
664
+ map: true,
665
+ injectOrdenation: { name: "asc" }, // injectOrdenation → defaultOrdenation ignorado
666
+ },
667
+ }
668
+ ```
669
+
670
+ > `defaultOrdenation` aceita o mesmo tipo do `orderBy` nativo do Prisma para o modelo — incluindo arrays de ordenações encadeadas.
671
+
672
+ ---
673
+
674
+ ## Opção `see`
675
+
676
+ Quando `softRemovekName` está configurado, todo método aceita a opção `see` para controlar a visibilidade de registros com soft-delete:
677
+
678
+ | Valor | Comportamento |
679
+ | ----------- | -----------------------------------------------------------------|
680
+ | `"active"` | Retorna apenas registros que **não** foram removidos (padrão) |
681
+ | `"removed"` | Retorna apenas registros removidos |
682
+ | `"all"` | Retorna todos os registros, independente do status |
683
+
684
+ ```ts
685
+ // Retorna apenas usuários ativos (padrão)
686
+ const active = await userRepository.getAll();
687
+
688
+ // Retorna apenas usuários removidos
689
+ const removed = await userRepository.getAll({ see: "removed" });
690
+
691
+ // Retorna todos
692
+ const all = await userRepository.getAll({ see: "all" });
693
+ ```
694
+
695
+ > A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não como substituta dele.
696
+
697
+ ---
698
+
699
+ ## Métodos dinâmicos
700
+
701
+ Métodos dinâmicos são definidos na propriedade `methods` e têm seu comportamento inferido pelo nome.
702
+
703
+ ```ts
704
+ methods: {
705
+ findOneByEmail: { map: true },
706
+ findManyPaginated: { map: true },
707
+ updateById: { map: true },
708
+ deleteManyByIdIn: { map: true },
709
+ }
710
+ ```
711
+
712
+ ---
713
+
714
+ ### Prefixos disponíveis
715
+
716
+ O prefixo do nome do método determina qual operação do Prisma será chamada e quais argumentos são esperados.
717
+
718
+ | Prefixo | Operação do Prisma | Retorno | Observações |
719
+ | ---------------------------- | ---------------------------- | ------------------------ | -----------------------------------------------------------------------------|
720
+ | `findOneBy` | `findFirst` | `T \| null` | Retorno único. |
721
+ | `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` para retorno único (**deprecated**, use `findOneBy`) |
722
+ | `findUniqueBy` | `findUnique` | `T \| null` | |
723
+ | `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Lança um erro se não encontrado |
724
+ | `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
725
+ | `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Aceita campos como filtro; lança um erro se não encontrado |
726
+ | `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica apenas `requiredWhere` e `pushWhere` |
727
+ | `findFirstOrThrow` | `findFirstOrThrow` | `T` | Sem filtros de campo; aplica apenas `requiredWhere` e `pushWhere`; lança um erro se não encontrado |
728
+ | `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
729
+ | `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica apenas `requiredWhere` e `pushWhere` |
730
+ | `findOneWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
731
+ | `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
732
+ | `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrado, `false` caso contrário |
733
+ | `existsWhere` | `findFirst` | `boolean` | Recebe um objeto `where` explícito e retorna se existe |
734
+ | `countBy` | `count` | `number` | Aceita campos como filtro |
735
+ | `countWhere` | `count` | `number` | Recebe um objeto `where` explícito como argumento |
736
+ | `count` | `count` | `number` | Sem filtros de campo; aplica apenas `requiredWhere` e `pushWhere` |
737
+ | `create` | `create` | `T` | Recebe `data` como argumento |
738
+ | `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
739
+ | `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
740
+ | `updateBy` | `update` | `T` | Recebe `data` como argumento |
741
+ | `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
742
+ | `updateManyWhere` | `updateMany` | `{ count: number }` | Recebe um objeto `where` e um objeto `data` como argumentos |
743
+ | `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
744
+ | `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Recebe um objeto `where` e um objeto `data` como argumentos |
745
+ | `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
746
+ | `deleteBy` | `delete` | `T` | |
747
+ | `deleteManyBy` | `deleteMany` | `{ count: number }` | |
748
+ | `deleteManyWhere` | `deleteMany` | `{ count: number }` | Recebe um objeto `where` explícito como argumento |
749
+ | `aggregate` | `aggregate` | `Dynamic` | O nome deve ser exato; recebe argumentos nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
750
+ | `groupBy` | `groupBy` | `Dynamic[]` | O nome deve ser exato; recebe argumentos nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
751
+
752
+ ---
753
+
754
+ ### Filtros de campo
755
+
756
+ Filtros são sufixos aplicados ao nome do campo dentro do método. O próprio campo vem capitalizado logo após o prefixo (ou após `By`).
757
+
758
+ | Sufixo | Operador Prisma | Argumento obrigatório |
759
+ | ---------------------- | ----------------------- | -----------------------------|
760
+ | *(sem sufixo)* | igualdade (`=`) | sim |
761
+ | `Not` | `not` | sim |
762
+ | `In` | `in` | sim (array) |
763
+ | `NotIn` | `notIn` | sim (array) |
764
+ | `Contains` | `contains` | sim |
765
+ | `NotContains` | `not.contains` | sim |
766
+ | `StartsWith` | `startsWith` | sim |
767
+ | `NotStartsWith` | `not.startsWith` | sim |
768
+ | `EndsWith` | `endsWith` | sim |
769
+ | `NotEndsWith` | `not.endsWith` | sim |
770
+ | `GreaterThan` | `gt` | sim |
771
+ | `GreaterThanEqual` | `gte` | sim |
772
+ | `LessThan` | `lt` | sim |
773
+ | `LessThanEqual` | `lte` | sim |
774
+ | `Between` | `gte` + `lte` | sim (tupla `[min, max]`) |
775
+ | `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`) |
776
+ | `IsNull` | `null` | não |
777
+ | `IsNotNull` | `not: null` | não |
778
+ | `IsTrue` | `true` | não |
779
+ | `IsFalse` | `false` | não |
780
+ | `Insensitive` | `mode: 'insensitive'` | combinador |
781
+
782
+ `Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
783
+
784
+ ```ts
785
+ findByNameContainsInsensitive // { name: { contains: value, mode: 'insensitive' } }
786
+ findByEmailStartsWithInsensitive // { email: { startsWith: value, mode: 'insensitive' } }
787
+ findByNameInsensitive // { name: { equals: value, mode: 'insensitive' } }
788
+ ```
789
+
790
+ `Between` e `NotBetween` recebem uma **tupla `[valorMinimo, valorMaximo]`**:
791
+
792
+ ```ts
793
+ methods: {
794
+ findManyByAgeBetween: { map: true },
795
+ findManyBySalaryNotBetween: { map: true },
796
+ findManyByCreatedAtBetween: { map: true },
797
+ }
798
+
799
+ await userRepository.findManyByAgeBetween([18, 65]);
800
+ await userRepository.findManyBySalaryNotBetween([1000, 5000]);
801
+ await userRepository.findManyByCreatedAtBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
802
+ ```
803
+
804
+ O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
805
+
806
+ ```ts
807
+ findByNameOptionalAndEmail // name é opcional, email é obrigatório
808
+ ```
809
+
810
+ ---
811
+
812
+ ### Operadores lógicos
813
+
814
+ | Operador | Uso no nome | Exemplo |
815
+ | --------- | ---------------------------------- | -------------------------------------|
816
+ | `And` | entre dois campos | `findOneByIdAndEmail` |
817
+ | `Or` | entre dois campos | `findByNameOrEmail` |
818
+ | `AND` | separa um bloco final de `AND` | `findByEmailOrNameANDActiveStatus` |
819
+
820
+ `AND` (em maiúsculas) tem uma regra específica:
821
+
822
+ - Só pode existir **um** `AND` por método.
823
+ - Todos os campos depois do `AND` são injetados dentro de `AND: []`.
824
+ - Depois de um `AND`, não pode haver um `Or`.
825
+
826
+ Exemplo:
827
+
828
+ ```ts
829
+ methods: {
830
+ findOneByIdAndEmail: { map: true },
831
+ findByNameOrEmail: { map: true },
832
+ findFirstByIdOrEmailAndName: { map: true },
833
+ findByEmailOrNameANDActiveStatusAndAgeGreaterThan: { map: true }
834
+ }
835
+
836
+ await userRepository.findOneByIdAndEmail(1, "john@email.com");
837
+ await userRepository.findByNameOrEmail("John", "john@email.com");
838
+ await userRepository.findFirstByIdOrEmailAndName(1, "john@email.com", "John");
839
+ await userRepository.findByEmailOrNameANDActiveStatusAndAgeGreaterThan("john@email.com", "John", true, 17)
840
+ ```
841
+
842
+ Gera (`findOneByIdAndEmail`):
843
+
844
+ ```ts
845
+ {
846
+ id: 1,
847
+ email: "john@email.com"
848
+ }
849
+ ```
850
+
851
+ Gera (`findByNameOrEmail`):
852
+
853
+ ```ts
854
+ {
855
+ OR: [
856
+ { name: "John" },
857
+ { email: "john@email.com" }
858
+ ]
859
+ }
860
+ ```
861
+
862
+ Gera (`findFirstByIdOrEmailAndName`):
863
+
864
+ ```ts
865
+ {
866
+ OR: [
867
+ { id: 1 },
868
+ {
869
+ email: "john@email.com",
870
+ name: "John"
871
+ }
872
+ ]
873
+ }
874
+ ```
875
+
876
+ Gera (`findByEmailOrNameANDActiveStatusAndAgeGreaterThan`):
877
+
878
+ ```ts
879
+ {
880
+ OR: [
881
+ { email: "john@email.com" },
882
+ { name: "John" }
883
+ ],
884
+ AND: [
885
+ { activeStatus: true },
886
+ { age: { gt: 17 } }
887
+ ]
888
+ }
889
+ ```
890
+
891
+ ---
892
+
893
+ ### Filtros de relação
894
+
895
+ Permitem filtrar por campos de modelos relacionados.
896
+
897
+ > [!IMPORTANT]
898
+ > - **Tipagem de relação**: Para que o TypeScript reconheça os tipos dos campos de relação nos métodos dinâmicos, o tipo genérico da entidade passado para `setupVSRepo` deve incluir as relações estruturadas (ex.: usando o `UserGetPayload<{ include: { profile: true, posts: true } }>` do Prisma).
899
+ > - **Compatibilidade de sufixos**:
900
+ > - Os sufixos `Some`, `Every` e `None` só funcionam em relações **to-many** (`many-to-many` e `one-to-many`).
901
+ > - Os sufixos `With` e `Without` só funcionam em relações **to-one** (`one-to-one` e `many-to-one`).
902
+
903
+ | Sufixo de relação | Operador Prisma | Observação |
904
+ | -------------------------- | ----------------- | ---------------------------------------------------------|
905
+ | `Some` | `some: {}` | A relação tem *algum* registro |
906
+ | `SomeField` | `some.field` | Filtra dentro dos registros da relação |
907
+ | `EveryField` | `every.field` | Filtra dentro dos registros da relação |
908
+ | `None` | `none: {}` | A relação não tem *nenhum* registro |
909
+ | `NoneField` | `none.field` | Filtra dentro dos registros da relação |
910
+ | `With` | `is: {}` | A relação existe (não é nula) |
911
+ | `WithField` | `is.field` | Filtra um campo dentro da relação |
912
+ | `Without` | `isNot: {}` | A relação não existe (é nula) |
913
+ | `WithoutField` | `isNot.field` | Filtra um campo dentro da relação com negação |
914
+
915
+ Considerando `user` com uma relação to-one `profile` e uma relação to-many `posts`:
916
+
917
+ ```ts
918
+ methods: {
919
+ // to-many (posts)
920
+ findByPostsSome: { map: true }, // tem pelo menos um post
921
+ findByPostsSomeTitle: { map: true }, // tem pelo menos um post com aquele título
922
+ findByPostsEveryPublishedIsTrue:{ map: true }, // todos os posts estão publicados
923
+ findByPostsNone: { map: true }, // não tem nenhum post
924
+ findByPostsNoneTitle: { map: true }, // nenhum post tem aquele título
925
+
926
+ // to-one (profile)
927
+ findByProfileWith: { map: true }, // tem um profile (não é nulo)
928
+ findByProfileWithBio: { map: true }, // tem um profile com aquela bio
929
+ findByProfileWithout: { map: true }, // não tem profile (é nulo)
930
+ findByProfileWithoutBio: { map: true }, // tem um profile, mas com uma bio diferente da informada
931
+ }
932
+
933
+ await userRepository.findByPostsSome();
934
+ await userRepository.findByPostsSomeTitle("My first post");
935
+ await userRepository.findByPostsEveryPublishedIsTrue();
936
+ await userRepository.findByPostsNone();
937
+ await userRepository.findByPostsNoneTitle("Draft");
938
+
939
+ await userRepository.findByProfileWith();
940
+ await userRepository.findByProfileWithBio("Hello, world!");
941
+ await userRepository.findByProfileWithout();
942
+ await userRepository.findByProfileWithoutBio("Old bio");
943
+ ```
944
+
945
+ Gera (`findByPostsSomeTitle`):
946
+
947
+ ```ts
948
+ {
949
+ posts: {
950
+ some: { title: "My first post" }
951
+ }
952
+ }
953
+ ```
954
+
955
+ Gera (`findByPostsEveryPublishedIsTrue`):
956
+
957
+ ```ts
958
+ {
959
+ posts: {
960
+ every: { published: true }
961
+ }
962
+ }
963
+ ```
964
+
965
+ Gera (`findByProfileWithBio`):
966
+
967
+ ```ts
968
+ {
969
+ profile: {
970
+ is: { bio: "Hello, world!" }
971
+ }
972
+ }
973
+ ```
974
+
975
+ Gera (`findByProfileWithout`):
976
+
977
+ ```ts
978
+ {
979
+ profile: {
980
+ isNot: {}
981
+ }
982
+ }
983
+ ```
984
+
985
+ > `Some`, `None`, `With` e `Without` (sem campo) não recebem argumento — toda a relação é testada quanto à existência de registros (`some`/`none`) ou quanto a ser `null`/não `null` (`is`/`isNot`). As variantes `SomeField`, `EveryField`, `NoneField`, `WithField` e `WithoutField` recebem como argumento o valor do campo filtrado.
986
+
987
+ ---
988
+
989
+ ### Sufixos de paginação e ordenação
990
+
991
+ Aplicados no **final** do nome do método, injetam automaticamente os argumentos de paginação e ordenação.
992
+
993
+ | Sufixo | Argumentos adicionais |
994
+ | -------------------------- | ----------------------------------|
995
+ | `Paginated` | `(pagination)` |
996
+ | `Ordered` | `(order)` |
997
+ | `OrderedAndPaginated` | `(order, pagination)` |
998
+ | `PaginatedAndOrdered` | `(pagination, order)` |
999
+
1000
+ Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está disponível:
1001
+
1002
+ | Sufixo | Efeito |
1003
+ | --------------------- | -----------------------------------------------|
1004
+ | `SkipDuplicates` | Ignora registros duplicados durante a inserção |
1005
+
1006
+ ---
1007
+
1008
+ ### Distinct
1009
+
1010
+ O sufixo `Distinct` permite obter apenas registros únicos com base em um ou mais campos, equivalente à opção `distinct` do Prisma.
1011
+
1012
+ Para usá-lo, coloque `Distinct` no nome do método (depois dos filtros de campo, se houver), seguido dos campos desejados separados por `And`. A primeira letra de cada campo deve ser maiúscula, assim como nos filtros de campo comuns.
1013
+
1014
+ ```ts
1015
+ methods: {
1016
+ // Retorna usuários únicos combinando "age" e "role" (sem filtro de campo)
1017
+ findManyDistinctAgeAndRole: { map: true },
1018
+
1019
+ // Distinct combinado com o sufixo Paginated
1020
+ findManyDistinctNamePaginated: { map: true },
1021
+
1022
+ // Distinct combinado com um filtro de campo (name) — filtra por name e depois aplica distinct em role
1023
+ findManyByNameDistinctRole: { map: true },
1024
+ },
1025
+ ```
1026
+
1027
+ ```ts
1028
+ // Sem argumentos: os campos distinct já estão fixados no nome do método
1029
+ await userRepository.findManyDistinctAgeAndRole();
1030
+
1031
+ // O argumento de paginação continua funcionando normalmente
1032
+ await userRepository.findManyDistinctNamePaginated({ take: 10, skip: 0 });
1033
+
1034
+ // O filtro do campo "name" continua sendo passado normalmente como argumento
1035
+ await userRepository.findManyByNameDistinctRole("John");
1036
+ ```
1037
+
1038
+ > Os campos especificados depois de `Distinct` são resolvidos a partir do nome do método em tempo de build — eles **não** se tornam argumentos em tempo de execução, diferente dos filtros de campo comuns.
1039
+
1040
+ `Distinct` está disponível nos prefixos que leem múltiplos ou um único registro: `findMany`, `findManyBy`, `findFirst`, `findFirstBy`, `findFirstOrThrow`, `findFirstOrThrowBy`, `findBy`, `findOneBy`, `findWhere`, `findOneWhere`, `findListWhere`, `existsBy` e `existsWhere`.
1041
+
1042
+ ---
1043
+
1044
+ ### Configuração de método
1045
+
1046
+ Cada entrada em `methods` aceita as seguintes opções:
1047
+
1048
+ | Opção | Tipo | Padrão | Descrição |
1049
+ | ---------------------- | ----------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------|
1050
+ | `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repositório. |
1051
+ | `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora `requiredWhere`. |
1052
+ | `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
1053
+ | `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Apenas para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
1054
+ | `proxyTo` | `Padrão de método válido` | — | Delega a lógica para outro padrão de método válido. |
1055
+ | `pushWhere` | `WhereModel<M>` | — | `where` extra adicionado à query além do `requiredWhere`. |
1056
+ | `injectOrdenation` | `OrdenationModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
1057
+ | `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
1058
+ | `query` | `{ value: string; modifying?: boolean }` | — | Transforma o método em um **Query Method** (SQL bruto). Ignora todas as outras opções acima — veja [Query Methods](#query-methods). |
1059
+
1060
+ ---
1061
+
1062
+ ### Aggregate e GroupBy
1063
+
1064
+ ```ts
1065
+ const userRepository = setupVSRepo<User, "user">()(({
1066
+ tableName: "user",
1067
+ pkName: "id",
1068
+ methods: {
1069
+ aggregate: { map: true },
1070
+ groupBy: { map: true },
1071
+ },
1072
+ }).build(prisma);
1073
+ ```
1074
+
1075
+ > [!NOTE]
1076
+ > Esses métodos devem ter exatamente esses nomes (`aggregate` e `groupBy`).
1077
+ > Diferente dos demais métodos dinâmicos, eles recebem argumentos nativos do Prisma e **ignoram** as configurações `selectModels`, `pushWhere` e `requiredWhere`.
1078
+
1079
+ ---
1080
+
1081
+ ### Query Methods
1082
+
1083
+ Query Methods permitem que um método execute **SQL bruto** diretamente, contornando totalmente o parser de nomes dos métodos dinâmicos. São úteis para consultas complexas (joins pesados, CTEs, funções específicas do banco) que não são práticas de expressar com os prefixos/sufixos padrão.
1084
+
1085
+ Internamente, o VSRepository executa a SQL através do Prisma usando `$queryRawUnsafe` (para leitura) ou `$executeRawUnsafe` (para escrita), e os valores do array `args` são passados como **parâmetros posicionais** (`$1`, `$2`, ...) — a mesma técnica de *prepared statements* usada pelo próprio Prisma. Isso significa que os valores nunca são concatenados na string SQL, o que é o que efetivamente previne SQL Injection.
1086
+
1087
+ > [!WARNING]
1088
+ > `$1`, `$2`, ... na sua SQL devem representar sempre **valores** (parâmetros de dados), nunca nomes de colunas, tabelas ou trechos de SQL dinâmicos. Nomes de identificadores (colunas/tabelas) não podem ser passados como parâmetro posicional — se seu método precisar variar isso, monte o SQL a partir de um conjunto fixo e conhecido de opções no seu próprio código, nunca a partir de entrada não confiável.
1089
+
1090
+ ```ts
1091
+ const userRepository = setupVSRepo<User, "user">()({
1092
+ tableName: "user",
1093
+ pkName: "id",
1094
+ methods: {
1095
+ // Query method de leitura (não-modifying)
1096
+ findActiveUsersRaw: {
1097
+ map: true,
1098
+ query: {
1099
+ value: 'SELECT * FROM "user" WHERE active = $1',
1100
+ },
1101
+ },
1102
+
1103
+ // Query method de escrita (modifying: true)
1104
+ deactivateUsersOlderThanRaw: {
1105
+ map: true,
1106
+ query: {
1107
+ value: 'UPDATE "user" SET active = false WHERE "createdAt" < $1',
1108
+ modifying: true,
1109
+ },
1110
+ },
1111
+ },
1112
+ }).build(prisma);
1113
+ ```
1114
+
1115
+ **Chamando um Query Method:**
1116
+
1117
+ Todo Query Method recebe um único argumento no formato `{ args: [...], db? }`:
1118
+
1119
+ ```ts
1120
+ // Não-modifying: retorna 'any' por padrão, mas aceita um generic para
1121
+ // inferir/afirmar o tipo de retorno na própria chamada
1122
+ const usuariosAtivos = await userRepository.findActiveUsersRaw<User[]>({
1123
+ args: [true],
1124
+ });
1125
+
1126
+ // Modifying: sempre retorna 'number' (quantidade de linhas afetadas)
1127
+ const afetados = await userRepository.deactivateUsersOlderThanRaw({
1128
+ args: [new Date("2024-01-01")],
1129
+ });
1130
+
1131
+ // Participando de uma transação, via 'db'
1132
+ await userRepository.prisma.$transaction(async (tx) => {
1133
+ await userRepository.deactivateUsersOlderThanRaw({
1134
+ args: [new Date("2024-01-01")],
1135
+ db: tx,
1136
+ });
1137
+ });
1138
+ ```
1139
+
1140
+ | Opção | Tipo | Padrão | Descrição |
1141
+ | ------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------ |
1142
+ | `value` | `string` | — | **Obrigatório.** SQL bruto a ser executado. Use `$1`, `$2`, ... para os placeholders dos valores em `args`. |
1143
+ | `modifying` | `boolean` | `false` | Quando `true`, executa via `$executeRawUnsafe` e o método sempre resolve para `number`. Quando `false`, executa via `$queryRawUnsafe` e o método resolve para `TReturn` (`any` por padrão, inferível via generic na chamada). |
1144
+
1145
+ > [!NOTE]
1146
+ > Diferente dos demais métodos dinâmicos, Query Methods **ignoram completamente** `selectModels`, `requiredWhere`, `pushWhere`, `whereType`, `injectOrdenation`, `injectPagination` e `proxyTo` — nada disso se aplica, já que não há parsing de nome nem montagem de `where`/`select` pelo VSRepository. Nomes de métodos livres (fora dos padrões de `findBy`, `updateBy`, etc.) também **não** exigem `proxyTo`.
1147
+
1148
+ A mesma funcionalidade está disponível na abordagem baseada em classes através do decorator `@QueryMethod` — veja o [README-DynamicRepo.pt-BR.md](./README-DynamicRepo.pt-BR.md#o-decorator-querymethod).
1149
+
1150
+ ---
1151
+
1152
+ ## Relações no save
1153
+
1154
+ Configure as relações para que `save` e `patch` as gerenciem automaticamente (`saveList` e `patchList` também gerenciam relações automaticamente).
1155
+
1156
+ ```ts
1157
+ import type { Prisma } from "../../generated/prisma/client";
1158
+
1159
+ type User = Prisma.userGetPayload<{
1160
+ include: { profile: true; posts: true };
1161
+ }>;
1162
+
1163
+ const userRepository = setupVSRepo<User, "user">()(({
1164
+ tableName: "user",
1165
+ pkName: "id",
1166
+
1167
+ relations: {
1168
+ profile: {
1169
+ pk: "id",
1170
+ mode: "oto",
1171
+ restriction: "set",
1172
+ },
1173
+ posts: {
1174
+ pk: "id",
1175
+ mode: "otm",
1176
+ restriction: "add",
1177
+ },
1178
+ },
1179
+ }).build(prisma);
1180
+ ```
1181
+
1182
+ **Modos de relação:**
1183
+
1184
+ | Modo | Relação |
1185
+ | ----- | ------------------ |
1186
+ | `oto` | um-para-um |
1187
+ | `otm` | um-para-muitos |
1188
+ | `mto` | muitos-para-um |
1189
+ | `mtm` | muitos-para-muitos |
1190
+
1191
+ **Restrições:**
1192
+
1193
+ | Restrição | Comportamento na atualização |
1194
+ | ------------ | -------------------------------------------------------------------|
1195
+ | `set` | Substitui totalmente (remove os que não foram enviados) |
1196
+ | `add` | Adiciona/atualiza sem remover os existentes |
1197
+
1198
+ > [!WARNING]
1199
+ > **`set` significa coisas diferentes dependendo do `mode` da relação — e isso pode causar perda de dados se você não tomar cuidado.**
1200
+ >
1201
+ > Em relações onde o registro relacionado **pertence** ao registro pai (`oto` e `otm`), "remover os que não foram enviados" significa **apagar o registro do banco de dados** (`delete`/`deleteMany`). Em relações onde o registro relacionado é **independente** (`mto` e `mtm`), "remover" significa apenas **desvincular** (`disconnect`/`set: []`) — o registro relacionado continua existindo no banco, apenas deixa de apontar para o pai (ou de estar na tabela de junção).
1202
+ >
1203
+ > | Modo | `restriction: "set"` quando um item é omitido | O item continua existindo no banco de dados? |
1204
+ > | ----- | ------------------------------------------------ | -----------------------------------------------------|
1205
+ > | `oto` | Passar `null` no campo → **apaga** o registro relacionado (`delete: true`) | Não |
1206
+ > | `otm` | Itens fora da lista enviada → **apagados** (`deleteMany` com `notIn`) | Não |
1207
+ > | `mto` | Passar `null` no campo (com `nullable: true`) → **desvincula** (`disconnect: true`) | Sim |
1208
+ > | `mtm` | Itens fora da lista enviada → **desvinculados** da tabela de junção (`set: []`) | Sim |
1209
+ >
1210
+ > Exemplo prático: se `posts` for `otm` com `restriction: "set"`, um `save`/`patch` que envia o usuário com apenas 2 dos 5 posts existentes vai **apagar os outros 3 posts do banco de dados**, não apenas desvinculá-los do usuário. Se o comportamento esperado for apenas desvincular sem apagar, use `restriction: "add"` (que nunca remove nada) e trate a remoção manualmente.
1211
+
1212
+ **Relação `mto` com nullable:**
1213
+
1214
+ Use `nullable` (minúsculo) para permitir desvincular uma relação many-to-one:
1215
+
1216
+ ```ts
1217
+ relations: {
1218
+ category: {
1219
+ pk: "id",
1220
+ mode: "mto",
1221
+ restriction: "set",
1222
+ nullable: true, // permite passar null para desvincular
1223
+ },
1224
+ }
1225
+ ```
1226
+
1227
+ ---
1228
+
1229
+ ## Transações
1230
+
1231
+ Todos os métodos aceitam `options.db` para participar de uma transação:
1232
+
1233
+ ```ts
1234
+ await userRepository.prisma.$transaction(async (tx) => {
1235
+ const user = await userRepository.save(
1236
+ { name: "Mary", email: "mary@email.com", password: "password" },
1237
+ { db: tx }
1238
+ );
1239
+
1240
+ await userLogsRepository.save(
1241
+ { action: "User registration", data: { registeredUser: user.id } },
1242
+ { db: tx }
1243
+ );
1244
+ });
1245
+ ```
1246
+
1247
+ Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction`:
1248
+
1249
+ ```ts
1250
+ await prisma.$transaction(async (tx) => {
1251
+ // CORRETO: tx é um DbTransaction
1252
+ const registeredUsers = await userRepository.saveList([{ name: "Mary" }, { name: "Lucas" }], { db: tx });
1253
+
1254
+ await userLogsRepository.save(
1255
+ { action: "User registration", data: { registeredUsers: registeredUsers.map(u => u.id) } },
1256
+ { db: tx }
1257
+ );
1258
+ });
1259
+ ```
1260
+
1261
+ ---
1262
+
1263
+ ## Estendendo um repositório
1264
+
1265
+ ```ts
1266
+ const userRepository = setupVSRepo<User, "user">()(({
1267
+ tableName: "user",
1268
+ pkName: "id",
1269
+ methods: {
1270
+ findOneByEmailEndsWith: { map: true },
1271
+ },
1272
+ })
1273
+ .build(prisma)
1274
+ .extend((repo) => ({
1275
+ findActiveByDomain: async (domain: string) => {
1276
+ return repo.findOneByEmailEndsWith(`@${domain}`);
1277
+ },
1278
+
1279
+ activateMultiple: async (ids: string[]) => {
1280
+ return repo.patchList(ids.map(id => [id, { active: true }]));
1281
+ },
1282
+ }));
1283
+ ```
1284
+
1285
+ ---
1286
+
1287
+ ## Tratamento de erros
1288
+
1289
+ O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (erros do Prisma não são sobrescritos):
1290
+
1291
+ ```ts
1292
+ import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
1293
+
1294
+ try {
1295
+ const user = await userRepository.getOrThrow("id-that-does-not-exist");
1296
+ } catch (error) {
1297
+ if (error instanceof VSRepoRuntimeError && error.code === "20727") {
1298
+ console.error("Record not found");
1299
+ } else if (error instanceof VSRepoError) {
1300
+ console.error("Repository error:", error.message);
1301
+ } else {
1302
+ console.error("Error:", error.message)
1303
+ }
1304
+ }
1305
+ ```
1306
+
1307
+ **Subclasses disponíveis:**
1308
+
1309
+ | Classe | Quando é lançada |
1310
+ | ------------------------ | ---------------------------------------------------------------------------|
1311
+ | `VSRepoConfigError` | Configuração inválida em `setupVSRepo` |
1312
+ | `VSRepoBuildError` | Nome de método, tipo de campo ou configuração inválida em `build` |
1313
+ | `VSRepoExtendError` | Argumento inválido em `extend` |
1314
+ | `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
1315
+
1316
+ `VSRepoRuntimeError` tem uma propriedade `code` para identificação programática. O código `"20727"`, por exemplo, é lançado por `getOrThrow` quando o registro não é encontrado.
1317
+
1318
+ ---
1319
+
1320
+ ## Tipos utilitários
1321
+
1322
+ ### Tipos de client
1323
+
1324
+ ```ts
1325
+ import type { DbClient, DbTransaction, ClientOrTransaction } from "../../generated/vsrepo";
1326
+
1327
+ type DbClient = PrismaClient;
1328
+ type DbTransaction = Prisma.TransactionClient;
1329
+ type ClientOrTransaction = DbClient | DbTransaction;
1330
+ ```
1331
+
1332
+ ### Tipo de visibilidade do soft-delete
1333
+
1334
+ ```ts
1335
+ import type { SeeMode } from "../../generated/vsrepo";
1336
+
1337
+ type SeeMode = "active" | "removed" | "all";
1338
+ ```
1339
+
1340
+ ### Tipos derivados do modelo Prisma
1341
+
1342
+ ```ts
1343
+ import type {
1344
+ SelectModel,
1345
+ SelectModels,
1346
+ IncludeModel,
1347
+ IncludeModels,
1348
+ WhereModel,
1349
+ OrdenationModel,
1350
+ PaginationModel,
1351
+ ModelUpsertInput,
1352
+ PrismaModelInputs,
1353
+ } from "../../generated/vsrepo";
1354
+ ```
1355
+
1356
+ ### Tipos de opções de método
1357
+
1358
+ ```ts
1359
+ import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
1360
+
1361
+ // MethodOptions<S, IM> — opções passadas para os métodos do repositório
1362
+ type Opts = MethodOptions<"public" | "minimal", "withPosts">;
1363
+
1364
+ // MethodOptionsModel<TRepo> — derivado de uma instância configurada do VSRepository
1365
+ const userVSRepo = setupVSRepo<User, "user">()(config);
1366
+ type OptsModel = MethodOptionsModel<typeof userVSRepo>;
1367
+ ```
1368
+
1369
+ > O segundo parâmetro de `MethodOptions` (`IM`) representa as chaves válidas de `includeModels`. Quando informado, `selectModel` e `includeModel` se tornam mutuamente exclusivos no tipo — não é possível passar os dois na mesma chamada.
1370
+
1371
+ ### Tipos de configuração
1372
+
1373
+ ```ts
1374
+ import type {
1375
+ MethodConfig,
1376
+ RepoConfig,
1377
+ BuildConfig,
1378
+ RepositoryRelations,
1379
+ ExtractRelationConfig,
1380
+ } from "../../generated/vsrepo";
1381
+ ```
1382
+
1383
+ ### Tipo do repositório construído
1384
+
1385
+ ```ts
1386
+ import type { RepositoryOf } from "../../generated/vsrepo";
1387
+
1388
+ const userVSRepo = setupVSRepo<User, "user">()({ ... });
1389
+ type UserRepository = RepositoryOf<typeof userVSRepo>;
1390
+ ```
1391
+
1392
+ `RepositoryOf` aceita três parâmetros:
1393
+
1394
+ ```ts
1395
+ type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
1396
+ ```
1397
+
1398
+ ### Tipos de payload para `save` e `patch`
1399
+
1400
+ ```ts
1401
+ import type { SaveObject, PatchObject } from "../../generated/vsrepo";
1402
+
1403
+ const userVSRepo = setupVSRepo<User, "user">()(({
1404
+ tableName: "user",
1405
+ pkName: "id",
1406
+ relations: {
1407
+ profile: { pk: "id", mode: "oto", restriction: "set" },
1408
+ },
1409
+ });
1410
+
1411
+ type UserSavePayload = SaveObject<Prisma.UserCreateInput, typeof userVSRepo>;
1412
+ type UserPatchPayload = PatchObject<Prisma.UserUpdateInput, typeof userVSRepo>;
1413
+ ```
1414
+
1415
+ ---
1416
+
1417
+ ## Referência da API
1418
+
1419
+ ### `setupVSRepo<T, M>()(config)`
1420
+
1421
+ ```ts
1422
+ setupVSRepo<TPayload, TTableName>()({
1423
+ tableName: Uncapitalize<M>; // Nome da tabela no Prisma
1424
+ pkName: keyof T; // Nome da chave primária
1425
+ softRemovekName?: keyof T & string; // Campo DateTime para soft-delete
1426
+ selectModels?: SelectModels<M>; // Projeções de dados nomeadas (select)
1427
+ defaultSelectModel?: keyof SM; // Select aplicado por padrão
1428
+ includeModels?: IncludeModels<M>; // Projeções de dados nomeadas (include) — sem padrão, apenas na chamada
1429
+ requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
1430
+ defaultOrdenation?: OrdenationModel<M>; // Ordenação padrão para queries sem Ordered/injectOrdenation
1431
+ relations?: RepositoryRelations<T>; // Configuração de relações
1432
+ methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
1433
+ });
1434
+ ```
1435
+
1436
+ ### `.build(prisma, config?)`
1437
+
1438
+ ```ts
1439
+ vsRepo.build(prisma, {
1440
+ showWorking?: boolean; // Exibe os logs internos no console (padrão = false)
1441
+
1442
+ baseMethods?: {
1443
+ // Métodos que podem usar um defaultSelect
1444
+ get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1445
+ getOrThrow?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1446
+ getList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1447
+ remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1448
+ save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1449
+ saveList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1450
+ patch?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1451
+ patchList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1452
+ merge?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1453
+ getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1454
+ softRemove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1455
+ restore?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1456
+
1457
+ // Métodos que NÃO aceitam defaultSelect
1458
+ removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1459
+ softRemoveList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1460
+ restoreList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1461
+ total?: { active?: boolean; ignoreRequiredWhere?: boolean };
1462
+ has?: { active?: boolean; ignoreRequiredWhere?: boolean };
1463
+ };
1464
+ });
1465
+ ```
1466
+
1467
+ ### `.extend(fn)`
1468
+
1469
+ ```ts
1470
+ repo.extend((repo) => ({
1471
+ myMethod: () => { ... }
1472
+ }));
1473
+ ```
1474
+
1475
+ ---
1476
+
1477
+ ## Exemplos práticos
1478
+
1479
+ Além deste README, o repositório tem uma pasta **[`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples)** com exemplos práticos, comentados e prontos para executar — é o melhor lugar para ver o VSRepository sendo usado em cenários reais.
1480
+
1481
+ ```
1482
+ examples/
1483
+ ├── prisma.ts # Instância do PrismaClient usada pelos exemplos
1484
+ ├── repositories.ts # Configuração dos repositórios (User, Address, Product) com setupVSRepo
1485
+ └── tests/
1486
+ ├── base-methods.test.ts # Métodos base: get, save, patch, remove, getAll, total, has...
1487
+ ├── relations.test.ts # Como configurar e usar relações em save/patch e em filtros
1488
+ ├── required-where.test.ts # Como requiredWhere é aplicado automaticamente às queries
1489
+ ├── dynamic-methods.test.ts # Prefixos, filtros de campo, operadores lógicos e paginação/ordenação
1490
+ ├── transactions.test.ts # Transações com options.db e acesso à instância via repository.prisma
1491
+ ├── soft-delete.test.ts # Soft-delete: softRemove, softRemoveList, restore, restoreList e SeeMode
1492
+ └── batch-methods.test.ts # Operações em lote: getList, saveList, patchList e merge
1493
+ ```
1494
+
1495
+ Cada arquivo em `tests/` é um script independente e executável (via `tsx`) que demonstra um conjunto específico de funcionalidades, com `console.log` em cada etapa para você acompanhar o resultado no terminal. A própria pasta tem um [README](https://github.com/jaobrabo123/VSRepository/blob/main/examples/README.md) explicando a ordem de leitura sugerida, como configurar o ambiente e como rodar os testes.
1496
+
1497
+ ---
1498
+
1499
+ ## Contribuindo
1500
+
1501
+ Contribuições são bem-vindas! Se você encontrou um bug, tem uma ideia de melhoria ou quer ajudar com a documentação, sinta-se à vontade para participar (**[Repositório no GitHub](https://github.com/jaobrabo123/VSRepository)**):
1502
+
1503
+ 1. Faça um **fork** do projeto.
1504
+ 2. Crie uma nova branch com sua alteração: `git checkout -b fixing-bug`.
1505
+ 3. Envie para sua branch: `git push origin fixing-bug`.
1506
+ 4. Abra um **Pull Request**.
1507
+
1508
+ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1509
+
1510
+ ---
1511
+
1512
+ ## Requisitos
1513
+
1514
+ - Node.js 18+ (ESM)
1515
+ - Prisma
1516
+ - TypeScript (opcional, mas fortemente recomendado)
1517
+ - `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
1518
+
1519
+ `tsconfig.json` recomendado:
1520
+
1521
+ ```json
1522
+ {
1523
+ "compilerOptions": {
1524
+ "target": "ES2020",
1525
+ "module": "NodeNext",
1526
+ "moduleResolution": "NodeNext",
1527
+ "strict": true,
1528
+ "skipLibCheck": true,
1529
+ "lib": ["ES2020"]
1530
+ }
1531
+ }
1532
+ ```
1533
+
1534
+ ---
1535
+
1536
+ ## Solução de problemas
1537
+
1538
+ **Tipos genéricos não são inferidos** — Verifique se `strict: true` e `moduleResolution: "bundler"` ou `"nodenext"` estão configurados no `tsconfig.json`.
1539
+
1540
+ **Método dinâmico não existe em tempo de execução** — O campo referenciado no nome do método deve existir no modelo do Prisma. Ex.: `findByEmail` requer que o modelo tenha um campo `email`.
1541
+
1542
+ **`proxyTo` obrigatório** — Nomes fora dos padrões conhecidos (ex.: `searchByEmail`) não são interpretados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
1543
+
1544
+ **Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera.
1545
+
1546
+ **`selectModel`, `includeModel` e `include` juntos na mesma chamada** — Não é permitido. Apenas um dos três pode ser informado por chamada: se `includeModel` ou `include` for informado, o `select` (incluindo `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
1547
+
1548
+ **`includeModel` não aparece como opção padrão do repositório** — Isso é esperado. Diferente do `defaultSelectModel`, não existe `defaultIncludeModel`/`defaultInclude`. Um `includeModel` só pode ser definido na chamada do método, via `options.includeModel`. Um include bruto e ad hoc pode ser definido via `options.include`, sem precisar ser registrado em `includeModels`.
1549
+
1550
+ **`softRemovekName` lança um erro no build** — O campo informado deve ser do tipo `DateTime` no schema do Prisma. Tipos como `Boolean` ou `String` não são aceitos.
1551
+
1552
+ **`defaultOrdenation` não está sendo aplicado** — Verifique se o método usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered`, e se tem `injectOrdenation` configurado. Ambos têm prioridade sobre a ordenação padrão.
1553
+
1554
+ **Sufixo `Distinct` não é reconhecido** — `Distinct` só é resolvido em prefixos de leitura (`findMany`, `findFirst`, `findBy`, `existsBy`, etc). Em métodos como `count`, `createMany`, `updateMany` ou `deleteMany` o sufixo é ignorado.
1555
+
1556
+ **`saveList`/`patchList` com `db` inválido** — O campo `db` nesses métodos só aceita uma `DbTransaction` (o retorno de `prisma.$transaction`), não o client principal. Passar o `PrismaClient` diretamente causará comportamento inesperado.