vsrepo 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +296 -437
- package/{VSRepository → dist}/VSRepoError.d.ts +1 -1
- package/dist/VSRepoError.js +17 -0
- package/{VSRepository → dist}/VSRepository.d.ts +937 -907
- package/dist/VSRepository.js +204 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/internal/errors/types/vs-repo-error-type.type.js +2 -0
- package/dist/internal/errors/vs-repo.error.js +27 -0
- package/dist/internal/resolvers/base-methods.resolve.js +535 -0
- package/dist/internal/resolvers/create-update-payloads-with-relations.resolve.js +143 -0
- package/dist/internal/resolvers/data-payload-with-relations.resolve.js +60 -0
- package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +46 -0
- package/dist/internal/resolvers/dinamic-method-customization.resolve.js +45 -0
- package/dist/internal/resolvers/dinamic-method-info.resolve.js +265 -0
- package/dist/internal/resolvers/merge-wheres.resolve.js +22 -0
- package/dist/internal/resolvers/pretty-wheres.resolve.js +87 -0
- package/dist/internal/resolvers/select.resolve.js +7 -0
- package/dist/internal/resolvers/specific-where.resolve.js +77 -0
- package/dist/internal/resolvers/types/base-method-function.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-customization.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-info.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-where-ops.type.js +2 -0
- package/dist/internal/resolvers/types/pretty-where.type.js +2 -0
- package/dist/internal/resolvers/types/prisma-args.type.js +2 -0
- package/dist/internal/resolvers/types/repository-build-instance.type.js +2 -0
- package/dist/internal/resolvers/types/resolve-db-and-prisma-args-data.type.js +2 -0
- package/dist/internal/resolvers/types/ugly-where.type.js +2 -0
- package/dist/internal/resolvers/ugly-where.resolve.js +178 -0
- package/dist/internal/utils/logger.util.js +21 -0
- package/dist/internal/utils/schemas.util.js +10 -0
- package/dist/internal/utils/uncapitalize.util.js +6 -0
- package/dist/internal/validation/build-config.validate.js +84 -0
- package/dist/internal/validation/constructor-config.validate.js +68 -0
- package/dist/internal/validation/extension.validate.js +15 -0
- package/dist/internal/validation/is-object.validate.js +6 -0
- package/dist/internal/validation/method-options.validate.js +26 -0
- package/dist/internal/validation/obj-with-relations.validate.js +37 -0
- package/dist/internal/validation/prisma-client.validate.js +10 -0
- package/dist/internal/validation/types/base-methods.type.js +2 -0
- package/dist/internal/validation/types/build-config.type.js +2 -0
- package/dist/internal/validation/types/constructor-config.type.js +2 -0
- package/dist/internal/validation/types/method-options.type.js +2 -0
- package/dist/internal/validation/types/method.type.js +2 -0
- package/dist/internal/validation/types/pagination.type.js +2 -0
- package/dist/internal/validation/types/relation.type.js +2 -0
- package/dist/internal/validation/types/see-mode.type.js +2 -0
- package/package.json +72 -70
- package/scripts/configure-prisma-import.mjs +3 -3
- package/scripts/copy-types.mjs +24 -0
- package/VSRepository/VSRepoError.js +0 -33
- package/VSRepository/VSRepoUtils.js +0 -155
- package/VSRepository/VSRepository.js +0 -1445
- package/VSRepository/index.d.ts +0 -2
- package/VSRepository/index.js +0 -2
package/README.md
CHANGED
|
@@ -8,11 +8,12 @@ Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte
|
|
|
8
8
|
|
|
9
9
|
O VSRepository permite criar repositories fortemente tipados com:
|
|
10
10
|
|
|
11
|
-
- **Métodos base** automáticos: `get`, `getOrThrow`, `save`, `remove`, `removeList`, `getAll`, `total`, `has`
|
|
11
|
+
- **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
|
|
12
|
+
- **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
|
|
12
13
|
- **Métodos dinâmicos** inferidos pelo nome: `findByEmail`, `findManyPaginated`, `updateById`, `deleteManyByIdIn`
|
|
13
14
|
- **Select models** reutilizáveis para diferentes projeções de dados
|
|
14
15
|
- **Type safety** em 100% das operações
|
|
15
|
-
- **Transações** nativas do Prisma
|
|
16
|
+
- **Transações** nativas do Prisma (automáticas em `saveList` e `patchList`)
|
|
16
17
|
- **Extensibilidade** com métodos personalizados
|
|
17
18
|
|
|
18
19
|
---
|
|
@@ -24,8 +25,13 @@ O VSRepository permite criar repositories fortemente tipados com:
|
|
|
24
25
|
- [Uso básico](#uso-básico)
|
|
25
26
|
- [Integração com NestJS](#integração-com-nestjs)
|
|
26
27
|
- [Métodos base](#métodos-base)
|
|
28
|
+
- [Soft-delete](#soft-delete)
|
|
29
|
+
- [Operações em lote](#operações-em-lote)
|
|
30
|
+
- [Merge](#merge)
|
|
31
|
+
- [Configurando os métodos base](#configurando-os-métodos-base)
|
|
27
32
|
- [Select Models](#select-models)
|
|
28
33
|
- [Required Where](#requiredwhere)
|
|
34
|
+
- [Opção `see`](#opção-see)
|
|
29
35
|
- [Métodos dinâmicos](#métodos-dinâmicos)
|
|
30
36
|
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
31
37
|
- [Filtros de campo](#filtros-de-campo)
|
|
@@ -130,7 +136,7 @@ import prisma from "../configs/db";
|
|
|
130
136
|
import { setupVSRepo } from "../../generated/vsrepo";
|
|
131
137
|
import type { Usuario } from "../../generated/prisma/client";
|
|
132
138
|
|
|
133
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
139
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
134
140
|
tableName: "usuario",
|
|
135
141
|
pkName: "id",
|
|
136
142
|
selectModels: {
|
|
@@ -183,7 +189,7 @@ import { setupVSRepo } from "../../../generated/vsrepo";
|
|
|
183
189
|
const userVSRepo = setupVSRepo<
|
|
184
190
|
UserGetPayload<{ include: { profile: true } }>,
|
|
185
191
|
"User"
|
|
186
|
-
>()({
|
|
192
|
+
>()(({
|
|
187
193
|
tableName: "user",
|
|
188
194
|
pkName: "id",
|
|
189
195
|
selectModels: {
|
|
@@ -291,31 +297,6 @@ export class UserService {
|
|
|
291
297
|
}
|
|
292
298
|
```
|
|
293
299
|
|
|
294
|
-
### Usando em um controller
|
|
295
|
-
|
|
296
|
-
```ts
|
|
297
|
-
// src/resources/user/user.controller.ts
|
|
298
|
-
import { Controller, Get, Post, Body, Param, Patch, Delete } from "@nestjs/common";
|
|
299
|
-
import { UserService } from "./user.service";
|
|
300
|
-
|
|
301
|
-
@Controller("users")
|
|
302
|
-
export class UserController {
|
|
303
|
-
constructor(private readonly userService: UserService) {}
|
|
304
|
-
|
|
305
|
-
@Get(":id")
|
|
306
|
-
async getUser(@Param("id") id: string) {
|
|
307
|
-
return this.userService.getUserById(id);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
@Post()
|
|
311
|
-
async createUser(
|
|
312
|
-
@Body() data: { email: string; password: string; name: string }
|
|
313
|
-
) {
|
|
314
|
-
return this.userService.createUser(data);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
300
|
**Benefícios desta abordagem:**
|
|
320
301
|
|
|
321
302
|
- ✅ Type-safe repositories com injeção de dependência
|
|
@@ -330,26 +311,99 @@ export class UserController {
|
|
|
330
311
|
|
|
331
312
|
Ao chamar `.build(prisma)` os métodos base abaixo são automaticamente disponibilizados:
|
|
332
313
|
|
|
333
|
-
| Método
|
|
334
|
-
|
|
|
335
|
-
| `get(pk)`
|
|
336
|
-
| `getOrThrow(pk)`
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
314
|
+
| Método | Descrição |
|
|
315
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
|
316
|
+
| `get(pk)` | Busca um registro pela primary key |
|
|
317
|
+
| `getOrThrow(pk)` | Busca um registro pela primary key; lança `VSRepoRuntimeError` (code `"20727"`) se não encontrado |
|
|
318
|
+
| `getList(pks)` | Busca múltiplos registros por uma lista de primary keys |
|
|
319
|
+
| `save(obj)` | Cria ou atualiza — se o objeto tiver a `pk` faz `upsert`, caso contrário faz `create` |
|
|
320
|
+
| `saveList(objs)` | Salva um array de objetos em uma única transação automática |
|
|
321
|
+
| `patch(pk, obj)` | Atualiza parcialmente um registro pela primary key |
|
|
322
|
+
| `patchList(tuples)` | Atualiza parcialmente múltiplos registros via array de tuplas `[pk, obj]` em transação automática |
|
|
323
|
+
| `merge(pk, obj)` | Busca um registro e faz um deep merge em memória — **não persiste**, retorna o objeto mesclado |
|
|
324
|
+
| `remove(pk)` | Remove um registro pela primary key |
|
|
325
|
+
| `removeList(pks)` | Remove vários registros pela lista de primary keys — retorna `{ count }` |
|
|
326
|
+
| `getAll()` | Retorna todos os registros (aceita `pagination` e `order` no `options`) |
|
|
327
|
+
| `total()` | Retorna o total de registros |
|
|
328
|
+
| `has(pk)` | Verifica existência de um registro pela primary key — retorna `boolean` |
|
|
344
329
|
|
|
345
330
|
Todos aceitam `options` como último argumento.
|
|
346
331
|
|
|
332
|
+
### Soft-delete
|
|
333
|
+
|
|
334
|
+
Quando `softRemovekName` está configurado no repository, os seguintes métodos adicionais ficam disponíveis:
|
|
335
|
+
|
|
336
|
+
| Método | Descrição |
|
|
337
|
+
| -------------------------- | --------------------------------------------------------------------------------- |
|
|
338
|
+
| `softRemove(pk)` | Marca um registro como removido preenchendo `softRemovekName` com a data atual |
|
|
339
|
+
| `softRemoveList(pks)` | Marca múltiplos registros como removidos em lote — retorna `{ count }` |
|
|
340
|
+
| `restore(pk)` | Restaura um registro soft-deletado, limpando o campo `softRemovekName` |
|
|
341
|
+
| `restoreList(pks)` | Restaura múltiplos registros soft-deletados em lote — retorna `{ count }` |
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
345
|
+
tableName: "usuario",
|
|
346
|
+
pkName: "id",
|
|
347
|
+
softRemovekName: "deletedAt", // deve ser um campo DateTime no schema do Prisma
|
|
348
|
+
}).build(prisma);
|
|
349
|
+
|
|
350
|
+
await usuarioRepository.softRemove(1);
|
|
351
|
+
await usuarioRepository.restore(1);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
> O campo informado em `softRemovekName` **deve** ser do tipo `DateTime` no schema do Prisma. O VSRepository valida isso no momento do `build` e lança `VSRepoBuildError` se o tipo for incorreto.
|
|
355
|
+
|
|
356
|
+
### Operações em lote
|
|
357
|
+
|
|
358
|
+
`saveList` e `patchList` executam todas as operações automaticamente dentro de uma única transação do Prisma. Se alguma falhar, todas as anteriores são revertidas.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
// saveList — cria ou atualiza múltiplos objetos em transação automática
|
|
362
|
+
const usuarios = await usuarioRepository.saveList([
|
|
363
|
+
{ nome: "Maria", email: "maria@email.com" },
|
|
364
|
+
{ id: 2, nome: "João Atualizado" },
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
// patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
|
|
368
|
+
const atualizados = await usuarioRepository.patchList([
|
|
369
|
+
[1, { ativo: false }],
|
|
370
|
+
[2, { nome: "Novo Nome" }],
|
|
371
|
+
]);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Quando você já está dentro de uma transação existente, passe-a em `options.db`. Nesse caso, o `db` deve ser um `DbTransaction` (não o cliente principal), pois o método não cria uma transação própria:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
await prisma.$transaction(async (tx) => {
|
|
378
|
+
await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
|
|
379
|
+
await usuarioRepository.patchList([[1, { ativo: false }]], { db: tx });
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Merge
|
|
384
|
+
|
|
385
|
+
O método `merge` busca um registro pela PK e mescla profundamente (`deepmerge`) o objeto fornecido com os dados existentes **em memória**. Ele **não persiste** as alterações — retorna o resultado mesclado para que você decida o que fazer com ele.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const existente = await usuarioRepository.get(1);
|
|
389
|
+
// existente: { id: 1, nome: "Maria", perfil: { bio: "Olá", idade: 25 } }
|
|
390
|
+
|
|
391
|
+
const mesclado = await usuarioRepository.merge(1, {
|
|
392
|
+
perfil: { bio: "Bio atualizada" },
|
|
393
|
+
});
|
|
394
|
+
// mesclado: { id: 1, nome: "Maria", perfil: { bio: "Bio atualizada", idade: 25 } }
|
|
395
|
+
|
|
396
|
+
// Para persistir, passe para save ou patch:
|
|
397
|
+
await usuarioRepository.save(mesclado);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Retorna `null` se o registro não for encontrado.
|
|
401
|
+
|
|
347
402
|
### Configurando os métodos base
|
|
348
403
|
|
|
349
404
|
```ts
|
|
350
405
|
usuarioVSRepo.build(prisma, {
|
|
351
|
-
|
|
352
|
-
showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar as queries criadas e os objetos passados para o prisma
|
|
406
|
+
showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar
|
|
353
407
|
|
|
354
408
|
baseMethods: {
|
|
355
409
|
get: {
|
|
@@ -359,17 +413,21 @@ usuarioVSRepo.build(prisma, {
|
|
|
359
413
|
remove: {
|
|
360
414
|
active: true,
|
|
361
415
|
defaultSelect: "minimal",
|
|
362
|
-
ignoreRequiredWhere: false,
|
|
416
|
+
ignoreRequiredWhere: false,
|
|
363
417
|
},
|
|
364
418
|
save: {
|
|
365
|
-
ignoreRequiredWhere: true,
|
|
419
|
+
ignoreRequiredWhere: true,
|
|
366
420
|
},
|
|
367
421
|
patch: {
|
|
368
422
|
defaultSelect: "minimal",
|
|
369
423
|
},
|
|
370
424
|
has: {
|
|
371
425
|
active: false, // Desativa o 'has' (padrão = true)
|
|
372
|
-
}
|
|
426
|
+
},
|
|
427
|
+
softRemove: {
|
|
428
|
+
active: true,
|
|
429
|
+
defaultSelect: "minimal",
|
|
430
|
+
},
|
|
373
431
|
},
|
|
374
432
|
});
|
|
375
433
|
```
|
|
@@ -423,7 +481,36 @@ const usuarios = await usuarioRepository.findMany();
|
|
|
423
481
|
const usuario = await usuarioRepository.findByEmail("joao@email.com");
|
|
424
482
|
```
|
|
425
483
|
|
|
426
|
-
Útil para soft-deletes, multi-tenancy e filtros globais de qualquer natureza.
|
|
484
|
+
Útil para soft-deletes manuais, multi-tenancy e filtros globais de qualquer natureza.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Opção `see`
|
|
489
|
+
|
|
490
|
+
Quando `softRemovekName` está configurado, todos os métodos base aceitam a opção `see` para controlar a visibilidade de registros soft-deletados:
|
|
491
|
+
|
|
492
|
+
| Valor | Comportamento |
|
|
493
|
+
| ----------- | ------------------------------------------------------------- |
|
|
494
|
+
| `"active"` | Retorna apenas registros **não** removidos (padrão) |
|
|
495
|
+
| `"removed"` | Retorna apenas registros removidos |
|
|
496
|
+
| `"all"` | Retorna todos os registros, independentemente do status |
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
// Retorna apenas usuários ativos (padrão)
|
|
500
|
+
const ativos = await usuarioRepository.getAll();
|
|
501
|
+
|
|
502
|
+
// Retorna apenas usuários removidos
|
|
503
|
+
const removidos = await usuarioRepository.getAll({ see: "removed" });
|
|
504
|
+
|
|
505
|
+
// Retorna todos
|
|
506
|
+
const todos = await usuarioRepository.getAll({ see: "all" });
|
|
507
|
+
|
|
508
|
+
// Funciona em qualquer método base
|
|
509
|
+
const usuario = await usuarioRepository.get(id, { see: "all" });
|
|
510
|
+
const existe = await usuarioRepository.has(id, { see: "removed" });
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não o substitui.
|
|
427
514
|
|
|
428
515
|
---
|
|
429
516
|
|
|
@@ -446,40 +533,40 @@ methods: {
|
|
|
446
533
|
|
|
447
534
|
O prefixo do nome do método determina qual operação Prisma será chamada e quais argumentos serão esperados.
|
|
448
535
|
|
|
449
|
-
| Prefixo
|
|
450
|
-
|
|
|
451
|
-
| `
|
|
452
|
-
| `
|
|
453
|
-
| `findUniqueBy`
|
|
454
|
-
| `findUniqueOrThrowBy`
|
|
455
|
-
| `findFirstBy`
|
|
456
|
-
| `findFirstOrThrowBy`
|
|
457
|
-
| `findFirst`
|
|
458
|
-
| `findFirstOrThrow`
|
|
459
|
-
| `findManyBy`
|
|
460
|
-
| `findMany`
|
|
461
|
-
| `
|
|
462
|
-
| `
|
|
463
|
-
| `findListWhere`
|
|
464
|
-
| `existsBy`
|
|
465
|
-
| `existsWhere`
|
|
466
|
-
| `countBy`
|
|
467
|
-
| `countWhere`
|
|
468
|
-
| `count`
|
|
469
|
-
| `create`
|
|
470
|
-
| `createMany`
|
|
471
|
-
| `createManyAndReturn`
|
|
472
|
-
| `updateBy`
|
|
473
|
-
| `updateManyBy`
|
|
474
|
-
| `updateManyWhere`
|
|
475
|
-
| `updateManyAndReturnBy`
|
|
476
|
-
| `updateManyAndReturnWhere` | `updateManyAndReturn`
|
|
477
|
-
| `upsertBy`
|
|
478
|
-
| `deleteBy`
|
|
479
|
-
| `deleteManyBy`
|
|
480
|
-
| `deleteManyWhere`
|
|
481
|
-
| `aggregate`
|
|
482
|
-
| `groupBy`
|
|
536
|
+
| Prefixo | Operação Prisma | Retorno | Observações |
|
|
537
|
+
| -------------------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------ |
|
|
538
|
+
| `findOneBy` | `findFirst` | `T \| null` | Retorno único. |
|
|
539
|
+
| `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` para retorno único (**obsoleto**, use `findOneBy`) |
|
|
540
|
+
| `findUniqueBy` | `findUnique` | `T \| null` | |
|
|
541
|
+
| `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Lança erro se não encontrar |
|
|
542
|
+
| `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
|
|
543
|
+
| `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Aceita campos como filtro; lança erro se não encontrar |
|
|
544
|
+
| `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
545
|
+
| `findFirstOrThrow` | `findFirstOrThrow` | `T` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere`; lança erro se não encontrar |
|
|
546
|
+
| `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
|
|
547
|
+
| `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
548
|
+
| `findOneWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
|
|
549
|
+
| `findWhere` | `findFirst` | `T \| null` | (**Obsoleto, use `findOneWhere`**) Recebe um objeto `where` explícito |
|
|
550
|
+
| `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
|
|
551
|
+
| `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrar, `false` caso contrário |
|
|
552
|
+
| `existsWhere` | `findFirst` | `boolean` | Recebe um objeto `where` explícito e retorna se existe |
|
|
553
|
+
| `countBy` | `count` | `number` | Aceita campos como filtro |
|
|
554
|
+
| `countWhere` | `count` | `number` | Recebe um objeto `where` explícito como argumento |
|
|
555
|
+
| `count` | `count` | `number` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
556
|
+
| `create` | `create` | `T` | Recebe `data` como argumento |
|
|
557
|
+
| `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
558
|
+
| `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
559
|
+
| `updateBy` | `update` | `T` | Recebe `data` como argumento |
|
|
560
|
+
| `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
|
|
561
|
+
| `updateManyWhere` | `updateMany` | `{ count: number }` | Recebe um objeto `where` e um objeto `data` como argumentos |
|
|
562
|
+
| `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
|
|
563
|
+
| `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Recebe um objeto `where` e um objeto `data` como argumentos |
|
|
564
|
+
| `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
|
|
565
|
+
| `deleteBy` | `delete` | `T` | |
|
|
566
|
+
| `deleteManyBy` | `deleteMany` | `{ count: number }` | |
|
|
567
|
+
| `deleteManyWhere` | `deleteMany` | `{ count: number }` | Recebe um objeto `where` explícito como argumento |
|
|
568
|
+
| `aggregate` | `aggregate` | `Dinâmico` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
|
|
569
|
+
| `groupBy` | `groupBy` | `Dinâmico[]` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
|
|
483
570
|
|
|
484
571
|
---
|
|
485
572
|
|
|
@@ -487,29 +574,29 @@ O prefixo do nome do método determina qual operação Prisma será chamada e qu
|
|
|
487
574
|
|
|
488
575
|
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`).
|
|
489
576
|
|
|
490
|
-
| Sufixo | Operador Prisma | Argumento necessário
|
|
491
|
-
| ------------------ | --------------------- |
|
|
492
|
-
| *(sem sufixo)* | igualdade (`=`) | sim
|
|
493
|
-
| `Not` | `not` | sim
|
|
494
|
-
| `In` | `in` | sim (array)
|
|
495
|
-
| `NotIn` | `notIn` | sim (array)
|
|
496
|
-
| `Contains` | `contains` | sim
|
|
497
|
-
| `NotContains` | `not.contains` | sim
|
|
498
|
-
| `StartsWith` | `startsWith` | sim
|
|
499
|
-
| `NotStartsWith` | `not.startsWith` | sim
|
|
500
|
-
| `EndsWith` | `endsWith` | sim
|
|
501
|
-
| `NotEndsWith` | `not.endsWith` | sim
|
|
502
|
-
| `GreaterThan` | `gt` | sim
|
|
503
|
-
| `GreaterThanEqual` | `gte` | sim
|
|
504
|
-
| `LessThan` | `lt` | sim
|
|
505
|
-
| `LessThanEqual` | `lte` | sim
|
|
506
|
-
| `Between` | `gte` + `lte` | sim (tupla `[min, max]`)
|
|
507
|
-
| `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`)
|
|
508
|
-
| `IsNull` | `null` | não
|
|
509
|
-
| `IsNotNull` | `not: null` | não
|
|
510
|
-
| `IsTrue` | `true` | não
|
|
511
|
-
| `IsFalse` | `false` | não
|
|
512
|
-
| `Insensitive` | `mode: 'insensitive'` | combinador
|
|
577
|
+
| Sufixo | Operador Prisma | Argumento necessário |
|
|
578
|
+
| ------------------ | --------------------- | ------------------------- |
|
|
579
|
+
| *(sem sufixo)* | igualdade (`=`) | sim |
|
|
580
|
+
| `Not` | `not` | sim |
|
|
581
|
+
| `In` | `in` | sim (array) |
|
|
582
|
+
| `NotIn` | `notIn` | sim (array) |
|
|
583
|
+
| `Contains` | `contains` | sim |
|
|
584
|
+
| `NotContains` | `not.contains` | sim |
|
|
585
|
+
| `StartsWith` | `startsWith` | sim |
|
|
586
|
+
| `NotStartsWith` | `not.startsWith` | sim |
|
|
587
|
+
| `EndsWith` | `endsWith` | sim |
|
|
588
|
+
| `NotEndsWith` | `not.endsWith` | sim |
|
|
589
|
+
| `GreaterThan` | `gt` | sim |
|
|
590
|
+
| `GreaterThanEqual` | `gte` | sim |
|
|
591
|
+
| `LessThan` | `lt` | sim |
|
|
592
|
+
| `LessThanEqual` | `lte` | sim |
|
|
593
|
+
| `Between` | `gte` + `lte` | sim (tupla `[min, max]`) |
|
|
594
|
+
| `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`) |
|
|
595
|
+
| `IsNull` | `null` | não |
|
|
596
|
+
| `IsNotNull` | `not: null` | não |
|
|
597
|
+
| `IsTrue` | `true` | não |
|
|
598
|
+
| `IsFalse` | `false` | não |
|
|
599
|
+
| `Insensitive` | `mode: 'insensitive'` | combinador |
|
|
513
600
|
|
|
514
601
|
`Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
|
|
515
602
|
|
|
@@ -519,41 +606,24 @@ findByEmailStartsWithInsensitive // { email: { startsWith: valor, mode: 'insensi
|
|
|
519
606
|
findByNomeInsensitive // { nome: { equals: valor, mode: 'insensitive' } }
|
|
520
607
|
```
|
|
521
608
|
|
|
522
|
-
`Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]
|
|
609
|
+
`Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]`**:
|
|
523
610
|
|
|
524
611
|
```ts
|
|
525
612
|
methods: {
|
|
526
|
-
findManyByIdadeBetween:
|
|
527
|
-
findManyBySalarioNotBetween:
|
|
528
|
-
findManyByCriadoEmBetween:
|
|
613
|
+
findManyByIdadeBetween: { map: true },
|
|
614
|
+
findManyBySalarioNotBetween: { map: true },
|
|
615
|
+
findManyByCriadoEmBetween: { map: true },
|
|
529
616
|
}
|
|
530
617
|
|
|
531
|
-
// Uso
|
|
532
618
|
await usuarioRepository.findManyByIdadeBetween([18, 65]);
|
|
533
619
|
await usuarioRepository.findManyBySalarioNotBetween([1000, 5000]);
|
|
534
620
|
await usuarioRepository.findManyByCriadoEmBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
|
|
535
621
|
```
|
|
536
622
|
|
|
537
|
-
Gera (`findManyByIdadeBetween`):
|
|
538
|
-
|
|
539
|
-
```ts
|
|
540
|
-
{
|
|
541
|
-
idade: { gte: 18, lte: 65 }
|
|
542
|
-
}
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
Gera (`findManyBySalarioNotBetween`):
|
|
546
|
-
|
|
547
|
-
```ts
|
|
548
|
-
{
|
|
549
|
-
salario: { not: { gte: 1000, lte: 5000 } }
|
|
550
|
-
}
|
|
551
|
-
```
|
|
552
|
-
|
|
553
623
|
O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
|
|
554
624
|
|
|
555
625
|
```ts
|
|
556
|
-
findByNomeOptionalAndEmail // nome é opcional
|
|
626
|
+
findByNomeOptionalAndEmail // nome é opcional, email é obrigatório
|
|
557
627
|
```
|
|
558
628
|
|
|
559
629
|
---
|
|
@@ -562,14 +632,14 @@ findByNomeOptionalAndEmail // nome é opcional (pode ser passado como undefined
|
|
|
562
632
|
|
|
563
633
|
| Operador | Uso no nome | Exemplo |
|
|
564
634
|
| --------- | ---------------------------- | -------------------------------- |
|
|
565
|
-
| `And` | entre dois campos | `findOneByIdAndEmail`
|
|
635
|
+
| `And` | entre dois campos | `findOneByIdAndEmail` |
|
|
566
636
|
| `Or` | entre dois campos | `findByNomeOrEmail` |
|
|
567
637
|
| `AND` | separa bloco final em `AND` | `findByEmailOrNameANDActiveStatus` |
|
|
568
638
|
|
|
569
639
|
`AND` (em capslock) tem uma regra específica:
|
|
570
640
|
|
|
571
641
|
- Só pode existir **um** `AND` por método.
|
|
572
|
-
- Todos os campos
|
|
642
|
+
- Todos os campos depois de `AND` são injetados dentro de `AND: []`.
|
|
573
643
|
- Depois de um `AND` não pode ter `Or`.
|
|
574
644
|
|
|
575
645
|
Exemplo:
|
|
@@ -582,47 +652,12 @@ methods: {
|
|
|
582
652
|
findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
|
|
583
653
|
}
|
|
584
654
|
|
|
585
|
-
// Uso
|
|
586
655
|
await usuarioRepository.findOneByIdAndEmail(1, "joao@email.com");
|
|
587
656
|
await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
|
|
588
657
|
await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "joao@email.com", "Joao");
|
|
589
658
|
await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("joao@email.com", "Joao", true, 17)
|
|
590
659
|
```
|
|
591
660
|
|
|
592
|
-
Gera (`findOneByIdAndEmail`):
|
|
593
|
-
|
|
594
|
-
```ts
|
|
595
|
-
{
|
|
596
|
-
id: 1,
|
|
597
|
-
email: "joao@email.com"
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Gera (`findByNomeOrEmail`):
|
|
602
|
-
|
|
603
|
-
```ts
|
|
604
|
-
{
|
|
605
|
-
OR: [
|
|
606
|
-
{ nome: "Joao" },
|
|
607
|
-
{ email: "joao@email.com" }
|
|
608
|
-
]
|
|
609
|
-
}
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
Gera (`findUniqueByIdOrEmailAndNome`):
|
|
613
|
-
|
|
614
|
-
```ts
|
|
615
|
-
{
|
|
616
|
-
OR: [
|
|
617
|
-
{ id: 1 },
|
|
618
|
-
{
|
|
619
|
-
email: "joao@email.com",
|
|
620
|
-
nome: "Joao"
|
|
621
|
-
}
|
|
622
|
-
]
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
661
|
Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
|
|
627
662
|
|
|
628
663
|
```ts
|
|
@@ -662,27 +697,11 @@ Permitem filtrar por campos de modelos relacionados.
|
|
|
662
697
|
| `Without` | `isNot: {}` | Relação não existe (é null) |
|
|
663
698
|
| `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
|
|
664
699
|
|
|
665
|
-
Exemplos:
|
|
666
|
-
|
|
667
|
-
```ts
|
|
668
|
-
methods: {
|
|
669
|
-
findByPostagensSomeTituloContains: { map: true }, // postagens: { some: { titulo: { contains: valor } } } >> (Busca usuários que o título de alguma postagem contém um valor)
|
|
670
|
-
findByPerfilWithDescricaoIsNotNull: { map: true }, // perfil: { is: { descricao: { not: null } } } >> (Busca usuários em que a descrição do perfil não é nula)
|
|
671
|
-
findByPerfilWithout: { map: true }, // perfil: { isNot: {} } >> (Busca usuários sem perfil)
|
|
672
|
-
findByPostagensSome: { map: true }, // postagens: { some: {} } >> (Busca usuários com alguma postagem)
|
|
673
|
-
findByPostagensEveryAtivoIsTrue: { map: true }, // postagens: { every: { ativo: true } } >> (Busca usuários que todas as postagens estão ativas)
|
|
674
|
-
findByPostagensNone: { map: true }, // postagens: { none: {} } >> (Busca usuários sem postagens)
|
|
675
|
-
}
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
> [!NOTE]
|
|
679
|
-
> Teoricamente você pode usar `Every` sem `Field` (ele geraria { every: { } }), porém isso não produz um filtro efetivo. A condição é considerada verdadeira para qualquer relação, inclusive quando não existem registros relacionados, tornando o resultado equivalente a não aplicar filtro algum.
|
|
680
|
-
|
|
681
700
|
---
|
|
682
701
|
|
|
683
702
|
### Sufixos de paginação e ordenação
|
|
684
703
|
|
|
685
|
-
Aplicados ao **final** do nome do método
|
|
704
|
+
Aplicados ao **final** do nome do método, eles injetam automaticamente os argumentos de paginação e ordenação.
|
|
686
705
|
|
|
687
706
|
| Sufixo | Argumentos adicionais |
|
|
688
707
|
| --------------------- | ----------------------------- |
|
|
@@ -697,43 +716,6 @@ Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está dispo
|
|
|
697
716
|
| ----------------- | ---------------------------------------- |
|
|
698
717
|
| `SkipDuplicates` | Ignora registros duplicados na inserção |
|
|
699
718
|
|
|
700
|
-
Exemplos completos:
|
|
701
|
-
|
|
702
|
-
```ts
|
|
703
|
-
methods: {
|
|
704
|
-
findManyPaginated: { map: true },
|
|
705
|
-
findManyByAtivoOrderedAndPaginated: { map: true },
|
|
706
|
-
findByEmailOrderedAndPaginated: { map: true },
|
|
707
|
-
createManyAndReturnSkipDuplicates: { map: true },
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// Uso
|
|
711
|
-
await usuarioRepository.findManyPaginated({ skip: 0, take: 10 });
|
|
712
|
-
|
|
713
|
-
await usuarioRepository.findManyByAtivoOrderedAndPaginated(
|
|
714
|
-
true,
|
|
715
|
-
{ dataCriacao: "desc" },
|
|
716
|
-
{ skip: 0, take: 10 }
|
|
717
|
-
);
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
`PaginationOptions`:
|
|
721
|
-
|
|
722
|
-
```ts
|
|
723
|
-
type PaginationOptions<TCursor = unknown> = {
|
|
724
|
-
skip?: number;
|
|
725
|
-
take?: number;
|
|
726
|
-
cursor?: TCursor;
|
|
727
|
-
};
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
`OrderOptions`:
|
|
731
|
-
|
|
732
|
-
```ts
|
|
733
|
-
type OrderOptions = OrderPattern | OrderPattern[];
|
|
734
|
-
// Exemplo: { dataCriacao: "desc" } ou [{ dataCriacao: "desc" }, { nome: "asc" }]
|
|
735
|
-
```
|
|
736
|
-
|
|
737
719
|
---
|
|
738
720
|
|
|
739
721
|
### Configuração de métodos
|
|
@@ -743,46 +725,20 @@ Cada entrada em `methods` aceita as seguintes opções:
|
|
|
743
725
|
| Opção | Tipo | Padrão | Descrição |
|
|
744
726
|
| ------------------- | ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
745
727
|
| `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repository. |
|
|
746
|
-
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`.
|
|
728
|
+
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`. |
|
|
747
729
|
| `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
|
|
748
730
|
| `fbMode` | `'one'` \| `'list'` | `'list'` | (**Obsoleto. Use `findOneBy`**) Somente para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
|
|
749
|
-
| `proxyTo` | `Padrão de método válido`
|
|
731
|
+
| `proxyTo` | `Padrão de método válido` | — | Delega a lógica para outro padrão de método válido. |
|
|
750
732
|
| `pushWhere` | `WhereModel<M>` | — | Where extra adicionado à query além do `requiredWhere`. |
|
|
751
733
|
| `injectOrdenation` | `OrdenationModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
|
|
752
734
|
| `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
|
|
753
735
|
|
|
754
|
-
Exemplos:
|
|
755
|
-
|
|
756
|
-
```ts
|
|
757
|
-
methods: {
|
|
758
|
-
// Retorna um único resultado
|
|
759
|
-
findOneByEmail: { map: true },
|
|
760
|
-
|
|
761
|
-
// Ignora o requiredWhere
|
|
762
|
-
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
763
|
-
|
|
764
|
-
// Usa um select model específico neste método
|
|
765
|
-
findManyByAtivo: { map: true, selectModel: "minimal" },
|
|
766
|
-
|
|
767
|
-
// Adiciona um where extra além do requiredWhere
|
|
768
|
-
findManyByPerfil: { map: true, pushWhere: { deletedAt: null } },
|
|
769
|
-
|
|
770
|
-
// Ordenação fixa sem precisar passar como argumento
|
|
771
|
-
findManyPaginado: { map: true, injectOrdenation: { dataCriacao: "desc" } },
|
|
772
|
-
|
|
773
|
-
// Nome personalizado precisa de proxyTo
|
|
774
|
-
buscarPorEmailEPerfil: { map: true, proxyTo: "findByEmailAndPerfil" },
|
|
775
|
-
}
|
|
776
|
-
```
|
|
777
|
-
|
|
778
736
|
---
|
|
779
737
|
|
|
780
738
|
### Aggregate e GroupBy
|
|
781
739
|
|
|
782
|
-
Para utilizar operações de agrupamento e agregação do Prisma (`aggregate` e `groupBy`), você deve expô-las explicitamente na configuração dos métodos dinâmicos (`methods`):
|
|
783
|
-
|
|
784
740
|
```ts
|
|
785
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
741
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
786
742
|
tableName: "usuario",
|
|
787
743
|
pkName: "id",
|
|
788
744
|
methods: {
|
|
@@ -796,65 +752,11 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
796
752
|
> Estes métodos devem ter exatamente esses nomes (`aggregate` e `groupBy`).
|
|
797
753
|
> Ao contrário dos outros métodos dinâmicos, eles recebem argumentos nativos do Prisma e **ignoram** as configurações de `selectModels`, `pushWhere` e `requiredWhere`.
|
|
798
754
|
|
|
799
|
-
#### Exemplo de uso do `aggregate`
|
|
800
|
-
|
|
801
|
-
O método `aggregate` permite calcular valores agregados (como média, soma, mínimo, máximo, contagem) sobre os registros:
|
|
802
|
-
|
|
803
|
-
```ts
|
|
804
|
-
const resultado = await usuarioRepository.aggregate({
|
|
805
|
-
_count: {
|
|
806
|
-
_all: true,
|
|
807
|
-
},
|
|
808
|
-
_avg: {
|
|
809
|
-
idade: true,
|
|
810
|
-
},
|
|
811
|
-
_sum: {
|
|
812
|
-
saldo: true,
|
|
813
|
-
},
|
|
814
|
-
where: {
|
|
815
|
-
ativo: true,
|
|
816
|
-
},
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
console.log(resultado._count._all); // Total de usuários ativos
|
|
820
|
-
console.log(resultado._avg.idade); // Média de idade dos usuários ativos
|
|
821
|
-
console.log(resultado._sum.saldo); // Soma dos saldos dos usuários ativos
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
#### Exemplo de uso do `groupBy`
|
|
825
|
-
|
|
826
|
-
O método `groupBy` permite agrupar registros por um ou mais campos para realizar operações de agregação em cada grupo:
|
|
827
|
-
|
|
828
|
-
```ts
|
|
829
|
-
const grupos = await usuarioRepository.groupBy({
|
|
830
|
-
by: ["status"],
|
|
831
|
-
_count: {
|
|
832
|
-
status: true,
|
|
833
|
-
},
|
|
834
|
-
_avg: {
|
|
835
|
-
idade: true,
|
|
836
|
-
},
|
|
837
|
-
having: {
|
|
838
|
-
idade: {
|
|
839
|
-
_avg: {
|
|
840
|
-
gt: 18,
|
|
841
|
-
},
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
for (const grupo of grupos) {
|
|
847
|
-
console.log(`Status: ${grupo.status}`);
|
|
848
|
-
console.log(`Quantidade: ${grupo._count.status}`);
|
|
849
|
-
console.log(`Média de Idade: ${grupo._avg.idade}`);
|
|
850
|
-
}
|
|
851
|
-
```
|
|
852
|
-
|
|
853
755
|
---
|
|
854
756
|
|
|
855
757
|
## Relações no save
|
|
856
758
|
|
|
857
|
-
Configure relações para que o `save`
|
|
759
|
+
Configure relações para que o `save` e o `patch` as gerenciem automaticamente (`saveList` e `pacthList` também gerenciam as relations automaticamente).
|
|
858
760
|
|
|
859
761
|
```ts
|
|
860
762
|
import type { Prisma } from "../../generated/prisma/client";
|
|
@@ -863,7 +765,7 @@ type Usuario = Prisma.usuarioGetPayload<{
|
|
|
863
765
|
include: { perfil: true; postagens: true };
|
|
864
766
|
}>;
|
|
865
767
|
|
|
866
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
768
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
867
769
|
tableName: "usuario",
|
|
868
770
|
pkName: "id",
|
|
869
771
|
|
|
@@ -882,33 +784,6 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
882
784
|
}).build(prisma);
|
|
883
785
|
```
|
|
884
786
|
|
|
885
|
-
Exemplos de uso com relações:
|
|
886
|
-
|
|
887
|
-
```ts
|
|
888
|
-
await usuarioRepository.save({
|
|
889
|
-
nome: "Maria",
|
|
890
|
-
email: "maria@email.com",
|
|
891
|
-
senha: "password",
|
|
892
|
-
perfil: {
|
|
893
|
-
bio: "Bio da Maria",
|
|
894
|
-
},
|
|
895
|
-
postagens: [
|
|
896
|
-
{ titulo: "Primeiro post", conteudo: "Olá!" },
|
|
897
|
-
],
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
await usuarioRepository.patch(1, {
|
|
901
|
-
perfil: {
|
|
902
|
-
bio: "Bio atualizada",
|
|
903
|
-
},
|
|
904
|
-
postagens: [
|
|
905
|
-
{ id: 10, titulo: "Post revisado", conteudo: "Conteúdo ajustado" },
|
|
906
|
-
],
|
|
907
|
-
});
|
|
908
|
-
```
|
|
909
|
-
|
|
910
|
-
> **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.
|
|
911
|
-
|
|
912
787
|
**Modos de relação:**
|
|
913
788
|
|
|
914
789
|
| Modo | Relação |
|
|
@@ -925,6 +800,23 @@ await usuarioRepository.patch(1, {
|
|
|
925
800
|
| `set` | Substitui completamente (remove os que não foram enviados) |
|
|
926
801
|
| `add` | Adiciona/atualiza sem remover os existentes |
|
|
927
802
|
|
|
803
|
+
**Relação `mto` com nullable:**
|
|
804
|
+
|
|
805
|
+
Use `nullable` (letra minúscula) para permitir a desvinculação de uma relação many-to-one:
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
relations: {
|
|
809
|
+
categoria: {
|
|
810
|
+
pk: "id",
|
|
811
|
+
mode: "mto",
|
|
812
|
+
restriction: "set",
|
|
813
|
+
nullable: true, // permite passar null para desvincular
|
|
814
|
+
},
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
> **Nota:** `nullAble` (com A maiúsculo) ainda é aceito por compatibilidade, mas está **obsoleto**. Prefira `nullable`.
|
|
819
|
+
|
|
928
820
|
---
|
|
929
821
|
|
|
930
822
|
## Transações
|
|
@@ -945,28 +837,38 @@ await usuarioRepository.prisma.$transaction(async (tx) => {
|
|
|
945
837
|
});
|
|
946
838
|
```
|
|
947
839
|
|
|
948
|
-
|
|
840
|
+
Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction` (não o cliente principal):
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
await prisma.$transaction(async (tx) => {
|
|
844
|
+
// CORRETO: tx é uma DbTransaction
|
|
845
|
+
await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
|
|
846
|
+
|
|
847
|
+
// ERRADO: não passe o prisma diretamente
|
|
848
|
+
// await usuarioRepository.saveList([{ nome: "Maria" }], { db: prisma });
|
|
849
|
+
});
|
|
850
|
+
```
|
|
949
851
|
|
|
950
852
|
---
|
|
951
853
|
|
|
952
854
|
## Estendendo um repository
|
|
953
855
|
|
|
954
856
|
```ts
|
|
955
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
857
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
956
858
|
tableName: "usuario",
|
|
957
859
|
pkName: "id",
|
|
958
860
|
methods: {
|
|
959
|
-
|
|
861
|
+
findOneByEmailEndsWith: { map: true },
|
|
960
862
|
},
|
|
961
863
|
})
|
|
962
864
|
.build(prisma)
|
|
963
865
|
.extend((repo) => ({
|
|
964
866
|
buscarAtivosPorDominio: async (dominio: string) => {
|
|
965
|
-
return repo.
|
|
867
|
+
return repo.findOneByEmailEndsWith(`@${dominio}`);
|
|
966
868
|
},
|
|
967
869
|
|
|
968
870
|
ativarMultiplos: async (ids: string[]) => {
|
|
969
|
-
return repo.
|
|
871
|
+
return repo.patchList(ids.map(id => [id, { ativo: true }]));
|
|
970
872
|
},
|
|
971
873
|
}));
|
|
972
874
|
```
|
|
@@ -975,15 +877,17 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
975
877
|
|
|
976
878
|
## Tratamento de erros
|
|
977
879
|
|
|
978
|
-
O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (
|
|
880
|
+
O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (erros do Prisma não são sobrescritos):
|
|
979
881
|
|
|
980
882
|
```ts
|
|
981
|
-
import { VSRepoError } from "../../generated/vsrepo";
|
|
883
|
+
import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
|
|
982
884
|
|
|
983
885
|
try {
|
|
984
|
-
const usuario = await usuarioRepository.
|
|
886
|
+
const usuario = await usuarioRepository.getOrThrow(id);
|
|
985
887
|
} catch (error) {
|
|
986
|
-
if (error instanceof
|
|
888
|
+
if (error instanceof VSRepoRuntimeError && error.code === "20727") {
|
|
889
|
+
// Registro não encontrado pelo getOrThrow
|
|
890
|
+
} else if (error instanceof VSRepoError) {
|
|
987
891
|
console.error("Erro no repository:", error.message);
|
|
988
892
|
}
|
|
989
893
|
}
|
|
@@ -991,29 +895,37 @@ try {
|
|
|
991
895
|
|
|
992
896
|
**Subclasses disponíveis:**
|
|
993
897
|
|
|
994
|
-
| Classe | Quando é lançada
|
|
995
|
-
| -------------------- |
|
|
996
|
-
| `VSRepoConfigError` | Configuração inválida em `setupVSRepo`
|
|
997
|
-
| `VSRepoBuildError` | Nome de método ou configuração inválida no `build`
|
|
998
|
-
| `VSRepoExtendError` | Argumento inválido em `extend`
|
|
999
|
-
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação
|
|
898
|
+
| Classe | Quando é lançada |
|
|
899
|
+
| -------------------- | ----------------------------------------------------------------- |
|
|
900
|
+
| `VSRepoConfigError` | Configuração inválida em `setupVSRepo` |
|
|
901
|
+
| `VSRepoBuildError` | Nome de método, tipo de campo ou configuração inválida no `build` |
|
|
902
|
+
| `VSRepoExtendError` | Argumento inválido em `extend` |
|
|
903
|
+
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
|
|
904
|
+
|
|
905
|
+
`VSRepoRuntimeError` possui a propriedade `code` para identificação programática. O código `"20727"` é lançado pelo `getOrThrow` quando o registro não é encontrado.
|
|
1000
906
|
|
|
1001
907
|
---
|
|
1002
908
|
|
|
1003
909
|
## Tipos utilitários
|
|
1004
910
|
|
|
1005
|
-
O VSRepository exporta os seguintes tipos para uso nas suas aplicações:
|
|
1006
|
-
|
|
1007
911
|
### Tipos de cliente
|
|
1008
912
|
|
|
1009
913
|
```ts
|
|
1010
914
|
import type { DbClient, DbTransaction, ClientOrTransaction } from "../../generated/vsrepo";
|
|
1011
915
|
|
|
1012
|
-
type DbClient
|
|
1013
|
-
type DbTransaction
|
|
916
|
+
type DbClient = PrismaClient;
|
|
917
|
+
type DbTransaction = Prisma.TransactionClient;
|
|
1014
918
|
type ClientOrTransaction = DbClient | DbTransaction;
|
|
1015
919
|
```
|
|
1016
920
|
|
|
921
|
+
### Tipo de visibilidade soft-delete
|
|
922
|
+
|
|
923
|
+
```ts
|
|
924
|
+
import type { SeeMode } from "../../generated/vsrepo";
|
|
925
|
+
|
|
926
|
+
type SeeMode = "active" | "removed" | "all";
|
|
927
|
+
```
|
|
928
|
+
|
|
1017
929
|
### Tipos derivados do modelo Prisma
|
|
1018
930
|
|
|
1019
931
|
```ts
|
|
@@ -1024,36 +936,8 @@ import type {
|
|
|
1024
936
|
OrdenationModel,
|
|
1025
937
|
PaginationModel,
|
|
1026
938
|
ModelUpsertInput,
|
|
939
|
+
PrismaModelInputs,
|
|
1027
940
|
} from "../../generated/vsrepo";
|
|
1028
|
-
|
|
1029
|
-
// Select de um campo específico
|
|
1030
|
-
type UsuarioSelect = SelectModel<"usuario">;
|
|
1031
|
-
|
|
1032
|
-
// Mapa de selects nomeados
|
|
1033
|
-
type UsuarioSelectModels = SelectModels<"usuario">;
|
|
1034
|
-
|
|
1035
|
-
// Where clause do modelo
|
|
1036
|
-
type UsuarioWhere = WhereModel<"usuario">;
|
|
1037
|
-
|
|
1038
|
-
// OrderBy do modelo
|
|
1039
|
-
type UsuarioOrder = OrdenationModel<"usuario">;
|
|
1040
|
-
|
|
1041
|
-
// Opções de paginação com cursor tipado
|
|
1042
|
-
type UsuarioPagination = PaginationModel<"usuario">;
|
|
1043
|
-
|
|
1044
|
-
// Payload de criação do modelo (para upsert)
|
|
1045
|
-
type UsuarioUpsertInput = ModelUpsertInput<"usuario">;
|
|
1046
|
-
```
|
|
1047
|
-
|
|
1048
|
-
### Todos os inputs do modelo
|
|
1049
|
-
|
|
1050
|
-
```ts
|
|
1051
|
-
import type { PrismaModelInputs } from "../../generated/vsrepo";
|
|
1052
|
-
|
|
1053
|
-
type UsuarioInputs = PrismaModelInputs<"usuario">;
|
|
1054
|
-
// Contém:
|
|
1055
|
-
// select, createInput, createManyInput, updateInput, updateManyInput,
|
|
1056
|
-
// whereInput, orderByInput, cursorInput, upsertCreateInput, upsertUpdateInput
|
|
1057
941
|
```
|
|
1058
942
|
|
|
1059
943
|
### Tipos de opções de método
|
|
@@ -1062,10 +946,9 @@ type UsuarioInputs = PrismaModelInputs<"usuario">;
|
|
|
1062
946
|
import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
|
|
1063
947
|
|
|
1064
948
|
// MethodOptions<S> — opções passadas nos métodos do repository
|
|
1065
|
-
// S = chave do select model ou false
|
|
1066
949
|
type Opts = MethodOptions<"public" | "minimal">;
|
|
1067
950
|
|
|
1068
|
-
// MethodOptionsModel<TRepo> — derivado
|
|
951
|
+
// MethodOptionsModel<TRepo> — derivado de uma instância VSRepository configurada
|
|
1069
952
|
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(config);
|
|
1070
953
|
type OptsModel = MethodOptionsModel<typeof usuarioVSRepo>;
|
|
1071
954
|
```
|
|
@@ -1080,21 +963,6 @@ import type {
|
|
|
1080
963
|
RepositoryRelations,
|
|
1081
964
|
ExtractRelationConfig,
|
|
1082
965
|
} from "../../generated/vsrepo";
|
|
1083
|
-
|
|
1084
|
-
// Configuração de um método dinâmico
|
|
1085
|
-
type MeuMethodConfig = MethodConfig<"usuario", typeof meuSelectModels>;
|
|
1086
|
-
|
|
1087
|
-
// Configuração completa do repository
|
|
1088
|
-
type MeuRepoConfig = RepoConfig<Usuario, "usuario">;
|
|
1089
|
-
|
|
1090
|
-
// Configuração do build
|
|
1091
|
-
type MeuBuildConfig = BuildConfig<"public" | "minimal">;
|
|
1092
|
-
|
|
1093
|
-
// Tipo de relações inferidas automaticamente
|
|
1094
|
-
type UsuarioRelations = RepositoryRelations<Usuario>;
|
|
1095
|
-
|
|
1096
|
-
// Configuração de relação inferida a partir de um campo
|
|
1097
|
-
type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
|
|
1098
966
|
```
|
|
1099
967
|
|
|
1100
968
|
### Tipo do repository construído
|
|
@@ -1102,7 +970,6 @@ type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
|
|
|
1102
970
|
```ts
|
|
1103
971
|
import type { RepositoryOf } from "../../generated/vsrepo";
|
|
1104
972
|
|
|
1105
|
-
// Inferência a partir de uma instância VSRepository (útil para injeção de dependência)
|
|
1106
973
|
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({ ... });
|
|
1107
974
|
type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
|
|
1108
975
|
```
|
|
@@ -1111,42 +978,25 @@ type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
|
|
|
1111
978
|
|
|
1112
979
|
```ts
|
|
1113
980
|
type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
|
|
1114
|
-
// ^ BuildConfig (opcional) ^ tipo do extend (opcional)
|
|
1115
981
|
```
|
|
1116
982
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
```ts
|
|
1120
|
-
const extension = { buscarPorDominio: (dominio: string) => Promise<Usuario[]> };
|
|
1121
|
-
type UsuarioRepositoryExtended = RepositoryOf<typeof usuarioVSRepo, undefined, typeof extension>;
|
|
1122
|
-
```
|
|
1123
|
-
|
|
1124
|
-
### Tipos do payload dos métodos `save` e `patch`
|
|
1125
|
-
|
|
1126
|
-
Use `SaveObject` e `PatchObject` para extrair o tipo do payload esperado pelos métodos `save` e `patch` diretamente a partir de uma instância `VSRepository` configurada. Esses tipos combinam o input base do Prisma com as relações configuradas no repository.
|
|
983
|
+
### Tipos do payload de `save` e `patch`
|
|
1127
984
|
|
|
1128
985
|
```ts
|
|
1129
986
|
import type { SaveObject, PatchObject } from "../../generated/vsrepo";
|
|
1130
|
-
import type { Prisma } from "../../generated/prisma/client";
|
|
1131
987
|
|
|
1132
|
-
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
988
|
+
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(({
|
|
1133
989
|
tableName: "usuario",
|
|
1134
990
|
pkName: "id",
|
|
1135
991
|
relations: {
|
|
1136
992
|
perfil: { pk: "id", mode: "oto", restriction: "set" },
|
|
1137
|
-
postagens: { pk: "id", mode: "otm", restriction: "add" },
|
|
1138
993
|
},
|
|
1139
994
|
});
|
|
1140
995
|
|
|
1141
|
-
|
|
1142
|
-
type UsuarioSavePayload = SaveObject<Prisma.UsuarioCreateInput, typeof usuarioVSRepo>;
|
|
1143
|
-
|
|
1144
|
-
// Tipo do objeto aceito pelo .patch()
|
|
996
|
+
type UsuarioSavePayload = SaveObject<Prisma.UsuarioCreateInput, typeof usuarioVSRepo>;
|
|
1145
997
|
type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuarioVSRepo>;
|
|
1146
998
|
```
|
|
1147
999
|
|
|
1148
|
-
> Útil para tipar DTOs, funções auxiliares ou serviços que chamam `save`/`patch` e precisam do tipo correto do payload sem referenciar diretamente os tipos internos do Prisma.
|
|
1149
|
-
|
|
1150
1000
|
---
|
|
1151
1001
|
|
|
1152
1002
|
## API Reference
|
|
@@ -1155,12 +1005,13 @@ type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuario
|
|
|
1155
1005
|
|
|
1156
1006
|
```ts
|
|
1157
1007
|
setupVSRepo<TPayload, TTableName>()({
|
|
1158
|
-
tableName: Uncapitalize<M>;
|
|
1159
|
-
pkName: keyof T;
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1008
|
+
tableName: Uncapitalize<M>; // Nome da tabela no Prisma
|
|
1009
|
+
pkName: keyof T; // Nome da primary key
|
|
1010
|
+
softRemovekName?: keyof T & string; // Campo DateTime para soft-delete (opcional)
|
|
1011
|
+
selectModels?: SelectModels<M>; // Projeções de dados nomeadas
|
|
1012
|
+
defaultSelectModel?: keyof SM; // Select aplicado por padrão
|
|
1013
|
+
requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
|
|
1014
|
+
relations?: RepositoryRelations<T>; // Configuração de relações
|
|
1164
1015
|
methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
|
|
1165
1016
|
});
|
|
1166
1017
|
```
|
|
@@ -1169,25 +1020,29 @@ setupVSRepo<TPayload, TTableName>()({
|
|
|
1169
1020
|
|
|
1170
1021
|
```ts
|
|
1171
1022
|
vsRepo.build(prisma, {
|
|
1172
|
-
freeze?: boolean; // Congela o objeto (default = true)
|
|
1173
1023
|
showWorking?: boolean; // Exibe logs internos no console (default = false)
|
|
1174
1024
|
|
|
1175
|
-
// `active` serve para definir se o método vai existir depois do build (default = true)
|
|
1176
|
-
// `defaultSelect` serve para definir qual `selectModel` esse método vai usar por default (default = `defaultSelectModel` definido no `setupVSRepo`)
|
|
1177
|
-
// `ignoreRequiredWhere` serve para definir se o método vai ignorar o `requiredWhere` definido no `setupVSRepo` (default = false)
|
|
1178
1025
|
baseMethods?: {
|
|
1179
|
-
// Métodos que podem utilizar um
|
|
1180
|
-
get?:
|
|
1181
|
-
getOrThrow?:{ active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1026
|
+
// Métodos que podem utilizar um defaultSelect
|
|
1027
|
+
get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1028
|
+
getOrThrow?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1029
|
+
getList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1030
|
+
remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1031
|
+
save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1032
|
+
saveList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1033
|
+
patch?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1034
|
+
patchList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1035
|
+
merge?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1036
|
+
getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1037
|
+
softRemove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1038
|
+
restore?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1039
|
+
|
|
1040
|
+
// Métodos que NÃO aceitam defaultSelect
|
|
1041
|
+
removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1042
|
+
softRemoveList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1043
|
+
restoreList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1044
|
+
total?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1045
|
+
has?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1191
1046
|
};
|
|
1192
1047
|
});
|
|
1193
1048
|
```
|
|
@@ -1217,7 +1072,7 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
|
|
|
1217
1072
|
|
|
1218
1073
|
## Requisitos
|
|
1219
1074
|
|
|
1220
|
-
- Node.js
|
|
1075
|
+
- Node.js 18+ (ESM)
|
|
1221
1076
|
- Prisma
|
|
1222
1077
|
- TypeScript (opcional, mas fortemente recomendado)
|
|
1223
1078
|
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
@@ -1248,3 +1103,7 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
|
|
|
1248
1103
|
**`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
|
|
1249
1104
|
|
|
1250
1105
|
**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.
|
|
1106
|
+
|
|
1107
|
+
**`softRemovekName` lança erro no build** — O campo informado deve ser do tipo `DateTime` no schema do Prisma. Tipos como `Boolean` ou `String` não são aceitos.
|
|
1108
|
+
|
|
1109
|
+
**`saveList`/`patchList` com `db` inválido** — O campo `db` nestes métodos aceita apenas `DbTransaction` (retorno de `prisma.$transaction`), não o cliente principal. Passar o `PrismaClient` diretamente causará comportamento inesperado.
|