vsrepo 1.0.4 → 1.0.6
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 +741 -266
- package/VSRepository/VSRepoError.d.ts +54 -54
- package/VSRepository/VSRepository.d.ts +323 -137
- package/VSRepository/VSRepository.js +3 -0
- package/package.json +15 -6
- package/scripts/configure-prisma-import.mjs +62 -14
package/README.md
CHANGED
|
@@ -1,60 +1,88 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VSRepository
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
- `findManyPaginated`
|
|
8
|
-
- `updateById`
|
|
9
|
-
- `deleteManyByIdIn`
|
|
6
|
+
Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **type inference** automático.
|
|
10
7
|
|
|
11
|
-
|
|
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
|
|
12
16
|
|
|
13
17
|
---
|
|
14
18
|
|
|
15
|
-
|
|
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
|
+
---
|
|
16
45
|
|
|
17
|
-
|
|
46
|
+
## Instalação
|
|
18
47
|
|
|
19
48
|
```bash
|
|
20
|
-
|
|
49
|
+
npm i vsrepo @prisma/client
|
|
21
50
|
```
|
|
22
51
|
|
|
23
52
|
Gere o Prisma Client:
|
|
24
53
|
|
|
25
54
|
```bash
|
|
26
|
-
|
|
55
|
+
npx prisma generate
|
|
27
56
|
```
|
|
28
57
|
|
|
29
58
|
---
|
|
30
59
|
|
|
31
|
-
|
|
60
|
+
## Gerando os tipos
|
|
32
61
|
|
|
33
|
-
O
|
|
34
|
-
|
|
35
|
-
Execute:
|
|
62
|
+
O VSRepository precisa conhecer o caminho real do seu Prisma Client para gerar as tipagens corretamente.
|
|
36
63
|
|
|
37
64
|
```bash
|
|
38
|
-
|
|
65
|
+
npx vsrepo generate
|
|
39
66
|
```
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
Equivale a:
|
|
42
69
|
|
|
43
70
|
```bash
|
|
44
|
-
|
|
71
|
+
npx vsrepo generate \
|
|
72
|
+
--output src/generated/vsrepo \
|
|
73
|
+
--prisma src/generated/prisma
|
|
45
74
|
```
|
|
46
75
|
|
|
47
|
-
|
|
76
|
+
**Flags disponíveis:**
|
|
48
77
|
|
|
49
|
-
|
|
78
|
+
| Flag | Alias | Padrão |
|
|
79
|
+
| ---------- | ----- | ---------------------- |
|
|
80
|
+
| `--output` | `-o` | `src/generated/vsrepo` |
|
|
81
|
+
| `--prisma` | `-p` | `src/generated/prisma` |
|
|
50
82
|
|
|
51
|
-
|
|
52
|
-
2. Substitui o import do Prisma pelo caminho informado em `--prisma`
|
|
53
|
-
3. Gera uma façade tipada dentro da pasta definida em `--output`
|
|
83
|
+
**Arquivos gerados:**
|
|
54
84
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```txt
|
|
85
|
+
```
|
|
58
86
|
src/generated/vsrepo/
|
|
59
87
|
├── VSRepoError.ts
|
|
60
88
|
├── VSRepoError.types.d.ts
|
|
@@ -63,125 +91,67 @@ src/generated/vsrepo/
|
|
|
63
91
|
└── index.ts
|
|
64
92
|
```
|
|
65
93
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# Como importar depois
|
|
69
|
-
|
|
70
|
-
Após gerar os arquivos:
|
|
94
|
+
Após gerar, importe sempre a partir da pasta gerada:
|
|
71
95
|
|
|
72
96
|
```ts
|
|
73
97
|
import { setupVSRepo } from "../generated/vsrepo";
|
|
98
|
+
// ou
|
|
99
|
+
import { setupVSRepo, type SelectModels, type WhereModel } from "../generated/vsrepo";
|
|
74
100
|
```
|
|
75
101
|
|
|
76
|
-
ou:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
import {
|
|
80
|
-
setupVSRepo,
|
|
81
|
-
type SelectModels,
|
|
82
|
-
type WhereModel,
|
|
83
|
-
} from "../generated/vsrepo";
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Você NÃO deve mais importar diretamente de `vsrepo`.
|
|
87
|
-
|
|
88
102
|
---
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
## Uso básico
|
|
91
105
|
|
|
92
|
-
|
|
106
|
+
### Configurando o Prisma Client
|
|
93
107
|
|
|
94
108
|
```ts
|
|
95
|
-
// src/db.ts
|
|
96
|
-
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';
|
|
97
113
|
|
|
98
|
-
const
|
|
114
|
+
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
115
|
+
const prisma = new PrismaClient({ adapter });
|
|
99
116
|
|
|
100
117
|
export default prisma;
|
|
101
118
|
```
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Criando um repository
|
|
120
|
+
### Criando um repository
|
|
106
121
|
|
|
107
122
|
```ts
|
|
108
123
|
// src/repositories/usuarioRepository.ts
|
|
124
|
+
import prisma from "../configs/db";
|
|
125
|
+
import { setupVSRepo } from "../generated/vsrepo";
|
|
126
|
+
import type { Usuario } from "../generated/prisma/client";
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
import {
|
|
113
|
-
setupVSRepo,
|
|
114
|
-
type SelectModels,
|
|
115
|
-
type WhereModel,
|
|
116
|
-
} from "../generated/vsrepo";
|
|
117
|
-
|
|
118
|
-
import type { Prisma } from "@prisma/client";
|
|
119
|
-
|
|
120
|
-
type Usuario = Prisma.usuarioGetPayload<{
|
|
121
|
-
include: {
|
|
122
|
-
perfil: true;
|
|
123
|
-
postagens: true;
|
|
124
|
-
};
|
|
125
|
-
}>;
|
|
126
|
-
|
|
127
|
-
const usuarioSelectModels = {
|
|
128
|
-
public: {
|
|
129
|
-
id: true,
|
|
130
|
-
nome: true,
|
|
131
|
-
email: true,
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
minimal: {
|
|
135
|
-
id: true,
|
|
136
|
-
},
|
|
137
|
-
} satisfies SelectModels<"usuario">;
|
|
138
|
-
|
|
139
|
-
const usuarioRequiredWhere = {
|
|
140
|
-
ativo: true,
|
|
141
|
-
} satisfies WhereModel<"usuario">;
|
|
142
|
-
|
|
143
|
-
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
128
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
144
129
|
tableName: "usuario",
|
|
145
130
|
pkName: "id",
|
|
146
131
|
|
|
147
|
-
selectModels:
|
|
132
|
+
selectModels: {
|
|
133
|
+
public: { id: true, nome: true, email: true },
|
|
134
|
+
minimal: { id: true },
|
|
135
|
+
},
|
|
148
136
|
defaultSelectModel: "public",
|
|
149
137
|
|
|
150
|
-
requiredWhere:
|
|
138
|
+
requiredWhere: { ativo: true },
|
|
151
139
|
|
|
152
140
|
methods: {
|
|
153
|
-
findByEmail: {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
findManyPaginated: {
|
|
159
|
-
map: true,
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
updateById: {
|
|
163
|
-
map: true,
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
deleteManyByIdIn: {
|
|
167
|
-
map: true,
|
|
168
|
-
whereType: "overwrite",
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
count: {
|
|
172
|
-
map: true,
|
|
173
|
-
},
|
|
141
|
+
findByEmail: { map: true, fbMode: "one" },
|
|
142
|
+
findManyPaginated: { map: true },
|
|
143
|
+
updateById: { map: true },
|
|
144
|
+
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
145
|
+
count: { map: true },
|
|
174
146
|
},
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const usuarioRepository = usuarioVSRepo.build(prisma);
|
|
147
|
+
}).build(prisma);
|
|
178
148
|
|
|
179
149
|
export default usuarioRepository;
|
|
180
150
|
```
|
|
181
151
|
|
|
182
|
-
|
|
152
|
+
> `selectModels` e `requiredWhere` podem ser declarados fora do `setupVSRepo` se você precisar exportá-los para uso em outros arquivos.
|
|
183
153
|
|
|
184
|
-
|
|
154
|
+
### Usando o repository
|
|
185
155
|
|
|
186
156
|
```ts
|
|
187
157
|
import usuarioRepository from "./repositories/usuarioRepository";
|
|
@@ -192,57 +162,196 @@ const usuario = await usuarioRepository.save({
|
|
|
192
162
|
senha: "password",
|
|
193
163
|
});
|
|
194
164
|
|
|
195
|
-
const
|
|
196
|
-
|
|
165
|
+
const encontrado = await usuarioRepository.get(usuario.id);
|
|
197
166
|
const porEmail = await usuarioRepository.findByEmail("joao@email.com");
|
|
198
167
|
|
|
199
|
-
await usuarioRepository.updateById(usuario.id, {
|
|
200
|
-
nome: "Joao Pedro",
|
|
201
|
-
});
|
|
202
|
-
|
|
168
|
+
await usuarioRepository.updateById(usuario.id, { nome: "Joao Pedro" });
|
|
203
169
|
await usuarioRepository.remove(usuario.id);
|
|
204
170
|
```
|
|
205
171
|
|
|
206
172
|
---
|
|
207
173
|
|
|
208
|
-
|
|
174
|
+
## Integração com NestJS
|
|
209
175
|
|
|
210
|
-
|
|
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
|
|
211
179
|
|
|
212
180
|
```ts
|
|
213
|
-
.
|
|
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
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
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
|
+
};
|
|
214
237
|
```
|
|
215
238
|
|
|
216
|
-
o
|
|
239
|
+
### Registrando o provider no módulo
|
|
217
240
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
```
|
|
223
256
|
|
|
224
|
-
|
|
257
|
+
### Utilizando o repository em um serviço
|
|
225
258
|
|
|
226
|
-
|
|
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
|
|
227
290
|
|
|
228
291
|
```ts
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
}
|
|
312
|
+
```
|
|
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
|
+
---
|
|
231
323
|
|
|
232
|
-
|
|
324
|
+
## Métodos base
|
|
325
|
+
|
|
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 |
|
|
333
|
+
|
|
334
|
+
Todos aceitam `options?: { selectModel?, db? }` como último argumento.
|
|
335
|
+
|
|
336
|
+
### Configurando os métodos base
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
usuarioVSRepo.build(prisma, {
|
|
340
|
+
freeze: true, // Congela o objeto (padrão: true)
|
|
341
|
+
showWorking: false, // Exibe logs internos no console
|
|
233
342
|
|
|
234
343
|
baseMethods: {
|
|
235
344
|
get: {
|
|
236
345
|
active: true,
|
|
237
346
|
defaultSelect: "public",
|
|
238
347
|
},
|
|
239
|
-
|
|
240
348
|
remove: {
|
|
349
|
+
active: true,
|
|
241
350
|
defaultSelect: "minimal",
|
|
242
351
|
},
|
|
243
|
-
|
|
244
352
|
save: {
|
|
245
353
|
active: true,
|
|
354
|
+
ignoreRequiredWhere: true, // Não aplica requiredWhere no upsert
|
|
246
355
|
},
|
|
247
356
|
},
|
|
248
357
|
});
|
|
@@ -250,135 +359,307 @@ const usuarioRepository = usuarioVSRepo.build(prisma, {
|
|
|
250
359
|
|
|
251
360
|
---
|
|
252
361
|
|
|
253
|
-
|
|
362
|
+
## Select Models
|
|
254
363
|
|
|
255
|
-
`selectModels`
|
|
364
|
+
`selectModels` define projeções de dados nomeadas e reutilizáveis.
|
|
256
365
|
|
|
257
366
|
```ts
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
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",
|
|
261
373
|
```
|
|
262
374
|
|
|
263
|
-
|
|
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:**
|
|
264
378
|
|
|
265
379
|
```ts
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
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 });
|
|
269
387
|
```
|
|
270
388
|
|
|
271
389
|
---
|
|
272
390
|
|
|
273
|
-
|
|
391
|
+
## requiredWhere
|
|
274
392
|
|
|
275
|
-
|
|
393
|
+
`requiredWhere` define filtros aplicados automaticamente em todas as queries do repository.
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
requiredWhere: { ativo: true },
|
|
397
|
+
```
|
|
398
|
+
|
|
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
|
+
---
|
|
412
|
+
|
|
413
|
+
## Métodos dinâmicos
|
|
414
|
+
|
|
415
|
+
Métodos dinâmicos são definidos na propriedade `methods` e têm seus comportamentos inferidos a partir do nome.
|
|
276
416
|
|
|
277
417
|
```ts
|
|
278
418
|
methods: {
|
|
419
|
+
findByEmail: { map: true, fbMode: "one" },
|
|
420
|
+
findManyPaginated: { map: true },
|
|
421
|
+
updateById: { map: true },
|
|
422
|
+
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
279
423
|
}
|
|
280
424
|
```
|
|
281
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
|
+
|
|
282
506
|
Exemplo:
|
|
283
507
|
|
|
284
508
|
```ts
|
|
285
509
|
methods: {
|
|
286
|
-
|
|
510
|
+
findByIdAndEmail: { map: true, fbMode: "one" },
|
|
511
|
+
findByNomeOrEmail: { map: true },
|
|
512
|
+
findUniqueByIdOrEmailAndNome: { map: true }
|
|
513
|
+
}
|
|
287
514
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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" } ] }
|
|
519
|
+
```
|
|
291
520
|
|
|
292
|
-
|
|
293
|
-
map: true,
|
|
294
|
-
},
|
|
521
|
+
---
|
|
295
522
|
|
|
296
|
-
|
|
297
|
-
map: true,
|
|
298
|
-
whereType: 'overwrite',
|
|
299
|
-
},
|
|
523
|
+
### Filtros de relação
|
|
300
524
|
|
|
301
|
-
|
|
302
|
-
map: true,
|
|
303
|
-
},
|
|
525
|
+
Permitem filtrar por campos de modelos relacionados.
|
|
304
526
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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 |
|
|
308
538
|
|
|
309
|
-
|
|
310
|
-
map: true,
|
|
311
|
-
whereType: 'overwrite',
|
|
312
|
-
},
|
|
539
|
+
Exemplos:
|
|
313
540
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
},
|
|
541
|
+
```ts
|
|
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 } }
|
|
321
548
|
}
|
|
322
549
|
```
|
|
323
550
|
|
|
324
551
|
---
|
|
325
552
|
|
|
326
|
-
|
|
553
|
+
### Sufixos de paginação e ordenação
|
|
327
554
|
|
|
328
|
-
|
|
329
|
-
const usuario = await usuarioRepository.findUniqueByIdAndEmail(id, email);
|
|
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.
|
|
330
556
|
|
|
331
|
-
|
|
557
|
+
| Sufixo | Argumentos adicionais |
|
|
558
|
+
| --------------------- | ----------------------------- |
|
|
559
|
+
| `Paginated` | `(pagination)` |
|
|
560
|
+
| `Ordered` | `(order)` |
|
|
561
|
+
| `OrderedAndPaginated` | `(order, pagination)` |
|
|
562
|
+
| `PaginatedAndOrdered` | `(pagination, order)` |
|
|
332
563
|
|
|
333
|
-
|
|
334
|
-
take: 10,
|
|
335
|
-
skip: 0,
|
|
336
|
-
});
|
|
564
|
+
Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está disponível:
|
|
337
565
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
startsWith: "A",
|
|
342
|
-
},
|
|
343
|
-
},
|
|
566
|
+
| Sufixo | Efeito |
|
|
567
|
+
| ----------------- | ---------------------------------------- |
|
|
568
|
+
| `SkipDuplicates` | Ignora registros duplicados na inserção |
|
|
344
569
|
|
|
345
|
-
|
|
346
|
-
dataCriacao: "desc",
|
|
347
|
-
},
|
|
570
|
+
Exemplos completos:
|
|
348
571
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
},
|
|
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,
|
|
585
|
+
{ dataCriacao: "desc" },
|
|
586
|
+
{ skip: 0, take: 10 }
|
|
352
587
|
);
|
|
353
588
|
```
|
|
354
589
|
|
|
590
|
+
`PaginationOptions`:
|
|
591
|
+
|
|
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
|
+
|
|
355
607
|
---
|
|
356
608
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
|
|
372
|
-
|
|
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` | `Padrão de método válido` | — | 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" },
|
|
636
|
+
|
|
637
|
+
// Adiciona um where extra além do requiredWhere
|
|
638
|
+
findManyByPerfil: { map: true, pushWhere: { deletedAt: null } },
|
|
639
|
+
|
|
640
|
+
// Ordenação fixa sem precisar passar como argumento
|
|
641
|
+
findManyPaginado: { map: true, injectOrdenation: { dataCriacao: "desc" } },
|
|
642
|
+
|
|
643
|
+
// Nome personalizado precisa de proxyTo
|
|
644
|
+
buscarPorEmailEPerfil: { map: true, proxyTo: "findByEmailAndPerfil" },
|
|
645
|
+
}
|
|
646
|
+
```
|
|
373
647
|
|
|
374
648
|
---
|
|
375
649
|
|
|
376
|
-
|
|
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`:
|
|
377
653
|
|
|
378
654
|
```ts
|
|
379
|
-
|
|
380
|
-
|
|
655
|
+
import type { Prisma } from "../generated/prisma/client";
|
|
656
|
+
|
|
657
|
+
type Usuario = Prisma.usuarioGetPayload<{
|
|
658
|
+
include: { perfil: true; postagens: true };
|
|
659
|
+
}>;
|
|
381
660
|
|
|
661
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
662
|
+
tableName: "usuario",
|
|
382
663
|
pkName: "id",
|
|
383
664
|
|
|
384
665
|
relations: {
|
|
@@ -387,17 +668,18 @@ const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
|
387
668
|
mode: "oto",
|
|
388
669
|
restriction: "set",
|
|
389
670
|
},
|
|
390
|
-
|
|
391
671
|
postagens: {
|
|
392
672
|
pk: "id",
|
|
393
673
|
mode: "otm",
|
|
394
674
|
restriction: "set",
|
|
395
675
|
},
|
|
396
676
|
},
|
|
397
|
-
});
|
|
677
|
+
}).build(prisma);
|
|
398
678
|
```
|
|
399
679
|
|
|
400
|
-
|
|
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.
|
|
681
|
+
|
|
682
|
+
**Modos de relação:**
|
|
401
683
|
|
|
402
684
|
| Modo | Relação |
|
|
403
685
|
| ----- | ------------ |
|
|
@@ -406,103 +688,296 @@ const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
|
406
688
|
| `mto` | many-to-one |
|
|
407
689
|
| `mtm` | many-to-many |
|
|
408
690
|
|
|
409
|
-
|
|
691
|
+
**Restrições:**
|
|
410
692
|
|
|
411
|
-
|
|
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 |
|
|
412
697
|
|
|
413
|
-
|
|
698
|
+
---
|
|
414
699
|
|
|
415
|
-
|
|
416
|
-
options.db;
|
|
417
|
-
```
|
|
700
|
+
## Transações
|
|
418
701
|
|
|
419
|
-
|
|
702
|
+
Todos os métodos aceitam `options.db` para participar de uma transação:
|
|
420
703
|
|
|
421
704
|
```ts
|
|
422
705
|
await prisma.$transaction(async (tx) => {
|
|
423
706
|
const usuario = await usuarioRepository.save(
|
|
424
|
-
{
|
|
425
|
-
|
|
426
|
-
email: "maria@email.com",
|
|
427
|
-
senha: "password",
|
|
428
|
-
},
|
|
429
|
-
|
|
430
|
-
{
|
|
431
|
-
db: tx,
|
|
432
|
-
},
|
|
707
|
+
{ nome: "Maria", email: "maria@email.com", senha: "password" },
|
|
708
|
+
{ db: tx }
|
|
433
709
|
);
|
|
434
710
|
|
|
435
711
|
await usuarioRepository.updateById(
|
|
436
712
|
usuario.id,
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
ativo: true,
|
|
440
|
-
},
|
|
441
|
-
|
|
442
|
-
{
|
|
443
|
-
db: tx,
|
|
444
|
-
},
|
|
713
|
+
{ ativo: true },
|
|
714
|
+
{ db: tx }
|
|
445
715
|
);
|
|
446
716
|
});
|
|
447
717
|
```
|
|
448
718
|
|
|
449
719
|
---
|
|
450
720
|
|
|
451
|
-
|
|
721
|
+
## Extendendo um repository
|
|
452
722
|
|
|
453
723
|
```ts
|
|
454
|
-
const
|
|
724
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
725
|
+
tableName: "usuario",
|
|
726
|
+
pkName: "id",
|
|
727
|
+
methods: {
|
|
728
|
+
findByEmailEndsWith: { map: true, fbMode: "one" },
|
|
729
|
+
},
|
|
730
|
+
})
|
|
455
731
|
.build(prisma)
|
|
456
|
-
|
|
457
732
|
.extend((repo) => ({
|
|
458
|
-
async
|
|
733
|
+
buscarAtivosPorDominio: async (dominio: string) => {
|
|
459
734
|
return repo.findByEmailEndsWith(`@${dominio}`);
|
|
460
735
|
},
|
|
736
|
+
|
|
737
|
+
ativarMultiplos: async (ids: string[]) => {
|
|
738
|
+
return repo.updateManyByIdIn(ids, { ativo: true });
|
|
739
|
+
},
|
|
461
740
|
}));
|
|
462
741
|
```
|
|
463
742
|
|
|
464
743
|
---
|
|
465
744
|
|
|
466
|
-
|
|
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";
|
|
467
751
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
└── services/
|
|
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
|
+
}
|
|
476
759
|
```
|
|
477
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
|
+
|
|
478
770
|
---
|
|
479
771
|
|
|
480
|
-
|
|
772
|
+
## Tipos utilitários
|
|
481
773
|
|
|
482
|
-
|
|
483
|
-
pnpm prisma generate
|
|
774
|
+
O VSRepository exporta os seguintes tipos para uso nas suas aplicações:
|
|
484
775
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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">;
|
|
488
906
|
```
|
|
489
907
|
|
|
490
908
|
---
|
|
491
909
|
|
|
492
|
-
|
|
910
|
+
## API Reference
|
|
493
911
|
|
|
494
|
-
|
|
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)
|
|
495
954
|
- Prisma
|
|
496
|
-
-
|
|
497
|
-
- `"moduleResolution": "bundler"` ou `"nodenext"`
|
|
955
|
+
- TypeScript (opcional, mas fortemente recomendado)
|
|
956
|
+
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
498
957
|
|
|
499
|
-
|
|
958
|
+
`tsconfig.json` recomendado:
|
|
500
959
|
|
|
501
960
|
```json
|
|
502
961
|
{
|
|
503
962
|
"compilerOptions": {
|
|
963
|
+
"target": "ES2020",
|
|
504
964
|
"module": "NodeNext",
|
|
505
|
-
"moduleResolution": "NodeNext"
|
|
965
|
+
"moduleResolution": "NodeNext",
|
|
966
|
+
"strict": true,
|
|
967
|
+
"skipLibCheck": true,
|
|
968
|
+
"lib": ["ES2020"]
|
|
506
969
|
}
|
|
507
970
|
}
|
|
508
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.
|