vsrepo 1.3.1 → 1.3.2

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