vsrepo 1.3.1 → 1.3.3

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.
package/README.md CHANGED
@@ -4,66 +4,68 @@
4
4
  ![NPM License](https://img.shields.io/npm/l/vsrepo?style=flat-square)
5
5
  ![NPM Downloads](https://img.shields.io/npm/dt/vsrepo?style=flat-square)
6
6
 
7
- Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **type inference** automático.
7
+ Repository pattern library for projects using **Prisma**, with full **TypeScript** support and automatic **type inference**.
8
8
 
9
- O VSRepository permite criar repositories fortemente tipados com:
9
+ VSRepository lets you create strongly-typed repositories with:
10
10
 
11
- - **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
12
- - **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
13
- - **Métodos dinâmicos** inferidos pelo nome: `findOneByEmail`, `findManyPaginated`, `updateById`, `deleteManyByNameStartsWith`
14
- - **Select models** reutilizáveis para diferentes projeções de dados
15
- - **Type safety** em 100% das operações
16
- - **Transações** nativas do Prisma (automáticas em `saveList` e `patchList`)
17
- - **Extensibilidade** com métodos personalizados
11
+ - Automatic **base methods**: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
12
+ - **Native soft-delete**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
13
+ - **Dynamic methods** inferred from their name: `findOneByEmail`, `findManyPaginated`, `updateById`, `deleteManyByNameStartsWith`
14
+ - Reusable **select models** for different data projections
15
+ - **Type safety** across 100% of operations
16
+ - Native Prisma **transactions** (automatic in `saveList` and `patchList`)
17
+ - **Extensibility** with custom methods
18
18
 
19
- > 💡 Quer ver tudo isso funcionando 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) abaixo.
19
+ > 💡 Want to see all of this in practice? The repository's [`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples) folder has commented, runnable examples for every feature — see the [Practical examples](#practical-examples) section below.
20
20
 
21
21
  ---
22
22
 
23
- ## Sumário
23
+ ## Table of contents
24
24
 
25
- - [Instalação](#instalação)
26
- - [Gerando os tipos](#gerando-os-tipos)
27
- - [Uso básico](#uso-básico)
28
- - [Integração com NestJS](#integração-com-nestjs)
29
- - [Métodos base](#métodos-base)
25
+ - [Installation](#installation)
26
+ - [Generating the types](#generating-the-types)
27
+ - [Basic usage](#basic-usage)
28
+ - [Class-based approach (DynamicRepository)](#class-based-approach-dynamicrepository)
29
+ - [NestJS integration](#nestjs-integration)
30
+ - [Base methods](#base-methods)
30
31
  - [Soft-delete](#soft-delete)
31
- - [Operações em lote](#operações-em-lote)
32
+ - [Batch operations](#batch-operations)
32
33
  - [Merge](#merge)
33
- - [Configurando os métodos base](#configurando-os-métodos-base)
34
+ - [Configuring the base methods](#configuring-the-base-methods)
34
35
  - [Select Models](#select-models)
35
36
  - [Include Models](#include-models)
36
- - [Required Where](#requiredwhere)
37
+ - [Required Where](#required-where)
37
38
  - [Default Ordenation](#default-ordenation)
38
- - [Opção `see`](#opção-see)
39
- - [Métodos dinâmicos](#métodos-dinâmicos)
40
- - [Prefixos disponíveis](#prefixos-disponíveis)
41
- - [Filtros de campo](#filtros-de-campo)
42
- - [Operadores lógicos](#operadores-lógicos)
43
- - [Filtros de relação](#filtros-de-relação)
44
- - [Sufixos de paginação e ordenação](#sufixos-de-paginação-e-ordenação)
45
- - [Configuração de métodos](#configuração-de-métodos)
46
- - [Aggregate e GroupBy](#aggregate-e-groupby)
47
- - [Relações no save](#relações-no-save)
48
- - [Transações](#transações)
49
- - [Estendendo um repository](#estendendo-um-repository)
50
- - [Tratamento de erros](#tratamento-de-erros)
51
- - [Tipos utilitários](#tipos-utilitários)
39
+ - [`see` option](#see-option)
40
+ - [Dynamic methods](#dynamic-methods)
41
+ - [Available prefixes](#available-prefixes)
42
+ - [Field filters](#field-filters)
43
+ - [Logical operators](#logical-operators)
44
+ - [Relation filters](#relation-filters)
45
+ - [Pagination and ordering suffixes](#pagination-and-ordering-suffixes)
46
+ - [Distinct](#distinct)
47
+ - [Method configuration](#method-configuration)
48
+ - [Aggregate and GroupBy](#aggregate-and-groupby)
49
+ - [Relations in save](#relations-in-save)
50
+ - [Transactions](#transactions)
51
+ - [Extending a repository](#extending-a-repository)
52
+ - [Error handling](#error-handling)
53
+ - [Utility types](#utility-types)
52
54
  - [API Reference](#api-reference)
53
- - [Exemplos práticos](#exemplos-práticos)
54
- - [Contribuindo](#contribuindo)
55
- - [Requisitos](#requisitos)
55
+ - [Practical examples](#practical-examples)
56
+ - [Contributing](#contributing)
57
+ - [Requirements](#requirements)
56
58
  - [Troubleshooting](#troubleshooting)
57
59
 
58
60
  ---
59
61
 
60
- ## Instalação
62
+ ## Installation
61
63
 
62
64
  ```bash
63
65
  npm i vsrepo @prisma/client
64
66
  ```
65
67
 
66
- Gere o Prisma Client:
68
+ Generate the Prisma Client:
67
69
 
68
70
  ```bash
69
71
  npx prisma generate
@@ -71,15 +73,15 @@ npx prisma generate
71
73
 
72
74
  ---
73
75
 
74
- ## Gerando os tipos
76
+ ## Generating the types
75
77
 
76
- O VSRepository precisa conhecer o caminho real do seu Prisma Client para gerar as tipagens corretamente.
78
+ VSRepository needs to know the real path of your Prisma Client to generate the typings correctly.
77
79
 
78
80
  ```bash
79
81
  npx vsrepo generate
80
82
  ```
81
83
 
82
- Equivale a:
84
+ Equivalent to:
83
85
 
84
86
  ```bash
85
87
  npx vsrepo generate \
@@ -87,14 +89,14 @@ npx vsrepo generate \
87
89
  --prisma generated/prisma
88
90
  ```
89
91
 
90
- **Flags disponíveis:**
92
+ **Available flags:**
91
93
 
92
- | Flag | Alias | Padrão |
93
- | ---------- | ----- | ---------------------- |
94
- | `--output` | `-o` | `generated/vsrepo` |
95
- | `--prisma` | `-p` | `generated/prisma` |
94
+ | Flag | Alias | Default |
95
+ | ---------- | ----- | -------------------- |
96
+ | `--output` | `-o` | `generated/vsrepo` |
97
+ | `--prisma` | `-p` | `generated/prisma` |
96
98
 
97
- **Arquivos gerados:**
99
+ **Generated files:**
98
100
 
99
101
  ```
100
102
  generated/vsrepo/
@@ -105,21 +107,21 @@ generated/vsrepo/
105
107
  └── index.ts
106
108
  ```
107
109
 
108
- Após gerar, importe sempre a partir da pasta gerada:
110
+ After generating, always import from the generated folder:
109
111
 
110
112
  ```ts
111
- // CORRETO ✅
113
+ // CORRECT ✅
112
114
  import { setupVSRepo } from "../../generated/vsrepo";
113
115
 
114
- // ERRADO ❌
116
+ // WRONG ❌
115
117
  import { setupVSRepo } from "vsrepo";
116
118
  ```
117
119
 
118
120
  ---
119
121
 
120
- ## Uso básico
122
+ ## Basic usage
121
123
 
122
- ### Configurando o Prisma Client
124
+ ### Configuring the Prisma Client
123
125
 
124
126
  ```ts
125
127
  // src/configs/db.ts
@@ -133,56 +135,64 @@ const prisma = new PrismaClient({ adapter });
133
135
  export default prisma;
134
136
  ```
135
137
 
136
- ### Criando um repository
138
+ ### Creating a repository
137
139
 
138
140
  ```ts
139
- // src/repositories/usuarioRepository.ts
141
+ // src/repositories/userRepository.ts
140
142
  import prisma from "../configs/db";
141
143
  import { setupVSRepo } from "../../generated/vsrepo";
142
- import type { Usuario } from "../../generated/prisma/client";
144
+ import type { User } from "../../generated/prisma/client";
143
145
 
144
- const usuarioRepository = setupVSRepo<Usuario, "Usuario">()(({
145
- tableName: "usuario",
146
+ const userRepository = setupVSRepo<User, "User">()(({
147
+ tableName: "user",
146
148
  pkName: "id",
147
149
  selectModels: {
148
- public: { id: true, nome: true, email: true },
150
+ public: { id: true, name: true, email: true },
149
151
  },
150
152
  defaultSelectModel: "public",
151
153
  }).build(prisma);
152
154
 
153
- export default usuarioRepository;
155
+ export default userRepository;
154
156
  ```
155
157
 
156
- ### Usando o repository
158
+ ### Using the repository
157
159
 
158
160
  ```ts
159
- import usuarioRepository from "./repositories/usuarioRepository";
161
+ import userRepository from "./repositories/userRepository";
160
162
 
161
- const usuario = await usuarioRepository.save({
162
- nome: "Joao",
163
- email: "joao@email.com",
164
- senha: "password",
163
+ const user = await userRepository.save({
164
+ name: "John",
165
+ email: "john@email.com",
166
+ password: "password",
165
167
  });
166
168
 
167
- const encontrado = await usuarioRepository.get(usuario.id);
168
- const todos = await usuarioRepository.getAll();
169
+ const found = await userRepository.get(user.id);
170
+ const all = await userRepository.getAll();
169
171
 
170
- usuario.nome = "Joao Pedro";
172
+ user.name = "John Smith";
171
173
 
172
- await usuarioRepository.save(usuario);
173
- await usuarioRepository.remove(usuario.id);
174
+ await userRepository.save(user);
175
+ await userRepository.remove(user.id);
174
176
  ```
175
177
 
176
178
  ---
177
179
 
178
- ## Integração com NestJS
180
+ ## Class-based approach (DynamicRepository)
179
181
 
180
- O VSRepository pode ser facilmente integrado em projetos NestJS através de providers. Abaixo está um exemplo completo usando o padrão de injeção de dependência do NestJS.
182
+ If you prefer an OOP style with decorators instead of the functional `setupVSRepo` approach, VSRepository also provides `DynamicRepository` — a class you can extend with `@DynamicMethod()` decorators to define your dynamic methods.
181
183
 
182
- ### Configurando o repository como provider
184
+ See **[README-DynamicRepo.md](./README-DynamicRepo.md)** for full documentation on the class-based approach, including NestJS integration examples, decorator config, and a comparison with `setupVSRepo`.
185
+
186
+ ---
187
+
188
+ ## NestJS integration
189
+
190
+ VSRepository can be easily integrated into NestJS projects through providers. Below is a complete example using NestJS's dependency injection pattern.
191
+
192
+ ### Configuring the repository as a provider
183
193
 
184
194
  ```ts
185
- // src/resources/user/user.repository.ts
195
+ // src/modules/user/user.repository.ts
186
196
  import { Provider } from "@nestjs/common";
187
197
  import { PrismaService } from "../../database/prisma.service";
188
198
  import { UserGetPayload } from "../../../generated/prisma/models";
@@ -236,12 +246,12 @@ const setupUserRepository = (prisma: PrismaService) => {
236
246
 
237
247
  export type UserRepository = ReturnType<typeof setupUserRepository>;
238
248
  /*
239
- A tipagem também pode ser inferida usando o `RepositoryOf` do VSRepository, passando o tipo do `userVSRepo`:
249
+ The type can also be inferred using VSRepository's `RepositoryOf`, passing the `userVSRepo` type:
240
250
 
241
251
  export type UserRepository = RepositoryOf<typeof userVSRepo>;
242
252
 
243
- OBS: Caso você use o `.extend` para estender o repository ou configure os métodos base, recomenda-se
244
- usar o `ReturnType` por ser mais simples de inferir a tipagem
253
+ NOTE: If you use `.extend` to extend the repository or configure the base methods,
254
+ using `ReturnType` is recommended since it's simpler to infer the type
245
255
  */
246
256
 
247
257
  export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
@@ -253,10 +263,10 @@ export const UserRepositoryProvider: Provider = {
253
263
  };
254
264
  ```
255
265
 
256
- ### Registrando o provider no módulo
266
+ ### Registering the provider in the module
257
267
 
258
268
  ```ts
259
- // src/resources/user/user.module.ts
269
+ // src/modules/user/user.module.ts
260
270
  import { Module } from "@nestjs/common";
261
271
  import { UserRepositoryProvider } from "./user.repository";
262
272
  import { UserService } from "./user.service";
@@ -271,12 +281,12 @@ import { UserController } from "./user.controller";
271
281
  export class UserModule {}
272
282
  ```
273
283
 
274
- ### Utilizando o repository em um serviço
284
+ ### Using the repository in a service
275
285
 
276
286
  ```ts
277
- // src/resources/user/user.service.ts
287
+ // src/modules/user/user.service.ts
278
288
  import { Injectable, Inject } from "@nestjs/common";
279
- import { USER_REPOSITORY, UserRepository } from "./user.repository";
289
+ import { USER_REPOSITORY, type UserRepository } from "./user.repository";
280
290
 
281
291
  @Injectable()
282
292
  export class UserService {
@@ -303,182 +313,182 @@ export class UserService {
303
313
  }
304
314
  ```
305
315
 
306
- **Benefícios desta abordagem:**
316
+ **Benefits of this approach:**
307
317
 
308
- - ✅ Type-safe repositories com injeção de dependência
309
- - ✅ Fácil de testar (mock do `USER_REPOSITORY`)
310
- - ✅ Isolamento da lógica de persistência
311
- - ✅ Reutilização do repository em múltiplos serviços
312
- - ✅ Suporte a transações via `PrismaService`
318
+ - ✅ Type-safe repositories with dependency injection
319
+ - ✅ Easy to test (mock the `USER_REPOSITORY`)
320
+ - ✅ Isolation of persistence logic
321
+ - ✅ Repository reuse across multiple services
322
+ - ✅ Transaction support via `PrismaService`
313
323
 
314
324
  ---
315
325
 
316
- ## Métodos base
326
+ ## Base methods
317
327
 
318
- Ao chamar `.build(prisma)` os métodos base abaixo são automaticamente disponibilizados:
328
+ When calling `.build(prisma)`, the base methods below are automatically made available:
319
329
 
320
- | Método | Descrição |
321
- | ------------------------ | ----------------------------------------------------------------------------------------------------------- |
322
- | `get(pk)` | Busca um registro pela primary key |
323
- | `getOrThrow(pk)` | Busca um registro pela primary key; lança `VSRepoRuntimeError` (code `"20727"`) se não encontrado |
324
- | `getList(pks)` | Busca múltiplos registros por uma lista de primary keys |
325
- | `save(obj)` | Cria ou atualiza — se o objeto tiver a `pk` faz `upsert`, caso contrário faz `create` |
326
- | `saveList(objs)` | Salva um array de objetos em uma única transação automática |
327
- | `patch(pk, obj)` | Atualiza parcialmente um registro pela primary key |
328
- | `patchList(tuples)` | Atualiza parcialmente múltiplos registros via array de tuplas `[pk, obj]` em transação automática |
329
- | `merge(pk, obj)` | Busca um registro e faz um deep merge em memória — **não persiste**, retorna o objeto mesclado |
330
- | `remove(pk)` | Remove um registro pela primary key |
331
- | `removeList(pks)` | Remove vários registros pela lista de primary keys — retorna `{ count }` |
332
- | `getAll()` | Retorna todos os registros (aceita `pagination` e `order` no `options`) |
333
- | `total()` | Retorna o total de registros |
334
- | `has(pk)` | Verifica existência de um registro pela primary key — retorna `boolean` |
330
+ | Method | Description |
331
+ | ------------------------ | -------------------------------------------------------------------------------------------------------------|
332
+ | `get(pk)` | Fetches a record by its primary key |
333
+ | `getOrThrow(pk)` | Fetches a record by its primary key; throws `VSRepoRuntimeError` (code `"20727"`) if not found |
334
+ | `getList(pks)` | Fetches multiple records from a list of primary keys |
335
+ | `save(obj)` | Creates or updates — if the object has a `pk` it performs an `upsert`, otherwise a `create` |
336
+ | `saveList(objs)` | Saves an array of objects in a single automatic transaction |
337
+ | `patch(pk, obj)` | Partially updates a record by its primary key |
338
+ | `patchList(tuples)` | Partially updates multiple records via an array of `[pk, obj]` tuples in an automatic transaction |
339
+ | `merge(pk, obj)` | Fetches a record and deep merges it in memory — **does not persist**, returns the merged object |
340
+ | `remove(pk)` | Removes a record by its primary key |
341
+ | `removeList(pks)` | Removes several records by a list of primary keys — returns `{ count }` |
342
+ | `getAll()` | Returns all records (accepts `pagination` and `order` in `options`) |
343
+ | `total()` | Returns the total number of records |
344
+ | `has(pk)` | Checks whether a record exists by its primary key — returns `boolean` |
335
345
 
336
- Todos aceitam `options` como último argumento.
346
+ All of them accept `options` as the last argument.
337
347
 
338
348
  ### Soft-delete
339
349
 
340
- Quando `softRemovekName` está configurado no repository, os seguintes métodos adicionais ficam disponíveis:
350
+ When `softRemovekName` is configured on the repository, the following additional methods become available:
341
351
 
342
- | Método | Descrição |
343
- | -------------------------- | --------------------------------------------------------------------------------- |
344
- | `softRemove(pk)` | Marca um registro como removido preenchendo `softRemovekName` com a data atual |
345
- | `softRemoveList(pks)` | Marca múltiplos registros como removidos em lote — retorna `{ count }` |
346
- | `restore(pk)` | Restaura um registro soft-deletado, limpando o campo `softRemovekName` |
347
- | `restoreList(pks)` | Restaura múltiplos registros soft-deletados em lote — retorna `{ count }` |
352
+ | Method | Description |
353
+ | -------------------------- | ------------------------------------------------------------------------------------|
354
+ | `softRemove(pk)` | Marks a record as removed by filling `softRemovekName` with the current date |
355
+ | `softRemoveList(pks)` | Marks multiple records as removed in batch — returns `{ count }` |
356
+ | `restore(pk)` | Restores a soft-deleted record, clearing the `softRemovekName` field |
357
+ | `restoreList(pks)` | Restores multiple soft-deleted records in batch — returns `{ count }` |
348
358
 
349
359
  ```ts
350
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
351
- tableName: "usuario",
360
+ const userRepository = setupVSRepo<User, "user">()(({
361
+ tableName: "user",
352
362
  pkName: "id",
353
- softRemovekName: "deletedAt", // deve ser um campo DateTime no schema do Prisma
363
+ softRemovekName: "deletedAt", // must be a DateTime field in the Prisma schema
354
364
  }).build(prisma);
355
365
 
356
- await usuarioRepository.softRemove(1);
357
- await usuarioRepository.restore(1);
366
+ await userRepository.softRemove(1);
367
+ await userRepository.restore(1);
358
368
  ```
359
369
 
360
- > 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 for incorreto.
370
+ > The field provided in `softRemovekName` **must** be of type `DateTime` in the Prisma schema. VSRepository validates this at `build` time and throws `VSRepoBuildError` if the type is incorrect.
361
371
 
362
- ### Operações em lote
372
+ ### Batch operations
363
373
 
364
- `saveList` e `patchList` executam todas as operações automaticamente dentro de uma única transação do Prisma. Se alguma falhar, todas as anteriores são revertidas.
374
+ `saveList` and `patchList` automatically run all operations inside a single Prisma transaction. If any operation fails, all previous ones are rolled back.
365
375
 
366
376
  ```ts
367
- // saveList — cria ou atualiza múltiplos objetos em transação automática
368
- const usuarios = await usuarioRepository.saveList([
369
- { nome: "Maria", email: "maria@email.com" },
370
- { id: 2, nome: "João Atualizado", email: "joao@email.com" },
377
+ // saveList — creates or updates multiple objects in an automatic transaction
378
+ const users = await userRepository.saveList([
379
+ { name: "Mary", email: "mary@email.com" },
380
+ { id: 2, name: "John Updated", email: "john@email.com" },
371
381
  ]);
372
382
 
373
- // patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
374
- const atualizados = await usuarioRepository.patchList([
375
- [1, { ativo: false }],
376
- [2, { nome: "Novo Nome" }],
383
+ // patchList — partially updates multiple records via [pk, obj] tuples
384
+ const updated = await userRepository.patchList([
385
+ [1, { active: false }],
386
+ [2, { name: "New Name" }],
377
387
  ]);
378
388
  ```
379
389
 
380
- Quando você já está dentro de uma transação existente, passe-a em `options.db`. Nesse caso, o `db` deve ser um `DbTransaction` (não o cliente principal), pois o método não cria uma transação própria:
390
+ When you're already inside an existing transaction, pass it in `options.db`. In this case, `db` must be a `DbTransaction` (not the main client):
381
391
 
382
392
  ```ts
383
393
  await prisma.$transaction(async (tx) => {
384
- await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
385
- await usuarioRepository.patchList([[1, { ativo: false }]], { db: tx });
394
+ await userRepository.saveList([{ name: "Mary" }, { name: "Gus" }], { db: tx });
395
+ await userRepository.patchList([[1, { active: false }], [2, { active: true }]], { db: tx });
386
396
  });
387
397
  ```
388
398
 
389
399
  ### Merge
390
400
 
391
- O método `merge` busca um registro pela PK e mescla profundamente (`deepmerge`) o objeto fornecido 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.
401
+ The `merge` method fetches a record by its PK and deeply merges (`deepmerge`) the provided object with the existing data **in memory**. It **does not persist** the changes — it returns the merged result so you can decide what to do with it.
392
402
 
393
403
  ```ts
394
- const existente = await usuarioRepository.get(1);
395
- // existente: { id: 1, nome: "Maria", perfil: { bio: "Olá", idade: 25 } }
404
+ const existing = await userRepository.get(1);
405
+ // existing: { id: 1, name: "Mary", profile: { bio: "Hi", age: 25 } }
396
406
 
397
- const mesclado = await usuarioRepository.merge(1, {
398
- perfil: { bio: "Bio atualizada" },
407
+ const merged = await userRepository.merge(1, {
408
+ profile: { bio: "Updated bio" },
399
409
  });
400
- // mesclado: { id: 1, nome: "Maria", perfil: { bio: "Bio atualizada", idade: 25 } }
410
+ // merged: { id: 1, name: "Mary", profile: { bio: "Updated bio", age: 25 } }
401
411
 
402
- // Para persistir, passe para save ou patch:
403
- await usuarioRepository.save(mesclado);
412
+ // To persist, pass it to save or patch:
413
+ await userRepository.save(merged);
404
414
  ```
405
415
 
406
- Retorna `null` se o registro não for encontrado.
416
+ Returns `null` if the record is not found.
407
417
 
408
- **Merge de relações to-many (`otm`/`mtm`) é feito por PK, não por concatenação simples.** Para relações to-one (`oto`/`mto`), o `merge` faz um deepmerge comum do objeto. Já para relações to-many, cada item do array enviado é casado com o item existente que tem a mesma PK (definida em `relations[chave].pk`): se a PK bate, os dois objetos são mesclados entre si; se não bate (item novo, sem correspondente), ele é apenas adicionado à lista. Itens existentes que não aparecem no array enviado são mantidos.
418
+ **Merging to-many relations (`otm`/`mtm`) is done by PK, not by simple concatenation.** For to-one relations (`oto`/`mto`), `merge` performs a regular deep merge of the object. For to-many relations, each item in the sent array is matched against the existing item that has the same PK (defined in `relations[key].pk`): if the PK matches, the two objects are merged together; if it doesn't match (a new item with no counterpart), it's simply added to the list. Existing items that don't appear in the sent array are kept.
409
419
 
410
420
  ```ts
411
- const existente = await usuarioRepository.get(1);
412
- // existente: {
421
+ const existing = await userRepository.get(1);
422
+ // existing: {
413
423
  // id: 1,
414
- // postagens: [
415
- // { id: 10, titulo: "Post A", publicada: false },
416
- // { id: 11, titulo: "Post B", publicada: true },
424
+ // posts: [
425
+ // { id: 10, title: "Post A", published: false },
426
+ // { id: 11, title: "Post B", published: true },
417
427
  // ],
418
428
  // }
419
429
 
420
- const mesclado = await usuarioRepository.merge(1, {
421
- postagens: [
422
- { id: 10, publicada: true }, // mesma PK (id: 10) → mescla com o item existente
423
- { titulo: "Post C" }, // sem PK → é adicionado como um novo item
430
+ const merged = await userRepository.merge(1, {
431
+ posts: [
432
+ { id: 10, published: true }, // same PK (id: 10) → merges with the existing item
433
+ { title: "Post C" }, // no PK → added as a new item
424
434
  ],
425
435
  });
426
- // mesclado: {
436
+ // merged: {
427
437
  // id: 1,
428
- // postagens: [
429
- // { id: 10, titulo: "Post A", publicada: true }, // mesclado
430
- // { id: 11, titulo: "Post B", publicada: true }, // mantido, não veio no array enviado
431
- // { titulo: "Post C" }, // adicionado
438
+ // posts: [
439
+ // { id: 10, title: "Post A", published: true }, // merged
440
+ // { id: 11, title: "Post B", published: true }, // kept, wasn't in the sent array
441
+ // { title: "Post C" }, // added
432
442
  // ],
433
443
  // }
434
444
  ```
435
445
 
436
- > Note que o `merge` nunca remove itens de uma relação to-many — ele só mescla os que casam por PK e adiciona os que não casam. Para remover itens de uma relação, use `save`/`patch` com `restriction: "set"` na configuração da relação.
446
+ > Note that `merge` never removes items from a to-many relation — it only merges the ones that match by PK and adds the ones that don't. To remove items from a relation, use `save`/`patch` with `restriction: "set"` in the relation configuration.
437
447
 
438
- ### Configurando os métodos base
448
+ ### Configuring the base methods
439
449
 
440
- O segundo argumento de `.build(prisma, config)` permite ajustar o comportamento global do repository e customizar cada método base individualmente através de `baseMethods`.
450
+ The second argument of `.build(prisma, config)` lets you adjust the repository's global behavior and customize each base method individually through `baseMethods`.
441
451
 
442
452
  ```ts
443
- usuarioVSRepo.build(prisma, {
444
- // Exibe logs internos do VSRepository no console (queries montadas, prefixo detectado,
445
- // filtros aplicados etc). Ótimo para debugar métodos dinâmicos. Padrão = false.
453
+ userVSRepo.build(prisma, {
454
+ // Shows VSRepository's internal logs on the console (built queries, detected prefix,
455
+ // applied filters, etc). Great for debugging dynamic methods. Default = false.
446
456
  showWorking: true,
447
457
 
448
458
  baseMethods: {
449
459
  get: {
450
- // Habilita/desabilita o método no repository final. Se `false`, o método
451
- // sequer aparece no tipo do repository (não é só um erro em runtime). Padrão = true.
460
+ // Enables/disables the method on the final repository. If `false`, the method
461
+ // doesn't even appear in the repository's type (it's not just a runtime error). Default = true.
452
462
  active: true,
453
463
 
454
- // Select model aplicado por padrão quando o método é chamado sem `options.selectModel`.
455
- // Sobrescreve o `defaultSelectModel` do setupVSRepo apenas para este método.
464
+ // Select model applied by default when the method is called without `options.selectModel`.
465
+ // Overrides the `defaultSelectModel` from setupVSRepo for this method only.
456
466
  defaultSelect: "public",
457
467
  },
458
468
  remove: {
459
469
  active: true,
460
470
  defaultSelect: "minimal",
461
471
 
462
- // Quando `true`, ignora o `requiredWhere` configurado no setupVSRepo para
463
- // este método específico — útil quando um método precisa "furar" um filtro
464
- // global (ex.: multi-tenancy) em um caso pontual. Padrão = false.
472
+ // When `true`, ignores the `requiredWhere` configured in setupVSRepo for
473
+ // this specific method — useful when a method needs to "punch through" a
474
+ // global filter (e.g. multi-tenancy) in a specific case. Default = false.
465
475
  ignoreRequiredWhere: false,
466
476
  },
467
477
  save: {
468
- // Aqui só `ignoreRequiredWhere` é definido — `active` e `defaultSelect`
469
- // continuam com seus padrões (true e o `defaultSelectModel` global).
478
+ // Here only `ignoreRequiredWhere` is set — `active` and `defaultSelect`
479
+ // keep their defaults (true and the global `defaultSelectModel`).
470
480
  ignoreRequiredWhere: true,
471
481
  },
472
482
  patch: {
473
- // Somente o select é sobrescrito; o método continua ativo normalmente.
483
+ // Only the select is overridden; the method stays active normally.
474
484
  defaultSelect: "minimal",
475
485
  },
476
486
  has: {
477
- active: false, // Desativa o 'has' (padrão = true) — o método some do repository
487
+ active: false, // Disables 'has' (default = true) — the method disappears from the repository
478
488
  },
479
489
  softRemove: {
480
- // Métodos de soft-delete seguem as mesmas opções (`active`, `defaultSelect`,
481
- // `ignoreRequiredWhere`). Só ficam disponíveis se `softRemovekName` estiver configurado.
490
+ // Soft-delete methods follow the same options (`active`, `defaultSelect`,
491
+ // `ignoreRequiredWhere`). They're only available if `softRemovekName` is configured.
482
492
  active: true,
483
493
  defaultSelect: "minimal",
484
494
  },
@@ -486,176 +496,176 @@ usuarioVSRepo.build(prisma, {
486
496
  });
487
497
  ```
488
498
 
489
- > Métodos em lote/agregados como `removeList`, `softRemoveList`, `restoreList`, `total` e `has` **não** aceitam `defaultSelect` (não retornam um registro selecionável — retornam `{ count }` ou `boolean`). Nesses casos `BaseMethodConfig` fica restrito a `active` e `ignoreRequiredWhere`.
499
+ > Batch/aggregate methods like `removeList`, `softRemoveList`, `restoreList`, `total`, and `has` **do not** accept `defaultSelect` (they don't return a selectable record — they return `{ count }` or `boolean`). In these cases `BaseMethodConfig` is restricted to `active` and `ignoreRequiredWhere`.
490
500
 
491
501
  ---
492
502
 
493
503
  ## Select Models
494
504
 
495
- `selectModels` define projeções de dados nomeadas e reutilizáveis.
505
+ `selectModels` defines named, reusable data projections.
496
506
 
497
507
  ```ts
498
508
  selectModels: {
499
- public: { id: true, nome: true, email: true },
500
- internal: { id: true, nome: true, email: true, senha: true },
509
+ public: { id: true, name: true, email: true },
510
+ internal: { id: true, name: true, email: true, password: true },
501
511
  minimal: { id: true },
502
512
  },
503
513
  defaultSelectModel: "public",
504
514
  ```
505
515
 
506
- `defaultSelectModel` define qual select é usado automaticamente quando nenhum é especificado na chamada. É recomendado sempre definí-lo junto com `selectModels`.
516
+ `defaultSelectModel` defines which select is used automatically when none is specified in the call. It's recommended to always define it together with `selectModels`.
507
517
 
508
- **Usando um select específico na chamada:**
518
+ **Using a specific select in the call:**
509
519
 
510
520
  ```ts
511
- const usuario = await usuarioRepository.get(id, { selectModel: "minimal" });
521
+ const user = await userRepository.get(id, { selectModel: "minimal" });
512
522
  ```
513
523
 
514
- **Retornando o payload padrão do Prisma (sem select):**
524
+ **Returning Prisma's default payload (without select):**
515
525
 
516
526
  ```ts
517
- const usuarioCompleto = await usuarioRepository.get(id, { selectModel: false });
527
+ const fullUser = await userRepository.get(id, { selectModel: false });
518
528
  ```
519
529
 
520
530
  ---
521
531
 
522
532
  ## Include Models
523
533
 
524
- `includeModels` funciona de forma parecida com o `selectModels`, mas em vez de receber um `select`, ele recebe um `include` válido do Prisma.
534
+ `includeModels` works similarly to `selectModels`, but instead of receiving a `select`, it receives a valid Prisma `include`.
525
535
 
526
536
  ```ts
527
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
528
- tableName: "usuario",
537
+ const userRepository = setupVSRepo<User, "user">()(({
538
+ tableName: "user",
529
539
  pkName: "id",
530
540
  selectModels: {
531
- public: { id: true, nome: true, email: true },
541
+ public: { id: true, name: true, email: true },
532
542
  },
533
543
  defaultSelectModel: "public",
534
544
  includeModels: {
535
- comPosts: { posts: true },
536
- comPostsEPerfil: { posts: true, perfil: true },
545
+ withPosts: { posts: true },
546
+ withPostsAndProfile: { posts: true, profile: true },
537
547
  },
538
548
  }).build(prisma);
539
549
  ```
540
550
 
541
- **Usando um `includeModel` na chamada:**
551
+ **Using an `includeModel` in the call:**
542
552
 
543
553
  ```ts
544
- const usuario = await usuarioRepository.get(id, { includeModel: "comPosts" });
554
+ const user = await userRepository.get(id, { includeModel: "withPosts" });
545
555
  ```
546
556
 
547
- Nesse caso, o `select` padrão (`selectModels`/`defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
557
+ In this case, the default `select` (`selectModels`/`defaultSelectModel`) is ignored and only the `include` is sent to Prisma.
548
558
 
549
- ### Diferenças em relação ao `selectModels`
559
+ ### Differences from `selectModels`
550
560
 
551
- - **Só pode ser passado na chamada do método**, via `options.includeModel`. Não existe `defaultIncludeModel` nem `defaultInclude` — não há como configurar um `includeModel` padrão no repository, diferente do que ocorre com `defaultSelectModel`.
552
- - **`includeModel` e `selectModel` não podem ser passados juntos** na mesma chamada. Se um `includeModel` for informado, qualquer `selectModel` (incluindo o padrão) é ignorado.
561
+ - **Can only be passed in the method call**, via `options.includeModel`. There's no `defaultIncludeModel` or `defaultInclude` — there's no way to configure a default `includeModel` on the repository, unlike what happens with `defaultSelectModel`.
562
+ - **`includeModel` and `selectModel` cannot be passed together** in the same call. If an `includeModel` is provided, any `selectModel` (including the default one) is ignored.
553
563
 
554
564
  ```ts
555
- // CORRETO ✅ — apenas includeModel
556
- await usuarioRepository.get(id, { includeModel: "comPosts" });
565
+ // CORRECT ✅ — includeModel only
566
+ await userRepository.get(id, { includeModel: "withPosts" });
557
567
 
558
- // CORRETO ✅ — apenas selectModel
559
- await usuarioRepository.get(id, { selectModel: "public" });
568
+ // CORRECT ✅ — selectModel only
569
+ await userRepository.get(id, { selectModel: "public" });
560
570
 
561
- // ERRADO ❌ — não é permitido combinar os dois
562
- await usuarioRepository.get(id, { selectModel: "public", includeModel: "comPosts" });
571
+ // WRONG ❌ — combining both is not allowed
572
+ await userRepository.get(id, { selectModel: "public", includeModel: "withPosts" });
563
573
  ```
564
574
 
565
575
  ---
566
576
 
567
577
  ## Required Where
568
578
 
569
- `requiredWhere` define filtros aplicados automaticamente em todas as queries do repository.
579
+ `requiredWhere` defines filters that are automatically applied to every query on the repository.
570
580
 
571
581
  ```ts
572
- requiredWhere: { ativo: true },
582
+ requiredWhere: { active: true },
573
583
  ```
574
584
 
575
- Agora toda query incluirá `ativo: true` automaticamente:
585
+ Now every query will automatically include `active: true`:
576
586
 
577
587
  ```ts
578
- // Internamente: WHERE ativo = true
579
- const usuarios = await usuarioRepository.findMany();
588
+ // Internally: WHERE active = true
589
+ const users = await userRepository.findMany();
580
590
 
581
- // Internamente: WHERE email = 'joao@email.com' AND ativo = true
582
- const usuario = await usuarioRepository.findByEmail("joao@email.com");
591
+ // Internally: WHERE email = 'john@email.com' AND active = true
592
+ const user = await userRepository.findByEmail("john@email.com");
583
593
  ```
584
594
 
585
- Útil para soft-deletes manuais, multi-tenancy e filtros globais de qualquer natureza.
595
+ Useful for manual soft-deletes, multi-tenancy, and global filters of any kind.
586
596
 
587
597
  ---
588
598
 
589
599
  ## Default Ordenation
590
600
 
591
- `defaultOrdenation` define uma ordenação padrão aplicada automaticamente em todas as queries que aceitam `orderBy`, sem precisar repetir o argumento `order` em cada chamada.
601
+ `defaultOrdenation` defines a default ordering that's automatically applied to every query that accepts `orderBy`, without needing to repeat the `order` argument on every call.
592
602
 
593
603
  ```ts
594
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
595
- tableName: "usuario",
604
+ const userRepository = setupVSRepo<User, "user">()(({
605
+ tableName: "user",
596
606
  pkName: "id",
597
- defaultOrdenation: { criadoEm: "desc" },
607
+ defaultOrdenation: { createdAt: "desc" },
598
608
  }).build(prisma);
599
609
  ```
600
610
 
601
- Com isso, toda query de listagem já virá ordenada por `criadoEm` decrescente:
611
+ With this, every listing query will already come ordered by `createdAt` descending:
602
612
 
603
613
  ```ts
604
- // Internamente: ORDER BY criadoEm DESC
605
- const usuarios = await usuarioRepository.getAll();
614
+ // Internally: ORDER BY createdAt DESC
615
+ const users = await userRepository.getAll();
606
616
 
607
- // Também aplica ao getAll com pagination
608
- const paginados = await usuarioRepository.getAll({ pagination: { take: 10 } });
617
+ // Also applies to getAll with pagination
618
+ const paginated = await userRepository.getAll({ pagination: { take: 10 } });
609
619
  ```
610
620
 
611
- **A `defaultOrdenation` é ignorada quando:**
621
+ **`defaultOrdenation` is ignored when:**
612
622
 
613
- - O método usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered` — nesses casos o argumento `order` passado na chamada tem prioridade.
614
- - O método dinâmico tem `injectOrdenation` configurado — a ordenação fixa do método prevalece.
623
+ - The method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix — in these cases the `order` argument passed in the call takes priority.
624
+ - The dynamic method has `injectOrdenation` configured — the method's fixed ordering takes precedence.
615
625
 
616
626
  ```ts
617
627
  methods: {
618
- findManyPaginatedAndOrdered: { map: true }, // ordem vem do argumento → defaultOrdenation ignorada
619
- findManyByAtivo: { map: true }, // sem Ordered → defaultOrdenation aplicada
628
+ findManyPaginatedAndOrdered: { map: true }, // order comes from the argument → defaultOrdenation ignored
629
+ findManyByActive: { map: true }, // no Ordered → defaultOrdenation applied
620
630
  findManyByStatus: {
621
631
  map: true,
622
- injectOrdenation: { nome: "asc" }, // injectOrdenation → defaultOrdenation ignorada
632
+ injectOrdenation: { name: "asc" }, // injectOrdenation → defaultOrdenation ignored
623
633
  },
624
634
  }
625
635
  ```
626
636
 
627
- > `defaultOrdenation` aceita o mesmo tipo que o `orderBy` nativo do Prisma para o modelo — incluindo arrays de ordenações encadeadas.
637
+ > `defaultOrdenation` accepts the same type as Prisma's native `orderBy` for the model — including arrays of chained orderings.
628
638
 
629
639
  ---
630
640
 
631
- ## Opção `see`
641
+ ## `see` option
632
642
 
633
- Quando `softRemovekName` está configurado, todos os métodos aceitam a opção `see` para controlar a visibilidade de registros soft-deletados:
643
+ When `softRemovekName` is configured, every method accepts the `see` option to control the visibility of soft-deleted records:
634
644
 
635
- | Valor | Comportamento |
636
- | ----------- | ------------------------------------------------------------- |
637
- | `"active"` | Retorna apenas registros **não** removidos (padrão) |
638
- | `"removed"` | Retorna apenas registros removidos |
639
- | `"all"` | Retorna todos os registros, independentemente do status |
645
+ | Value | Behavior |
646
+ | ----------- | --------------------------------------------------------------|
647
+ | `"active"` | Returns only records that are **not** removed (default) |
648
+ | `"removed"` | Returns only removed records |
649
+ | `"all"` | Returns all records, regardless of status |
640
650
 
641
651
  ```ts
642
- // Retorna apenas usuários ativos (padrão)
643
- const ativos = await usuarioRepository.getAll();
652
+ // Returns only active users (default)
653
+ const active = await userRepository.getAll();
644
654
 
645
- // Retorna apenas usuários removidos
646
- const removidos = await usuarioRepository.getAll({ see: "removed" });
655
+ // Returns only removed users
656
+ const removed = await userRepository.getAll({ see: "removed" });
647
657
 
648
- // Retorna todos
649
- const todos = await usuarioRepository.getAll({ see: "all" });
658
+ // Returns all
659
+ const all = await userRepository.getAll({ see: "all" });
650
660
  ```
651
661
 
652
- > A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não o substitui.
662
+ > The `see` option works independently of `requiredWhere` — it's applied on top of the soft-delete filter, not as a replacement for it.
653
663
 
654
664
  ---
655
665
 
656
- ## Métodos dinâmicos
666
+ ## Dynamic methods
657
667
 
658
- Métodos dinâmicos são definidos na propriedade `methods` e têm seus comportamentos inferidos a partir do nome.
668
+ Dynamic methods are defined in the `methods` property and have their behavior inferred from their name.
659
669
 
660
670
  ```ts
661
671
  methods: {
@@ -668,323 +678,358 @@ methods: {
668
678
 
669
679
  ---
670
680
 
671
- ### Prefixos disponíveis
672
-
673
- O prefixo do nome do método determina qual operação Prisma será chamada e quais argumentos serão esperados.
674
-
675
- | Prefixo | Operação Prisma | Retorno | Observações |
676
- | -------------------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------ |
677
- | `findOneBy` | `findFirst` | `T \| null` | Retorno único. |
678
- | `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` para retorno único (**obsoleto**, use `findOneBy`) |
679
- | `findUniqueBy` | `findUnique` | `T \| null` | |
680
- | `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Lança erro se não encontrar |
681
- | `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
682
- | `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Aceita campos como filtro; lança erro se não encontrar |
683
- | `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
684
- | `findFirstOrThrow` | `findFirstOrThrow` | `T` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere`; lança erro se não encontrar |
685
- | `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
686
- | `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
687
- | `findOneWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
688
- | `findWhere` | `findFirst` | `T \| null` | (**Obsoleto, use `findOneWhere`**) Recebe um objeto `where` explícito |
689
- | `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
690
- | `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrar, `false` caso contrário |
691
- | `existsWhere` | `findFirst` | `boolean` | Recebe um objeto `where` explícito e retorna se existe |
692
- | `countBy` | `count` | `number` | Aceita campos como filtro |
693
- | `countWhere` | `count` | `number` | Recebe um objeto `where` explícito como argumento |
694
- | `count` | `count` | `number` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
695
- | `create` | `create` | `T` | Recebe `data` como argumento |
696
- | `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
697
- | `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
698
- | `updateBy` | `update` | `T` | Recebe `data` como argumento |
699
- | `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
700
- | `updateManyWhere` | `updateMany` | `{ count: number }` | Recebe um objeto `where` e um objeto `data` como argumentos |
701
- | `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
702
- | `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Recebe um objeto `where` e um objeto `data` como argumentos |
703
- | `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
704
- | `deleteBy` | `delete` | `T` | |
705
- | `deleteManyBy` | `deleteMany` | `{ count: number }` | |
706
- | `deleteManyWhere` | `deleteMany` | `{ count: number }` | Recebe um objeto `where` explícito como argumento |
707
- | `aggregate` | `aggregate` | `Dinâmico` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
708
- | `groupBy` | `groupBy` | `Dinâmico[]` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
681
+ ### Available prefixes
682
+
683
+ The method name's prefix determines which Prisma operation will be called and which arguments are expected.
684
+
685
+ | Prefix | Prisma operation | Return | Notes |
686
+ | ---------------------------- | -------------------------- | ------------------------ | ---------------------------------------------------------------------------|
687
+ | `findOneBy` | `findFirst` | `T \| null` | Single return. |
688
+ | `findBy` | `findMany` / `findFirst` | `T[]` or `T \| null` | Default is list; use `fbMode: "one"` for a single return (**deprecated**, use `findOneBy`) |
689
+ | `findUniqueBy` | `findUnique` | `T \| null` | |
690
+ | `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Throws an error if not found |
691
+ | `findFirstBy` | `findFirst` | `T \| null` | Accepts fields as filter |
692
+ | `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Accepts fields as filter; throws an error if not found |
693
+ | `findFirst` | `findFirst` | `T \| null` | No field filters; applies only `requiredWhere` and `pushWhere` |
694
+ | `findFirstOrThrow` | `findFirstOrThrow` | `T` | No field filters; applies only `requiredWhere` and `pushWhere`; throws an error if not found |
695
+ | `findManyBy` | `findMany` | `T[]` | Accepts fields as filter |
696
+ | `findMany` | `findMany` | `T[]` | No field filters; applies only `requiredWhere` and `pushWhere` |
697
+ | `findOneWhere` | `findFirst` | `T \| null` | Receives an explicit `where` object as argument |
698
+ | `findListWhere` | `findMany` | `T[]` | Receives an explicit `where` object as argument |
699
+ | `existsBy` | `findFirst` | `boolean` | Returns `true` if found, `false` otherwise |
700
+ | `existsWhere` | `findFirst` | `boolean` | Receives an explicit `where` object and returns whether it exists |
701
+ | `countBy` | `count` | `number` | Accepts fields as filter |
702
+ | `countWhere` | `count` | `number` | Receives an explicit `where` object as argument |
703
+ | `count` | `count` | `number` | No field filters; applies only `requiredWhere` and `pushWhere` |
704
+ | `create` | `create` | `T` | Receives `data` as argument |
705
+ | `createMany` | `createMany` | `{ count: number }` | Receives `data` as argument; supports `SkipDuplicates` |
706
+ | `createManyAndReturn` | `createManyAndReturn` | `T[]` | Receives `data` as argument; supports `SkipDuplicates` |
707
+ | `updateBy` | `update` | `T` | Receives `data` as argument |
708
+ | `updateManyBy` | `updateMany` | `{ count: number }` | Receives `data` as argument |
709
+ | `updateManyWhere` | `updateMany` | `{ count: number }` | Receives a `where` object and a `data` object as arguments |
710
+ | `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Receives `data` as argument |
711
+ | `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Receives a `where` object and a `data` object as arguments |
712
+ | `upsertBy` | `upsert` | `T` | Receives `update` and `create` as arguments |
713
+ | `deleteBy` | `delete` | `T` | |
714
+ | `deleteManyBy` | `deleteMany` | `{ count: number }` | |
715
+ | `deleteManyWhere` | `deleteMany` | `{ count: number }` | Receives an explicit `where` object as argument |
716
+ | `aggregate` | `aggregate` | `Dynamic` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
717
+ | `groupBy` | `groupBy` | `Dynamic[]` | Name must be exact; receives native Prisma args; ignores `selectModels`, `pushWhere`, and `requiredWhere` |
709
718
 
710
719
  ---
711
720
 
712
- ### Filtros de campo
713
-
714
- Os filtros são sufixos aplicados ao nome do campo dentro do método. O campo em si vem capitalizado logo após o prefixo (ou após `By`).
715
-
716
- | Sufixo | Operador Prisma | Argumento necessário |
717
- | ------------------ | --------------------- | ------------------------- |
718
- | *(sem sufixo)* | igualdade (`=`) | sim |
719
- | `Not` | `not` | sim |
720
- | `In` | `in` | sim (array) |
721
- | `NotIn` | `notIn` | sim (array) |
722
- | `Contains` | `contains` | sim |
723
- | `NotContains` | `not.contains` | sim |
724
- | `StartsWith` | `startsWith` | sim |
725
- | `NotStartsWith` | `not.startsWith` | sim |
726
- | `EndsWith` | `endsWith` | sim |
727
- | `NotEndsWith` | `not.endsWith` | sim |
728
- | `GreaterThan` | `gt` | sim |
729
- | `GreaterThanEqual` | `gte` | sim |
730
- | `LessThan` | `lt` | sim |
731
- | `LessThanEqual` | `lte` | sim |
732
- | `Between` | `gte` + `lte` | sim (tupla `[min, max]`) |
733
- | `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`) |
734
- | `IsNull` | `null` | não |
735
- | `IsNotNull` | `not: null` | não |
736
- | `IsTrue` | `true` | não |
737
- | `IsFalse` | `false` | não |
738
- | `Insensitive` | `mode: 'insensitive'` | combinador |
739
-
740
- `Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
721
+ ### Field filters
722
+
723
+ Filters are suffixes applied to the field name inside the method. The field itself comes capitalized right after the prefix (or after `By`).
724
+
725
+ | Suffix | Prisma operator | Argument required |
726
+ | -------------------- | ---------------------- | ---------------------------|
727
+ | *(no suffix)* | equality (`=`) | yes |
728
+ | `Not` | `not` | yes |
729
+ | `In` | `in` | yes (array) |
730
+ | `NotIn` | `notIn` | yes (array) |
731
+ | `Contains` | `contains` | yes |
732
+ | `NotContains` | `not.contains` | yes |
733
+ | `StartsWith` | `startsWith` | yes |
734
+ | `NotStartsWith` | `not.startsWith` | yes |
735
+ | `EndsWith` | `endsWith` | yes |
736
+ | `NotEndsWith` | `not.endsWith` | yes |
737
+ | `GreaterThan` | `gt` | yes |
738
+ | `GreaterThanEqual` | `gte` | yes |
739
+ | `LessThan` | `lt` | yes |
740
+ | `LessThanEqual` | `lte` | yes |
741
+ | `Between` | `gte` + `lte` | yes (tuple `[min, max]`) |
742
+ | `NotBetween` | `not.gte` + `not.lte` | yes (tuple `[min, max]`) |
743
+ | `IsNull` | `null` | no |
744
+ | `IsNotNull` | `not: null` | no |
745
+ | `IsTrue` | `true` | no |
746
+ | `IsFalse` | `false` | no |
747
+ | `Insensitive` | `mode: 'insensitive'` | combinator |
748
+
749
+ `Insensitive` is a combinator and can be used together with another text filter:
741
750
 
742
751
  ```ts
743
- findByNomeContainsInsensitive // { nome: { contains: valor, mode: 'insensitive' } }
744
- findByEmailStartsWithInsensitive // { email: { startsWith: valor, mode: 'insensitive' } }
745
- findByNomeInsensitive // { nome: { equals: valor, mode: 'insensitive' } }
752
+ findByNameContainsInsensitive // { name: { contains: value, mode: 'insensitive' } }
753
+ findByEmailStartsWithInsensitive // { email: { startsWith: value, mode: 'insensitive' } }
754
+ findByNameInsensitive // { name: { equals: value, mode: 'insensitive' } }
746
755
  ```
747
756
 
748
- `Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]`**:
757
+ `Between` and `NotBetween` receive a **tuple `[minValue, maxValue]`**:
749
758
 
750
759
  ```ts
751
760
  methods: {
752
- findManyByIdadeBetween: { map: true },
753
- findManyBySalarioNotBetween: { map: true },
754
- findManyByCriadoEmBetween: { map: true },
761
+ findManyByAgeBetween: { map: true },
762
+ findManyBySalaryNotBetween: { map: true },
763
+ findManyByCreatedAtBetween: { map: true },
755
764
  }
756
765
 
757
- await usuarioRepository.findManyByIdadeBetween([18, 65]);
758
- await usuarioRepository.findManyBySalarioNotBetween([1000, 5000]);
759
- await usuarioRepository.findManyByCriadoEmBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
766
+ await userRepository.findManyByAgeBetween([18, 65]);
767
+ await userRepository.findManyBySalaryNotBetween([1000, 5000]);
768
+ await userRepository.findManyByCreatedAtBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
760
769
  ```
761
770
 
762
- O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
771
+ The `Optional` suffix can be added to any field to make the argument optional:
763
772
 
764
773
  ```ts
765
- findByNomeOptionalAndEmail // nome é opcional, email é obrigatório
774
+ findByNameOptionalAndEmail // name is optional, email is required
766
775
  ```
767
776
 
768
777
  ---
769
778
 
770
- ### Operadores lógicos
779
+ ### Logical operators
771
780
 
772
- | Operador | Uso no nome | Exemplo |
773
- | --------- | ---------------------------- | -------------------------------- |
774
- | `And` | entre dois campos | `findOneByIdAndEmail` |
775
- | `Or` | entre dois campos | `findByNomeOrEmail` |
776
- | `AND` | separa bloco final em `AND` | `findByEmailOrNameANDActiveStatus` |
781
+ | Operator | Usage in the name | Example |
782
+ | --------- | ------------------------------ | -----------------------------------|
783
+ | `And` | between two fields | `findOneByIdAndEmail` |
784
+ | `Or` | between two fields | `findByNameOrEmail` |
785
+ | `AND` | separates a final `AND` block | `findByEmailOrNameANDActiveStatus` |
777
786
 
778
- `AND` (em capslock) tem uma regra específica:
787
+ `AND` (in caps) has a specific rule:
779
788
 
780
- - Só pode existir **um** `AND` por método.
781
- - Todos os campos depois de `AND` são injetados dentro de `AND: []`.
782
- - Depois de um `AND` não pode ter `Or`.
789
+ - Only **one** `AND` can exist per method.
790
+ - All fields after `AND` are injected inside `AND: []`.
791
+ - After an `AND`, there can't be an `Or`.
783
792
 
784
- Exemplo:
793
+ Example:
785
794
 
786
795
  ```ts
787
796
  methods: {
788
797
  findOneByIdAndEmail: { map: true },
789
- findByNomeOrEmail: { map: true },
790
- findFirstByIdOrEmailAndNome: { map: true },
791
- findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
798
+ findByNameOrEmail: { map: true },
799
+ findFirstByIdOrEmailAndName: { map: true },
800
+ findByEmailOrNameANDActiveStatusAndAgeGreaterThan: { map: true }
792
801
  }
793
802
 
794
- await usuarioRepository.findOneByIdAndEmail(1, "joao@email.com");
795
- await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
796
- await usuarioRepository.findFirstByIdOrEmailAndNome(1, "joao@email.com", "Joao");
797
- await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("joao@email.com", "Joao", true, 17)
803
+ await userRepository.findOneByIdAndEmail(1, "john@email.com");
804
+ await userRepository.findByNameOrEmail("John", "john@email.com");
805
+ await userRepository.findFirstByIdOrEmailAndName(1, "john@email.com", "John");
806
+ await userRepository.findByEmailOrNameANDActiveStatusAndAgeGreaterThan("john@email.com", "John", true, 17)
798
807
  ```
799
808
 
800
- Gera (`findOneByIdAndEmail`):
809
+ Generates (`findOneByIdAndEmail`):
801
810
 
802
811
  ```ts
803
812
  {
804
813
  id: 1,
805
- email: "joao@email.com"
814
+ email: "john@email.com"
806
815
  }
807
816
  ```
808
817
 
809
- Gera (`findByNomeOrEmail`):
818
+ Generates (`findByNameOrEmail`):
810
819
 
811
820
  ```ts
812
821
  {
813
822
  OR: [
814
- { nome: "Joao" },
815
- { email: "joao@email.com" }
823
+ { name: "John" },
824
+ { email: "john@email.com" }
816
825
  ]
817
826
  }
818
827
  ```
819
828
 
820
- Gera (`findFirstByIdOrEmailAndNome`):
829
+ Generates (`findFirstByIdOrEmailAndName`):
821
830
 
822
831
  ```ts
823
832
  {
824
833
  OR: [
825
834
  { id: 1 },
826
835
  {
827
- email: "joao@email.com",
828
- nome: "Joao"
836
+ email: "john@email.com",
837
+ name: "John"
829
838
  }
830
839
  ]
831
840
  }
832
841
  ```
833
842
 
834
- Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
843
+ Generates (`findByEmailOrNameANDActiveStatusAndAgeGreaterThan`):
835
844
 
836
845
  ```ts
837
846
  {
838
847
  OR: [
839
- { email: "joao@email.com" },
840
- { name: "Joao" }
848
+ { email: "john@email.com" },
849
+ { name: "John" }
841
850
  ],
842
851
  AND: [
843
852
  { activeStatus: true },
844
- { idade: { gt: 17 } }
853
+ { age: { gt: 17 } }
845
854
  ]
846
855
  }
847
856
  ```
848
857
 
849
858
  ---
850
859
 
851
- ### Filtros de relação
860
+ ### Relation filters
852
861
 
853
- Permitem filtrar por campos de modelos relacionados.
862
+ Allow filtering by fields of related models.
854
863
 
855
864
  > [!IMPORTANT]
856
- > - **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 no `setupVSRepo` deve incluir as relações estruturadas (ex: usando `UsuarioGetPayload<{ include: { perfil: true, postagens: true } }>` do Prisma).
857
- > - **Compatibilidade de sufixos**:
858
- > - Os sufixos `Some`, `Every` e `None` só funcionam para relações **to-many** (`many-to-many` e `one-to-many`).
859
- > - Os sufixos `With` e `Without` só funcionam para relações **to-one** (`one-to-one` e `many-to-one`).
860
-
861
- | Sufixo de relação | Operador Prisma | Observação |
862
- | ---------------------- | --------------- | -------------------------------------------------- |
863
- | `Some` | `some: {}` | Relação tem *algum* registro |
864
- | `SomeField` | `some.field` | Filtra dentro dos registros da relação |
865
- | `EveryField` | `every.field` | Filtra dentro dos registros da relação |
866
- | `None` | `none: {}` | Relação não tem *nenhum* registro |
867
- | `NoneField` | `none.field` | Filtra dentro dos registros da relação |
868
- | `With` | `is: {}` | Relação existe (não é null) |
869
- | `WithField` | `is.field` | Filtra campo dentro da relação |
870
- | `Without` | `isNot: {}` | Relação não existe (é null) |
871
- | `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
872
-
873
- Considerando `usuario` com uma relação to-one `perfil` e uma relação to-many `postagens`:
865
+ > - **Relation typing**: For TypeScript to recognize the types of relation fields in dynamic methods, the generic entity type passed to `setupVSRepo` must include the structured relations (e.g. using Prisma's `UserGetPayload<{ include: { profile: true, posts: true } }>`).
866
+ > - **Suffix compatibility**:
867
+ > - The `Some`, `Every`, and `None` suffixes only work for **to-many** relations (`many-to-many` and `one-to-many`).
868
+ > - The `With` and `Without` suffixes only work for **to-one** relations (`one-to-one` and `many-to-one`).
869
+
870
+ | Relation suffix | Prisma operator | Note |
871
+ | ------------------------ | ----------------- | -------------------------------------------------------|
872
+ | `Some` | `some: {}` | Relation has *some* record |
873
+ | `SomeField` | `some.field` | Filters within the relation's records |
874
+ | `EveryField` | `every.field` | Filters within the relation's records |
875
+ | `None` | `none: {}` | Relation has *no* records |
876
+ | `NoneField` | `none.field` | Filters within the relation's records |
877
+ | `With` | `is: {}` | Relation exists (not null) |
878
+ | `WithField` | `is.field` | Filters a field within the relation |
879
+ | `Without` | `isNot: {}` | Relation doesn't exist (is null) |
880
+ | `WithoutField` | `isNot.field` | Filters a field within the relation with negation |
881
+
882
+ Considering `user` with a to-one relation `profile` and a to-many relation `posts`:
874
883
 
875
884
  ```ts
876
885
  methods: {
877
- // to-many (postagens)
878
- findByPostagensSome: { map: true }, // tem ao menos uma postagem
879
- findByPostagensSomeTitulo: { map: true }, // tem ao menos uma postagem com esse título
880
- findByPostagensEveryPublicada:{ map: true }, // todas as postagens estão publicadas
881
- findByPostagensNone: { map: true }, // não tem nenhuma postagem
882
- findByPostagensNoneTitulo: { map: true }, // nenhuma postagem tem esse título
883
-
884
- // to-one (perfil)
885
- findByPerfilWith: { map: true }, // possui perfil (não é null)
886
- findByPerfilWithBio: { map: true }, // possui perfil com essa bio
887
- findByPerfilWithout: { map: true }, // não possui perfil (é null)
888
- findByPerfilWithoutBio: { map: true }, // possui perfil, mas com bio diferente da informada
886
+ // to-many (posts)
887
+ findByPostsSome: { map: true }, // has at least one post
888
+ findByPostsSomeTitle: { map: true }, // has at least one post with that title
889
+ findByPostsEveryPublishedIsTrue:{ map: true }, // all posts are published
890
+ findByPostsNone: { map: true }, // has no posts
891
+ findByPostsNoneTitle: { map: true }, // no post has that title
892
+
893
+ // to-one (profile)
894
+ findByProfileWith: { map: true }, // has a profile (not null)
895
+ findByProfileWithBio: { map: true }, // has a profile with that bio
896
+ findByProfileWithout: { map: true }, // has no profile (is null)
897
+ findByProfileWithoutBio: { map: true }, // has a profile, but with a different bio than the one provided
889
898
  }
890
899
 
891
- await usuarioRepository.findByPostagensSome();
892
- await usuarioRepository.findByPostagensSomeTitulo("Meu primeiro post");
893
- await usuarioRepository.findByPostagensEveryPublicada(true);
894
- await usuarioRepository.findByPostagensNone();
895
- await usuarioRepository.findByPostagensNoneTitulo("Rascunho");
900
+ await userRepository.findByPostsSome();
901
+ await userRepository.findByPostsSomeTitle("My first post");
902
+ await userRepository.findByPostsEveryPublishedIsTrue();
903
+ await userRepository.findByPostsNone();
904
+ await userRepository.findByPostsNoneTitle("Draft");
896
905
 
897
- await usuarioRepository.findByPerfilWith();
898
- await usuarioRepository.findByPerfilWithBio("Olá, mundo!");
899
- await usuarioRepository.findByPerfilWithout();
900
- await usuarioRepository.findByPerfilWithoutBio("Bio antiga");
906
+ await userRepository.findByProfileWith();
907
+ await userRepository.findByProfileWithBio("Hello, world!");
908
+ await userRepository.findByProfileWithout();
909
+ await userRepository.findByProfileWithoutBio("Old bio");
901
910
  ```
902
911
 
903
- Gera (`findByPostagensSomeTitulo`):
912
+ Generates (`findByPostsSomeTitle`):
904
913
 
905
914
  ```ts
906
915
  {
907
- postagens: {
908
- some: { titulo: "Meu primeiro post" }
916
+ posts: {
917
+ some: { title: "My first post" }
909
918
  }
910
919
  }
911
920
  ```
912
921
 
913
- Gera (`findByPostagensEveryPublicada`):
922
+ Generates (`findByPostsEveryPublishedIsTrue`):
914
923
 
915
924
  ```ts
916
925
  {
917
- postagens: {
918
- every: { publicada: true }
926
+ posts: {
927
+ every: { published: true }
919
928
  }
920
929
  }
921
930
  ```
922
931
 
923
- Gera (`findByPerfilWithBio`):
932
+ Generates (`findByProfileWithBio`):
924
933
 
925
934
  ```ts
926
935
  {
927
- perfil: {
928
- is: { bio: "Olá, mundo!" }
936
+ profile: {
937
+ is: { bio: "Hello, world!" }
929
938
  }
930
939
  }
931
940
  ```
932
941
 
933
- Gera (`findByPerfilWithout`):
942
+ Generates (`findByProfileWithout`):
934
943
 
935
944
  ```ts
936
945
  {
937
- perfil: {
946
+ profile: {
938
947
  isNot: {}
939
948
  }
940
949
  }
941
950
  ```
942
951
 
943
- > `Some`, `None`, `With` e `Without` (sem campo) não recebem argumento — a relação inteira é testada quanto à existência de registros (`some`/`none`) ou a ser `null`/não-`null` (`is`/`isNot`). Já as variantes `SomeField`, `EveryField`, `NoneField`, `WithField` e `WithoutField` recebem o valor do campo filtrado como argumento.
952
+ > `Some`, `None`, `With`, and `Without` (without a field) don't receive an argument — the whole relation is tested for the existence of records (`some`/`none`) or for being `null`/not `null` (`is`/`isNot`). The `SomeField`, `EveryField`, `NoneField`, `WithField`, and `WithoutField` variants receive the filtered field's value as an argument.
944
953
 
945
954
  ---
946
955
 
947
- ### Sufixos de paginação e ordenação
956
+ ### Pagination and ordering suffixes
948
957
 
949
- Aplicados ao **final** do nome do método, eles injetam automaticamente os argumentos de paginação e ordenação.
958
+ Applied at the **end** of the method name, they automatically inject the pagination and ordering arguments.
950
959
 
951
- | Sufixo | Argumentos adicionais |
952
- | --------------------- | ----------------------------- |
953
- | `Paginated` | `(pagination)` |
954
- | `Ordered` | `(order)` |
955
- | `OrderedAndPaginated` | `(order, pagination)` |
956
- | `PaginatedAndOrdered` | `(pagination, order)` |
960
+ | Suffix | Additional arguments |
961
+ | ------------------------ | -------------------------------|
962
+ | `Paginated` | `(pagination)` |
963
+ | `Ordered` | `(order)` |
964
+ | `OrderedAndPaginated` | `(order, pagination)` |
965
+ | `PaginatedAndOrdered` | `(pagination, order)` |
957
966
 
958
- Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está disponível:
967
+ For `createMany` and `createManyAndReturn`, the `SkipDuplicates` suffix is available:
959
968
 
960
- | Sufixo | Efeito |
961
- | ----------------- | ---------------------------------------- |
962
- | `SkipDuplicates` | Ignora registros duplicados na inserção |
969
+ | Suffix | Effect |
970
+ | --------------------- | ---------------------------------------------|
971
+ | `SkipDuplicates` | Skips duplicate records during insertion |
963
972
 
964
973
  ---
965
974
 
966
- ### Configuração de métodos
975
+ ### Distinct
967
976
 
968
- Cada entrada em `methods` aceita as seguintes opções:
977
+ The `Distinct` suffix lets you get only unique records based on one or more fields, equivalent to Prisma's `distinct` option.
969
978
 
970
- | Opção | Tipo | Padrão | Descrição |
971
- | ------------------- | ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
972
- | `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repository. |
973
- | `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`. |
974
- | `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
975
- | `fbMode` | `'one'` \| `'list'` | `'list'` | (**Obsoleto. Use `findOneBy`**) Somente para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
976
- | `proxyTo` | `Padrão de método válido` | — | Delega a lógica para outro padrão de método válido. |
977
- | `pushWhere` | `WhereModel<M>` | — | Where extra adicionado à query além do `requiredWhere`. |
978
- | `injectOrdenation` | `OrdenationModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
979
- | `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
979
+ To use it, put `Distinct` in the method name (after the field filters, if any) followed by the desired fields separated by `And`. The first character of each field must be uppercase, just like in regular field filters.
980
+
981
+ ```ts
982
+ methods: {
983
+ // Returns unique users combining "age" and "role" (no field filter)
984
+ findManyDistinctAgeAndRole: { map: true },
985
+
986
+ // Distinct combined with the Paginated suffix
987
+ findManyDistinctNamePaginated: { map: true },
988
+
989
+ // Distinct combined with a field filter (name) — filters by name and then applies distinct on role
990
+ findManyByNameDistinctRole: { map: true },
991
+ },
992
+ ```
993
+
994
+ ```ts
995
+ // No arguments: the distinct fields are already fixed in the method name
996
+ await userRepository.findManyDistinctAgeAndRole();
997
+
998
+ // The pagination argument still works normally
999
+ await userRepository.findManyDistinctNamePaginated({ take: 10, skip: 0 });
1000
+
1001
+ // The "name" field filter is still passed normally as an argument
1002
+ await userRepository.findManyByNameDistinctRole("John");
1003
+ ```
1004
+
1005
+ > The fields specified after `Distinct` are resolved from the method name at build time — they **don't** become runtime arguments, unlike regular field filters.
1006
+
1007
+ `Distinct` is available on prefixes that read multiple or single records: `findMany`, `findManyBy`, `findFirst`, `findFirstBy`, `findFirstOrThrow`, `findFirstOrThrowBy`, `findBy`, `findOneBy`, `findWhere`, `findOneWhere`, `findListWhere`, `existsBy`, and `existsWhere`.
980
1008
 
981
1009
  ---
982
1010
 
983
- ### Aggregate e GroupBy
1011
+ ### Method configuration
1012
+
1013
+ Each entry in `methods` accepts the following options:
1014
+
1015
+ | Option | Type | Default | Description |
1016
+ | --------------------- | --------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------|
1017
+ | `map` | `boolean` | — | **Required.** Defines whether the method will be exposed on the repository. |
1018
+ | `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combines with `requiredWhere`. `overwrite` ignores `requiredWhere`. |
1019
+ | `selectModel` | `keyof SelectModels \| false` | — | Overrides `defaultSelectModel` for this method. |
1020
+ | `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Only for `findBy`. `'one'` returns `T \| null`; `'list'` returns `T[]`. |
1021
+ | `proxyTo` | `Valid method pattern` | — | Delegates the logic to another valid method pattern. |
1022
+ | `pushWhere` | `WhereModel<M>` | — | Extra `where` added to the query in addition to `requiredWhere`. |
1023
+ | `injectOrdenation` | `OrdenationModel<M>` | — | Fixed ordering automatically injected into the query. |
1024
+ | `injectPagination` | `PaginationModel<M>` | — | Fixed pagination automatically injected into the query. |
1025
+
1026
+ ---
1027
+
1028
+ ### Aggregate and GroupBy
984
1029
 
985
1030
  ```ts
986
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
987
- tableName: "usuario",
1031
+ const userRepository = setupVSRepo<User, "user">()(({
1032
+ tableName: "user",
988
1033
  pkName: "id",
989
1034
  methods: {
990
1035
  aggregate: { map: true },
@@ -994,33 +1039,33 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
994
1039
  ```
995
1040
 
996
1041
  > [!NOTE]
997
- > Estes métodos devem ter exatamente esses nomes (`aggregate` e `groupBy`).
998
- > Ao contrário dos outros métodos dinâmicos, eles recebem argumentos nativos do Prisma e **ignoram** as configurações de `selectModels`, `pushWhere` e `requiredWhere`.
1042
+ > These methods must have exactly these names (`aggregate` and `groupBy`).
1043
+ > Unlike the other dynamic methods, they receive native Prisma arguments and **ignore** the `selectModels`, `pushWhere`, and `requiredWhere` configurations.
999
1044
 
1000
1045
  ---
1001
1046
 
1002
- ## Relações no save
1047
+ ## Relations in save
1003
1048
 
1004
- Configure relações para que o `save` e o `patch` as gerenciem automaticamente (`saveList` e `pacthList` também gerenciam as relations automaticamente).
1049
+ Configure relations so that `save` and `patch` manage them automatically (`saveList` and `patchList` also manage relations automatically).
1005
1050
 
1006
1051
  ```ts
1007
1052
  import type { Prisma } from "../../generated/prisma/client";
1008
1053
 
1009
- type Usuario = Prisma.usuarioGetPayload<{
1010
- include: { perfil: true; postagens: true };
1054
+ type User = Prisma.userGetPayload<{
1055
+ include: { profile: true; posts: true };
1011
1056
  }>;
1012
1057
 
1013
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
1014
- tableName: "usuario",
1058
+ const userRepository = setupVSRepo<User, "user">()(({
1059
+ tableName: "user",
1015
1060
  pkName: "id",
1016
1061
 
1017
1062
  relations: {
1018
- perfil: {
1063
+ profile: {
1019
1064
  pk: "id",
1020
1065
  mode: "oto",
1021
1066
  restriction: "set",
1022
1067
  },
1023
- postagens: {
1068
+ posts: {
1024
1069
  pk: "id",
1025
1070
  mode: "otm",
1026
1071
  restriction: "add",
@@ -1029,80 +1074,80 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
1029
1074
  }).build(prisma);
1030
1075
  ```
1031
1076
 
1032
- **Modos de relação:**
1077
+ **Relation modes:**
1033
1078
 
1034
- | Modo | Relação |
1035
- | ----- | ------------ |
1036
- | `oto` | one-to-one |
1037
- | `otm` | one-to-many |
1038
- | `mto` | many-to-one |
1039
- | `mtm` | many-to-many |
1079
+ | Mode | Relation |
1080
+ | ----- | -------------- |
1081
+ | `oto` | one-to-one |
1082
+ | `otm` | one-to-many |
1083
+ | `mto` | many-to-one |
1084
+ | `mtm` | many-to-many |
1040
1085
 
1041
- **Restrições:**
1086
+ **Restrictions:**
1042
1087
 
1043
- | Restrição | Comportamento no update |
1044
- | --------- | ----------------------------------------------------------- |
1045
- | `set` | Substitui completamente (remove os que não foram enviados) |
1046
- | `add` | Adiciona/atualiza sem remover os existentes |
1088
+ | Restriction | Behavior on update |
1089
+ | ------------ | ----------------------------------------------------------------|
1090
+ | `set` | Fully replaces (removes the ones that weren't sent) |
1091
+ | `add` | Adds/updates without removing existing ones |
1047
1092
 
1048
1093
  > [!WARNING]
1049
- > **`set` significa coisas diferentes dependendo do `mode` da relação — e isso pode causar perda de dados se você não prestar atenção.**
1094
+ > **`set` means different things depending on the relation's `mode` — and this can cause data loss if you're not careful.**
1050
1095
  >
1051
- > Em relações onde o registro relacionado **pertence** ao registro pai (`oto` e `otm`), "remover os que não foram enviados" significa **deletar o registro do banco** (`delete`/`deleteMany`). Já em relações onde o registro relacionado é **independente** (`mto` e `mtm`), "remover" significa apenas **desvincular** (`disconnect`/`set: []`) — o registro relacionado continua existindo no banco, só deixa de apontar para o pai (ou de estar na tabela de junção).
1096
+ > In relations where the related record **belongs** to the parent record (`oto` and `otm`), "removing the ones that weren't sent" means **deleting the record from the database** (`delete`/`deleteMany`). In relations where the related record is **independent** (`mto` and `mtm`), "removing" just means **unlinking** (`disconnect`/`set: []`) — the related record continues to exist in the database, it just stops pointing to the parent (or being in the join table).
1052
1097
  >
1053
- > | Modo | `restriction: "set"` ao omitir um item | O item continua existindo no banco? |
1054
- > | ----- | ---------------------------------------- | ------------------------------------ |
1055
- > | `oto` | Passar `null` no campo → **deleta** o registro relacionado (`delete: true`) | Não |
1056
- > | `otm` | Itens fora da lista enviada → **deletados** (`deleteMany` com `notIn`) | Não |
1057
- > | `mto` | Passar `null` no campo (com `nullable: true`) → **desvincula** (`disconnect: true`) | Sim |
1058
- > | `mtm` | Itens fora da lista enviada → **desvinculados** da tabela de junção (`set: []`) | Sim |
1098
+ > | Mode | `restriction: "set"` when an item is omitted | Does the item continue to exist in the database? |
1099
+ > | ----- | ------------------------------------------------ | -----------------------------------------------------|
1100
+ > | `oto` | Passing `null` in the field → **deletes** the related record (`delete: true`) | No |
1101
+ > | `otm` | Items outside the sent list → **deleted** (`deleteMany` with `notIn`) | No |
1102
+ > | `mto` | Passing `null` in the field (with `nullable: true`) → **unlinks** (`disconnect: true`) | Yes |
1103
+ > | `mtm` | Items outside the sent list → **unlinked** from the join table (`set: []`) | Yes |
1059
1104
  >
1060
- > Exemplo prático: se `postagens` é `otm` com `restriction: "set"`, um `save`/`patch` que envie o usuário com apenas 2 das 5 postagens existentes vai **apagar as outras 3 postagens do banco**, não apenas desvinculá-las do usuário. Se o comportamento esperado é só desvincular sem apagar, use `restriction: "add"` (que nunca remove nada) e gerencie a remoção manualmente.
1105
+ > Practical example: if `posts` is `otm` with `restriction: "set"`, a `save`/`patch` that sends the user with only 2 of the 5 existing posts will **delete the other 3 posts from the database**, not just unlink them from the user. If the expected behavior is just to unlink without deleting, use `restriction: "add"` (which never removes anything) and handle removal manually.
1061
1106
 
1062
- **Relação `mto` com nullable:**
1107
+ **`mto` relation with nullable:**
1063
1108
 
1064
- Use `nullable` (letra minúscula) para permitir a desvinculação de uma relação many-to-one:
1109
+ Use `nullable` (lowercase) to allow unlinking a many-to-one relation:
1065
1110
 
1066
1111
  ```ts
1067
1112
  relations: {
1068
- categoria: {
1113
+ category: {
1069
1114
  pk: "id",
1070
1115
  mode: "mto",
1071
1116
  restriction: "set",
1072
- nullable: true, // permite passar null para desvincular
1117
+ nullable: true, // allows passing null to unlink
1073
1118
  },
1074
1119
  }
1075
1120
  ```
1076
1121
 
1077
1122
  ---
1078
1123
 
1079
- ## Transações
1124
+ ## Transactions
1080
1125
 
1081
- Todos os métodos aceitam `options.db` para participar de uma transação:
1126
+ All methods accept `options.db` to participate in a transaction:
1082
1127
 
1083
1128
  ```ts
1084
- await usuarioRepository.prisma.$transaction(async (tx) => {
1085
- const usuario = await usuarioRepository.save(
1086
- { nome: "Maria", email: "maria@email.com", senha: "password" },
1129
+ await userRepository.prisma.$transaction(async (tx) => {
1130
+ const user = await userRepository.save(
1131
+ { name: "Mary", email: "mary@email.com", password: "password" },
1087
1132
  { db: tx }
1088
1133
  );
1089
1134
 
1090
- await usuarioLogsRepository.save(
1091
- { acao: "Cadastro de usuário", data: { usuarioCadastrado: usuario.id } },
1135
+ await userLogsRepository.save(
1136
+ { action: "User registration", data: { registeredUser: user.id } },
1092
1137
  { db: tx }
1093
1138
  );
1094
1139
  });
1095
1140
  ```
1096
1141
 
1097
- Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction`:
1142
+ For `saveList` and `patchList`, the `db` field must be a `DbTransaction`:
1098
1143
 
1099
1144
  ```ts
1100
1145
  await prisma.$transaction(async (tx) => {
1101
- // CORRETO: tx é uma DbTransaction
1102
- const usuariosCadastrados = await usuarioRepository.saveList([{ nome: "Maria" }, { nome: "Lucas" }], { db: tx });
1146
+ // CORRECT: tx is a DbTransaction
1147
+ const registeredUsers = await userRepository.saveList([{ name: "Mary" }, { name: "Lucas" }], { db: tx });
1103
1148
 
1104
- await usuarioLogsRepository.save(
1105
- { acao: "Cadastro de usuários", data: { usuariosCadastrados: usuariosCadastrados.map(u => u.id) } },
1149
+ await userLogsRepository.save(
1150
+ { action: "User registration", data: { registeredUsers: registeredUsers.map(u => u.id) } },
1106
1151
  { db: tx }
1107
1152
  );
1108
1153
  });
@@ -1110,11 +1155,11 @@ await prisma.$transaction(async (tx) => {
1110
1155
 
1111
1156
  ---
1112
1157
 
1113
- ## Estendendo um repository
1158
+ ## Extending a repository
1114
1159
 
1115
1160
  ```ts
1116
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
1117
- tableName: "usuario",
1161
+ const userRepository = setupVSRepo<User, "user">()(({
1162
+ tableName: "user",
1118
1163
  pkName: "id",
1119
1164
  methods: {
1120
1165
  findOneByEmailEndsWith: { map: true },
@@ -1122,54 +1167,54 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
1122
1167
  })
1123
1168
  .build(prisma)
1124
1169
  .extend((repo) => ({
1125
- buscarAtivosPorDominio: async (dominio: string) => {
1126
- return repo.findOneByEmailEndsWith(`@${dominio}`);
1170
+ findActiveByDomain: async (domain: string) => {
1171
+ return repo.findOneByEmailEndsWith(`@${domain}`);
1127
1172
  },
1128
1173
 
1129
- ativarMultiplos: async (ids: string[]) => {
1130
- return repo.patchList(ids.map(id => [id, { ativo: true }]));
1174
+ activateMultiple: async (ids: string[]) => {
1175
+ return repo.patchList(ids.map(id => [id, { active: true }]));
1131
1176
  },
1132
1177
  }));
1133
1178
  ```
1134
1179
 
1135
1180
  ---
1136
1181
 
1137
- ## Tratamento de erros
1182
+ ## Error handling
1138
1183
 
1139
- O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (erros do Prisma não são sobrescritos):
1184
+ VSRepository throws `VSRepoError` and its subclasses in specific situations (Prisma errors are not overridden):
1140
1185
 
1141
1186
  ```ts
1142
1187
  import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
1143
1188
 
1144
1189
  try {
1145
- const usuario = await usuarioRepository.getOrThrow("id-que-nao-existe");
1190
+ const user = await userRepository.getOrThrow("id-that-does-not-exist");
1146
1191
  } catch (error) {
1147
1192
  if (error instanceof VSRepoRuntimeError && error.code === "20727") {
1148
- console.error("Registro não encontrado");
1193
+ console.error("Record not found");
1149
1194
  } else if (error instanceof VSRepoError) {
1150
- console.error("Erro no repository:", error.message);
1195
+ console.error("Repository error:", error.message);
1151
1196
  } else {
1152
- console.error("Erro:", error.message)
1197
+ console.error("Error:", error.message)
1153
1198
  }
1154
1199
  }
1155
1200
  ```
1156
1201
 
1157
- **Subclasses disponíveis:**
1202
+ **Available subclasses:**
1158
1203
 
1159
- | Classe | Quando é lançada |
1160
- | -------------------- | ----------------------------------------------------------------- |
1161
- | `VSRepoConfigError` | Configuração inválida em `setupVSRepo` |
1162
- | `VSRepoBuildError` | Nome de método, tipo de campo ou configuração inválida no `build` |
1163
- | `VSRepoExtendError` | Argumento inválido em `extend` |
1164
- | `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
1204
+ | Class | When it's thrown |
1205
+ | ---------------------- | ------------------------------------------------------------------------|
1206
+ | `VSRepoConfigError` | Invalid configuration in `setupVSRepo` |
1207
+ | `VSRepoBuildError` | Invalid method name, field type, or configuration in `build` |
1208
+ | `VSRepoExtendError` | Invalid argument in `extend` |
1209
+ | `VSRepoRuntimeError` | Runtime error during an operation |
1165
1210
 
1166
- `VSRepoRuntimeError` possui a propriedade `code` para identificação programática. O código `"20727"` é lançado pelo `getOrThrow` quando o registro não é encontrado, por exemplo.
1211
+ `VSRepoRuntimeError` has a `code` property for programmatic identification. Code `"20727"` is thrown by `getOrThrow` when the record is not found, for example.
1167
1212
 
1168
1213
  ---
1169
1214
 
1170
- ## Tipos utilitários
1215
+ ## Utility types
1171
1216
 
1172
- ### Tipos de cliente
1217
+ ### Client types
1173
1218
 
1174
1219
  ```ts
1175
1220
  import type { DbClient, DbTransaction, ClientOrTransaction } from "../../generated/vsrepo";
@@ -1179,7 +1224,7 @@ type DbTransaction = Prisma.TransactionClient;
1179
1224
  type ClientOrTransaction = DbClient | DbTransaction;
1180
1225
  ```
1181
1226
 
1182
- ### Tipo de visibilidade soft-delete
1227
+ ### Soft-delete visibility type
1183
1228
 
1184
1229
  ```ts
1185
1230
  import type { SeeMode } from "../../generated/vsrepo";
@@ -1187,7 +1232,7 @@ import type { SeeMode } from "../../generated/vsrepo";
1187
1232
  type SeeMode = "active" | "removed" | "all";
1188
1233
  ```
1189
1234
 
1190
- ### Tipos derivados do modelo Prisma
1235
+ ### Types derived from the Prisma model
1191
1236
 
1192
1237
  ```ts
1193
1238
  import type {
@@ -1203,22 +1248,22 @@ import type {
1203
1248
  } from "../../generated/vsrepo";
1204
1249
  ```
1205
1250
 
1206
- ### Tipos de opções de método
1251
+ ### Method options types
1207
1252
 
1208
1253
  ```ts
1209
1254
  import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
1210
1255
 
1211
- // MethodOptions<S, IM> — opções passadas nos métodos do repository
1212
- type Opts = MethodOptions<"public" | "minimal", "comPosts">;
1256
+ // MethodOptions<S, IM> — options passed into the repository's methods
1257
+ type Opts = MethodOptions<"public" | "minimal", "withPosts">;
1213
1258
 
1214
- // MethodOptionsModel<TRepo> — derivado de uma instância VSRepository configurada
1215
- const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(config);
1216
- type OptsModel = MethodOptionsModel<typeof usuarioVSRepo>;
1259
+ // MethodOptionsModel<TRepo> — derived from a configured VSRepository instance
1260
+ const userVSRepo = setupVSRepo<User, "user">()(config);
1261
+ type OptsModel = MethodOptionsModel<typeof userVSRepo>;
1217
1262
  ```
1218
1263
 
1219
- > O segundo parâmetro de `MethodOptions` (`IM`) representa as chaves válidas de `includeModels`. Quando informado, `selectModel` e `includeModel` tornam-se mutuamente exclusivos no tipo — não é possível passar os dois na mesma chamada.
1264
+ > The second parameter of `MethodOptions` (`IM`) represents the valid keys of `includeModels`. When provided, `selectModel` and `includeModel` become mutually exclusive in the type — it's not possible to pass both in the same call.
1220
1265
 
1221
- ### Tipos de configuração
1266
+ ### Configuration types
1222
1267
 
1223
1268
  ```ts
1224
1269
  import type {
@@ -1230,36 +1275,36 @@ import type {
1230
1275
  } from "../../generated/vsrepo";
1231
1276
  ```
1232
1277
 
1233
- ### Tipo do repository construído
1278
+ ### Built repository type
1234
1279
 
1235
1280
  ```ts
1236
1281
  import type { RepositoryOf } from "../../generated/vsrepo";
1237
1282
 
1238
- const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({ ... });
1239
- type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
1283
+ const userVSRepo = setupVSRepo<User, "user">()({ ... });
1284
+ type UserRepository = RepositoryOf<typeof userVSRepo>;
1240
1285
  ```
1241
1286
 
1242
- `RepositoryOf` aceita três parâmetros:
1287
+ `RepositoryOf` accepts three parameters:
1243
1288
 
1244
1289
  ```ts
1245
1290
  type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
1246
1291
  ```
1247
1292
 
1248
- ### Tipos do payload de `save` e `patch`
1293
+ ### `save` and `patch` payload types
1249
1294
 
1250
1295
  ```ts
1251
1296
  import type { SaveObject, PatchObject } from "../../generated/vsrepo";
1252
1297
 
1253
- const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(({
1254
- tableName: "usuario",
1298
+ const userVSRepo = setupVSRepo<User, "user">()(({
1299
+ tableName: "user",
1255
1300
  pkName: "id",
1256
1301
  relations: {
1257
- perfil: { pk: "id", mode: "oto", restriction: "set" },
1302
+ profile: { pk: "id", mode: "oto", restriction: "set" },
1258
1303
  },
1259
1304
  });
1260
1305
 
1261
- type UsuarioSavePayload = SaveObject<Prisma.UsuarioCreateInput, typeof usuarioVSRepo>;
1262
- type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuarioVSRepo>;
1306
+ type UserSavePayload = SaveObject<Prisma.UserCreateInput, typeof userVSRepo>;
1307
+ type UserPatchPayload = PatchObject<Prisma.UserUpdateInput, typeof userVSRepo>;
1263
1308
  ```
1264
1309
 
1265
1310
  ---
@@ -1270,16 +1315,16 @@ type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuario
1270
1315
 
1271
1316
  ```ts
1272
1317
  setupVSRepo<TPayload, TTableName>()({
1273
- tableName: Uncapitalize<M>; // Nome da tabela no Prisma
1274
- pkName: keyof T; // Nome da primary key
1275
- softRemovekName?: keyof T & string; // Campo DateTime para soft-delete
1276
- selectModels?: SelectModels<M>; // Projeções de dados nomeadas (select)
1277
- defaultSelectModel?: keyof SM; // Select aplicado por padrão
1278
- includeModels?: IncludeModels<M>; // Projeções de dados nomeadas (include) — sem default, só na chamada
1279
- requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
1280
- defaultOrdenation?: OrdenationModel<M>; // Ordenação padrão para queries sem Ordered/injectOrdenation
1281
- relations?: RepositoryRelations<T>; // Configuração de relações
1282
- methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
1318
+ tableName: Uncapitalize<M>; // Table name in Prisma
1319
+ pkName: keyof T; // Primary key name
1320
+ softRemovekName?: keyof T & string; // DateTime field for soft-delete
1321
+ selectModels?: SelectModels<M>; // Named data projections (select)
1322
+ defaultSelectModel?: keyof SM; // Select applied by default
1323
+ includeModels?: IncludeModels<M>; // Named data projections (include) — no default, only in the call
1324
+ requiredWhere?: WhereModel<M>; // Always-applied filters
1325
+ defaultOrdenation?: OrdenationModel<M>; // Default ordering for queries without Ordered/injectOrdenation
1326
+ relations?: RepositoryRelations<T>; // Relation configuration
1327
+ methods?: Record<string, MethodConfig<M, SM>>; // Dynamic methods
1283
1328
  });
1284
1329
  ```
1285
1330
 
@@ -1287,10 +1332,10 @@ setupVSRepo<TPayload, TTableName>()({
1287
1332
 
1288
1333
  ```ts
1289
1334
  vsRepo.build(prisma, {
1290
- showWorking?: boolean; // Exibe logs internos no console (default = false)
1335
+ showWorking?: boolean; // Shows internal logs on the console (default = false)
1291
1336
 
1292
1337
  baseMethods?: {
1293
- // Métodos que podem utilizar um defaultSelect
1338
+ // Methods that can use a defaultSelect
1294
1339
  get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1295
1340
  getOrThrow?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1296
1341
  getList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
@@ -1304,7 +1349,7 @@ vsRepo.build(prisma, {
1304
1349
  softRemove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1305
1350
  restore?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1306
1351
 
1307
- // Métodos que NÃO aceitam defaultSelect
1352
+ // Methods that do NOT accept defaultSelect
1308
1353
  removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1309
1354
  softRemoveList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1310
1355
  restoreList?: { active?: boolean; ignoreRequiredWhere?: boolean };
@@ -1318,55 +1363,55 @@ vsRepo.build(prisma, {
1318
1363
 
1319
1364
  ```ts
1320
1365
  repo.extend((repo) => ({
1321
- meuMetodo: () => { ... }
1366
+ myMethod: () => { ... }
1322
1367
  }));
1323
1368
  ```
1324
1369
 
1325
1370
  ---
1326
1371
 
1327
- ## Exemplos práticos
1372
+ ## Practical examples
1328
1373
 
1329
- Além deste README, o repositório tem uma pasta **[`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples)** com exemplos práticos e comentados, prontos para rodar — é o melhor lugar para ver o VSRepository sendo usado em cenários reais.
1374
+ Besides this README, the repository has an **[`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples)** folder with practical, commented, ready-to-run examples — it's the best place to see VSRepository being used in real scenarios.
1330
1375
 
1331
1376
  ```
1332
1377
  examples/
1333
- ├── prisma.ts # Instância do PrismaClient usada pelos exemplos
1334
- ├── repositories.ts # Configuração dos repositories (User, Address, Product) com setupVSRepo
1378
+ ├── prisma.ts # PrismaClient instance used by the examples
1379
+ ├── repositories.ts # Repository configuration (User, Address, Product) with setupVSRepo
1335
1380
  └── tests/
1336
- ├── base-methods.test.ts # Métodos base: get, save, patch, remove, getAll, total, has...
1337
- ├── relations.test.ts # Como configurar e usar relations no save/patch e em filtros
1338
- ├── required-where.test.ts # Como o requiredWhere é aplicado automaticamente nas queries
1339
- ├── dynamic-methods.test.ts # Prefixos, filtros de campo, operadores lógicos e paginação/ordenação
1340
- ├── transactions.test.ts # Transactions com options.db e acesso à instância via repository.prisma
1341
- ├── soft-delete.test.ts # Soft-delete: softRemove, softRemoveList, restore, restoreList e SeeMode
1342
- └── batch-methods.test.ts # Operações em lote: getList, saveList, patchList e merge
1381
+ ├── base-methods.test.ts # Base methods: get, save, patch, remove, getAll, total, has...
1382
+ ├── relations.test.ts # How to configure and use relations in save/patch and in filters
1383
+ ├── required-where.test.ts # How requiredWhere is automatically applied to queries
1384
+ ├── dynamic-methods.test.ts # Prefixes, field filters, logical operators, and pagination/ordering
1385
+ ├── transactions.test.ts # Transactions with options.db and instance access via repository.prisma
1386
+ ├── soft-delete.test.ts # Soft-delete: softRemove, softRemoveList, restore, restoreList and SeeMode
1387
+ └── batch-methods.test.ts # Batch operations: getList, saveList, patchList and merge
1343
1388
  ```
1344
1389
 
1345
- 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 passo 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 sugerida de leitura, como configurar o ambiente e como rodar os testes.
1390
+ Each file in `tests/` is an independent, runnable script (via `tsx`) that demonstrates a specific set of features, with `console.log` at each step so you can follow the result in the terminal. The folder itself has a [README](https://github.com/jaobrabo123/VSRepository/blob/main/examples/README.md) explaining the suggested reading order, how to set up the environment, and how to run the tests.
1346
1391
 
1347
1392
  ---
1348
1393
 
1349
- ## Contribuindo
1394
+ ## Contributing
1350
1395
 
1351
- 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 do GitHub](https://github.com/jaobrabo123/VSRepository)**):
1396
+ Contributions are welcome! If you found a bug, have an improvement idea, or want to help with the documentation, feel free to get involved (**[GitHub Repository](https://github.com/jaobrabo123/VSRepository)**):
1352
1397
 
1353
- 1. Faça um **Fork** do projeto.
1354
- 2. Crie uma nova branch com a sua alteração: `git checkout -b corrigindo-bug`.
1355
- 3. Faça o push para a sua branch: `git push origin corrigindo-bug`.
1356
- 4. Abra um **Pull Request**.
1398
+ 1. **Fork** the project.
1399
+ 2. Create a new branch with your change: `git checkout -b fixing-bug`.
1400
+ 3. Push to your branch: `git push origin fixing-bug`.
1401
+ 4. Open a **Pull Request**.
1357
1402
 
1358
- Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1403
+ To report issues or suggest new features, open an **Issue**.
1359
1404
 
1360
1405
  ---
1361
1406
 
1362
- ## Requisitos
1407
+ ## Requirements
1363
1408
 
1364
1409
  - Node.js 18+ (ESM)
1365
1410
  - Prisma
1366
- - TypeScript (opcional, mas fortemente recomendado)
1367
- - `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
1411
+ - TypeScript (optional, but strongly recommended)
1412
+ - `"moduleResolution": "bundler"` or `"nodenext"` in tsconfig
1368
1413
 
1369
- `tsconfig.json` recomendado:
1414
+ Recommended `tsconfig.json`:
1370
1415
 
1371
1416
  ```json
1372
1417
  {
@@ -1385,20 +1430,22 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1385
1430
 
1386
1431
  ## Troubleshooting
1387
1432
 
1388
- **Tipos genéricos não inferidos** — Verifique se `strict: true` e `moduleResolution: "bundler"` ou `"nodenext"` estão no `tsconfig.json`.
1433
+ **Generic types not inferred** — Check that `strict: true` and `moduleResolution: "bundler"` or `"nodenext"` are set in `tsconfig.json`.
1434
+
1435
+ **Dynamic method doesn't exist at runtime** — The field referenced in the method name must exist in the Prisma model. E.g.: `findByEmail` requires the model to have an `email` field.
1389
1436
 
1390
- **Método dinâmico não existe em runtime** — O campo referenciado no nome do método deve existir no modelo Prisma. Ex.: `findByEmail` exige que o modelo tenha um campo `email`.
1437
+ **`proxyTo` required** — Names outside the standard patterns (e.g. `searchByEmail`) aren't parsed directly. Use `proxyTo: "findByEmail"` in these cases.
1391
1438
 
1392
- **`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
1439
+ **Select model returns unexpected fields** — Check that the select model defines exactly the fields your TypeScript type expects.
1393
1440
 
1394
- **Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera.
1441
+ **`selectModel` and `includeModel` together in the same call** — Not allowed. Choose one or the other: if `includeModel` is provided, the `select` (including `defaultSelectModel`) is ignored and only the `include` is sent to Prisma.
1395
1442
 
1396
- **`selectModel` e `includeModel` juntos na mesma chamada** — Não é permitido. Escolha um ou outro: se `includeModel` for informado, o `select` (incluindo o `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
1443
+ **`includeModel` doesn't appear as a default repository option** — This is expected. Unlike `defaultSelectModel`, there's no `defaultIncludeModel`/`defaultInclude`. An `includeModel` can only be set in the method call, via `options.includeModel`.
1397
1444
 
1398
- **`includeModel` não aparece como opção padrão do repository** — Isso é esperado. Diferente de `defaultSelectModel`, não existe `defaultIncludeModel`/`defaultInclude`. Um `includeModel` só pode ser definido na chamada do método, via `options.includeModel`.
1445
+ **`softRemovekName` throws an error at build** — The provided field must be of type `DateTime` in the Prisma schema. Types like `Boolean` or `String` are not accepted.
1399
1446
 
1400
- **`softRemovekName` lança 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.
1447
+ **`defaultOrdenation` isn't being applied** — Check whether the method uses the `Ordered`, `OrderedAndPaginated`, or `PaginatedAndOrdered` suffix, and whether it has `injectOrdenation` configured. Both take priority over the default ordering.
1401
1448
 
1402
- **`defaultOrdenation` não está sendo aplicada** — Verifique se o método não usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered`, e se não possui `injectOrdenation` configurado. Ambos têm prioridade sobre a ordenação padrão.
1449
+ **`Distinct` suffix not recognized** — `Distinct` is only resolved on read prefixes (`findMany`, `findFirst`, `findBy`, `existsBy`, etc). In methods like `count`, `createMany`, `updateMany`, or `deleteMany` the suffix is ignored.
1403
1450
 
1404
- **`saveList`/`patchList` com `db` inválido** — O campo `db` nestes métodos aceita apenas `DbTransaction` (retorno de `prisma.$transaction`), não o cliente principal. Passar o `PrismaClient` diretamente causará comportamento inesperado.
1451
+ **`saveList`/`patchList` with invalid `db`** — The `db` field in these methods only accepts a `DbTransaction` (the return of `prisma.$transaction`), not the main client. Passing the `PrismaClient` directly will cause unexpected behavior.