vsrepo 2.5.0 → 2.7.0

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +611 -531
  2. package/LICENSE +20 -20
  3. package/README.md +221 -1243
  4. package/README.pt-BR.md +221 -1246
  5. package/dist/VSRepoAdapter.d.ts +12 -0
  6. package/dist/VSRepoAdapter.js.map +1 -1
  7. package/dist/VSRepository.d.ts +93 -5
  8. package/dist/VSRepository.js +116 -35
  9. package/dist/VSRepository.js.map +1 -1
  10. package/dist/decorators/dynamic-method.decorator.d.ts +1 -1
  11. package/dist/decorators/dynamic-method.decorator.js +2 -4
  12. package/dist/decorators/dynamic-method.decorator.js.map +1 -1
  13. package/dist/decorators/query-method.decorator.d.ts +3 -24
  14. package/dist/decorators/query-method.decorator.js +4 -27
  15. package/dist/decorators/query-method.decorator.js.map +1 -1
  16. package/dist/index.d.ts +5 -0
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/enums/vsrepo-error-type.enum.d.ts +3 -1
  20. package/dist/internal/enums/vsrepo-error-type.enum.js +2 -0
  21. package/dist/internal/enums/vsrepo-error-type.enum.js.map +1 -1
  22. package/dist/internal/resolvers/dynamic-methods.resolver.d.ts +7 -1
  23. package/dist/internal/resolvers/dynamic-methods.resolver.js +191 -112
  24. package/dist/internal/resolvers/dynamic-methods.resolver.js.map +1 -1
  25. package/dist/internal/utils/vs-logger.util.js.map +1 -1
  26. package/dist/internal/utils/vs-placeholder-parser.util.d.ts +2 -0
  27. package/dist/internal/utils/vs-placeholder-parser.util.js +62 -0
  28. package/dist/internal/utils/vs-placeholder-parser.util.js.map +1 -0
  29. package/dist/internal/utils/vs-query-builder.util.d.ts +239 -0
  30. package/dist/internal/utils/vs-query-builder.util.js +401 -0
  31. package/dist/internal/utils/vs-query-builder.util.js.map +1 -0
  32. package/dist/internal/utils/vs-raw-query-builder.util.d.ts +238 -0
  33. package/dist/internal/utils/vs-raw-query-builder.util.js +433 -0
  34. package/dist/internal/utils/vs-raw-query-builder.util.js.map +1 -0
  35. package/dist/internal/utils/vs-sql.util.d.ts +100 -0
  36. package/dist/internal/utils/vs-sql.util.js +151 -0
  37. package/dist/internal/utils/vs-sql.util.js.map +1 -0
  38. package/dist/internal/utils/with-db.util.js.map +1 -1
  39. package/dist/internal/validators/decorators.validator.js +3 -7
  40. package/dist/internal/validators/decorators.validator.js.map +1 -1
  41. package/dist/internal/validators/schemas/pagination.schema.d.ts +2 -2
  42. package/dist/internal/validators/schemas/pagination.schema.js +2 -2
  43. package/dist/internal/validators/schemas/pagination.schema.js.map +1 -1
  44. package/dist/internal/validators/schemas/relations.schema.d.ts +4 -0
  45. package/dist/internal/validators/schemas/relations.schema.js +39 -0
  46. package/dist/internal/validators/schemas/relations.schema.js.map +1 -0
  47. package/dist/internal/validators/schemas/see-mode.schema.d.ts +3 -0
  48. package/dist/internal/validators/schemas/see-mode.schema.js +38 -0
  49. package/dist/internal/validators/schemas/see-mode.schema.js.map +1 -0
  50. package/dist/internal/validators/schemas/select.schema.d.ts +4 -0
  51. package/dist/internal/validators/schemas/select.schema.js +39 -0
  52. package/dist/internal/validators/schemas/select.schema.js.map +1 -0
  53. package/dist/internal/validators/vsrepo.validator.js +7 -5
  54. package/dist/internal/validators/vsrepo.validator.js.map +1 -1
  55. package/dist/types/dynamic-methods/dynamic-method-info.type.d.ts +1 -0
  56. package/dist/types/utils/query-args.type.d.ts +1 -4
  57. package/dist/types/vsrepo/vs-raw-query-builder-cte-query.type.d.ts +8 -0
  58. package/dist/types/vsrepo/vs-raw-query-builder-cte-query.type.js +3 -0
  59. package/dist/types/vsrepo/vs-raw-query-builder-cte-query.type.js.map +1 -0
  60. package/dist/types/vsrepo/vs-raw-query-builder-target.type.d.ts +7 -0
  61. package/dist/types/vsrepo/vs-raw-query-builder-target.type.js +3 -0
  62. package/dist/types/vsrepo/vs-raw-query-builder-target.type.js.map +1 -0
  63. package/dist/types/vsrepo/vsrepo-options.type.d.ts +27 -0
  64. package/package.json +88 -80
package/README.pt-BR.md CHANGED
@@ -1,1246 +1,221 @@
1
- <div align="center">
2
- <img src="https://res.cloudinary.com/ddbfifdxd/image/upload/w_200,q_auto,f_auto/v1786386427/VS_logo_TextoAbaixo_yev4tq.png" alt="VSRepository Logo" width="200"/>
3
-
4
- <p style="margin-top: 12px;">
5
- <img src="https://img.shields.io/npm/v/vsrepo?style=flat-square" alt="npm version"/>
6
- <img src="https://img.shields.io/npm/l/vsrepo?style=flat-square" alt="npm license"/>
7
- <img src="https://img.shields.io/npm/dt/vsrepo?style=flat-square" alt="npm downloads"/>
8
- <img src="https://img.shields.io/badge/inspired%20by-JpaRepository-E73121?style=flat-square" alt="inspired by JpaRepository"/>
9
- </p>
10
- </div>
11
-
12
- # VSRepository v2
13
-
14
- 🇧🇷 Você está lendo a versão em português. [🇺🇸 Read in English](./README.md)
15
-
16
- Biblioteca de repository pattern **agnóstica de ORM**, com suporte completo a **TypeScript** e **type inference** automático. O VSRepository v2 é uma reescrita da biblioteca [v1](https://github.com/jaobrabo123/VSRepository/tree/v1): em vez de falar diretamente com o Prisma, o núcleo agora delega toda operação a um **adapter** plugável, permitindo que a mesma API de repository funcione com Prisma, Drizzle ou qualquer outro ORM/banco que implemente o contrato de adapter.
17
-
18
- O VSRepository permite criar repositories fortemente tipados com:
19
-
20
- - **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `merge`, `getAll`, `total`, `has`
21
- - **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
22
- - **Métodos dinâmicos** inferidos a partir do nome de um campo `declare` via o decorador `@DynamicMethod`: `findByEmail`, `findManyByStatusPaginated`, `updateById`
23
- - **Métodos de query SQL raw** através do decorador `@QueryMethod`, ignorando totalmente o engine de parsing por nome
24
- - **`select`/`relations`** ad-hoc em cada chamada — sem mais projeções nomeadas pré-declaradas
25
- - **Type safety** em 100% das operações
26
- - **Transações** nativas do ORM, compartilhadas entre repositories
27
- - Um **núcleo agnóstico de ORM** — a mesma classe de repository funciona com qualquer implementação de `VSRepoAdapter`
28
-
29
- ---
30
-
31
- ## Sumário
32
-
33
- - [O que mudou da v1](#o-que-mudou-da-v1)
34
- - [Status dos adapters](#status-dos-adapters)
35
- - [Instalação](#instalação)
36
- - [Uso básico](#uso-básico)
37
- - [Options do construtor](#options-do-construtor)
38
- - [Métodos base](#métodos-base)
39
- - [Soft-delete](#soft-delete)
40
- - [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)
41
- - [Quais campos são elegíveis](#quais-campos-são-elegíveis)
42
- - [Escrevendo um adapter](#escrevendo-um-adapter)
43
- - [`select` e `relations`](#select-e-relations)
44
- - [Tipagem de retorno restrita com `InferMethodReturn`](#tipagem-de-retorno-restrita-com-infermethodreturn)
45
- - [Métodos dinâmicos](#métodos-dinâmicos)
46
- - [Prefixos disponíveis](#prefixos-disponíveis)
47
- - [Filtros de campo](#filtros-de-campo)
48
- - [Operadores lógicos](#operadores-lógicos)
49
- - [Filtros de relação](#filtros-de-relação)
50
- - [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct)
51
- - [Options do decorador](#options-do-decorador)
52
- - [Tipagem de retorno restrita com `InferMethodType`](#tipagem-de-retorno-restrita-com-infermethodtype)
53
- - [Query methods (SQL raw)](#query-methods-sql-raw)
54
- - [Argumentos via spread com `spreadArgs`](#argumentos-via-spread-com-spreadargs)
55
- - [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query)
56
- - [Transações](#transações)
57
- - [Tipos utilitários](#tipos-utilitários)
58
- - [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)
59
- - [Tratamento de erros](#tratamento-de-erros)
60
- - [`VSRepoAdapterError` e `AdapterErrorCode`](#vsrepoadaptererror-e-adaptererrorcode)
61
- - [Logging](#logging)
62
- - [Desenvolvimento](#desenvolvimento)
63
- - [Requisitos](#requisitos)
64
- - [Contribuindo](#contribuindo)
65
-
66
- ---
67
-
68
- ## O que mudou da v1
69
-
70
- Se você vem do código/docs da [v1](https://github.com/jaobrabo123/VSRepository/tree/v1), aqui está o resumo. Veja cada seção linkada para detalhes.
71
-
72
- | Área | v1 | v2 |
73
- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74
- | Acesso ao banco | Fala diretamente com o **Prisma**, embutido no pacote core | Fala com um **`VSRepoAdapter`**; o suporte a cada ORM é distribuído em pacotes separados (`@vsrepo/prisma7-adapter`, `@vsrepo/drizzle-adapter`, ...) em vez de vir embutido no pacote core `vsrepo` |
75
- | Definindo um repository | `setupVSRepo<T, M>()({...}).build(prisma)` funcional, **ou** uma classe `DynamicRepository` | Uma única API **baseada em classes**: `extends VSRepository<Entity, PKType, OrmTypes>` |
76
- | Métodos dinâmicos | Objeto de config `methods: { findByEmail: { map: true } }` | Decorador `@DynamicMethod()` em um campo `declare` |
77
- | Projeções de dados | `selectModels` + `defaultSelectModel` nomeados e reutilizáveis | `select`/`relations` ad-hoc passados em cada chamada (sem modelos nomeados) |
78
- | Eager loading | `include`/`includeModels` (específico do Prisma) | Option `relations` agnóstica de ORM |
79
- | Filtros globais | `requiredWhere` e `pushWhere` | **Removidos**; Agora aceita apenas `softRemoveKey` + `see: "active" \| "removed" \| "all"` |
80
- | Sufixo de filtro case-insensitive | `Insensitive` | `IgnoreCase` |
81
- | Ordenação inline no nome do método | Não suportado (`order` tinha que ser passado como argumento via `Ordered`/`Paginated`) | Cadeias `OrderBy<Campo>Asc`/`OrderBy<Campo>Desc` embutidas diretamente no nome do método |
82
- | Tratamento de duplicatas no `createMany` | Sufixo `SkipDuplicates` | Sufixo `IgnoreConflicts` |
83
- | `aggregate` / `groupBy` | Suportado (passthrough nativo do Prisma) | `groupBy` **não está planejado** para a v2. Um prefixo `aggregate` separado também dificilmente será implementado: as operações mais comuns já são cobertas por métodos base dedicados (`sum`, `average`, `min`, `max`, `increment`, `decrement`, `multiply`, `divide`) — veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). Para qualquer coisa mais complexa, use `@QueryMethod`. |
84
- | Tipos de erro | `VSRepoError` + subclasses (`VSRepoConfigError`, `VSRepoBuildError`, `VSRepoExtendError`, `VSRepoRuntimeError`) | Uma classe base `VSRepoError` com um campo `type: VSRepoErrorType` (`DECORATOR`, `RESOLVER`, `DYNAMIC`, `VALIDATOR`, `BASE`, `ADAPTER`), além de uma subclasse `VSRepoAdapterError` que carrega um `AdapterErrorCode` e o erro original do ORM |
85
- | Log de debug | Boolean `showWorking: true` | `logLevel: VSLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`) + `logSlowThresholdMs` para avisos de queries lentas |
86
- | CLI `vsrepo generate` (etapa de geração de tipos) | Obrigatória antes de usar | Não faz parte do núcleo da v2 — os tipos vêm diretamente das suas entidades/tipos do ORM |
87
- | Extras de CRUD | `patchList`, `options.select`/`options.include` raw | `select`/`relations` já são o padrão (sempre "raw"); `patch`/`merge` mantêm a mesma semântica. **`patchList` foi removido** — para uma atualização parcial em lote, use um dynamic method `updateManyBy`/`updateManyWhere` |
88
-
89
- ---
90
-
91
- ## Status dos adapters
92
-
93
- O VSRepository v2 é **agnóstico de ORM por design**. O pacote core (`vsrepo`) traz apenas a classe de repository, os decoradores, o engine de parsing de nomes, o tratamento de erros e o logging — ele **não** inclui um adapter de produção. O suporte de fato a cada ORM/banco deve viver em **pacotes separados, versionados de forma independente**, um por ORM (e, quando fizer sentido, um por versão principal do ORM), por exemplo:
94
-
95
- - `@vsrepo/prisma7-adapter`
96
- - `@vsrepo/prisma8-adapter`
97
- - `@vsrepo/typeorm-adapter`
98
- - `@vsrepo/drizzle-adapter`
99
-
100
- O adapter do Prisma 7 já foi publicado no npm como `@vsrepo/prisma7-adapter`. O adapter do Drizzle está disponível em versão **alpha** — instale com `npm i @vsrepo/drizzle-adapter@alpha`. Os adapters para outros ORMs estão **planejados**, mas ainda não foram publicados. Até que exista um pacote `@vsrepo/*-adapter` oficial para o seu ORM, você pode escrever o seu próprio para o seu projeto e, se quiser, publicá-lo e abrir um PR para ajudar a fazer o ecossistema crescer — contribuições nesse sentido são muito bem-vindas.
101
-
102
- | Adapter | Status |
103
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
- | Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa o contrato de `VSRepoAdapter` (CRUD, relations, transactions, `merge`, logging, etc.) com testes; veja o [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) para o código-fonte e docs. |
105
- | Drizzle (`@vsrepo/drizzle-adapter`) | 🔵 **Alpha** — uma versão inicial já está disponível no npm; instale com `npm i @vsrepo/drizzle-adapter@alpha`. A API ainda pode mudar antes do release estável. Veja o repositório do [`DrizzleAdapter`](https://github.com/jaobrabo123/VSRepoDrizzleAdapter) para o estado atual e limitações conhecidas, e sinta-se à vontade para contribuir. |
106
- | Outros ORMs (Prisma 8, TypeORM, etc.) | 🟡 **Planejados, ainda não publicados.** Nenhum pacote oficial existe ainda — por enquanto, escreva o seu próprio adapter (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) e considere publicá-lo/contribuir de volta com o projeto. |
107
- | Adapters customizados | 🟢 Totalmente suportados hoje — implemente você mesmo a classe abstrata [`VSRepoAdapter`](#escrevendo-seu-próprio-adapter) para qualquer ORM/banco que precisar, no seu próprio projeto ou pacote. |
108
-
109
- Resumindo: a classe de repository, os decoradores `@DynamicMethod`/`@QueryMethod`, o engine de parsing de nomes, o tratamento de erros e o logging já funcionam de ponta a ponta, e o suporte ao Prisma 7 agora é um adapter lançado e publicado. O adapter do Drizzle está disponível em alpha. Adapters oficiais para os demais ORMs estão no roadmap e serão distribuídos como pacotes `@vsrepo/*-adapter` separados, e não como parte do pacote core `vsrepo` — mas você não precisa esperar por isso: escrever (e opcionalmente publicar) o seu próprio adapter enquanto isso é uma forma totalmente suportada de usar a v2 hoje e de contribuir de volta com o projeto.
110
-
111
- ---
112
-
113
- ## Instalação
114
-
115
- A v2 é instalada como o pacote core mais um pacote de adapter para o seu ORM, por exemplo:
116
-
117
- ```bash
118
- npm i vsrepo @vsrepo/prisma7-adapter
119
- ```
120
-
121
- ---
122
-
123
- ## Uso básico
124
-
125
- ### Implementando/escolhendo um adapter
126
-
127
- ```typescript
128
- // src/configs/db.ts
129
- import { PrismaClient } from "../../generated/prisma/client";
130
- import { PrismaPg } from "@prisma/adapter-pg";
131
- import "dotenv/config";
132
-
133
- const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
134
- const prisma = new PrismaClient({ adapter });
135
-
136
- export default prisma;
137
- ```
138
-
139
- ### Criando um repository
140
-
141
- ```typescript
142
- // src/repositories/user.repository.ts
143
- import { VSRepository, DynamicMethod } from "vsrepo";
144
- import { VSRepoPrisma7Adapter } from "@vsrepo/prisma7-adapter";
145
- import prisma from "../configs/db";
146
- import type { UserGetPayload } from "../../generated/prisma/models";
147
-
148
- type User = UserGetPayload<{ include: { address: true } }>;
149
-
150
- class UserRepository extends VSRepository<User, string> {
151
- constructor() {
152
- super({
153
- pkName: "id",
154
- adapter: new VSRepoPrisma7Adapter<User>(prisma, { tableName: "user", pkName: "id" }),
155
- softRemoveKey: "deletedAt",
156
- defaultOrdering: { createdAt: "desc" },
157
- });
158
- }
159
-
160
- @DynamicMethod()
161
- declare findByEmail: (email: string) => Promise<User[]>;
162
-
163
- @DynamicMethod()
164
- declare findOneByEmail: (email: string) => Promise<User | null>;
165
- }
166
-
167
- export default new UserRepository();
168
- ```
169
-
170
- > A API do core (`VSRepository`, `VSRepoAdapter`, `DynamicMethod`, `QueryMethod`, `VSRepoError`, enums e tipos) é importada do entry point único `vsrepo`. O adapter concreto vem de um pacote **separado** (`@vsrepo/*-adapter`). No Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter).
171
-
172
- > **O terceiro generic (`OrmTypes`):** `VSRepository<Entity, PKType, OrmTypes>` aceita um terceiro type parameter opcional descrevendo os tipos de client/transaction do seu ORM, via `VSRepoOrmTypes` (`{ dbClient; dbTransaction }`). Ao fornecê-lo, `getDbClient()`, o callback de `transaction()` e a option `db` de todo método passam a ser tipados corretamente, em vez de `any`:
173
- >
174
- > ```typescript
175
- > import { Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
176
- >
177
- > type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
178
- >
179
- > class UserRepository extends VSRepository<User, string, MyOrmTypes> {
180
- > // getDbClient() agora retorna PrismaClient, e transaction(fn) tipa `tx` como Prisma.TransactionClient
181
- > }
182
- > ```
183
- >
184
- > Se omitido, o padrão é `VSRepoOrmTypes` (`dbClient`/`dbTransaction` como `any`).
185
-
186
- ### Usando o repository
187
-
188
- ```typescript
189
- import userRepository from "./repositories/user.repository";
190
-
191
- const usuario = await userRepository.save({
192
- name: "Joao",
193
- email: "joao@email.com",
194
- password: "password",
195
- });
196
-
197
- const encontrado = await userRepository.get(usuario.id);
198
- const todos = await userRepository.getAll();
199
- const porEmail = await userRepository.findByEmail("joao@email.com");
200
-
201
- await userRepository.patch(usuario.id, { name: "Joao Pedro" });
202
- await userRepository.remove(usuario.id);
203
- ```
204
-
205
- ---
206
-
207
- ## Options do construtor
208
-
209
- `VSRepoOptions<T, K>`, passado para o `super(...)` dentro do construtor do seu repository:
210
-
211
- | Option | Tipo | Descrição |
212
- | -------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
213
- | `adapter` | `VSRepoAdapter<T>` | **Obrigatório.** A instância do adapter que traduz as chamadas do repository em chamadas contra o ORM/banco por trás dele. |
214
- | `pkName` | `keyof T` | Opcional. Nome do campo que representa a primary key da entidade. Quando omitido, o repository usa o `getPkName()` do adapter. Se o adapter também não implementar, o construtor lança um `VSRepoError`. |
215
- | `softRemoveKey` | `keyof T` | Opcional. Quando definido, habilita `softRemove`, `softRemoveList`, `restore` e `restoreList`. |
216
- | `defaultOrdering` | `Ordering<T>` | Opcional. Ordenação padrão aplicada automaticamente em queries que aceitam `order`, a menos que seja sobrescrita em uma chamada específica. |
217
- | `logLevel` | `VSLogLevel` | Opcional. Severidade mínima impressa pelo logger interno. Padrão: `VSLogLevel.WARN`. |
218
- | `logSlowThresholdMs` | `number \| boolean` | Opcional. Duração (ms) acima da qual uma operação concluída é logada como `WARN`. Padrão: 300ms. Passe `false` para desabilitar completamente os avisos de operação lenta; passe `true` para usar explicitamente o threshold padrão de 300ms. |
219
-
220
- ---
221
-
222
- ## Métodos base
223
-
224
- Disponíveis automaticamente em toda subclasse de `VSRepository`:
225
-
226
- | Método | Descrição |
227
- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
228
- | `get(pk, options?)` | Busca um registro pela primary key. |
229
- | `getOrThrow(pk, options?)` | Busca um registro pela primary key, lançando erro se não encontrar. |
230
- | `getList(pks, options?)` | Busca vários registros por uma lista de primary keys. |
231
- | `getAll(options?)` | Busca todos os registros; aceita `pagination` e `order` em `options`. |
232
- | `save(obj, options?)` | Cria ou atualiza (upsert) um único registro. |
233
- | `saveList(objs, options?)` | Cria ou atualiza (upsert) vários registros em uma única chamada. |
234
- | `patch(pk, obj, options?)` | Atualiza parcialmente um registro pela primary key. |
235
- | `merge(pk, obj, options?)` | Busca um registro e o retorna mesclado (deep-merge), em memória, com o objeto informado — **não** persiste nada. |
236
- | `remove(pk, options?)` | Remove um registro pela primary key. |
237
- | `removeList(pks, options?)` | Remove vários registros pela primary key, retornando `{ count }`. |
238
- | `total(options?)` | Retorna o total de registros. |
239
- | `has(pk, options?)` | Verifica se um registro existe, retornando `boolean`. |
240
- | `increment(pk, field, value, options?)` | Adiciona `value` a um campo numérico de forma atômica. Veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
241
- | `decrement(pk, field, value, options?)` | Subtrai `value` de um campo numérico de forma atômica. |
242
- | `multiply(pk, field, value, options?)` | Multiplica um campo numérico por `value` de forma atômica. |
243
- | `divide(pk, field, value, options?)` | Divide um campo numérico por `value` de forma atômica. |
244
- | `sum(field, where?, options?)` | Soma um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
245
- | `average(field, where?, options?)` | Média aritmética de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
246
- | `min(field, where?, options?)` | Valor mínimo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
247
- | `max(field, where?, options?)` | Valor máximo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
248
- | `transaction(fn, options?)` | Executa `fn` dentro de uma transação nativa do ORM. |
249
- | `getDbClient()` | Retorna a instância do client do ORM. |
250
- | `query<T>(query, options?)` | Executa uma instrução SQL raw diretamente contra o banco. Veja [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query). |
251
-
252
- A maioria dos métodos acima aceita um objeto `MethodOptions<Entity, OrmTypes>` como último argumento (`select`, `relations`, `see`, `db`). Alguns — `total`, `has`, `removeList`, `sum`, `average`, `min`, `max`, e os métodos em lote de soft-delete (`softRemoveList`/`restoreList`) — não retornam/moldam uma `Entity`, então aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db`; sem `select`/`relations`). `transaction`, `query` e `getDbClient` recebem options próprias ou nenhuma.
253
-
254
- ---
255
-
256
- ## Soft-delete
257
-
258
- O soft-delete é um **conceito nativo de primeira classe**. Configure `softRemoveKey` uma vez no repository:
259
-
260
- ```typescript
261
- super({
262
- pkName: "id",
263
- adapter,
264
- softRemoveKey: "deletedAt",
265
- });
266
- ```
267
-
268
- Isso libera quatro métodos extras:
269
-
270
- | Método | Efeito |
271
- | ------------------------------- | --------------------------------------- |
272
- | `softRemove(pk, options?)` | Define `deletedAt` para a data atual. |
273
- | `softRemoveList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
274
- | `restore(pk, options?)` | Volta `deletedAt` para `null`. |
275
- | `restoreList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
276
-
277
- Todo o restante dos métodos aceita uma option `see` que controla a visibilidade de registros com soft-delete:
278
-
279
- ```typescript
280
- await userRepository.getAll({ see: "active" }); // padrão — apenas registros não removidos
281
- await userRepository.getAll({ see: "removed" }); // apenas registros com soft-delete
282
- await userRepository.getAll({ see: "all" }); // todos, ignorando o soft-delete
283
- ```
284
-
285
- ---
286
-
287
- ## Métodos atômicos e de agregação
288
-
289
- Toda subclasse de `VSRepository` ganha 8 métodos para trabalhar com campos numéricos, divididos em dois grupos:
290
-
291
- **Updates atômicos** — avaliados no servidor contra o valor _atual_ da linha (`UPDATE ... SET field = field + value`), não um read-modify-write feito no client:
292
-
293
- ```typescript
294
- await userRepository.increment("user-1", "balance", 50); // balance = balance + 50
295
- await userRepository.decrement("user-1", "balance", 50); // balance = balance - 50
296
- await userRepository.multiply("user-1", "balance", 2); // balance = balance * 2
297
- await userRepository.divide("user-1", "balance", 4); // balance = balance / 4
298
- ```
299
-
300
- Os quatro retornam a `Entity` atualizada e aceitam o `MethodOptions<Entity, OrmTypes>` completo (`select`, `relations`, `see`, `db`) como último argumento, igual `get`/`save`/`patch`.
301
-
302
- **Agregações** — calculadas sobre todos os registros que baterem num `where` (opcional):
303
-
304
- ```typescript
305
- await userRepository.sum("balance"); // soma do saldo de todos os registros ativos
306
- await userRepository.sum("balance", { active: true }); // ...restrito por um where
307
- await userRepository.average("balance");
308
- await userRepository.min("balance");
309
- await userRepository.max("balance");
310
- ```
311
-
312
- Os quatro retornam `number | null` — `null` quando nenhum registro bate no filtro, espelhando o comportamento de `SUM()`/`AVG()`/`MIN()`/`MAX()` do SQL, que retornam `NULL` (não `0`) sobre um conjunto vazio. Diferente dos métodos atômicos, eles aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db` — sem `select`/`relations`, já que o resultado é um número simples, não uma `Entity` moldada).
313
-
314
- Os dois grupos respeitam `softRemoveKey`/`see` do mesmo jeito que todo outro método base — `sum("balance")` só soma registros não removidos por padrão; passe `{ see: "all" }` ou `{ see: "removed" }` para mudar isso.
315
-
316
- ### Quais campos são elegíveis
317
-
318
- `field` é restrito a `NumericKeys<Entity>` — chaves cujo valor (ignorando `null`/`undefined`) é um `number`, um `bigint`, ou um objeto `DecimalLike` (qualquer coisa que exponha `toNumber()` e `decimalPlaces()`, como o `Prisma.Decimal` do Prisma):
319
-
320
- ```typescript
321
- type Product = { id: string; name: string; price: Decimal; stock: number | null };
322
-
323
- await productRepository.increment(id, "price", new Decimal(10.5)); // ok — Decimal-like
324
- await productRepository.increment(id, "stock", 5); // ok — campos numéricos nullable são incluídos
325
- await productRepository.increment(id, "name", 1); // erro de compilação — "name" não é numérico
326
- ```
327
-
328
- `value` é tipado como `NonNullable<Entity[Field]>` — precisa bater exatamente com o tipo do próprio campo. Um campo `Decimal` espera uma instância de `Decimal`, não um `number`/`string` puro:
329
-
330
- ```typescript
331
- await productRepository.increment(id, "price", new Decimal(10.5)); // ok
332
- await productRepository.increment(id, "price", 10.5); // erro de compilação — envolva: new Decimal(10.5)
333
- ```
334
-
335
- Vale notar que vários ORMs (Drizzle, MikroORM, TypeORM) representam colunas `decimal`/`numeric` como `string` pura por padrão, para evitar perda de precisão de ponto flutuante — um campo `string` **não** satisfaz `NumericKeys<Entity>` por padrão. Configure a coluna em modo numérico (ou um transformer) nesses ORMs se quiser que o campo fique disponível para esses 8 métodos.
336
-
337
- ### Escrevendo um adapter
338
-
339
- O `VSRepoAdapter` espelha as mesmas 8 operações (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)). Cada adapter traduz isso para o que o ORM/banco considera "nativo": o Prisma tem um formato de update embutido (`{ field: { increment: value } }`) e uma chamada `aggregate()`; outros ORMs em geral precisam de um `QueryBuilder`/expressão `sql` raw (ex.: `SET field = field * :value`, `SELECT SUM(field) ...`). Os métodos atômicos precisam retornar o registro refletindo o estado _depois_ do write — se a API de update atômico do ORM só retorna a quantidade de linhas afetadas, faça uma leitura extra em vez de devolver uma cópia desatualizada que já estava em memória.
340
-
341
- ---
342
-
343
- ## `select` e `relations`
344
-
345
- Os `selectModels`/`defaultSelectModel` nomeados e reutilizáveis da v1 não existem mais. Na v2 você passa `select` e `relations` diretamente em cada chamada — não há nada para pré-registrar:
346
-
347
- ```typescript
348
- const usuario = await userRepository.get(id, {
349
- select: { id: true, name: true, address: { city: true } },
350
- });
351
-
352
- const usuarioComEndereco = await userRepository.get(id, {
353
- relations: { address: true },
354
- });
355
- ```
356
-
357
- - `select` espelha o formato da entidade: campos escalares recebem um `boolean`; campos de relação recebem um `boolean` ou um `select` aninhado.
358
- - `relations` carrega registros relacionados; cada campo de relação recebe um `boolean` ou um objeto `relations` aninhado.
359
- - Se `select` e `relations` podem ser combinados depende do adapter (veja abaixo).
360
-
361
- > ⚠️ **Comportamento de `relations` depende do adapter:**
362
- >
363
- > O core apenas repassa `MethodOptions.select` e `MethodOptions.relations` ao adapter — cada adapter decide como traduzi-los para o ORM subjacente:
364
- >
365
- > - **Prisma 7 (`@vsrepo/prisma7-adapter` / `VSRepoPrisma7Adapter`)** — `relations` é convertido para `include` do Prisma (`parsePrismaInclude`). **Se `select` estiver presente, `relations` é ignorado** porque o Prisma não permite `select` + `include` na mesma query:
366
- > ```typescript
367
- > // Prisma7: relations é ignorado quando select existe
368
- > await userRepository.get(id, {
369
- > select: { id: true, name: true },
370
- > relations: { address: true }, // ← ignorado, include = undefined
371
- > });
372
- > ```
373
- >
374
- > Adapters customizados podem mapear `relations` de forma diferente — consulte a documentação do adapter para a semântica exata.
375
-
376
- ### Tipagem de retorno restrita com `InferMethodReturn`
377
-
378
- Por padrão, os métodos são tipados como se retornassem a **entidade inteira**, ignorando o `select` e o `relations` que você passa (a mesma abordagem do TypeORM). Se você prefere uma tipagem mais restrita, `InferMethodReturn<T, Options>` a estreita para o que foi realmente pedido. É opt-in e puramente em nível de tipos — nada muda em runtime.
379
-
380
- ```typescript
381
- import type { InferMethodReturn, MethodOptions } from "vsrepo";
382
-
383
- type Address = { id: string; city: string };
384
- type Product = { id: string; name: string };
385
- type User = {
386
- id: string;
387
- name: string;
388
- email: string;
389
- address: Address | null;
390
- products: Product[];
391
- };
392
-
393
- const options = {
394
- select: { id: true, name: true, products: { id: true } },
395
- } satisfies MethodOptions<User>;
396
-
397
- const users: InferMethodReturn<User[], typeof options> = await userRepository.getAll(options);
398
- // { id: string; name: string; products: { id: string }[] }[]
399
- ```
400
-
401
- - O **primeiro** argumento de tipo é o que o método retorna: `User`, `User | null` ou `User[]`. `null` e array são preservados — inclusive nos campos de relação (`address: Address | null`).
402
- - O **segundo** é o objeto de options passado ao método (`typeof options`). Pode ser omitido, o que equivale a não passar options.
403
-
404
- | Options passadas | Resultado inferido |
405
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
406
- | Nenhuma (ou `{}`) | Apenas os campos escalares da entidade — sem relações. |
407
- | `relations` | Os campos escalares mais as relações pedidas (inclusive as aninhadas); cada relação traz todos os seus campos escalares. |
408
- | `select` | Apenas os campos selecionados. Uma relação com `true` traz todos os seus campos escalares; um `select` aninhado a restringe ainda mais. Relações selecionadas assim são carregadas mesmo sem `relations`. |
409
- | `select` + `relations` | `select` vence e `relations` é ignorado. |
410
-
411
- - `see` e `db` não afetam o resultado.
412
- - Mantenha os tipos literais das options, usando `satisfies MethodOptions<T>` (como acima) ou passando-as inline. Se estiverem tipadas como um `MethodOptions<T>` genérico (ex.: `const options: MethodOptions<User> = ...`), nada é conhecido em tempo de compilação e `T` é retornado sem alterações.
413
- - Campos e relações opcionais (`?`) da entidade continuam opcionais.
414
- - Para ter essa inferência direto nos métodos dinâmicos, veja [Tipagem de retorno restrita com `InferMethodType`](#tipagem-de-retorno-restrita-com-infermethodtype).
415
-
416
- ---
417
-
418
- ## Métodos dinâmicos
419
-
420
- Métodos dinâmicos são declarados como um campo `declare` anotado com `@DynamicMethod()`. O comportamento deles — qual método do adapter chamar, quais filtros aplicar e como os argumentos se mapeiam para eles — é inferido inteiramente a partir do **nome** do campo.
421
-
422
- ```typescript
423
- class UserRepository extends VSRepository<User, string> {
424
- @DynamicMethod()
425
- declare findByEmail: (email: string, options?: MethodOptions<User>) => Promise<User[]>;
426
-
427
- @DynamicMethod()
428
- declare findOneByEmail: (email: string) => Promise<User | null>;
429
-
430
- @DynamicMethod()
431
- declare updateById: (id: string, data: DeepPartial<User>) => Promise<User>;
432
-
433
- // Baseado em where: VSRepoWhere<T> como primeiro parâmetro, pagination penúltimo, MethodOptions por último
434
- @DynamicMethod()
435
- declare findWherePaginated: (
436
- where: VSRepoWhere<User>,
437
- pagination: Pagination,
438
- options?: MethodOptions<User>,
439
- ) => Promise<User[]>;
440
-
441
- // filtros de campo, depois pagination, depois MethodOptions
442
- @DynamicMethod()
443
- declare findByNameIgnoreCaseOrAgeBetweenOrderByCreatedAtAscPaginated: (
444
- name: string,
445
- age: [number, number],
446
- pagination: Pagination,
447
- options?: MethodOptions<User>,
448
- ) => Promise<User[]>;
449
- }
450
- ```
451
-
452
- > Quer que o tipo de retorno acompanhe o `select`/`relations` passados, em vez de ser sempre a entidade inteira? Declare o método com [`InferMethodType`](#tipagem-de-retorno-restrita-com-infermethodtype).
453
-
454
- ### Prefixos disponíveis
455
-
456
- | Prefixo | Método do adapter | Observações |
457
- | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
458
- | `findBy` | `findMany` | Filtros de campo seguem o prefixo. |
459
- | `findOneBy` | `findOne` | Filtros de campo seguem o prefixo; resultado único. |
460
- | `findOneOrThrowBy` | `findOneOrThrow` | Lança erro se não encontrar. |
461
- | `findOneOrThrow` | `findOneOrThrow` | Sem filtros de campo; aplica só soft-delete/`see`. |
462
- | `findOneOrThrowWhere` | `findOneOrThrow` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
463
- | `findWhere` | `findMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
464
- | `findOneWhere` | `findOne` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
465
- | `findOne` | `findOne` | Sem filtros de campo; aplica só soft-delete/`see`. |
466
- | `countBy` | `count` | Filtros de campo seguem o prefixo. |
467
- | `countWhere` | `count` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
468
- | `count` | `count` | Sem filtros de campo. |
469
- | `existsBy` | `exists` | Retorna `boolean`. |
470
- | `existsWhere` | `exists` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
471
- | `create` | `create` | Recebe `DeepPartial<Entity>` como argumento. |
472
- | `createMany` | `createMany` | Recebe `DeepPartial<Entity>[]` como argumento; suporta `IgnoreConflicts`. |
473
- | `createManyReturning` | `createManyReturning` | Recebe `DeepPartial<Entity>[]` como argumento; suporta `IgnoreConflicts`; retorna os registros criados (`T[]`), em vez de `CountResult`. |
474
- | `updateBy` | `update` | Filtros de campo + `DeepPartial<Entity>` como argumento. |
475
- | `updateWhere` | `update` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `DeepPartial<Entity>`. |
476
- | `updateManyBy` | `updateMany` | Filtros de campo + `DeepPartial<Entity>`. |
477
- | `updateManyWhere` | `updateMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `DeepPartial<Entity>`. |
478
- | `updateManyReturningBy` | `updateManyReturning` | Filtros de campo + `DeepPartial<Entity>`; retorna os registros atualizados. |
479
- | `updateManyReturningWhere` | `updateManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `DeepPartial<Entity>`; retorna os registros atualizados. |
480
- | `upsertBy` | `upsert` | Filtros de campo + payloads `create`/`update`. |
481
- | `upsertWhere` | `upsert` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois os payloads `create`/`update`. |
482
- | `deleteBy` | `delete` | Filtros de campo seguem o prefixo. |
483
- | `deleteWhere` | `delete` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
484
- | `deleteManyBy` | `deleteMany` | Filtros de campo seguem o prefixo. |
485
- | `deleteManyWhere` | `deleteMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
486
- | `deleteManyReturningBy` | `deleteManyReturning` | Filtros de campo seguem o prefixo; retorna os registros removidos. |
487
- | `deleteManyReturningWhere` | `deleteManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento; retorna os registros removidos. |
488
-
489
- > `groupBy` **não está planejado** para a v2 — ele não se encaixa bem no contrato agnóstico de ORM. Um prefixo `aggregate` separado também dificilmente será implementado: as operações de agregação mais comuns (`sum`, `average`, `min`, `max`, `increment`, `decrement`, `multiply`, `divide`) já estão disponíveis como métodos base dedicados — veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). Para qualquer coisa mais complexa, use um `@QueryMethod` com SQL raw.
490
-
491
- ### Filtros de campo
492
-
493
- Aplicados como sufixos ao nome do campo dentro do método (mesma ideia da v1, com um sufixo renomeado):
494
-
495
- | Sufixo | Significado | Argumento |
496
- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
497
- | _(sem sufixo)_ | igualdade (`=`) | sim |
498
- | `Not` | negação | sim |
499
- | `In` | está em | sim (array) |
500
- | `NotIn` | não está em | sim (array) |
501
- | `Contains` | contém substring | sim |
502
- | `NotContains` | não contém substring | sim |
503
- | `StartsWith` | começa com | sim |
504
- | `NotStartsWith` | não começa com | sim |
505
- | `EndsWith` | termina com | sim |
506
- | `NotEndsWith` | não termina com | sim |
507
- | `GreaterThan` | `>` | sim |
508
- | `GreaterThanEqual` | `>=` | sim |
509
- | `LessThan` | `<` | sim |
510
- | `LessThanEqual` | `<=` | sim |
511
- | `Between` | intervalo inclusivo | sim (tupla `[min, max]`) |
512
- | `NotBetween` | fora de um intervalo inclusivo | sim (tupla `[min, max]`) |
513
- | `IsNull` | campo é `null` | não |
514
- | `IsNotNull` | campo não é `null` | não |
515
- | `IsTrue` | campo é `true` | não |
516
- | `IsFalse` | campo é `false` | não |
517
- | `IgnoreCase` | combinador case-insensitive para filtros de texto | não _(renomeado do `Insensitive` da v1)_ |
518
- | `Optional` | **explícita** o argumento do campo como opcional — ele já é opcional por padrão, então esse sufixo é facultativo e serve apenas para deixar isso explícito | — |
519
-
520
- ```typescript
521
- @DynamicMethod()
522
- declare findByNameContainsIgnoreCase: (name: string) => Promise<User[]>;
523
-
524
- @DynamicMethod()
525
- declare findByAgeBetween: (age: [number, number]) => Promise<User[]>;
526
- ```
527
-
528
- ### Operadores lógicos
529
-
530
- | Operador | Uso no nome | Exemplo |
531
- | -------- | ------------------------------ | --------------------------------------------------- |
532
- | `And` | entre dois campos | `findOneByIdAndEmail` |
533
- | `Or` | entre dois campos | `findByNameOrEmail` |
534
- | `AND` | separa um bloco final em `AND` | `findByEmailOrNameANDActiveStatusAndAgeGreaterThan` |
535
-
536
- Regras do `AND` (em capslock), iguais às da v1: só é permitido **um** `AND` por nome de método; todo campo conectado por `And` depois dele é aninhado dentro de `AND: []`; `Or` não pode aparecer depois de um `AND`.
537
-
538
- ### Filtros de relação
539
-
540
- Filtram por campos de entidades relacionadas. Internamente, mapeiam para os operadores `_some`/`_every`/`_none`/`_with`/`_without` de `VSRepoWhere` (veja [`select` e `relations`](#select-e-relations) para o equivalente de eager loading).
541
-
542
- | Sufixo | Significado | Restrição |
543
- | -------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
544
- | `Some` | pelo menos um registro relacionado corresponde | apenas relações to-many |
545
- | `SomeField` | filtra dentro dos registros relacionados | apenas relações to-many |
546
- | `Every` | todo registro relacionado corresponde | apenas relações to-many (precisa de `Field` para ser um filtro efetivo) |
547
- | `EveryField` | filtra dentro dos registros relacionados | apenas relações to-many |
548
- | `None` | nenhum registro relacionado corresponde | apenas relações to-many |
549
- | `NoneField` | filtra dentro dos registros relacionados | apenas relações to-many |
550
- | `With` | o registro relacionado existe | apenas relações to-one |
551
- | `WithField` | filtra um campo dentro do registro relacionado | apenas relações to-one |
552
- | `Without` | o registro relacionado não existe | apenas relações to-one |
553
- | `WithoutField` | filtro negado em um campo do registro relacionado | apenas relações to-one |
554
-
555
- ```typescript
556
- @DynamicMethod()
557
- declare findByAddressWithCityStartsWithIgnoreCase: (city: string) => Promise<User[]>;
558
-
559
- @DynamicMethod()
560
- declare findByProductsSome: () => Promise<User[]>;
561
- ```
562
-
563
- ### Ordenação, paginação e distinct
564
-
565
- | Sufixo | Efeito |
566
- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
567
- | `Paginated` | Injeta um argumento `pagination` (`{ limit?, offset? }`) como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
568
- | `Ordered` | Injeta um argumento `order: Ordering<T>` como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
569
- | `OrderedAndPaginated` | Injeta `order` como antepenúltimo, depois `pagination` como penúltimo — ambos antes do `MethodOptions`. |
570
- | `PaginatedAndOrdered` | Injeta `pagination` como antepenúltimo, depois `order` como penúltimo — ambos antes do `MethodOptions`. |
571
- | `OrderBy<Campo>Asc` / `OrderBy<Campo>Desc` | Embute uma ordenação fixa diretamente no nome do método — encadeie campos com `And` (ex.: `OrderByCreatedAtAscAndNameDesc`). Não precisa de argumento `order`. *OBS: Se você não especificar `Asc` ou `Desc` ele considera como `Asc`* |
572
- | `Distinct<Campo>And<Campo>...` | Embute campos `distinct` fixos diretamente no nome do método (só válido em métodos da família `findBy`/`findWhere`). |
573
- | `IgnoreConflicts` | No `createMany`/`createManyReturning`, ignora registros que violariam uma constraint única, em vez de lançar erro. _(Renomeado do `SkipDuplicates` da v1.)_ |
574
-
575
- > ⚠️ **Ordem dos parâmetros:** `pagination` e `order` sempre vêm **antes** do último argumento opcional `MethodOptions<T>`. Quando `order` e `pagination` estão presentes juntos, a ordem relativa entre eles segue o nome do sufixo (`OrderedAndPaginated` → order, pagination; `PaginatedAndOrdered` → pagination, order).
576
-
577
- ```typescript
578
- // Paginated: pagination é o penúltimo parâmetro (antes do MethodOptions)
579
- @DynamicMethod()
580
- declare findByActiveOrderByCreatedAtDescPaginated:
581
- (active: boolean, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
582
-
583
- // OrderedAndPaginated: order, depois pagination, depois MethodOptions
584
- @DynamicMethod()
585
- declare findByNameContainsIgnoreCaseOrderedAndPaginated:
586
- (name: string, order: Ordering<User>, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
587
-
588
- @DynamicMethod()
589
- declare createManyIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<{ count: number }>;
590
-
591
- // createManyReturning: mesmo comportamento do createMany, mas retorna os registros criados
592
- @DynamicMethod()
593
- declare createManyReturningIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<User[]>;
594
-
595
- // findOne sem filtro (equivalente ao findOneOrThrow sem filtro, mas retorna null em vez de lançar)
596
- @DynamicMethod()
597
- declare findOne: (options?: MethodOptions<User>) => Promise<User | null>;
598
- ```
599
-
600
- > ⚠️ **Precedência entre `Distinct` e `OrderBy`:** quando os dois são usados no mesmo nome de método, **`Distinct` deve vir antes de `OrderBy`**:
601
- >
602
- > ```typescript
603
- > @DynamicMethod()
604
- > declare findByActiveDistinctNameOrderByCreatedAtDesc:
605
- > (active: boolean) => Promise<User[]>;
606
- > ```
607
- >
608
- > Colocar `OrderBy` antes de `Distinct` (ex.: `findByActiveOrderByCreatedAtDescDistinctName`) não é um padrão válido e não será interpretado como esperado.
609
-
610
- ### Options do decorador
611
-
612
- `@DynamicMethod<T>(options?)` aceita:
613
-
614
- | Option | Tipo | Descrição |
615
- | ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
616
- | `proxyTo` | `string` | Redireciona a lógica do método para outro padrão de método dinâmico válido — útil para nomes que não seguem a convenção de nomenclatura. |
617
- | `injectOrdering` | `Ordering<T>` | Ordenação fixa injetada automaticamente, sobrescrevendo o `defaultOrdering` do repository. |
618
-
619
- ```typescript
620
- // proxyTo: dá um nome customizado ao método reutilizando um padrão existente
621
- @DynamicMethod<User>({ proxyTo: "findByEmail" })
622
- declare buscarPorEmail: (email: string, options?: MethodOptions<User>) => Promise<User[]>;
623
-
624
- // injectOrdering: sempre ordena por createdAt desc, sobrescrevendo o defaultOrdering
625
- @DynamicMethod<User>({ injectOrdering: { createdAt: "desc" } })
626
- declare findByStatus: (status: string) => Promise<User[]>;
627
- ```
628
-
629
- ### Tipagem de retorno restrita com `InferMethodType`
630
-
631
- Normalmente você escreve à mão a assinatura de um método dinâmico, e o retorno é o que você declarar (em geral a entidade inteira). `InferMethodType<Args, Return, OrmTypes?>` declara o método para você e infere o retorno **a cada chamada** a partir do `select`/`relations` passados — com as mesmas regras do [`InferMethodReturn`](#tipagem-de-retorno-restrita-com-infermethodreturn):
632
-
633
- ```typescript
634
- class UserRepository extends VSRepository<User, string, MyOrmTypes> {
635
- @DynamicMethod()
636
- declare findByName: InferMethodType<[name: string], User[], MyOrmTypes>;
637
-
638
- // a terceira generic (OrmTypes) é opcional
639
- @DynamicMethod()
640
- declare findOneByEmail: InferMethodType<[email: string], User | null>;
641
- }
642
-
643
- await userRepository.findByName("John");
644
- // { id: string; name: string; email: string }[] (apenas os campos escalares)
645
-
646
- await userRepository.findByName("John", { select: { id: true, products: { id: true } } });
647
- // { id: string; products: { id: string }[] }[]
648
-
649
- await userRepository.findOneByEmail("john@example.com", { relations: { address: true } });
650
- // { id: string; name: string; email: string; address: Address | null } | null
651
- ```
652
-
653
- | Generic | Descrição |
654
- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
655
- | `Args` | Tupla com os argumentos posicionais do método, **sem** `options` — ex.: `[name: string]` ou `[where: VSRepoWhere<User>, pagination: Pagination]`. |
656
- | `Return` | O que o método resolve: `Entity`, `Entity \| null` ou `Entity[]`. O tipo da entidade usado em `select`/`relations` é extraído daqui. |
657
- | `OrmTypes` | _Opcional._ `VSRepoOrmTypes` do seu ORM, usado para tipar a option `db` (veja [Criando um repository](#criando-um-repository)). Padrão: `VSRepoOrmTypes`. |
658
-
659
- - `options` (`MethodOptions<Entity, OrmTypes>`) é sempre o **último** parâmetro, opcional, depois de todos os argumentos de `Args`. Se algum desses argumentos for opcional, passe `undefined` explicitamente para alcançar `options`.
660
- - Sem `options` o resultado tem apenas os campos escalares; com elas, segue as [mesmas regras](#tipagem-de-retorno-restrita-com-infermethodreturn) do `InferMethodReturn` (inclusive `select` vencendo `relations`).
661
- - Chaves inexistentes em `select`/`relations` (em qualquer profundidade) são rejeitadas em tempo de compilação, e o editor as sugere via autocomplete — igual a um parâmetro `MethodOptions<Entity>` comum.
662
- - Funciona junto com as [options do decorador](#options-do-decorador) (`proxyTo`, `injectOrdering`).
663
- - Foi pensado para métodos dinâmicos que retornam entidades (`findBy…`, `findOneBy…`, `findWhere…`, …). Os que não retornam — `countBy…`, `existsBy…` — mantêm a assinatura normal.
664
-
665
- ---
666
-
667
- ## Query methods (SQL raw)
668
-
669
- `@QueryMethod` ignora totalmente o engine de parsing por nome e executa uma instrução SQL raw através do método `query()` do adapter. Use placeholders para os valores passados via `args` — nunca interpole valores diretamente na string SQL. **A sintaxe dos placeholders depende do banco/driver usado pelo seu adapter:** o estilo `$1`, `$2`, ... usado nos exemplos abaixo é a convenção do PostgreSQL — o MySQL, por exemplo, usa `?`. Consulte a documentação do seu adapter para saber a sintaxe exata.
670
-
671
- ```typescript
672
- class UserRepository extends VSRepository<User, string> {
673
- @QueryMethod('SELECT * FROM "user" WHERE email = $1')
674
- declare findByEmailRaw: (arg: QueryMethodArg<[email: string]>) => Promise<User[]>;
675
-
676
- @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
677
- declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
678
-
679
- // Aqui só se espera uma linha, então `singleResult` transforma o array
680
- // em um único objeto (ou `null` quando nenhuma linha corresponde).
681
- @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
682
- declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
683
- }
684
- ```
685
-
686
- | Option | Tipo | Padrão | Descrição |
687
- | -------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
688
- | `modifying` | `boolean` | `false` | Quando `true`, o método resolve para o número de linhas afetadas. Quando `false`, executa como query de leitura e resolve para o tipo de retorno declarado. |
689
- | `singleResult` | `boolean` | `false` | Quando `true`, transforma um resultado em array no seu primeiro elemento (`null` se vazio), permitindo declarar o tipo de retorno como um objeto único em vez de array. Não tem efeito em resultados que não são array (ex.: o número de linhas afetadas de uma query `modifying`). |
690
-
691
- Query methods aceitam `{ args, db? }` na chamada — `db` permite que participem de um bloco `transaction()`, assim como os métodos base e dinâmicos.
692
-
693
- ### Argumentos via spread com `spreadArgs`
694
-
695
- Por padrão, um `@QueryMethod` recebe seus valores de placeholder através de um único objeto `QueryMethodArg` (`method({ args: [...] })`). Defina `spreadArgs: true` para recebê-los como argumentos posicionais separados, no estilo do JpaRepository:
696
-
697
- ```typescript
698
- class UserRepository extends VSRepository<User, string> {
699
- @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
700
- spreadArgs: true,
701
- })
702
- declare findByEmailAndType: (
703
- ...args: QueryArgs<[email: string, userType: string]>
704
- ) => Promise<User[]>;
705
-
706
- // Ao invés de usar o `QueryArgs`, você também pode simplesmente definir `DbArg` como último parâmetro
707
- @QueryMethod('SELECT * FROM "user" WHERE id = $1', { spreadArgs: true })
708
- declare findById: (id: string, db?: DbArg) => Promise<User[]>;
709
- }
710
-
711
- const admins = await userRepository.findByEmailAndType("joao@email.com", "admin");
712
- ```
713
-
714
- Para rodar a query com um client ou transação específico em vez do client padrão do repository, passe `withDb(tx)` como argumento final — ele embrulha `tx` em um `DbArg`, que o resolver reconhece via `instanceof`, então nunca é confundido com um argumento posicional comum, mesmo que esse argumento seja um objeto:
715
-
716
- ```typescript
717
- await userRepository.transaction(async tx => {
718
- await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
719
- });
720
- ```
721
-
722
- `spreadArgs` afeta apenas campos declarados com `@QueryMethod` — o padrão é `false`, e chamar um método declarado sem essa opção usando mais de um argumento lança erro, já que se espera o estilo de chamada com um único `QueryMethodArg`. Não tem efeito sobre `query()`, que sempre aceita `{ args, db? }`.
723
-
724
- ### Queries raw pontuais com `query()`
725
-
726
- Para SQL raw pontual que não justifica declarar um `@QueryMethod` na classe do repository, chame `query()` diretamente — ele está disponível em toda instância de `VSRepository` e usa o `query()` do adapter por baixo dos panos:
727
-
728
- ```typescript
729
- query<T = any>(query: string, options?: VSRepoQueryOptions<OrmTypes>): Promise<T>;
730
- ```
731
-
732
- ```typescript
733
- const users = await userRepository.query<User[]>('SELECT * FROM "user" WHERE email = $1', {
734
- args: ["maria@email.com"],
735
- });
736
-
737
- const linhasAfetadas = await userRepository.query<number>(
738
- 'UPDATE "user" SET active = true WHERE id = $1',
739
- { args: ["123"], modifying: true },
740
- );
741
-
742
- // Aqui só se espera uma linha, então `singleResult` transforma o array
743
- // em um único objeto (ou `null` quando nenhuma linha corresponde).
744
- const user = await userRepository.query<User | null>('SELECT * FROM "user" WHERE id = $1 LIMIT 1', {
745
- args: ["123"],
746
- singleResult: true,
747
- });
748
- ```
749
-
750
- | Option | Tipo | Padrão | Descrição |
751
- | -------------- | --------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
752
- | `args` | `any[]` | `undefined` | Parâmetros posicionais injetados nos placeholders do SQL — a sintaxe dos placeholders depende do banco/driver usado pelo seu adapter. Nunca interpole valores diretamente na string SQL. |
753
- | `db` | `any` | Client padrão do repository | Client ou transação do banco em que essa query deve rodar. |
754
- | `modifying` | `boolean` | `false` | Quando `true`, retorna o número de linhas afetadas. |
755
- | `singleResult` | `boolean` | `false` | Quando `true`, transforma um resultado em array no seu primeiro elemento (`null` se vazio). Não tem efeito em resultados que não são array (ex.: o número de linhas afetadas de uma query `modifying`). |
756
-
757
- Assim como os métodos base, dinâmicos e query, `query()` aceita `db` em `options` para participar de um bloco `transaction()`.
758
-
759
- ---
760
-
761
- ## Transações
762
-
763
- Todos os métodos (base, dinâmicos e de query) aceitam `options.db` para participar de uma transação compartilhada:
764
-
765
- ```typescript
766
- await userRepository.transaction(async tx => {
767
- const usuario = await userRepository.save(
768
- { name: "Maria", email: "maria@email.com" },
769
- { db: tx },
770
- );
771
-
772
- await userLogsRepository.save(
773
- { action: "Usuário criado", data: { userId: usuario.id } },
774
- { db: tx },
775
- );
776
- });
777
- ```
778
-
779
- Repositories diferentes podem compartilhar a mesma transação, desde que seus adapters apontem para a mesma conexão do ORM por trás deles.
780
-
781
- `transaction()` aceita um `VSRepoTransactionOptions` opcional como segundo argumento:
782
-
783
- ```typescript
784
- import { TransactionIsolationLevel } from "vsrepo";
785
-
786
- await userRepository.transaction(
787
- async tx => {
788
- await userRepository.save({ name: "Maria", email: "maria@email.com" }, { db: tx });
789
- },
790
- { isolationLevel: TransactionIsolationLevel.SERIALIZABLE, timeoutMs: 5000 },
791
- );
792
- ```
793
-
794
- | Option | Type | Descrição |
795
- | ---------------- | --------------------------- | ---------------------------------------------------------------------------------- |
796
- | `isolationLevel` | `TransactionIsolationLevel` | Nível de isolamento usado na transação. O padrão é o default do ORM por trás dela. |
797
- | `timeoutMs` | `number` | Tempo máximo (em ms) que a transação pode rodar antes de ser abortada. |
798
-
799
- `TransactionIsolationLevel` espelha os níveis de isolamento SQL padrão: `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, `SERIALIZABLE`. O suporte a um determinado nível depende do adapter/ORM e do banco de dados por trás dele.
800
-
801
- ---
802
-
803
- ## Tipos utilitários
804
-
805
- Além dos tipos que descrevem o formato da entidade já vistos acima (`VSRepoSelect`, `VSRepoRelations`, `VSRepoWhere`), o VSRepository exporta um conjunto de tipos utilitários. Eles aparecem ao longo de várias seções anteriores, mas aqui está uma referência consolidada. Todos fazem parte da API pública e podem ser importados diretamente:
806
-
807
- ```typescript
808
- import type {
809
- MethodOptions,
810
- RestrictMethodOptions,
811
- InferMethodReturn,
812
- InferMethodType,
813
- Pagination,
814
- Ordering,
815
- OrderByField,
816
- SortDirection,
817
- SeeMode,
818
- DeepPartial,
819
- CountResult,
820
- QueryMethodArg,
821
- QueryArgs,
822
- KeysOfType,
823
- NumericKeys,
824
- NumericLike,
825
- DecimalLike,
826
- Primitive,
827
- VSRepoWhere,
828
- VSRepoOrmTypes,
829
- VSRepoTransactionOptions,
830
- TransactionIsolationLevel,
831
- } from "vsrepo";
832
- ```
833
-
834
- | Tipo | Descrição | Usado por |
835
- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
836
- | `MethodOptions<T, K>` | Options aceitas como último argumento por todos os métodos dinâmicos e pela maioria dos métodos base: `select`, `relations`, `see`, `db`. | [Métodos base](#métodos-base), [Métodos Dinâmicos](#métodos-dinâmicos). |
837
- | `RestrictMethodOptions<T, K>` | `MethodOptions<T, K>` restrito, expondo só `see`/`db` — usado pelos métodos que não retornam/moldam uma `Entity` (`total`, `has`, `sum`, `average`, `min`, `max`, `removeList`, `softRemoveList`, `restoreList`). | [Métodos base](#métodos-base), [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
838
- | `InferMethodReturn<T, Options>` | Tipagem de retorno restrita (opt-in): estreita `T` (`Entity`, `Entity \| null` ou `Entity[]`) para os campos e relações realmente pedidos via `select`/`relations`. `select` vence `relations`. | [Tipagem de retorno restrita com `InferMethodReturn`](#tipagem-de-retorno-restrita-com-infermethodreturn). |
839
- | `InferMethodType<Args, Return, OrmTypes?>` | Declara um método dinâmico cujo retorno é inferido a cada chamada a partir do `select`/`relations` passados como `options`. `OrmTypes` é opcional e tipa a option `db`. | [Tipagem de retorno restrita com `InferMethodType`](#tipagem-de-retorno-restrita-com-infermethodtype). |
840
- | `Pagination` | `{ limit?, offset? }` aceito por `getAll` e pelos métodos dinâmicos com `Paginated`. | [Métodos base](#métodos-base), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
841
- | `Ordering<T>` / `OrderByField<T>` / `SortDirection` | Formato de ordenação aceito por `getAll`, `defaultOrdering`, `injectOrdering` e pelos métodos dinâmicos com `Ordered`. Pode ser um único objeto ou um array encadeado. | [Options do construtor](#options-do-construtor), [Options do decorador](#options-do-decorador), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
842
- | `SeeMode` | `"active" \| "removed" \| "all"` — controla a visibilidade de registros com soft-delete. | [Soft-delete](#soft-delete). |
843
- | `DeepPartial<T>` | Torna todas as propriedades de `T` opcionais recursivamente, incluindo objetos aninhados e elementos de array. | `save`, `saveList`, `patch`, `merge`, e todo os métodos dinâmicos de escrita. |
844
- | `CountResult` | `{ count: number }` — o formato retornado por operações em lote. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
845
- | `QueryMethodArg<T>` | `{ args?: T, db? }` — parâmetros posicionais do SQL (a sintaxe dos placeholders depende do banco/driver usado pelo seu adapter: `$1`, `$2`, ... para PostgreSQL, `?` para MySQL) e cliente de transação para o `@QueryMethod`. | [Query methods (SQL raw)](#query-methods-sql-raw). |
846
- | `QueryArgs<T, O>` | Tipa a lista de parâmetros via spread de um `@QueryMethod` declarado com `{ spreadArgs: true }`: os valores de `T`, em ordem, seguidos de um `DbArg<O>` opcional construído via `withDb()`. | [Argumentos via spread com `spreadArgs`](#argumentos-via-spread-com-spreadargs). |
847
- | `KeysOfType<T, K>` | Extrai as chaves de `T` cujo tipo de valor é atribuível a `K`. | Restringe `pkName`, em [Options do construtor](#options-do-construtor), aos campos da entidade compatíveis com o tipo de chave primária configurado. |
848
- | `NumericKeys<T>` | Extrai as chaves de `T` cujo tipo de valor (ignorando `null`/`undefined`) é atribuível a `NumericLike`. Campos numéricos nullable (`number \| null`) são incluídos. | Restringe `field` em [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação) (`increment`, `sum`, etc). |
849
- | `NumericLike` | `number \| bigint \| DecimalLike`. | [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
850
- | `DecimalLike` | Formato estrutural de um valor decimal de precisão arbitrária (`{ toNumber(): number; decimalPlaces(): number }`), compatível com o `Prisma.Decimal` do Prisma sem precisar importá-lo diretamente. | [Quais campos são elegíveis](#quais-campos-são-elegíveis). |
851
- | `Primitive` | União de tipos escalares (`string \| number \| boolean \| bigint \| symbol \| undefined \| null \| Date \| DecimalLike`) tratados como valores-folha — e não relações — ao percorrer o formato de uma entidade. | Usado por `Ordering<T>` para distinguir campos escalares de campos de relação. |
852
- | `VSRepoWhere<T>` | Tipo de filtro agnóstico de ORM aceito pelos métodos dinâmicos `*Where` (ex.: `findWhere`, `findOneWhere`, `updateWhere`). Suporta filtros de campo, operadores lógicos (`AND`/`OR`/`NOT`) e filtros de relação. | [Prefixos `findWhere`, `findOneWhere` e demais `*Where`](#prefixos-disponíveis). |
853
- | `VSRepoOrmTypes` | `{ dbClient; dbTransaction }` — descreve os tipos de client/transaction do seu ORM. Passado como terceiro generic de `VSRepository<Entity, PKType, OrmTypes>` para tipar `getDbClient()`, `transaction()` e a option `db` em vez de `any`. | [Criando um repository](#criando-um-repository). |
854
- | `VSRepoTransactionOptions` | `{ isolationLevel?, timeoutMs? }` — options aceitas como segundo argumento de `transaction()`. | [Transações](#transações). |
855
- | `TransactionIsolationLevel` | Enum dos níveis de isolamento SQL padrão (`READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, `SERIALIZABLE`) aceitos por `VSRepoTransactionOptions.isolationLevel`. | [Transações](#transações). |
856
-
857
- ### `DeepPartial<T>`
858
-
859
- Torna todas as propriedades opcionais recursivamente, percorrendo objetos aninhados e elementos de array — diferente do `Partial<T>` nativo do TypeScript, que só torna o nível superior opcional:
860
-
861
- ```typescript
862
- type User = { id: string; name: string; address: { city: string; zip: string } };
863
-
864
- const patch: DeepPartial<User> = {
865
- address: { city: "São Paulo" }, // zip pode ser omitido; city mantém seu tipo
866
- };
867
-
868
- await userRepository.patch(id, patch);
869
- ```
870
-
871
- ### `KeysOfType<T, K>`
872
-
873
- Filtra um tipo de objeto para as chaves cujo valor corresponde a um tipo dado — é isso que permite que `pkName` aceite apenas campos da entidade que sejam de fato atribuíveis ao tipo de chave primária do repository:
874
-
875
- ```typescript
876
- type User = { id: string; age: number; name: string };
877
- type StringKeys = KeysOfType<User, string>; // "id" | "name"
878
- ```
879
-
880
- ### `Ordering<T>`
881
-
882
- Aceita um único objeto de ordenação ou um array deles, aplicados na ordem declarada:
883
-
884
- ```typescript
885
- const order: Ordering<User> = { createdAt: "desc" };
886
- const chained: Ordering<User> = [{ name: "asc" }, { createdAt: "desc" }];
887
-
888
- await userRepository.getAll({ order: chained });
889
- ```
890
-
891
- ---
892
-
893
- ## Escrevendo seu próprio adapter
894
-
895
- Como o núcleo é agnóstico de ORM e é distribuído sem um adapter embutido, adicionar suporte a um ORM/banco — seja como solução provisória para o seu próprio projeto, seja como candidato a um futuro pacote `@vsrepo/*-adapter` — significa implementar a classe abstrata `VSRepoAdapter<T>`:
896
-
897
- ```typescript
898
- export abstract class VSRepoAdapter<T> {
899
- abstract runInTransaction<R>(
900
- fn: (tx: any) => Promise<R>,
901
- options?: VSRepoTransactionOptions,
902
- ): Promise<R>;
903
- abstract getDbClient(): any;
904
- abstract query<T = any>(query: string, options?: AdapterQueryOptions): Promise<T>;
905
- abstract findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T | null>;
906
- abstract findOneOrThrow(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
907
- abstract findMany(
908
- where: VSRepoWhere<T>,
909
- options?: AdapterMethodOptions<T> & { distinct?: (keyof T)[] },
910
- ): Promise<T[]>;
911
- abstract save(obj: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
912
- abstract saveMany(objs: DeepPartial<T>[], options?: AdapterMethodOptions<T>): Promise<T[]>;
913
- abstract create(objs: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
914
- abstract createMany(
915
- objs: DeepPartial<T>[],
916
- options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
917
- ): Promise<CountResult>;
918
- abstract createManyReturning(
919
- objs: DeepPartial<T>[],
920
- options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
921
- ): Promise<T[]>;
922
- abstract delete(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
923
- abstract deleteMany(
924
- where: VSRepoWhere<T>,
925
- options?: AdapterMethodOptions<T>,
926
- ): Promise<CountResult>;
927
- abstract deleteManyReturning(
928
- where: VSRepoWhere<T>,
929
- options?: AdapterMethodOptions<T>,
930
- ): Promise<T[]>;
931
- abstract update(
932
- where: VSRepoWhere<T>,
933
- obj: DeepPartial<T>,
934
- options?: AdapterMethodOptions<T>,
935
- ): Promise<T>;
936
- abstract updateMany(
937
- where: VSRepoWhere<T>,
938
- obj: DeepPartial<T>,
939
- options?: AdapterMethodOptions<T>,
940
- ): Promise<CountResult>;
941
- abstract updateManyReturning(
942
- where: VSRepoWhere<T>,
943
- obj: DeepPartial<T>,
944
- options?: AdapterMethodOptions<T>,
945
- ): Promise<T[]>;
946
- abstract count(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number>;
947
- abstract exists(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<boolean>;
948
- abstract merge<K>(
949
- where: VSRepoWhere<T>,
950
- obj: DeepPartial<T>,
951
- options?: AdapterMethodOptions<T>,
952
- ): Promise<K & T>;
953
- abstract upsert(
954
- where: VSRepoWhere<T>,
955
- create: DeepPartial<T>,
956
- update: DeepPartial<T>,
957
- options?: AdapterMethodOptions<T>,
958
- ): Promise<T>;
959
- abstract incrementOne<K extends NumericKeys<T>>(
960
- field: K,
961
- value: NonNullable<T[K]>,
962
- where: VSRepoWhere<T>,
963
- options?: AdapterMethodOptions<T>,
964
- ): Promise<T>;
965
- abstract decrementOne<K extends NumericKeys<T>>(
966
- field: K,
967
- value: NonNullable<T[K]>,
968
- where: VSRepoWhere<T>,
969
- options?: AdapterMethodOptions<T>,
970
- ): Promise<T>;
971
- abstract multiplyOne<K extends NumericKeys<T>>(
972
- field: K,
973
- value: NonNullable<T[K]>,
974
- where: VSRepoWhere<T>,
975
- options?: AdapterMethodOptions<T>,
976
- ): Promise<T>;
977
- abstract divideOne<K extends NumericKeys<T>>(
978
- field: K,
979
- value: NonNullable<T[K]>,
980
- where: VSRepoWhere<T>,
981
- options?: AdapterMethodOptions<T>,
982
- ): Promise<T>;
983
- abstract sum(
984
- field: NumericKeys<T>,
985
- where?: VSRepoWhere<T>,
986
- options?: AdapterMethodOptions<T>,
987
- ): Promise<number | null>;
988
- abstract average(
989
- field: NumericKeys<T>,
990
- where?: VSRepoWhere<T>,
991
- options?: AdapterMethodOptions<T>,
992
- ): Promise<number | null>;
993
- abstract min(
994
- field: NumericKeys<T>,
995
- where?: VSRepoWhere<T>,
996
- options?: AdapterMethodOptions<T>,
997
- ): Promise<number | null>;
998
- abstract max(
999
- field: NumericKeys<T>,
1000
- where?: VSRepoWhere<T>,
1001
- options?: AdapterMethodOptions<T>,
1002
- ): Promise<number | null>;
1003
- getPkName?(): string;
1004
- }
1005
- ```
1006
-
1007
- O `getPkName()` opcional permite que o adapter declare ao repository qual campo é a primary key da entidade. Ao instanciar um `VSRepository`, você pode omitir o `pkName` das options do construtor e ele será lido do `adapter.getPkName()`. Se você omitir e o adapter não implementar o `getPkName()`, o construtor lança um `VSRepoError`.
1008
-
1009
- O `VSRepository` nunca fala diretamente com o ORM — ele só chama esses métodos com um `VSRepoWhere<T>` e um `AdapterMethodOptions<T>` já resolvidos. Uma vez que um adapter implemente esse contrato, todo método base, método dinâmico e query method passa a funcionar com ele automaticamente. Pra uma implementação completa e funcional, veja o repositório externo [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter).
1010
-
1011
- ### Logging a partir do seu adapter
1012
-
1013
- O `vsrepo` exporta a mesma classe `VSLogger` usada internamente pelo core, então seu adapter pode logar no mesmo formato/estilo (timestamps, labels de nível coloridos, avisos de operação lenta) em vez de implementar o seu próprio:
1014
-
1015
- ```typescript
1016
- import { VSLogger, VSLogLevel } from "vsrepo";
1017
-
1018
- export class MyOrmAdapter<T> extends VSRepoAdapter<T> {
1019
- private readonly logger = new VSLogger(VSLogLevel.WARN, "MyOrmAdapterLogger");
1020
-
1021
- async findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>) {
1022
- const start = this.logger.startPerformLog("adapter findOne");
1023
- try {
1024
- // ... fala com o ORM ...
1025
- this.logger.endPerformLog(start);
1026
- return result;
1027
- } catch (err) {
1028
- this.logger.endPerformLog(start);
1029
- this.logger.logError("adapter findOne falhou", err);
1030
- throw err;
1031
- }
1032
- }
1033
- }
1034
- ```
1035
-
1036
- | Método | Descrição |
1037
- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1038
- | `new VSLogger(logLevel, name, slowThresholdMs?)` | Cria um logger; `name` prefixa cada linha. `slowThresholdMs` controla o threshold de operação lenta: um `number` define o valor em ms (padrão 300), `false` desabilita os avisos de operação lenta completamente, `true` ou omitido usa o padrão de 300ms. |
1039
- | `logDebug/logInfo/logWarn(text, obj?)` | Loga no nível dado se `logLevel` permitir; `obj` é anexado como JSON formatado. |
1040
- | `logError(text, err?)` | Loga em `ERROR`; se `err` for uma `Error`, só `name`/`message`/`stack`/`cause` são logados. |
1041
- | `startPerformLog(operation)` / `endPerformLog(data)` | Envolve um trecho de código para logar sua duração, escalando pra `WARN` se ultrapassar `slowThresholdMs`. |
1042
- | `getLogLevel()` | Retorna o `VSLogLevel` configurado do logger. |
1043
-
1044
- Isso é puramente uma conveniência para autores de adapters — nada no core exige que seu adapter o utilize.
1045
-
1046
- ---
1047
-
1048
- ## Tratamento de erros
1049
-
1050
- A v2 simplifica a hierarquia de erros da v1: em vez de várias subclasses, existe uma classe base `VSRepoError` carregando um campo `type: VSRepoErrorType`, além de uma subclasse dedicada `VSRepoAdapterError` (veja abaixo) para falhas vindas do ORM/banco subjacente.
1051
-
1052
- ```typescript
1053
- import { VSRepoError } from "vsrepo";
1054
-
1055
- try {
1056
- await userRepository.get(id);
1057
- } catch (error) {
1058
- if (error instanceof VSRepoError) {
1059
- console.error(`[${error.type}] ${error.message}`);
1060
- }
1061
- }
1062
- ```
1063
-
1064
- | `VSRepoErrorType` | Quando é lançado |
1065
- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
1066
- | `DECORATOR` | Argumentos inválidos foram passados para `@DynamicMethod` ou `@QueryMethod`. |
1067
- | `RESOLVER` | A biblioteca falhou ao resolver a configuração de um método dinâmico/de query em um método chamável (ex.: um nome de método desconhecido). |
1068
- | `DYNAMIC` | Um dynamic/query method já resolvido falhou em tempo de execução (ex.: argumentos faltando). |
1069
- | `VALIDATOR` | Options ou argumentos de método inválidos foram detectados durante a validação (ex.: `pkName` ausente quando o adapter não tem `getPkName()`). |
1070
- | `BASE` | Uso inválido de um método base (`get`, `save`, `remove`, etc). |
1071
- | `ADAPTER` | Um `VSRepoAdapter` falhou ao falar com o ORM/banco subjacente — sempre é lançado como `VSRepoAdapterError`. |
1072
-
1073
- ### `VSRepoAdapterError` e `AdapterErrorCode`
1074
-
1075
- Quando um adapter fala com o ORM/banco subjacente e essa operação falha, o adapter encapsula a falha em um `VSRepoAdapterError` — uma subclasse de `VSRepoError` com `type: VSRepoErrorType.ADAPTER`. Ele carrega um `code: AdapterErrorCode` **estável e agnóstico de adapter** além do erro bruto lançado pelo ORM/driver, para que quem chama possa reagir às falhas sem depender do formato de erro de nenhum ORM específico:
1076
-
1077
- ```typescript
1078
- import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
1079
-
1080
- try {
1081
- await userRepository.save({ name: "Maria" });
1082
- } catch (error) {
1083
- if (error instanceof VSRepoAdapterError) {
1084
- console.error(`[${error.code}] ${error.message}`, error.originalError);
1085
-
1086
- if (error.code === AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION) {
1087
- // tratar chave duplicada, ex.: retornar uma mensagem amigável
1088
- }
1089
- }
1090
- }
1091
- ```
1092
-
1093
- | Propriedade | Tipo | Descrição |
1094
- | --------------- | ------------------ | --------------------------------------------------------------------------------- |
1095
- | `code` | `AdapterErrorCode` | Código estável e agnóstico que classifica a falha. |
1096
- | `originalError` | `unknown` | O erro bruto (ou `null`/`undefined`) lançado pelo driver do ORM/banco subjacente. |
1097
- | `message` | `string` | Descrição legível da falha do adapter. |
1098
- | `type` | `VSRepoErrorType` | Sempre `VSRepoErrorType.ADAPTER`. |
1099
- | `cause` | `unknown` | Causa raiz opcional da qual o erro foi encadeado. |
1100
-
1101
- As implementações de adapter o constroem diretamente ao mapear uma falha do ORM:
1102
-
1103
- ```typescript
1104
- import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
1105
-
1106
- throw new VSRepoAdapterError(
1107
- "falha ao criar o usuário",
1108
- AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION,
1109
- originalError, // erro bruto do banco/driver
1110
- );
1111
- ```
1112
-
1113
- #### `AdapterErrorCode`
1114
-
1115
- `AdapterErrorCode` é um enum de códigos granulares e agnósticos que um adapter pode lançar através de `VSRepoAdapterError`. Eles espelham as falhas mais comuns lançadas por ORMs e drivers de banco, para que erros de qualquer ORM possam ser mapeados para o mesmo código estável:
1116
-
1117
- ```typescript
1118
- import { AdapterErrorCode } from "vsrepo";
1119
-
1120
- console.log(AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION); // "UNIQUE_CONSTRAINT_VIOLATION"
1121
- ```
1122
-
1123
- | Código | Significado |
1124
- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
1125
- | `UNKNOWN` | Erro não classificado/desconhecido; o fallback quando nenhum código mais específico corresponde. |
1126
- | `TRANSACTION_ROLLED_BACK` | Alguns adapters podem usar esse código para rollbacks forçados de transações (como o `tx.rollback()` do Drizzle) |
1127
- | `MISSING_DB_CLIENT` | Cliente de banco (ou pool de conexões) não fornecido ou que não pôde ser resolvido. |
1128
- | `CONNECTION_FAILED` | Não foi possível alcançar/conectar ao banco, ou uma conexão estabelecida foi perdida/terminada. |
1129
- | `CONNECTION_POOL_EXHAUSTED` | Pool de conexões esgotado/depletado — nenhuma conexão disponível, todas ocupadas ou o limite foi atingido. |
1130
- | `TIMEOUT` | O banco não respondeu a tempo; uma query excedeu o timeout permitido. |
1131
- | `UNIQUE_CONSTRAINT_VIOLATION` | Violação de constraint unique (chave duplicada). Ex.: Postgres/SQLite `23505`, MySQL `1062`. |
1132
- | `FOREIGN_KEY_VIOLATION` | Violação de constraint de foreign key (linha referenciada não existe). |
1133
- | `NOT_NULL_VIOLATION` | Violação de constraint NOT NULL. |
1134
- | `CHECK_VIOLATION` | Violação de constraint CHECK. |
1135
- | `CONSTRAINT_VIOLATION` | Violação geral de integridade/constraint não coberta por um código mais específico. |
1136
- | `NOT_FOUND` | Registro solicitado não encontrado (ex.: uma operação tipo `findOneOrThrow`). |
1137
- | `INVALID_DATA` | Valor de campo inválido para o tipo/tamanho, ou um valor obrigatório ausente. |
1138
- | `VALUE_TOO_LONG` | Valor fornecido excede o limite de tamanho da coluna/campo. |
1139
- | `CONVERSION_ERROR` | Um valor não pôde ser convertido/convertido para o tipo alvo. Ex.: Postgres `22P02`, MySQL `1366`. |
1140
- | `INVALID_QUERY` | A query/stored procedure SQL está malformada ou é inválida. |
1141
- | `TABLE_OR_COLUMN_NOT_FOUND` | A tabela/coluna/relação referenciada não existe. |
1142
- | `DEADLOCK` | Operação abortada por timeout de lock ou deadlock entre transações concorrentes. |
1143
- | `LOCK_TIMEOUT` | Não foi possível adquirir um lock de banco obrigatório a tempo. |
1144
- | `LOCKED` | O registro está travado e não pode ser modificado. |
1145
- | `ACCESS_DENIED` | O usuário/role atual não tem permissão para a operação. |
1146
- | `INVALID_CREDENTIALS` | Credenciais de conexão inválidas (host/usuário/senha). |
1147
- | `ROW_NOT_ALLOWED` | O usuário autenticado não é dono do registro / a segurança em nível de linha rejeitou. |
1148
- | `MODEL_NOT_FOUND` | Entidade/modelo ou tabela não definida/mapeada no ORM, ou o adapter não tem os metadados do modelo. |
1149
- | `FIELD_NOT_FOUND` | Nome de campo/coluna nos dados ou no `where` não existe na entidade/modelo. |
1150
- | `TRANSACTION_CLOSED` | Transação usada depois de commit/rollback. |
1151
- | `TRANSACTION_ALREADY_STARTED` | Uma transação aninhada não pôde ser aberta (ex.: chamadas `transaction()` aninhadas). |
1152
- | `TRANSACTION_CONFLICT` | Uma transação falhou ao commitar e foi desfeita. |
1153
- | `TRANSACTION_NOT_STARTED` | Nenhuma transação ativa quando uma era obrigatória. |
1154
- | `CONNECTION_CLOSED` | Conexão fechada/terminada enquanto uma transação ou query estava em andamento. |
1155
- | `INVALID_PARTIAL` | `merge`/`upsert`/`update` recebeu um objeto parcial inválido ou faltando chaves obrigatórias. |
1156
- | `NOT_SUPPORTED` | Feature/operação não suportada solicitada ao adapter (ex.: `query()` bruto não suportado). |
1157
- | `INVALID_ADAPTER_CONFIG` | Configuração do adapter inválida ou incompleta (options obrigatórias ausentes, ou com tipo/valor inválido). |
1158
- | `INTERNAL` | Bug interno do adapter ou estado irrecuperável; deve raramente ser usado — prefira um código mais específico. |
1159
-
1160
- #### `VSRepoError` vs. erros brutos do ORM
1161
-
1162
- Erros de uso/configuração fora do adapter lançam o `VSRepoError` base. Falhas lançadas _pelo ORM subjacente_ enquanto um método do adapter roda são **encapsuladas** em `VSRepoAdapterError` (classificadas por um `AdapterErrorCode`, com o erro original preservado em `originalError`) em vez de se propagarem cruas — é isso que torna quem chama independente do formato de erro de qualquer ORM específico.
1163
-
1164
- ---
1165
-
1166
- ## Logging
1167
-
1168
- Todo repository tem um logger interno, configurado via `logLevel` e `logSlowThresholdMs` nas options do construtor:
1169
-
1170
- ```typescript
1171
- import { VSLogLevel } from "vsrepo";
1172
-
1173
- super({
1174
- pkName: "id",
1175
- adapter,
1176
- logLevel: VSLogLevel.DEBUG,
1177
- logSlowThresholdMs: 200, // avisa se qualquer operação levar mais de 200ms
1178
- // logSlowThresholdMs: false, // desabilita os avisos de operação lenta completamente
1179
- });
1180
- ```
1181
-
1182
- | Nível | Significado |
1183
- | --------------- | ------------------------------------------------------------------------------------------------------- |
1184
- | `DEBUG` | Detalhes internos verbosos, incluindo toda query resolvida — muito útil para debugar métodos dinâmicos. |
1185
- | `INFO` | Eventos de alto nível do ciclo de vida, como a inicialização do repository. |
1186
- | `WARN` (padrão) | Problemas recuperáveis e operações lentas (veja `logSlowThresholdMs`, padrão de 300ms). |
1187
- | `ERROR` | Falhas lançadas durante a execução de uma operação. |
1188
-
1189
- ---
1190
-
1191
- ## Desenvolvimento
1192
-
1193
- O core da v2 é compilado e empacotado a partir desta branch como um pacote npm padrão:
1194
-
1195
- ```bash
1196
- # 1. Instalar as dependências
1197
- pnpm install
1198
-
1199
- # 2. Compilar os fontes TypeScript em dist/ (remove um dist/ anterior primeiro)
1200
- pnpm build
1201
-
1202
- # 3. (Opcional) Inspecionar o que seria publicado sem gerar um tarball
1203
- npm pack --dry-run
1204
-
1205
- # 4. Gerar o tarball instalável (roda `prepack` -> `pnpm build` automaticamente)
1206
- npm pack
1207
-
1208
- # 5. Consumir localmente em outro projeto
1209
- npm install ../caminho/vsrepo-*.tgz
1210
- ```
1211
-
1212
- Observações:
1213
-
1214
- - `pnpm build` executa `tsc -p tsconfig.build.json`, que gera o JS compilado e as declarações de tipo em `dist/` com `rootDir: src`.
1215
- - O pacote publicado contém **apenas** a pasta `dist/` além dos READMEs e da `LICENSE` (veja `files` no `package.json`). Os adapters viverão em seus próprios pacotes `@vsrepo/*-adapter`.
1216
-
1217
- ---
1218
-
1219
- ## Requisitos
1220
-
1221
- - Node.js 18+
1222
- - TypeScript, com **decorators legacy/experimentais** habilitados (necessário para `@DynamicMethod`/`@QueryMethod`):
1223
-
1224
- ```json
1225
- {
1226
- "compilerOptions": {
1227
- "experimentalDecorators": true
1228
- }
1229
- }
1230
- ```
1231
-
1232
- - `reflect-metadata` (já incluso como dependência, importado internamente — você não precisa importá-lo você mesmo)
1233
- - Pelo menos um `VSRepoAdapter` funcional para o seu banco — no Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já publicado (veja [Status dos adapters](#status-dos-adapters)); adapters oficiais para outros ORMs estão planejados, mas ainda não publicados, então por enquanto isso significa escrever o seu próprio (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) — e, se publicá-lo, contribuir de volta com o projeto é bem-vindo
1234
-
1235
- ---
1236
-
1237
- ## Contribuindo
1238
-
1239
- Contribuições são bem-vindas, especialmente para melhorar o adapter do Prisma e finalizar o do Drizzle! (**[Repositório do GitHub](https://github.com/jaobrabo123/VSRepository)**):
1240
-
1241
- 1. Faça um **Fork** do projeto.
1242
- 2. Crie uma branch para sua alteração: `git checkout -b minha-alteracao`.
1243
- 3. Faça o push da sua branch: `git push origin minha-alteracao`.
1244
- 4. Abra um **Pull Request**.
1245
-
1246
- Para reportar problemas ou sugerir funcionalidades, abra uma **Issue**.
1
+ <div align="center">
2
+ <img src="https://res.cloudinary.com/ddbfifdxd/image/upload/w_200,q_auto,f_auto/v1786386427/VS_logo_TextoAbaixo_yev4tq.png" alt="VSRepository Logo" width="200"/>
3
+
4
+ <p style="margin-top: 12px;">
5
+ <img src="https://img.shields.io/npm/v/vsrepo?style=flat-square" alt="npm version"/>
6
+ <img src="https://img.shields.io/npm/l/vsrepo?style=flat-square" alt="npm license"/>
7
+ <img src="https://img.shields.io/npm/dt/vsrepo?style=flat-square" alt="npm downloads"/>
8
+ <img src="https://img.shields.io/badge/inspired%20by-JpaRepository-E73121?style=flat-square" alt="inspired by JpaRepository"/>
9
+ </p>
10
+ </div>
11
+
12
+ # VSRepository
13
+
14
+ 🇧🇷 Você está lendo a versão em português. [🇺🇸 Read in English](./README.md)
15
+
16
+ Biblioteca de repository pattern **agnóstica de ORM**, com suporte completo a **TypeScript** e **type inference** automático. O núcleo delega toda operação a um **adapter** plugável, permitindo que a mesma API de repository funcione com Prisma, Drizzle ou qualquer outro ORM/banco que implemente o contrato de adapter. Vindo da [v1](https://github.com/jaobrabo123/VSRepository/tree/v1)? Veja [Migrando da v1](./docs/migrating-from-v1.pt-BR.md).
17
+
18
+ O VSRepository permite criar repositories fortemente tipados com:
19
+
20
+ - **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `merge`, `getAll`, `total`, `has`
21
+ - **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
22
+ - **Métodos dinâmicos** inferidos a partir do nome de um campo `declare` via o decorador `@DynamicMethod`: `findOneByEmail`, `findByStatusPaginated`, `updateById`
23
+ - **Métodos de query SQL raw** através do decorador `@QueryMethod` (ignorando totalmente o engine de parsing por nome), fragmentos parametrizados `VSSql` para chamadas pontuais de `query()`, e placeholders agnósticos `?1`, `?2` com `vsPlaceholders`
24
+ - **`select`/`relations`** ad-hoc em cada chamada — sem mais projeções nomeadas pré-declaradas
25
+ - **Type safety** em 100% das operações
26
+ - **Transações** nativas do ORM, compartilhadas entre repositories
27
+ - Um **núcleo agnóstico de ORM** — a mesma classe de repository funciona com qualquer implementação de `VSRepoAdapter`
28
+
29
+ ---
30
+
31
+ ## Documentação
32
+
33
+ As seções abaixo (status dos adapters, instalação, uso básico) são o essencial para começar. Tudo sobre uma funcionalidade específica — com mais detalhe e mais exemplos — vive em um guia próprio dentro de [`docs/`](./docs/README.pt-BR.md), cada um disponível em português e em [English](./docs/README.md):
34
+
35
+ | Guia | Cobre |
36
+ | --- | --- |
37
+ | [Métodos base, configuração & soft-delete](./docs/base-methods.pt-BR.md) | Options do construtor, os 12 métodos CRUD automáticos, soft-delete nativo, e os 8 métodos atômicos/de agregação (`increment`, `sum`, ...). |
38
+ | [`select` e `relations`](./docs/select-and-relations.pt-BR.md) | Seleção de campos e carregamento de relações ad-hoc em qualquer chamada, e o `InferMethodReturn` para estreitar o tipo de retorno de acordo. |
39
+ | [Métodos dinâmicos](./docs/dynamic-methods.pt-BR.md) | Métodos no estilo `findByEmail`, resolvidos a partir de um nome de método `declare`d: prefixos, filtros de campo, operadores lógicos, filtros de relação, ordenação/paginação/distinct. |
40
+ | [Query methods (SQL raw)](./docs/query-methods.pt-BR.md) | SQL raw via `@QueryMethod`, fragmentos parametrizados `VSSql` e os placeholders agnósticos `?1`/`?2` (`vsPlaceholders`). |
41
+ | [Query builder](./docs/query-builder.pt-BR.md) | A API fluente `createQueryBuilder()` para queries montadas em tempo de execução, incluindo paginação, visibilidade de soft-delete e transações. |
42
+ | [Raw query builder](./docs/raw-query-builder.pt-BR.md) | A API fluente `createRawQueryBuilder()` para queries `SELECT` escritas à mão, específicas demais para o query builder — joins, subqueries, CTEs (`with`/`withRecursive`). |
43
+ | [Transações](./docs/transactions.pt-BR.md) | Rodando vários repositories na mesma transação nativa do ORM. |
44
+ | [Tipos utilitários](./docs/utility-types.pt-BR.md) | Os tipos utilitários exportados (`InferMethodType`, `InferMethodReturn`, `KeysOfType`, ...) e onde cada um é usado. |
45
+ | [Escrevendo seu próprio adapter](./docs/writing-an-adapter.pt-BR.md) | Como implementar o `VSRepoAdapter` para um novo ORM ou banco, método a método. |
46
+ | [Tratamento de erros](./docs/error-handling.pt-BR.md) | `VSRepoError`, `VSRepoErrorType`, e `VSRepoAdapterError`/`AdapterErrorCode`. |
47
+ | [Logging](./docs/logging.pt-BR.md) | `logLevel`, `logSlowThresholdMs`, e o formato de log usado pelo repository e pelo query builder. |
48
+ | [Migrando da v1](./docs/migrating-from-v1.pt-BR.md) | Tudo o que mudou entre a v1 e a v2 — API, config, sufixos renomeados, funcionalidades removidas — em uma única referência para migrar repositories existentes. |
49
+
50
+ ---
51
+
52
+ ## Status dos adapters
53
+
54
+ O VSRepository é **agnóstico de ORM por design**. O pacote core (`vsrepo`) traz apenas a classe de repository, os decoradores, o engine de parsing de nomes, o tratamento de erros e o logging — ele **não** inclui um adapter de produção. O suporte de fato a cada ORM/banco deve viver em **pacotes separados, versionados de forma independente**, um por ORM (e, quando fizer sentido, um por versão principal do ORM), por exemplo:
55
+
56
+ - `@vsrepo/prisma7-adapter`
57
+ - `@vsrepo/prisma8-adapter`
58
+ - `@vsrepo/typeorm-adapter`
59
+ - `@vsrepo/drizzle-adapter`
60
+
61
+ O adapter do Prisma 7 já foi publicado no npm como `@vsrepo/prisma7-adapter`. O adapter do Drizzle está disponível em versão **alpha** — instale com `npm i @vsrepo/drizzle-adapter@alpha`. Os adapters para outros ORMs estão **planejados**, mas ainda não foram publicados. Até que exista um pacote `@vsrepo/*-adapter` oficial para o seu ORM, você pode escrever o seu próprio para o seu projeto e, se quiser, publicá-lo e abrir um PR para ajudar a fazer o ecossistema crescer — contribuições nesse sentido são muito bem-vindas.
62
+
63
+ | Adapter | Status |
64
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa o contrato de `VSRepoAdapter` (CRUD, relations, transactions, `merge`, logging, etc.) com testes; veja o [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) para o código-fonte e docs. |
66
+ | Drizzle (`@vsrepo/drizzle-adapter`) | 🔵 **Alpha** — uma versão inicial já está disponível no npm; instale com `npm i @vsrepo/drizzle-adapter@alpha`. A API ainda pode mudar antes do release estável. Veja o repositório do [`DrizzleAdapter`](https://github.com/jaobrabo123/VSRepoDrizzleAdapter) para o estado atual e limitações conhecidas, e sinta-se à vontade para contribuir. |
67
+ | Outros ORMs (Prisma 8, TypeORM, etc.) | 🟡 **Planejados, ainda não publicados.** Nenhum pacote oficial existe ainda — por enquanto, escreva o seu próprio adapter (veja [Escrevendo seu próprio adapter](./docs/writing-an-adapter.pt-BR.md#escrevendo-seu-próprio-adapter)) e considere publicá-lo/contribuir de volta com o projeto. |
68
+ | Adapters customizados | 🟢 Totalmente suportados hoje — implemente você mesmo a classe abstrata [`VSRepoAdapter`](./docs/writing-an-adapter.pt-BR.md#escrevendo-seu-próprio-adapter) para qualquer ORM/banco que precisar, no seu próprio projeto ou pacote. |
69
+
70
+ Resumindo: a classe de repository, os decoradores `@DynamicMethod`/`@QueryMethod`, o engine de parsing de nomes, o tratamento de erros e o logging já funcionam de ponta a ponta, e o suporte ao Prisma 7 é um adapter lançado e publicado. O adapter do Drizzle está disponível em alpha. Adapters oficiais para os demais ORMs estão no roadmap e serão distribuídos como pacotes `@vsrepo/*-adapter` separados, e não como parte do pacote core `vsrepo` — mas você não precisa esperar por isso: escrever (e opcionalmente publicar) o seu próprio adapter enquanto isso é uma forma totalmente suportada de usar o VSRepository hoje e de contribuir de volta com o projeto.
71
+
72
+ ---
73
+
74
+ ## Instalação
75
+
76
+ O VSRepository é instalado como o pacote core mais um pacote de adapter para o seu ORM, por exemplo:
77
+
78
+ ```bash
79
+ npm i vsrepo @vsrepo/prisma7-adapter
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Uso básico
85
+
86
+ ### Implementando/escolhendo um adapter
87
+
88
+ ```typescript
89
+ // src/configs/db.ts
90
+ import { PrismaClient } from "../../generated/prisma/client";
91
+ import { PrismaPg } from "@prisma/adapter-pg";
92
+ import "dotenv/config";
93
+
94
+ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
95
+ const prisma = new PrismaClient({ adapter });
96
+
97
+ export default prisma;
98
+ ```
99
+
100
+ ### Criando um repository
101
+
102
+ ```typescript
103
+ // src/repositories/user.repository.ts
104
+ import { VSRepository, DynamicMethod } from "vsrepo";
105
+ import { VSRepoPrisma7Adapter } from "@vsrepo/prisma7-adapter";
106
+ import prisma from "../configs/db";
107
+ import type { UserGetPayload } from "../../generated/prisma/models";
108
+
109
+ type User = UserGetPayload<{ include: { address: true } }>;
110
+
111
+ class UserRepository extends VSRepository<User, string> {
112
+ constructor() {
113
+ super({
114
+ pkName: "id",
115
+ adapter: new VSRepoPrisma7Adapter<User>(prisma, { tableName: "user", pkName: "id" }),
116
+ softRemoveKey: "deletedAt",
117
+ defaultOrdering: { createdAt: "desc" },
118
+ });
119
+ }
120
+
121
+ @DynamicMethod()
122
+ declare findByEmail: (email: string) => Promise<User[]>;
123
+
124
+ @DynamicMethod()
125
+ declare findOneByEmail: (email: string) => Promise<User | null>;
126
+ }
127
+
128
+ export default new UserRepository();
129
+ ```
130
+
131
+ > A API do core (`VSRepository`, `VSRepoAdapter`, `DynamicMethod`, `QueryMethod`, `VSRepoError`, enums e tipos) é importada do entry point único `vsrepo`. O adapter concreto vem de um pacote **separado** (`@vsrepo/*-adapter`). No Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter).
132
+
133
+ > **O terceiro generic (`OrmTypes`):** `VSRepository<Entity, PKType, OrmTypes>` aceita um terceiro type parameter opcional descrevendo os tipos de client/transaction do seu ORM, via `VSRepoOrmTypes` (`{ dbClient; dbTransaction }`). Ao fornecê-lo, `getDbClient()`, o callback de `transaction()` e a option `db` de todo método passam a ser tipados corretamente, em vez de `any`:
134
+ >
135
+ > ```typescript
136
+ > import { Prisma7OrmTypes } from "@vsrepo/prisma7-adapter";
137
+ >
138
+ > type MyOrmTypes = Prisma7OrmTypes<PrismaClient>;
139
+ >
140
+ > class UserRepository extends VSRepository<User, string, MyOrmTypes> {
141
+ > // getDbClient() agora retorna PrismaClient, e transaction(fn) tipa `tx` como Prisma.TransactionClient
142
+ > }
143
+ > ```
144
+ >
145
+ > Se omitido, o padrão é `VSRepoOrmTypes` (`dbClient`/`dbTransaction` como `any`).
146
+
147
+ ### Usando o repository
148
+
149
+ ```typescript
150
+ import userRepository from "./repositories/user.repository";
151
+
152
+ const usuario = await userRepository.save({
153
+ name: "Joao",
154
+ email: "joao@email.com",
155
+ password: "password",
156
+ });
157
+
158
+ const encontrado = await userRepository.get(usuario.id);
159
+ const todos = await userRepository.getAll();
160
+ const porEmail = await userRepository.findByEmail("joao@email.com");
161
+
162
+ await userRepository.patch(usuario.id, { name: "Joao Pedro" });
163
+ await userRepository.remove(usuario.id);
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Desenvolvimento
169
+
170
+ ```bash
171
+ # 1. Instalar as dependências
172
+ bun install
173
+
174
+ # 2. Compilar os fontes TypeScript em dist/ (remove um dist/ anterior primeiro)
175
+ bun run build
176
+
177
+ # 3. (Opcional) Inspecionar o que seria publicado sem gerar um tarball
178
+ npm pack --dry-run
179
+
180
+ # 4. Gerar o tarball instalável (roda `prepack` -> `bun run build` automaticamente)
181
+ npm pack
182
+
183
+ # 5. Consumir localmente em outro projeto
184
+ npm install ../caminho/vsrepo-*.tgz
185
+ ```
186
+
187
+ Observações:
188
+
189
+ - `bun run build` executa `tsc -p tsconfig.build.json`, que gera o JS compilado e as declarações de tipo em `dist/` com `rootDir: src`.
190
+ - O pacote publicado contém **apenas** a pasta `dist/`, os READMEs, o `CHANGELOG.md` e a `LICENSE` (veja `files` no `package.json`). Os adapters viverão em seus próprios pacotes `@vsrepo/*-adapter`.
191
+
192
+ ---
193
+
194
+ ## Requisitos
195
+
196
+ - Node.js 18+
197
+ - TypeScript, com **decorators legacy/experimentais** habilitados (necessário para `@DynamicMethod`/`@QueryMethod`):
198
+
199
+ ```json
200
+ {
201
+ "compilerOptions": {
202
+ "experimentalDecorators": true
203
+ }
204
+ }
205
+ ```
206
+
207
+ - `reflect-metadata` (já incluso como dependência, importado internamente — você não precisa importá-lo você mesmo)
208
+ - Pelo menos um `VSRepoAdapter` funcional para o seu banco — no Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já publicado (veja [Status dos adapters](#status-dos-adapters)); adapters oficiais para outros ORMs estão planejados, mas ainda não publicados, então por enquanto isso significa escrever o seu próprio (veja [Escrevendo seu próprio adapter](./docs/writing-an-adapter.pt-BR.md#escrevendo-seu-próprio-adapter)) — e, se publicá-lo, contribuir de volta com o projeto é bem-vindo
209
+
210
+ ---
211
+
212
+ ## Contribuindo
213
+
214
+ Contribuições são bem-vindas, especialmente para melhorar o adapter do Prisma e finalizar o do Drizzle! (**[Repositório do GitHub](https://github.com/jaobrabo123/VSRepository)**):
215
+
216
+ 1. Faça um **Fork** do projeto.
217
+ 2. Crie uma branch para sua alteração: `git checkout -b minha-alteracao`.
218
+ 3. Faça o push da sua branch: `git push origin minha-alteracao`.
219
+ 4. Abra um **Pull Request**.
220
+
221
+ Para reportar problemas ou sugerir funcionalidades, abra uma **Issue**.