vsrepo 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +828 -126
- package/VSRepository/VSRepository.d.ts +31 -18
- package/VSRepository/VSRepository.js +3 -0
- package/VSRepository/index.d.ts +2 -2
- package/package.json +24 -3
- package/scripts/configure-prisma-import.mjs +192 -0
package/README.md
CHANGED
|
@@ -1,68 +1,142 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# VSRepository
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **type inference** automático.
|
|
7
|
+
|
|
8
|
+
O VSRepository permite criar repositories fortemente tipados com:
|
|
9
|
+
|
|
10
|
+
- **Métodos base** automáticos: `get`, `save`, `remove`
|
|
11
|
+
- **Métodos dinâmicos** inferidos pelo nome: `findByEmail`, `findManyPaginated`, `updateById`, `deleteManyByIdIn`
|
|
12
|
+
- **Select models** reutilizáveis para diferentes projeções de dados
|
|
13
|
+
- **Type safety** em 100% das operações
|
|
14
|
+
- **Transações** nativas do Prisma
|
|
15
|
+
- **Extensibilidade** com métodos personalizados
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Sumário
|
|
20
|
+
|
|
21
|
+
- [Instalação](#instalação)
|
|
22
|
+
- [Gerando os tipos](#gerando-os-tipos)
|
|
23
|
+
- [Uso básico](#uso-básico)
|
|
24
|
+
- [Integração com NestJS](#integração-com-nestjs)
|
|
25
|
+
- [Métodos base](#métodos-base)
|
|
26
|
+
- [Select Models](#select-models)
|
|
27
|
+
- [requiredWhere](#requiredwhere)
|
|
28
|
+
- [Métodos dinâmicos](#métodos-dinâmicos)
|
|
29
|
+
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
30
|
+
- [Filtros de campo](#filtros-de-campo)
|
|
31
|
+
- [Operadores lógicos](#operadores-lógicos)
|
|
32
|
+
- [Filtros de relação](#filtros-de-relação)
|
|
33
|
+
- [Sufixos de paginação e ordenação](#sufixos-de-paginação-e-ordenação)
|
|
34
|
+
- [Configuração de métodos](#configuração-de-métodos)
|
|
35
|
+
- [Relações no save](#relações-no-save)
|
|
36
|
+
- [Transações](#transações)
|
|
37
|
+
- [Extendendo um repository](#extendendo-um-repository)
|
|
38
|
+
- [Tratamento de erros](#tratamento-de-erros)
|
|
39
|
+
- [Tipos utilitários](#tipos-utilitários)
|
|
40
|
+
- [API Reference](#api-reference)
|
|
41
|
+
- [Requisitos](#requisitos)
|
|
42
|
+
- [Troubleshooting](#troubleshooting)
|
|
43
|
+
|
|
44
|
+
---
|
|
4
45
|
|
|
5
46
|
## Instalação
|
|
6
47
|
|
|
7
48
|
```bash
|
|
8
|
-
|
|
49
|
+
npm i vsrepo @prisma/client
|
|
9
50
|
```
|
|
10
51
|
|
|
11
|
-
|
|
52
|
+
Gere o Prisma Client:
|
|
12
53
|
|
|
13
54
|
```bash
|
|
14
|
-
|
|
55
|
+
npx prisma generate
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Gerando os tipos
|
|
61
|
+
|
|
62
|
+
O VSRepository precisa conhecer o caminho real do seu Prisma Client para gerar as tipagens corretamente.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx vsrepo generate
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Equivale a:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx vsrepo generate \
|
|
72
|
+
--output src/generated/vsrepo \
|
|
73
|
+
--prisma src/generated/prisma
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Flags disponíveis:**
|
|
77
|
+
|
|
78
|
+
| Flag | Alias | Padrão |
|
|
79
|
+
| ---------- | ----- | ---------------------- |
|
|
80
|
+
| `--output` | `-o` | `src/generated/vsrepo` |
|
|
81
|
+
| `--prisma` | `-p` | `src/generated/prisma` |
|
|
82
|
+
|
|
83
|
+
**Arquivos gerados:**
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
src/generated/vsrepo/
|
|
87
|
+
├── VSRepoError.ts
|
|
88
|
+
├── VSRepoError.types.d.ts
|
|
89
|
+
├── VSRepository.ts
|
|
90
|
+
├── VSRepository.types.d.ts
|
|
91
|
+
└── index.ts
|
|
15
92
|
```
|
|
16
93
|
|
|
94
|
+
Após gerar, importe sempre a partir da pasta gerada:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { setupVSRepo } from "../generated/vsrepo";
|
|
98
|
+
// ou
|
|
99
|
+
import { setupVSRepo, type SelectModels, type WhereModel } from "../generated/vsrepo";
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
17
104
|
## Uso básico
|
|
18
105
|
|
|
19
|
-
|
|
106
|
+
### Configurando o Prisma Client
|
|
20
107
|
|
|
21
108
|
```ts
|
|
22
|
-
// src/db.ts
|
|
23
|
-
import { PrismaClient } from
|
|
109
|
+
// src/configs/db.ts
|
|
110
|
+
import { PrismaClient } from '../generated/prisma/client';
|
|
111
|
+
import { PrismaPg } from '@prisma/adapter-pg';
|
|
112
|
+
import 'dotenv/config';
|
|
24
113
|
|
|
25
|
-
const
|
|
114
|
+
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
115
|
+
const prisma = new PrismaClient({ adapter });
|
|
26
116
|
|
|
27
117
|
export default prisma;
|
|
28
118
|
```
|
|
29
119
|
|
|
30
|
-
|
|
120
|
+
### Criando um repository
|
|
31
121
|
|
|
32
122
|
```ts
|
|
33
123
|
// src/repositories/usuarioRepository.ts
|
|
34
|
-
import prisma from "../db";
|
|
35
|
-
import { setupVSRepo
|
|
36
|
-
import type {
|
|
124
|
+
import prisma from "../configs/db";
|
|
125
|
+
import { setupVSRepo } from "../generated/vsrepo";
|
|
126
|
+
import type { Usuario } from "../generated/prisma/client";
|
|
37
127
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
postagens: true;
|
|
42
|
-
};
|
|
43
|
-
}>;
|
|
128
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
129
|
+
tableName: "usuario",
|
|
130
|
+
pkName: "id",
|
|
44
131
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
id: true,
|
|
48
|
-
nome: true,
|
|
49
|
-
email: true,
|
|
50
|
-
},
|
|
51
|
-
minimal: {
|
|
52
|
-
id: true,
|
|
132
|
+
selectModels: {
|
|
133
|
+
public: { id: true, nome: true, email: true },
|
|
134
|
+
minimal: { id: true },
|
|
53
135
|
},
|
|
54
|
-
|
|
136
|
+
defaultSelectModel: "public",
|
|
55
137
|
|
|
56
|
-
|
|
57
|
-
ativo: true,
|
|
58
|
-
} satisfies WhereModel<"usuario">;
|
|
138
|
+
requiredWhere: { ativo: true },
|
|
59
139
|
|
|
60
|
-
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
61
|
-
tableName: "usuario",
|
|
62
|
-
pkName: "id",
|
|
63
|
-
selectModels: usuarioSelectModels,
|
|
64
|
-
defaultSelectModel: "public",
|
|
65
|
-
requiredWhere: usuarioRequiredWhere,
|
|
66
140
|
methods: {
|
|
67
141
|
findByEmail: { map: true, fbMode: "one" },
|
|
68
142
|
findManyPaginated: { map: true },
|
|
@@ -70,14 +144,14 @@ const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
|
70
144
|
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
71
145
|
count: { map: true },
|
|
72
146
|
},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const usuarioRepository = usuarioVSRepo.build(prisma);
|
|
147
|
+
}).build(prisma);
|
|
76
148
|
|
|
77
149
|
export default usuarioRepository;
|
|
78
150
|
```
|
|
79
151
|
|
|
80
|
-
|
|
152
|
+
> `selectModels` e `requiredWhere` podem ser declarados fora do `setupVSRepo` se você precisar exportá-los para uso em outros arquivos.
|
|
153
|
+
|
|
154
|
+
### Usando o repository
|
|
81
155
|
|
|
82
156
|
```ts
|
|
83
157
|
import usuarioRepository from "./repositories/usuarioRepository";
|
|
@@ -88,128 +162,506 @@ const usuario = await usuarioRepository.save({
|
|
|
88
162
|
senha: "password",
|
|
89
163
|
});
|
|
90
164
|
|
|
91
|
-
const
|
|
92
|
-
|
|
165
|
+
const encontrado = await usuarioRepository.get(usuario.id);
|
|
93
166
|
const porEmail = await usuarioRepository.findByEmail("joao@email.com");
|
|
94
167
|
|
|
95
|
-
await usuarioRepository.updateById(usuario.id, {
|
|
96
|
-
|
|
168
|
+
await usuarioRepository.updateById(usuario.id, { nome: "Joao Pedro" });
|
|
169
|
+
await usuarioRepository.remove(usuario.id);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Integração com NestJS
|
|
175
|
+
|
|
176
|
+
O VSRepository pode ser facilmente integrado em projetos NestJS através de providers. Abaixo está um exemplo completo usando o padrão de injeção de dependência do NestJS.
|
|
177
|
+
|
|
178
|
+
### Configurando o repository como provider
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// src/repositories/user.repository.ts
|
|
182
|
+
import { Provider } from "@nestjs/common";
|
|
183
|
+
import { PrismaService } from "../../database/prisma.service";
|
|
184
|
+
import { UserGetPayload } from "../../generated/prisma/models";
|
|
185
|
+
import { RepositoryOf, setupVSRepo } from "../../generated/vsrepo";
|
|
186
|
+
|
|
187
|
+
const userVSRepo = setupVSRepo<
|
|
188
|
+
UserGetPayload<{ include: { profile: true } }>,
|
|
189
|
+
"User"
|
|
190
|
+
>()({
|
|
191
|
+
tableName: "user",
|
|
192
|
+
pkName: "id",
|
|
193
|
+
selectModels: {
|
|
194
|
+
public: {
|
|
195
|
+
id: true,
|
|
196
|
+
email: true,
|
|
197
|
+
createdAt: true,
|
|
198
|
+
updatedAt: true,
|
|
199
|
+
},
|
|
200
|
+
auth: {
|
|
201
|
+
id: true,
|
|
202
|
+
email: true,
|
|
203
|
+
password: true,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
defaultSelectModel: "public",
|
|
207
|
+
requiredWhere: {
|
|
208
|
+
deletedAt: null,
|
|
209
|
+
},
|
|
210
|
+
relations: {
|
|
211
|
+
profile: {
|
|
212
|
+
mode: "oto",
|
|
213
|
+
pk: "id",
|
|
214
|
+
restriction: "add",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
methods: {
|
|
218
|
+
findAuthByEmail: {
|
|
219
|
+
map: true,
|
|
220
|
+
proxyTo: "findUniqueByEmail",
|
|
221
|
+
selectModel: "auth",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
97
224
|
});
|
|
98
225
|
|
|
99
|
-
|
|
226
|
+
export type UserRepository = RepositoryOf<typeof userVSRepo>;
|
|
227
|
+
|
|
228
|
+
export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
|
|
229
|
+
|
|
230
|
+
export const UserRepositoryProvider: Provider = {
|
|
231
|
+
provide: USER_REPOSITORY,
|
|
232
|
+
inject: [PrismaService],
|
|
233
|
+
useFactory: (prisma: PrismaService) => {
|
|
234
|
+
return userVSRepo.build(prisma);
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Registrando o provider no módulo
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// src/modules/user/user.module.ts
|
|
243
|
+
import { Module } from "@nestjs/common";
|
|
244
|
+
import { UserRepositoryProvider } from "../../repositories/user.repository";
|
|
245
|
+
import { UserService } from "./user.service";
|
|
246
|
+
import { UserController } from "./user.controller";
|
|
247
|
+
|
|
248
|
+
@Module({
|
|
249
|
+
imports: [DatabaseModule],
|
|
250
|
+
providers: [UserRepositoryProvider, UserService],
|
|
251
|
+
controllers: [UserController],
|
|
252
|
+
exports: [UserService],
|
|
253
|
+
})
|
|
254
|
+
export class UserModule {}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Utilizando o repository em um serviço
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// src/modules/user/user.service.ts
|
|
261
|
+
import { Injectable, Inject } from "@nestjs/common";
|
|
262
|
+
import { USER_REPOSITORY, UserRepository } from "../../repositories/user.repository";
|
|
263
|
+
|
|
264
|
+
@Injectable()
|
|
265
|
+
export class UserService {
|
|
266
|
+
constructor(
|
|
267
|
+
@Inject(USER_REPOSITORY)
|
|
268
|
+
private readonly userRepository: UserRepository,
|
|
269
|
+
) {}
|
|
270
|
+
|
|
271
|
+
async getUserById(id: string) {
|
|
272
|
+
return this.userRepository.get(id);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async getUserAuthByEmail(email: string) {
|
|
276
|
+
return this.userRepository.findAuthByEmail(email);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async createUser(data: { email: string; password: string; name: string }) {
|
|
280
|
+
return this.userRepository.save({
|
|
281
|
+
email: data.email,
|
|
282
|
+
password: data.password,
|
|
283
|
+
name: data.name,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Usando em um controller
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
// src/modules/user/user.controller.ts
|
|
293
|
+
import { Controller, Get, Post, Body, Param, Patch, Delete } from "@nestjs/common";
|
|
294
|
+
import { UserService } from "./user.service";
|
|
295
|
+
|
|
296
|
+
@Controller("users")
|
|
297
|
+
export class UserController {
|
|
298
|
+
constructor(private readonly userService: UserService) {}
|
|
299
|
+
|
|
300
|
+
@Get(":id")
|
|
301
|
+
async getUser(@Param("id") id: string) {
|
|
302
|
+
return this.userService.getUserById(id);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@Post()
|
|
306
|
+
async createUser(
|
|
307
|
+
@Body() data: { email: string; password: string; name: string }
|
|
308
|
+
) {
|
|
309
|
+
return this.userService.createUser(data);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
100
312
|
```
|
|
101
313
|
|
|
314
|
+
**Benefícios desta abordagem:**
|
|
315
|
+
|
|
316
|
+
- ✅ Type-safe repositories com injeção de dependência
|
|
317
|
+
- ✅ Fácil de testar (mock do `USER_REPOSITORY`)
|
|
318
|
+
- ✅ Isolamento da lógica de persistência
|
|
319
|
+
- ✅ Reutilização do repository em múltiplos serviços
|
|
320
|
+
- ✅ Suporte a transações via `PrismaService`
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
102
324
|
## Métodos base
|
|
103
325
|
|
|
104
|
-
Ao chamar
|
|
326
|
+
Ao chamar `.build(prisma)`, três métodos são automaticamente disponibilizados:
|
|
327
|
+
|
|
328
|
+
| Método | Descrição |
|
|
329
|
+
| ------------ | ------------------------------------------ |
|
|
330
|
+
| `get(pk)` | Busca um registro pela primary key |
|
|
331
|
+
| `save(obj)` | Cria ou atualiza (upsert pela primary key) |
|
|
332
|
+
| `remove(pk)` | Remove um registro pela primary key |
|
|
105
333
|
|
|
106
|
-
|
|
107
|
-
| --- | --- |
|
|
108
|
-
| `get(pk, options?)` | Busca um registro pela primary key definida em `pkName`. |
|
|
109
|
-
| `save(obj, options?)` | Cria ou atualiza um registro. Se `obj` tiver a primary key, usa `upsert`; se não tiver, usa `create`. |
|
|
110
|
-
| `remove(pk, options?)` | Remove um registro pela primary key. |
|
|
334
|
+
Todos aceitam `options?: { selectModel?, db? }` como último argumento.
|
|
111
335
|
|
|
112
|
-
|
|
336
|
+
### Configurando os métodos base
|
|
113
337
|
|
|
114
338
|
```ts
|
|
115
|
-
|
|
116
|
-
freeze: true,
|
|
117
|
-
showWorking: false,
|
|
339
|
+
usuarioVSRepo.build(prisma, {
|
|
340
|
+
freeze: true, // Congela o objeto (padrão: true)
|
|
341
|
+
showWorking: false, // Exibe logs internos no console
|
|
342
|
+
|
|
118
343
|
baseMethods: {
|
|
119
344
|
get: {
|
|
120
345
|
active: true,
|
|
121
346
|
defaultSelect: "public",
|
|
122
347
|
},
|
|
123
348
|
remove: {
|
|
349
|
+
active: true,
|
|
124
350
|
defaultSelect: "minimal",
|
|
125
351
|
},
|
|
126
352
|
save: {
|
|
127
353
|
active: true,
|
|
354
|
+
ignoreRequiredWhere: true, // Não aplica requiredWhere no upsert
|
|
128
355
|
},
|
|
129
356
|
},
|
|
130
357
|
});
|
|
131
358
|
```
|
|
132
359
|
|
|
133
|
-
|
|
360
|
+
---
|
|
134
361
|
|
|
135
|
-
|
|
362
|
+
## Select Models
|
|
363
|
+
|
|
364
|
+
`selectModels` define projeções de dados nomeadas e reutilizáveis.
|
|
136
365
|
|
|
137
366
|
```ts
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
367
|
+
selectModels: {
|
|
368
|
+
public: { id: true, nome: true, email: true },
|
|
369
|
+
internal: { id: true, nome: true, email: true, senha: true },
|
|
370
|
+
minimal: { id: true },
|
|
371
|
+
},
|
|
372
|
+
defaultSelectModel: "public",
|
|
373
|
+
```
|
|
141
374
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
375
|
+
`defaultSelectModel` define qual select é usado automaticamente quando nenhum é especificado na chamada. É recomendado sempre definí-lo junto com `selectModels`.
|
|
376
|
+
|
|
377
|
+
**Usando um select específico na chamada:**
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const usuario = await usuarioRepository.get(id, { selectModel: "minimal" });
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Retornando o payload padrão do Prisma (sem select):**
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
const usuarioCompleto = await usuarioRepository.get(id, { selectModel: false });
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## requiredWhere
|
|
392
|
+
|
|
393
|
+
`requiredWhere` define filtros aplicados automaticamente em todas as queries do repository.
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
requiredWhere: { ativo: true },
|
|
145
397
|
```
|
|
146
398
|
|
|
147
|
-
|
|
399
|
+
Agora toda query incluirá `ativo: true` automaticamente:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
// Internamente: WHERE ativo = true
|
|
403
|
+
const usuarios = await usuarioRepository.findMany();
|
|
404
|
+
|
|
405
|
+
// Internamente: WHERE email = 'joao@email.com' AND ativo = true
|
|
406
|
+
const usuario = await usuarioRepository.findByEmail("joao@email.com");
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Útil para soft-deletes, multi-tenancy e filtros globais de qualquer natureza.
|
|
410
|
+
|
|
411
|
+
---
|
|
148
412
|
|
|
149
413
|
## Métodos dinâmicos
|
|
150
414
|
|
|
151
|
-
Métodos dinâmicos são
|
|
415
|
+
Métodos dinâmicos são definidos na propriedade `methods` e têm seus comportamentos inferidos a partir do nome.
|
|
152
416
|
|
|
153
|
-
|
|
417
|
+
```ts
|
|
418
|
+
methods: {
|
|
419
|
+
findByEmail: { map: true, fbMode: "one" },
|
|
420
|
+
findManyPaginated: { map: true },
|
|
421
|
+
updateById: { map: true },
|
|
422
|
+
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
### Prefixos disponíveis
|
|
429
|
+
|
|
430
|
+
O prefixo do nome do método determina qual operação Prisma será chamada e quais argumentos serão esperados.
|
|
431
|
+
|
|
432
|
+
| Prefixo | Operação Prisma | Retorno | Observações |
|
|
433
|
+
| ------------------------- | ------------------------ | ---------------------- | -------------------------------------------------------- |
|
|
434
|
+
| `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` para retorno único |
|
|
435
|
+
| `findUniqueBy` | `findUnique` | `T \| null` | |
|
|
436
|
+
| `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
|
|
437
|
+
| `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
438
|
+
| `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
|
|
439
|
+
| `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
440
|
+
| `findWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
|
|
441
|
+
| `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
|
|
442
|
+
| `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrar, `false` caso contrário |
|
|
443
|
+
| `countBy` | `count` | `number` | Aceita campos como filtro |
|
|
444
|
+
| `count` | `count` | `number` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
445
|
+
| `create` | `create` | `T` | Recebe `data` como argumento |
|
|
446
|
+
| `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
447
|
+
| `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
448
|
+
| `updateBy` | `update` | `T` | Recebe `data` como argumento |
|
|
449
|
+
| `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
|
|
450
|
+
| `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
|
|
451
|
+
| `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
|
|
452
|
+
| `deleteBy` | `delete` | `T` | |
|
|
453
|
+
| `deleteManyBy` | `deleteMany` | `{ count: number }` | |
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### Filtros de campo
|
|
458
|
+
|
|
459
|
+
Os filtros são sufixos aplicados ao nome do campo dentro do método. O campo em si vem capitalizado logo após o prefixo (ou após `By`).
|
|
460
|
+
|
|
461
|
+
| Sufixo | Operador Prisma | Argumento necessário |
|
|
462
|
+
| ------------------ | --------------------- | -------------------- |
|
|
463
|
+
| *(sem sufixo)* | igualdade (`=`) | sim |
|
|
464
|
+
| `Not` | `not` | sim |
|
|
465
|
+
| `In` | `in` | sim (array) |
|
|
466
|
+
| `NotIn` | `notIn` | sim (array) |
|
|
467
|
+
| `Contains` | `contains` | sim |
|
|
468
|
+
| `NotContains` | `not.contains` | sim |
|
|
469
|
+
| `StartsWith` | `startsWith` | sim |
|
|
470
|
+
| `NotStartsWith` | `not.startsWith` | sim |
|
|
471
|
+
| `EndsWith` | `endsWith` | sim |
|
|
472
|
+
| `NotEndsWith` | `not.endsWith` | sim |
|
|
473
|
+
| `GreaterThan` | `gt` | sim |
|
|
474
|
+
| `GreaterThanEqual` | `gte` | sim |
|
|
475
|
+
| `LessThan` | `lt` | sim |
|
|
476
|
+
| `LessThanEqual` | `lte` | sim |
|
|
477
|
+
| `IsNull` | `null` | não (zero-arg) |
|
|
478
|
+
| `IsNotNull` | `not: null` | não (zero-arg) |
|
|
479
|
+
| `IsTrue` | `true` | não (zero-arg) |
|
|
480
|
+
| `IsFalse` | `false` | não (zero-arg) |
|
|
481
|
+
| `Insensitive` | `mode: 'insensitive'` | combinador |
|
|
482
|
+
|
|
483
|
+
`Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
findByNomeContainsInsensitive // { nome: { contains: valor, mode: 'insensitive' } }
|
|
487
|
+
findByEmailStartsWithInsensitive // { email: { startsWith: valor, mode: 'insensitive' } }
|
|
488
|
+
findByNomeInsensitive // { nome: { equals: valor, mode: 'insensitive' } }
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
findByNomeOptionalAndEmail // nome é opcional, email é obrigatório
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### Operadores lógicos
|
|
500
|
+
|
|
501
|
+
| Operador | Uso no nome | Exemplo |
|
|
502
|
+
| --------- | ---------------------------- | -------------------------------- |
|
|
503
|
+
| `And` | entre dois campos | `findByIdAndEmail` |
|
|
504
|
+
| `Or` | entre dois campos | `findByNomeOrEmail` |
|
|
505
|
+
|
|
506
|
+
Exemplo:
|
|
154
507
|
|
|
155
508
|
```ts
|
|
156
509
|
methods: {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
findListWhereOrderedAndPaginated: { map: true, whereType: "overwrite" },
|
|
161
|
-
createManyAndReturn: { map: true },
|
|
162
|
-
updateById: { map: true },
|
|
163
|
-
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
164
|
-
existsByEmail: { map: true },
|
|
165
|
-
countByPerfil: { map: true },
|
|
510
|
+
findByIdAndEmail: { map: true, fbMode: "one" },
|
|
511
|
+
findByNomeOrEmail: { map: true },
|
|
512
|
+
findUniqueByIdOrEmailAndNome: { map: true }
|
|
166
513
|
}
|
|
514
|
+
|
|
515
|
+
// Uso
|
|
516
|
+
await usuarioRepository.findByIdAndEmail(1, "joao@email.com");
|
|
517
|
+
await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
|
|
518
|
+
await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "joao@email.com", "Joao") // { OR: [ { id: 1 }, { email: "joao@email.com", nome: "Joao" } ] }
|
|
167
519
|
```
|
|
168
520
|
|
|
169
|
-
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### Filtros de relação
|
|
524
|
+
|
|
525
|
+
Permitem filtrar por campos de modelos relacionados.
|
|
526
|
+
|
|
527
|
+
| Sufixo de relação | Operador Prisma | Observação |
|
|
528
|
+
| ---------------------- | --------------- | -------------------------------------------------- |
|
|
529
|
+
| `Some` | `some: {}` | Relação tem *algum* registro |
|
|
530
|
+
| `SomeField` | `some.field` | Filtra dentro dos registros da relação |
|
|
531
|
+
| `EveryField` | `every.field` | Filtra dentro dos registros da relação |
|
|
532
|
+
| `None` | `none: {}` | Relação não tem *nenhum* registro |
|
|
533
|
+
| `NoneField` | `none.field` | Filtra dentro dos registros da relação |
|
|
534
|
+
| `With` | `is: {}` | Relação existe (não é null) |
|
|
535
|
+
| `WithField` | `is.field` | Filtra campo dentro da relação |
|
|
536
|
+
| `Without` | `isNot: {}` | Relação não existe (é null) |
|
|
537
|
+
| `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
|
|
538
|
+
|
|
539
|
+
Exemplos:
|
|
170
540
|
|
|
171
541
|
```ts
|
|
172
|
-
|
|
542
|
+
methods: {
|
|
543
|
+
findByPostagensSomeTituloContains: { map: true }, // postagens: { some: { titulo: { contains: valor } } }
|
|
544
|
+
findByPerfilWith: { map: true }, // perfil: { is: {} }
|
|
545
|
+
findByPerfilWithout: { map: true }, // perfil: { isNot: {} }
|
|
546
|
+
findByPostagensSome: { map: true }, // postagens: { some: {} }
|
|
547
|
+
findByPostagensEveryAtivoIsTrue: { map: true }, // postagens: { every: { ativo: true } }
|
|
548
|
+
}
|
|
549
|
+
```
|
|
173
550
|
|
|
174
|
-
|
|
551
|
+
---
|
|
175
552
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
553
|
+
### Sufixos de paginação e ordenação
|
|
554
|
+
|
|
555
|
+
Aplicados ao **final** do nome do método (após os filtros de campo), eles injetam automaticamente os argumentos de paginação e ordenação.
|
|
556
|
+
|
|
557
|
+
| Sufixo | Argumentos adicionais |
|
|
558
|
+
| --------------------- | ----------------------------- |
|
|
559
|
+
| `Paginated` | `(pagination)` |
|
|
560
|
+
| `Ordered` | `(order)` |
|
|
561
|
+
| `OrderedAndPaginated` | `(order, pagination)` |
|
|
562
|
+
| `PaginatedAndOrdered` | `(pagination, order)` |
|
|
563
|
+
|
|
564
|
+
Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está disponível:
|
|
180
565
|
|
|
181
|
-
|
|
182
|
-
|
|
566
|
+
| Sufixo | Efeito |
|
|
567
|
+
| ----------------- | ---------------------------------------- |
|
|
568
|
+
| `SkipDuplicates` | Ignora registros duplicados na inserção |
|
|
569
|
+
|
|
570
|
+
Exemplos completos:
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
methods: {
|
|
574
|
+
findManyPaginated: { map: true },
|
|
575
|
+
findManyByAtivoOrderedAndPaginated: { map: true },
|
|
576
|
+
findByEmailOrderedAndPaginated: { map: true },
|
|
577
|
+
createManyAndReturnSkipDuplicates: { map: true },
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Uso
|
|
581
|
+
await usuarioRepository.findManyPaginated({ skip: 0, take: 10 });
|
|
582
|
+
|
|
583
|
+
await usuarioRepository.findManyByAtivoOrderedAndPaginated(
|
|
584
|
+
true,
|
|
183
585
|
{ dataCriacao: "desc" },
|
|
184
|
-
{ take: 10 }
|
|
586
|
+
{ skip: 0, take: 10 }
|
|
185
587
|
);
|
|
186
588
|
```
|
|
187
589
|
|
|
188
|
-
|
|
590
|
+
`PaginationOptions`:
|
|
189
591
|
|
|
190
|
-
|
|
592
|
+
```ts
|
|
593
|
+
type PaginationOptions<TCursor = unknown> = {
|
|
594
|
+
skip?: number;
|
|
595
|
+
take?: number;
|
|
596
|
+
cursor?: TCursor;
|
|
597
|
+
};
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`OrderOptions`:
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
type OrderOptions = OrderPattern | OrderPattern[];
|
|
604
|
+
// Exemplo: { dataCriacao: "desc" } ou [{ dataCriacao: "desc" }, { nome: "asc" }]
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
### Configuração de métodos
|
|
610
|
+
|
|
611
|
+
Cada entrada em `methods` aceita as seguintes opções:
|
|
612
|
+
|
|
613
|
+
| Opção | Tipo | Padrão | Descrição |
|
|
614
|
+
| ------------------- | ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
615
|
+
| `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repository. |
|
|
616
|
+
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`. |
|
|
617
|
+
| `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
|
|
618
|
+
| `fbMode` | `'one'` \| `'list'` | `'list'` | Somente para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
|
|
619
|
+
| `proxyTo` | `ValidMethodPatterns` | — | Delega a lógica para outro padrão de método válido. Útil para methods com nomes personalizados. |
|
|
620
|
+
| `pushWhere` | `WhereModel<M>` | — | Where extra adicionado à query além do `requiredWhere`. |
|
|
621
|
+
| `injectOrdenation` | `OrdenationModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
|
|
622
|
+
| `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
|
|
623
|
+
|
|
624
|
+
Exemplos:
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
methods: {
|
|
628
|
+
// Retorna um único resultado em vez de array
|
|
629
|
+
findByEmail: { map: true, fbMode: "one" },
|
|
630
|
+
|
|
631
|
+
// Ignora o requiredWhere
|
|
632
|
+
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
633
|
+
|
|
634
|
+
// Usa um select model específico neste método
|
|
635
|
+
findManyByAtivo: { map: true, selectModel: "minimal" },
|
|
191
636
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
| `And` / `Or` | `findByIdAndEmail`, `findByNomeOrEmail` |
|
|
195
|
-
| `In` / `NotIn` | `deleteManyByIdIn` |
|
|
196
|
-
| `Contains` / `StartsWith` / `EndsWith` | `findByNomeContains` |
|
|
197
|
-
| `Insensitive` | `findByNomeContainsInsensitive` |
|
|
198
|
-
| `GreaterThan` / `GreaterThanEqual` | `findByIdadeGreaterThan` |
|
|
199
|
-
| `LessThan` / `LessThanEqual` | `findByIdadeLessThanEqual` |
|
|
200
|
-
| `IsNull` / `IsNotNull` | `findByPerfilIsNotNull` |
|
|
201
|
-
| `IsTrue` / `IsFalse` | `findByAtivoIsTrue` |
|
|
202
|
-
| `Some` / `Every` / `None` | `findByPostagensSomeTituloContains` |
|
|
203
|
-
| `With` / `Without` | `findByPerfilWith`, `findByPerfilWithout` |
|
|
637
|
+
// Adiciona um where extra além do requiredWhere
|
|
638
|
+
findManyByPerfil: { map: true, pushWhere: { deletedAt: null } },
|
|
204
639
|
|
|
205
|
-
|
|
640
|
+
// Ordenação fixa sem precisar passar como argumento
|
|
641
|
+
findManyPaginado: { map: true, injectOrdenation: { dataCriacao: "desc" } },
|
|
206
642
|
|
|
207
|
-
|
|
643
|
+
// Nome personalizado precisa de proxyTo
|
|
644
|
+
buscarPorEmailEPerfil: { map: true, proxyTo: "findByEmailAndPerfil" },
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
## Relações no save
|
|
651
|
+
|
|
652
|
+
Configure relações para que o `save` as gerencie automaticamente. Para que as relações apareçam no autocomplete, o tipo genérico deve incluir as relações usando `GetPayload`:
|
|
208
653
|
|
|
209
654
|
```ts
|
|
210
|
-
|
|
655
|
+
import type { Prisma } from "../generated/prisma/client";
|
|
656
|
+
|
|
657
|
+
type Usuario = Prisma.usuarioGetPayload<{
|
|
658
|
+
include: { perfil: true; postagens: true };
|
|
659
|
+
}>;
|
|
660
|
+
|
|
661
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
211
662
|
tableName: "usuario",
|
|
212
663
|
pkName: "id",
|
|
664
|
+
|
|
213
665
|
relations: {
|
|
214
666
|
perfil: {
|
|
215
667
|
pk: "id",
|
|
@@ -222,32 +674,37 @@ const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
|
222
674
|
restriction: "set",
|
|
223
675
|
},
|
|
224
676
|
},
|
|
225
|
-
});
|
|
677
|
+
}).build(prisma);
|
|
226
678
|
```
|
|
227
679
|
|
|
228
|
-
|
|
680
|
+
> **Dica:** Se o tipo genérico não incluir `include: { relação: true }`, o VSRepository ainda funcionará, mas o autocomplete não sugerirá as relações no `save`. O tipo recomendado é sempre `GetPayload<{ include: { /* suas relações */ } }>` para melhor experiência de desenvolvimento.
|
|
229
681
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
|
233
|
-
|
|
|
234
|
-
| `
|
|
682
|
+
**Modos de relação:**
|
|
683
|
+
|
|
684
|
+
| Modo | Relação |
|
|
685
|
+
| ----- | ------------ |
|
|
686
|
+
| `oto` | one-to-one |
|
|
687
|
+
| `otm` | one-to-many |
|
|
688
|
+
| `mto` | many-to-one |
|
|
235
689
|
| `mtm` | many-to-many |
|
|
236
690
|
|
|
237
|
-
|
|
691
|
+
**Restrições:**
|
|
692
|
+
|
|
693
|
+
| Restrição | Comportamento no update |
|
|
694
|
+
| --------- | ----------------------------------------------------------- |
|
|
695
|
+
| `set` | Substitui completamente (remove os que não foram enviados) |
|
|
696
|
+
| `add` | Adiciona/atualiza sem remover os existentes |
|
|
697
|
+
|
|
698
|
+
---
|
|
238
699
|
|
|
239
700
|
## Transações
|
|
240
701
|
|
|
241
|
-
Todos os métodos aceitam `options.db
|
|
702
|
+
Todos os métodos aceitam `options.db` para participar de uma transação:
|
|
242
703
|
|
|
243
704
|
```ts
|
|
244
705
|
await prisma.$transaction(async (tx) => {
|
|
245
706
|
const usuario = await usuarioRepository.save(
|
|
246
|
-
{
|
|
247
|
-
nome: "Maria",
|
|
248
|
-
email: "maria@email.com",
|
|
249
|
-
senha: "password",
|
|
250
|
-
},
|
|
707
|
+
{ nome: "Maria", email: "maria@email.com", senha: "password" },
|
|
251
708
|
{ db: tx }
|
|
252
709
|
);
|
|
253
710
|
|
|
@@ -259,23 +716,268 @@ await prisma.$transaction(async (tx) => {
|
|
|
259
716
|
});
|
|
260
717
|
```
|
|
261
718
|
|
|
262
|
-
|
|
719
|
+
---
|
|
263
720
|
|
|
264
|
-
|
|
721
|
+
## Extendendo um repository
|
|
265
722
|
|
|
266
723
|
```ts
|
|
267
|
-
const
|
|
724
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
725
|
+
tableName: "usuario",
|
|
726
|
+
pkName: "id",
|
|
727
|
+
methods: {
|
|
728
|
+
findByEmailEndsWith: { map: true, fbMode: "one" },
|
|
729
|
+
},
|
|
730
|
+
})
|
|
268
731
|
.build(prisma)
|
|
269
732
|
.extend((repo) => ({
|
|
270
|
-
async
|
|
733
|
+
buscarAtivosPorDominio: async (dominio: string) => {
|
|
271
734
|
return repo.findByEmailEndsWith(`@${dominio}`);
|
|
272
735
|
},
|
|
736
|
+
|
|
737
|
+
ativarMultiplos: async (ids: string[]) => {
|
|
738
|
+
return repo.updateManyByIdIn(ids, { ativo: true });
|
|
739
|
+
},
|
|
273
740
|
}));
|
|
274
741
|
```
|
|
275
742
|
|
|
276
|
-
|
|
743
|
+
---
|
|
277
744
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
745
|
+
## Tratamento de erros
|
|
746
|
+
|
|
747
|
+
O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (OBS: Erros do Prisma não são sobrescritos como `VSRepoError`):
|
|
748
|
+
|
|
749
|
+
```ts
|
|
750
|
+
import { VSRepoError } from "../generated/vsrepo";
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const usuario = await usuarioRepository.get();
|
|
754
|
+
} catch (error) {
|
|
755
|
+
if (error instanceof VSRepoError) {
|
|
756
|
+
console.error("Erro no repository:", error.message);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
281
759
|
```
|
|
760
|
+
|
|
761
|
+
**Subclasses disponíveis:**
|
|
762
|
+
|
|
763
|
+
| Classe | Quando é lançada |
|
|
764
|
+
| -------------------- | ------------------------------------------------------- |
|
|
765
|
+
| `VSRepoConfigError` | Configuração inválida em `setupVSRepo` ou `build` |
|
|
766
|
+
| `VSRepoBuildError` | Nome de método inválido ou desconhecido no `build` |
|
|
767
|
+
| `VSRepoExtendError` | Argumento inválido em `extend` |
|
|
768
|
+
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## Tipos utilitários
|
|
773
|
+
|
|
774
|
+
O VSRepository exporta os seguintes tipos para uso nas suas aplicações:
|
|
775
|
+
|
|
776
|
+
### Tipos de cliente
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
import type { DbClient, DbTransaction, ClientOrTransaction } from "../generated/vsrepo";
|
|
780
|
+
|
|
781
|
+
type DbClient = PrismaClient;
|
|
782
|
+
type DbTransaction = Prisma.TransactionClient;
|
|
783
|
+
type ClientOrTransaction = DbClient | DbTransaction;
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### Tipos derivados do modelo Prisma
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
import type {
|
|
790
|
+
SelectModel,
|
|
791
|
+
SelectModels,
|
|
792
|
+
WhereModel,
|
|
793
|
+
OrdenationModel,
|
|
794
|
+
PaginationModel,
|
|
795
|
+
ModelUpsertInput,
|
|
796
|
+
} from "../generated/vsrepo";
|
|
797
|
+
|
|
798
|
+
// Select de um campo específico
|
|
799
|
+
type UsuarioSelect = SelectModel<"usuario">;
|
|
800
|
+
|
|
801
|
+
// Mapa de selects nomeados
|
|
802
|
+
type UsuarioSelectModels = SelectModels<"usuario">;
|
|
803
|
+
|
|
804
|
+
// Where clause do modelo
|
|
805
|
+
type UsuarioWhere = WhereModel<"usuario">;
|
|
806
|
+
|
|
807
|
+
// OrderBy do modelo
|
|
808
|
+
type UsuarioOrder = OrdenationModel<"usuario">;
|
|
809
|
+
|
|
810
|
+
// Opções de paginação com cursor tipado
|
|
811
|
+
type UsuarioPagination = PaginationModel<"usuario">;
|
|
812
|
+
|
|
813
|
+
// Payload de criação do modelo (para upsert)
|
|
814
|
+
type UsuarioUpsertInput = ModelUpsertInput<"usuario">;
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### Todos os inputs do modelo
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
import type { PrismaModelInputs } from "../generated/vsrepo";
|
|
821
|
+
|
|
822
|
+
type UsuarioInputs = PrismaModelInputs<"usuario">;
|
|
823
|
+
// Contém:
|
|
824
|
+
// select, createInput, createManyInput, updateInput, updateManyInput,
|
|
825
|
+
// whereInput, orderByInput, cursorInput, upsertCreateInput, upsertUpdateInput
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### Tipos de opções de método
|
|
829
|
+
|
|
830
|
+
```ts
|
|
831
|
+
import type { MethodOptions, MethodOptionsModel } from "../generated/vsrepo";
|
|
832
|
+
|
|
833
|
+
// MethodOptions<S> — opções passadas nos métodos do repository
|
|
834
|
+
// S = chave do select model ou false
|
|
835
|
+
type Opts = MethodOptions<"public" | "minimal">;
|
|
836
|
+
|
|
837
|
+
// MethodOptionsModel<T> — versão derivada do tipo do repository
|
|
838
|
+
type OptsModel = MethodOptionsModel<typeof usuarioSelectModels>;
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
### Tipos de configuração
|
|
842
|
+
|
|
843
|
+
```ts
|
|
844
|
+
import type {
|
|
845
|
+
MethodConfig,
|
|
846
|
+
RepoConfig,
|
|
847
|
+
BuildConfig,
|
|
848
|
+
RepositoryRelations,
|
|
849
|
+
ExtractRelationConfig,
|
|
850
|
+
UpsertWithRelations,
|
|
851
|
+
} from "../generated/vsrepo";
|
|
852
|
+
|
|
853
|
+
// Configuração de um método dinâmico
|
|
854
|
+
type MeuMethodConfig = MethodConfig<"usuario", typeof meuSelectModels>;
|
|
855
|
+
|
|
856
|
+
// Configuração completa do repository
|
|
857
|
+
type MeuRepoConfig = RepoConfig<Usuario, "usuario">;
|
|
858
|
+
|
|
859
|
+
// Configuração do build
|
|
860
|
+
type MeuBuildConfig = BuildConfig<"public" | "minimal">;
|
|
861
|
+
|
|
862
|
+
// Tipo de relações inferidas automaticamente
|
|
863
|
+
type UsuarioRelations = RepositoryRelations<Usuario>;
|
|
864
|
+
|
|
865
|
+
// Configuração de relação inferida a partir de um campo
|
|
866
|
+
type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
|
|
867
|
+
|
|
868
|
+
// Payload do save com relações
|
|
869
|
+
type UsuarioComRelacoes = UpsertWithRelations<Usuario, "usuario", typeof relations>;
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Tipo do repository construído
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
import type { BuiltRepository, RepositoryOf } from "../generated/vsrepo";
|
|
876
|
+
|
|
877
|
+
// Tipo completo de um repository construído
|
|
878
|
+
type MeuRepo = BuiltRepository<Usuario, "usuario", typeof config, typeof buildConfig>;
|
|
879
|
+
|
|
880
|
+
// Inferência a partir de uma instância VSRepository (útil para injeção de dependência)
|
|
881
|
+
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({ ... });
|
|
882
|
+
type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
`RepositoryOf` aceita três parâmetros:
|
|
886
|
+
|
|
887
|
+
```ts
|
|
888
|
+
type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
|
|
889
|
+
// ^ BuildConfig (opcional) ^ tipo do extend (opcional)
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
Exemplo com extend tipado:
|
|
893
|
+
|
|
894
|
+
```ts
|
|
895
|
+
const extension = { buscarPorDominio: (dominio: string) => Promise<Usuario[]> };
|
|
896
|
+
type UsuarioRepositoryExtended = RepositoryOf<typeof usuarioVSRepo, undefined, typeof extension>;
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### Tipo auxiliar
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
import type { DistributiveOmit } from "../generated/vsrepo";
|
|
903
|
+
|
|
904
|
+
// Omit distributivo — preserva unions ao omitir propriedades
|
|
905
|
+
type SemEmail = DistributiveOmit<Usuario | Perfil, "email">;
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
---
|
|
909
|
+
|
|
910
|
+
## API Reference
|
|
911
|
+
|
|
912
|
+
### `setupVSRepo<T, M>()(config)`
|
|
913
|
+
|
|
914
|
+
```ts
|
|
915
|
+
setupVSRepo<TPayload, TTableName>()({
|
|
916
|
+
tableName: Uncapitalize<M>; // Nome da tabela no Prisma
|
|
917
|
+
pkName: keyof T; // Nome da primary key
|
|
918
|
+
selectModels?: SelectModels<M>; // Projeções de dados nomeadas
|
|
919
|
+
defaultSelectModel?: keyof SM; // Select aplicado por padrão
|
|
920
|
+
requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
|
|
921
|
+
relations?: RepositoryRelations<T>; // Configuração de relações
|
|
922
|
+
methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
|
|
923
|
+
});
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
### `.build(prisma, config?)`
|
|
927
|
+
|
|
928
|
+
```ts
|
|
929
|
+
vsRepo.build(prisma, {
|
|
930
|
+
freeze?: boolean; // Congela o objeto (padrão: true)
|
|
931
|
+
showWorking?: boolean; // Exibe logs internos no console
|
|
932
|
+
|
|
933
|
+
baseMethods?: {
|
|
934
|
+
get?: { active?: boolean; defaultSelect?: string };
|
|
935
|
+
remove?: { active?: boolean; defaultSelect?: string };
|
|
936
|
+
save?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
937
|
+
};
|
|
938
|
+
});
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### `.extend(fn)`
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
repo.extend((repo) => ({
|
|
945
|
+
meuMetodo() { ... }
|
|
946
|
+
}));
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## Requisitos
|
|
952
|
+
|
|
953
|
+
- Node.js 16+ (ESM)
|
|
954
|
+
- Prisma
|
|
955
|
+
- TypeScript (opcional, mas fortemente recomendado)
|
|
956
|
+
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
957
|
+
|
|
958
|
+
`tsconfig.json` recomendado:
|
|
959
|
+
|
|
960
|
+
```json
|
|
961
|
+
{
|
|
962
|
+
"compilerOptions": {
|
|
963
|
+
"target": "ES2020",
|
|
964
|
+
"module": "NodeNext",
|
|
965
|
+
"moduleResolution": "NodeNext",
|
|
966
|
+
"strict": true,
|
|
967
|
+
"skipLibCheck": true,
|
|
968
|
+
"lib": ["ES2020"]
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
## Troubleshooting
|
|
976
|
+
|
|
977
|
+
**Tipos genéricos não inferidos** — Verifique se `strict: true` e `moduleResolution: "bundler"` ou `"nodenext"` estão no `tsconfig.json`.
|
|
978
|
+
|
|
979
|
+
**Método dinâmico não existe em runtime** — O campo referenciado no nome do método deve existir no modelo Prisma. Ex.: `findByEmail` exige que o modelo tenha um campo `email`.
|
|
980
|
+
|
|
981
|
+
**`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
|
|
982
|
+
|
|
983
|
+
**Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera. Campos com `false` não serão retornados pelo Prisma.
|