vsrepo 1.2.6 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +296 -437
  2. package/{VSRepository → dist}/VSRepoError.d.ts +12 -12
  3. package/dist/VSRepoError.js +17 -0
  4. package/{VSRepository → dist}/VSRepository.d.ts +973 -907
  5. package/dist/VSRepository.js +204 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/internal/errors/types/vs-repo-error-type.type.js +2 -0
  9. package/dist/internal/errors/vs-repo.error.js +27 -0
  10. package/dist/internal/resolvers/base-methods.resolve.js +535 -0
  11. package/dist/internal/resolvers/create-update-payloads-with-relations.resolve.js +143 -0
  12. package/dist/internal/resolvers/data-payload-with-relations.resolve.js +60 -0
  13. package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +46 -0
  14. package/dist/internal/resolvers/dinamic-method-customization.resolve.js +45 -0
  15. package/dist/internal/resolvers/dinamic-method-info.resolve.js +265 -0
  16. package/dist/internal/resolvers/merge-wheres.resolve.js +22 -0
  17. package/dist/internal/resolvers/pretty-wheres.resolve.js +87 -0
  18. package/dist/internal/resolvers/select.resolve.js +7 -0
  19. package/dist/internal/resolvers/specific-where.resolve.js +77 -0
  20. package/dist/internal/resolvers/types/base-method-function.type.js +2 -0
  21. package/dist/internal/resolvers/types/dinamic-method-customization.type.js +2 -0
  22. package/dist/internal/resolvers/types/dinamic-method-info.type.js +2 -0
  23. package/dist/internal/resolvers/types/dinamic-method-where-ops.type.js +2 -0
  24. package/dist/internal/resolvers/types/pretty-where.type.js +2 -0
  25. package/dist/internal/resolvers/types/prisma-args.type.js +2 -0
  26. package/dist/internal/resolvers/types/repository-build-instance.type.js +2 -0
  27. package/dist/internal/resolvers/types/resolve-db-and-prisma-args-data.type.js +2 -0
  28. package/dist/internal/resolvers/types/ugly-where.type.js +2 -0
  29. package/dist/internal/resolvers/ugly-where.resolve.js +178 -0
  30. package/dist/internal/utils/logger.util.js +21 -0
  31. package/dist/internal/utils/schemas.util.js +10 -0
  32. package/dist/internal/utils/uncapitalize.util.js +6 -0
  33. package/dist/internal/validation/build-config.validate.js +84 -0
  34. package/dist/internal/validation/constructor-config.validate.js +68 -0
  35. package/dist/internal/validation/extension.validate.js +15 -0
  36. package/dist/internal/validation/is-object.validate.js +6 -0
  37. package/dist/internal/validation/method-options.validate.js +26 -0
  38. package/dist/internal/validation/obj-with-relations.validate.js +37 -0
  39. package/dist/internal/validation/prisma-client.validate.js +10 -0
  40. package/dist/internal/validation/types/base-methods.type.js +2 -0
  41. package/dist/internal/validation/types/build-config.type.js +2 -0
  42. package/dist/internal/validation/types/constructor-config.type.js +2 -0
  43. package/dist/internal/validation/types/method-options.type.js +2 -0
  44. package/dist/internal/validation/types/method.type.js +2 -0
  45. package/dist/internal/validation/types/pagination.type.js +2 -0
  46. package/dist/internal/validation/types/relation.type.js +2 -0
  47. package/dist/internal/validation/types/see-mode.type.js +2 -0
  48. package/package.json +72 -70
  49. package/scripts/configure-prisma-import.mjs +3 -3
  50. package/scripts/copy-types.mjs +24 -0
  51. package/VSRepository/VSRepoError.js +0 -33
  52. package/VSRepository/VSRepoUtils.js +0 -155
  53. package/VSRepository/VSRepository.js +0 -1445
  54. package/VSRepository/index.d.ts +0 -2
  55. 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 | Descrição |
334
- | ------------ | ------------------------------------------ |
335
- | `get(pk)` | Busca um registro pela primary key |
336
- | `getOrThrow(pk)` | Busca um registro pela primary key e lança erro se não encontrar |
337
- | `save(obj)` | Cria ou atualiza (se o objeto passado tiver a `pk` faz `upsert`, se não faz `create`) |
338
- | `patch(pk, obj)` | Atualiza parcialmente um registro pela primary key (equivalente ao `update` com payload parcial) |
339
- | `remove(pk)` | Remove um registro pela primary key |
340
- | `removeList(pks)` | Remove vários registros pela lista de primary keys — retorna `{ count }` |
341
- | `getAll()` | Retorna todos os registros (aceita `pagination` e `order` no `options`) |
342
- | `total()` | Retorna o total de registros |
343
- | `has(pk)` | Verifica existência de um registro pela primary key — retorna `boolean` |
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
- freeze: true, // Congela o objeto (padrão = true)
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, // Aplica o requiredWhere no remove (padrão = false)
416
+ ignoreRequiredWhere: false,
363
417
  },
364
418
  save: {
365
- ignoreRequiredWhere: true, // Não aplica requiredWhere no upsert do save
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 | Operação Prisma | Retorno | Observações |
450
- | ------------------------- | ------------------------ | ---------------------- | -------------------------------------------------------- |
451
- | `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` (obsoleto, use `findOneBy`) para retorno único |
452
- | `findOneBy` | `findFirst` | `T \| null` | Faz o mesmo que `findBy` com `fbMode: "one"`. Retorno único. |
453
- | `findUniqueBy` | `findUnique` | `T \| null` | |
454
- | `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Lança erro se não encontrar |
455
- | `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
456
- | `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Aceita campos como filtro; lança erro se não encontrar |
457
- | `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
458
- | `findFirstOrThrow` | `findFirstOrThrow` | `T` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere`; lança erro se não encontrar |
459
- | `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
460
- | `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
461
- | `findWhere` | `findFirst` | `T \| null` | (**Obsoleto, use `findOneWhere`**) Recebe um objeto `where` explícito como argumento |
462
- | `findOneWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
463
- | `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
464
- | `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrar, `false` caso contrário |
465
- | `existsWhere` | `findFirst` | `boolean` | Recebe um objeto `where` explícito como argumento e retorna se existe |
466
- | `countBy` | `count` | `number` | Aceita campos como filtro |
467
- | `countWhere` | `count` | `number` | Recebe um objeto `where` explícito como argumento |
468
- | `count` | `count` | `number` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
469
- | `create` | `create` | `T` | Recebe `data` como argumento |
470
- | `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
471
- | `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
472
- | `updateBy` | `update` | `T` | Recebe `data` como argumento |
473
- | `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
474
- | `updateManyWhere` | `updateMany` | `{ count: number }` | Recebe um objeto `where` e um objeto `data` como argumentos |
475
- | `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
476
- | `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Recebe um objeto `where` e um objeto `data` como argumentos |
477
- | `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
478
- | `deleteBy` | `delete` | `T` | |
479
- | `deleteManyBy` | `deleteMany` | `{ count: number }` | |
480
- | `deleteManyWhere` | `deleteMany` | `{ count: number }` | Recebe um objeto `where` explícito como argumento |
481
- | `aggregate` | `aggregate` | `Dinâmico` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
482
- | `groupBy` | `groupBy` | `Dinâmico[]` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
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]`** e são ideais para filtros de intervalo em números, datas ou qualquer campo comparável:
609
+ `Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]`**:
523
610
 
524
611
  ```ts
525
612
  methods: {
526
- findManyByIdadeBetween: { map: true },
527
- findManyBySalarioNotBetween: { map: true },
528
- findManyByCriadoEmBetween: { map: true },
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 (pode ser passado como undefined no parâmetro), email é obrigatório
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 (conectados por `And`) **depois** de `AND` são injetados dentro de `AND: []`.
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 (após os filtros de campo), eles injetam automaticamente os argumentos de paginação e ordenação.
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` | — | Delega a lógica para outro padrão de método válido. Útil para methods com nomes personalizados. |
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` as gerencie automaticamente. Essas mesmas relações também são usadas no `patch`. Para que as relações apareçam no autocomplete, o tipo genérico deve incluir as relações usando `GetPayload`:
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
- Observe que é possível diferentes repositories participarem da mesma transação.
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
- findByEmailEndsWith: { map: true, fbMode: "one" },
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.findByEmailEndsWith(`@${dominio}`);
867
+ return repo.findOneByEmailEndsWith(`@${dominio}`);
966
868
  },
967
869
 
968
870
  ativarMultiplos: async (ids: string[]) => {
969
- return repo.updateManyByIdIn(ids, { ativo: true });
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 (OBS: Erros do Prisma não são sobrescritos como `VSRepoError`):
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.get();
886
+ const usuario = await usuarioRepository.getOrThrow(id);
985
887
  } catch (error) {
986
- if (error instanceof VSRepoError) {
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 = PrismaClient;
1013
- type DbTransaction = Prisma.TransactionClient;
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 diretamente de uma instância VSRepository configurada
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
- Exemplo com extend tipado:
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
- // Tipo do objeto aceito pelo .save()
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>; // Nome da tabela no Prisma
1159
- pkName: keyof T; // Nome da primary key
1160
- selectModels?: SelectModels<M>; // Projeções de dados nomeadas
1161
- defaultSelectModel?: keyof SM; // Select aplicado por padrão
1162
- requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
1163
- relations?: RepositoryRelations<T>; // Configuração de relações
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 `defaultSelect`
1180
- get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1181
- getOrThrow?:{ active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1182
- remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1183
- save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1184
- patch?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1185
- getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
1186
-
1187
- // Métodos que NÃO aceitam `defaultSelect`
1188
- removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
1189
- total?: { active?: boolean; ignoreRequiredWhere?: boolean };
1190
- has?: { active?: boolean; ignoreRequiredWhere?: boolean };
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 16+ (ESM)
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.