vsrepo 1.2.9 → 1.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # VSRepository
2
2
 
3
3
  ![npm](https://img.shields.io/npm/v/vsrepo?style=flat-square)
4
- ![NPM License](https://img.shields.io/npm/l/vsrepo)
4
+ ![NPM License](https://img.shields.io/npm/l/vsrepo?style=flat-square)
5
5
  ![NPM Downloads](https://img.shields.io/npm/dt/vsrepo?style=flat-square)
6
6
 
7
7
  Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **type inference** automático.
@@ -10,12 +10,14 @@ O VSRepository permite criar repositories fortemente tipados com:
10
10
 
11
11
  - **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
12
12
  - **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
13
- - **Métodos dinâmicos** inferidos pelo nome: `findByEmail`, `findManyPaginated`, `updateById`, `deleteManyByIdIn`
13
+ - **Métodos dinâmicos** inferidos pelo nome: `findOneByEmail`, `findManyPaginated`, `updateById`, `deleteManyByNameStartsWith`
14
14
  - **Select models** reutilizáveis para diferentes projeções de dados
15
15
  - **Type safety** em 100% das operações
16
16
  - **Transações** nativas do Prisma (automáticas em `saveList` e `patchList`)
17
17
  - **Extensibilidade** com métodos personalizados
18
18
 
19
+ > 💡 Quer ver tudo isso funcionando na prática? A pasta [`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples) do repositório tem exemplos comentados e executáveis para cada funcionalidade — veja a seção [Exemplos práticos](#exemplos-práticos) abaixo.
20
+
19
21
  ---
20
22
 
21
23
  ## Sumário
@@ -30,6 +32,7 @@ O VSRepository permite criar repositories fortemente tipados com:
30
32
  - [Merge](#merge)
31
33
  - [Configurando os métodos base](#configurando-os-métodos-base)
32
34
  - [Select Models](#select-models)
35
+ - [Include Models](#include-models)
33
36
  - [Required Where](#requiredwhere)
34
37
  - [Default Ordenation](#default-ordenation)
35
38
  - [Opção `see`](#opção-see)
@@ -47,6 +50,7 @@ O VSRepository permite criar repositories fortemente tipados com:
47
50
  - [Tratamento de erros](#tratamento-de-erros)
48
51
  - [Tipos utilitários](#tipos-utilitários)
49
52
  - [API Reference](#api-reference)
53
+ - [Exemplos práticos](#exemplos-práticos)
50
54
  - [Contribuindo](#contribuindo)
51
55
  - [Requisitos](#requisitos)
52
56
  - [Troubleshooting](#troubleshooting)
@@ -137,21 +141,18 @@ import prisma from "../configs/db";
137
141
  import { setupVSRepo } from "../../generated/vsrepo";
138
142
  import type { Usuario } from "../../generated/prisma/client";
139
143
 
140
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
144
+ const usuarioRepository = setupVSRepo<Usuario, "Usuario">()(({
141
145
  tableName: "usuario",
142
146
  pkName: "id",
143
147
  selectModels: {
144
148
  public: { id: true, nome: true, email: true },
145
149
  },
146
150
  defaultSelectModel: "public",
147
- requiredWhere: { ativo: true },
148
151
  }).build(prisma);
149
152
 
150
153
  export default usuarioRepository;
151
154
  ```
152
155
 
153
- > `selectModels` e `requiredWhere` podem ser declarados fora do `setupVSRepo` se você precisar exportá-los para uso em outros arquivos.
154
-
155
156
  ### Usando o repository
156
157
 
157
158
  ```ts
@@ -230,14 +231,18 @@ const userVSRepo = setupVSRepo<
230
231
  });
231
232
 
232
233
  const setupUserRepository = (prisma: PrismaService) => {
233
- return userVSRepo.build(prisma).extend((repo) => ({
234
- buscarPorDominio: async (dominio: string) => {
235
- return repo.findByEmailEndsWith(`@${dominio}`);
236
- },
237
- }));
234
+ return userVSRepo.build(prisma);
238
235
  };
239
236
 
240
237
  export type UserRepository = ReturnType<typeof setupUserRepository>;
238
+ /*
239
+ A tipagem também pode ser inferida usando o `RepositoryOf` do VSRepository, passando o tipo do `userVSRepo`:
240
+
241
+ export type UserRepository = RepositoryOf<typeof userVSRepo>;
242
+
243
+ OBS: Caso você use o `.extend` para estender o repository ou configure os métodos base, recomenda-se
244
+ usar o `ReturnType` por ser mais simples de inferir a tipagem
245
+ */
241
246
 
242
247
  export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
243
248
 
@@ -362,7 +367,7 @@ await usuarioRepository.restore(1);
362
367
  // saveList — cria ou atualiza múltiplos objetos em transação automática
363
368
  const usuarios = await usuarioRepository.saveList([
364
369
  { nome: "Maria", email: "maria@email.com" },
365
- { id: 2, nome: "João Atualizado" },
370
+ { id: 2, nome: "João Atualizado", email: "joao@email.com" },
366
371
  ]);
367
372
 
368
373
  // patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
@@ -400,32 +405,80 @@ await usuarioRepository.save(mesclado);
400
405
 
401
406
  Retorna `null` se o registro não for encontrado.
402
407
 
408
+ **Merge de relações to-many (`otm`/`mtm`) é feito por PK, não por concatenação simples.** Para relações to-one (`oto`/`mto`), o `merge` faz um deepmerge comum do objeto. Já para relações to-many, cada item do array enviado é casado com o item existente que tem a mesma PK (definida em `relations[chave].pk`): se a PK bate, os dois objetos são mesclados entre si; se não bate (item novo, sem correspondente), ele é apenas adicionado à lista. Itens existentes que não aparecem no array enviado são mantidos.
409
+
410
+ ```ts
411
+ const existente = await usuarioRepository.get(1);
412
+ // existente: {
413
+ // id: 1,
414
+ // postagens: [
415
+ // { id: 10, titulo: "Post A", publicada: false },
416
+ // { id: 11, titulo: "Post B", publicada: true },
417
+ // ],
418
+ // }
419
+
420
+ const mesclado = await usuarioRepository.merge(1, {
421
+ postagens: [
422
+ { id: 10, publicada: true }, // mesma PK (id: 10) → mescla com o item existente
423
+ { titulo: "Post C" }, // sem PK → é adicionado como um novo item
424
+ ],
425
+ });
426
+ // mesclado: {
427
+ // id: 1,
428
+ // postagens: [
429
+ // { id: 10, titulo: "Post A", publicada: true }, // mesclado
430
+ // { id: 11, titulo: "Post B", publicada: true }, // mantido, não veio no array enviado
431
+ // { titulo: "Post C" }, // adicionado
432
+ // ],
433
+ // }
434
+ ```
435
+
436
+ > Note que o `merge` nunca remove itens de uma relação to-many — ele só mescla os que casam por PK e adiciona os que não casam. Para remover itens de uma relação, use `save`/`patch` com `restriction: "set"` na configuração da relação.
437
+
403
438
  ### Configurando os métodos base
404
439
 
440
+ O segundo argumento de `.build(prisma, config)` permite ajustar o comportamento global do repository e customizar cada método base individualmente através de `baseMethods`.
441
+
405
442
  ```ts
406
443
  usuarioVSRepo.build(prisma, {
407
- showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar
444
+ // Exibe logs internos do VSRepository no console (queries montadas, prefixo detectado,
445
+ // filtros aplicados etc). Ótimo para debugar métodos dinâmicos. Padrão = false.
446
+ showWorking: true,
408
447
 
409
448
  baseMethods: {
410
449
  get: {
450
+ // Habilita/desabilita o método no repository final. Se `false`, o método
451
+ // sequer aparece no tipo do repository (não é só um erro em runtime). Padrão = true.
411
452
  active: true,
453
+
454
+ // Select model aplicado por padrão quando o método é chamado sem `options.selectModel`.
455
+ // Sobrescreve o `defaultSelectModel` do setupVSRepo apenas para este método.
412
456
  defaultSelect: "public",
413
457
  },
414
458
  remove: {
415
459
  active: true,
416
460
  defaultSelect: "minimal",
461
+
462
+ // Quando `true`, ignora o `requiredWhere` configurado no setupVSRepo para
463
+ // este método específico — útil quando um método precisa "furar" um filtro
464
+ // global (ex.: multi-tenancy) em um caso pontual. Padrão = false.
417
465
  ignoreRequiredWhere: false,
418
466
  },
419
467
  save: {
468
+ // Aqui só `ignoreRequiredWhere` é definido — `active` e `defaultSelect`
469
+ // continuam com seus padrões (true e o `defaultSelectModel` global).
420
470
  ignoreRequiredWhere: true,
421
471
  },
422
472
  patch: {
473
+ // Somente o select é sobrescrito; o método continua ativo normalmente.
423
474
  defaultSelect: "minimal",
424
475
  },
425
476
  has: {
426
- active: false, // Desativa o 'has' (padrão = true)
477
+ active: false, // Desativa o 'has' (padrão = true) — o método some do repository
427
478
  },
428
479
  softRemove: {
480
+ // Métodos de soft-delete seguem as mesmas opções (`active`, `defaultSelect`,
481
+ // `ignoreRequiredWhere`). Só ficam disponíveis se `softRemovekName` estiver configurado.
429
482
  active: true,
430
483
  defaultSelect: "minimal",
431
484
  },
@@ -433,6 +486,8 @@ usuarioVSRepo.build(prisma, {
433
486
  });
434
487
  ```
435
488
 
489
+ > Métodos em lote/agregados como `removeList`, `softRemoveList`, `restoreList`, `total` e `has` **não** aceitam `defaultSelect` (não retornam um registro selecionável — retornam `{ count }` ou `boolean`). Nesses casos `BaseMethodConfig` fica restrito a `active` e `ignoreRequiredWhere`.
490
+
436
491
  ---
437
492
 
438
493
  ## Select Models
@@ -464,6 +519,51 @@ const usuarioCompleto = await usuarioRepository.get(id, { selectModel: false });
464
519
 
465
520
  ---
466
521
 
522
+ ## Include Models
523
+
524
+ `includeModels` funciona de forma parecida com o `selectModels`, mas em vez de receber um `select`, ele recebe um `include` válido do Prisma.
525
+
526
+ ```ts
527
+ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
528
+ tableName: "usuario",
529
+ pkName: "id",
530
+ selectModels: {
531
+ public: { id: true, nome: true, email: true },
532
+ },
533
+ defaultSelectModel: "public",
534
+ includeModels: {
535
+ comPosts: { posts: true },
536
+ comPostsEPerfil: { posts: true, perfil: true },
537
+ },
538
+ }).build(prisma);
539
+ ```
540
+
541
+ **Usando um `includeModel` na chamada:**
542
+
543
+ ```ts
544
+ const usuario = await usuarioRepository.get(id, { includeModel: "comPosts" });
545
+ ```
546
+
547
+ Nesse caso, o `select` padrão (`selectModels`/`defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
548
+
549
+ ### Diferenças em relação ao `selectModels`
550
+
551
+ - **Só pode ser passado na chamada do método**, via `options.includeModel`. Não existe `defaultIncludeModel` nem `defaultInclude` — não há como configurar um `includeModel` padrão no repository, diferente do que ocorre com `defaultSelectModel`.
552
+ - **`includeModel` e `selectModel` não podem ser passados juntos** na mesma chamada. Se um `includeModel` for informado, qualquer `selectModel` (incluindo o padrão) é ignorado.
553
+
554
+ ```ts
555
+ // CORRETO ✅ — apenas includeModel
556
+ await usuarioRepository.get(id, { includeModel: "comPosts" });
557
+
558
+ // CORRETO ✅ — apenas selectModel
559
+ await usuarioRepository.get(id, { selectModel: "public" });
560
+
561
+ // ERRADO ❌ — não é permitido combinar os dois
562
+ await usuarioRepository.get(id, { selectModel: "public", includeModel: "comPosts" });
563
+ ```
564
+
565
+ ---
566
+
467
567
  ## Required Where
468
568
 
469
569
  `requiredWhere` define filtros aplicados automaticamente em todas as queries do repository.
@@ -528,7 +628,9 @@ methods: {
528
628
 
529
629
  ---
530
630
 
531
- Quando `softRemovekName` está configurado, todos os métodos base aceitam a opção `see` para controlar a visibilidade de registros soft-deletados:
631
+ ## Opção `see`
632
+
633
+ Quando `softRemovekName` está configurado, todos os métodos aceitam a opção `see` para controlar a visibilidade de registros soft-deletados:
532
634
 
533
635
  | Valor | Comportamento |
534
636
  | ----------- | ------------------------------------------------------------- |
@@ -545,10 +647,6 @@ const removidos = await usuarioRepository.getAll({ see: "removed" });
545
647
 
546
648
  // Retorna todos
547
649
  const todos = await usuarioRepository.getAll({ see: "all" });
548
-
549
- // Funciona em qualquer método base
550
- const usuario = await usuarioRepository.get(id, { see: "all" });
551
- const existe = await usuarioRepository.has(id, { see: "removed" });
552
650
  ```
553
651
 
554
652
  > A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não o substitui.
@@ -689,16 +787,50 @@ Exemplo:
689
787
  methods: {
690
788
  findOneByIdAndEmail: { map: true },
691
789
  findByNomeOrEmail: { map: true },
692
- findUniqueByIdOrEmailAndNome: { map: true },
790
+ findFirstByIdOrEmailAndNome: { map: true },
693
791
  findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
694
792
  }
695
793
 
696
794
  await usuarioRepository.findOneByIdAndEmail(1, "joao@email.com");
697
795
  await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
698
- await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "joao@email.com", "Joao");
796
+ await usuarioRepository.findFirstByIdOrEmailAndNome(1, "joao@email.com", "Joao");
699
797
  await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("joao@email.com", "Joao", true, 17)
700
798
  ```
701
799
 
800
+ Gera (`findOneByIdAndEmail`):
801
+
802
+ ```ts
803
+ {
804
+ id: 1,
805
+ email: "joao@email.com"
806
+ }
807
+ ```
808
+
809
+ Gera (`findByNomeOrEmail`):
810
+
811
+ ```ts
812
+ {
813
+ OR: [
814
+ { nome: "Joao" },
815
+ { email: "joao@email.com" }
816
+ ]
817
+ }
818
+ ```
819
+
820
+ Gera (`findFirstByIdOrEmailAndNome`):
821
+
822
+ ```ts
823
+ {
824
+ OR: [
825
+ { id: 1 },
826
+ {
827
+ email: "joao@email.com",
828
+ nome: "Joao"
829
+ }
830
+ ]
831
+ }
832
+ ```
833
+
702
834
  Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
703
835
 
704
836
  ```ts
@@ -738,6 +870,78 @@ Permitem filtrar por campos de modelos relacionados.
738
870
  | `Without` | `isNot: {}` | Relação não existe (é null) |
739
871
  | `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
740
872
 
873
+ Considerando `usuario` com uma relação to-one `perfil` e uma relação to-many `postagens`:
874
+
875
+ ```ts
876
+ methods: {
877
+ // to-many (postagens)
878
+ findByPostagensSome: { map: true }, // tem ao menos uma postagem
879
+ findByPostagensSomeTitulo: { map: true }, // tem ao menos uma postagem com esse título
880
+ findByPostagensEveryPublicada:{ map: true }, // todas as postagens estão publicadas
881
+ findByPostagensNone: { map: true }, // não tem nenhuma postagem
882
+ findByPostagensNoneTitulo: { map: true }, // nenhuma postagem tem esse título
883
+
884
+ // to-one (perfil)
885
+ findByPerfilWith: { map: true }, // possui perfil (não é null)
886
+ findByPerfilWithBio: { map: true }, // possui perfil com essa bio
887
+ findByPerfilWithout: { map: true }, // não possui perfil (é null)
888
+ findByPerfilWithoutBio: { map: true }, // possui perfil, mas com bio diferente da informada
889
+ }
890
+
891
+ await usuarioRepository.findByPostagensSome();
892
+ await usuarioRepository.findByPostagensSomeTitulo("Meu primeiro post");
893
+ await usuarioRepository.findByPostagensEveryPublicada(true);
894
+ await usuarioRepository.findByPostagensNone();
895
+ await usuarioRepository.findByPostagensNoneTitulo("Rascunho");
896
+
897
+ await usuarioRepository.findByPerfilWith();
898
+ await usuarioRepository.findByPerfilWithBio("Olá, mundo!");
899
+ await usuarioRepository.findByPerfilWithout();
900
+ await usuarioRepository.findByPerfilWithoutBio("Bio antiga");
901
+ ```
902
+
903
+ Gera (`findByPostagensSomeTitulo`):
904
+
905
+ ```ts
906
+ {
907
+ postagens: {
908
+ some: { titulo: "Meu primeiro post" }
909
+ }
910
+ }
911
+ ```
912
+
913
+ Gera (`findByPostagensEveryPublicada`):
914
+
915
+ ```ts
916
+ {
917
+ postagens: {
918
+ every: { publicada: true }
919
+ }
920
+ }
921
+ ```
922
+
923
+ Gera (`findByPerfilWithBio`):
924
+
925
+ ```ts
926
+ {
927
+ perfil: {
928
+ is: { bio: "Olá, mundo!" }
929
+ }
930
+ }
931
+ ```
932
+
933
+ Gera (`findByPerfilWithout`):
934
+
935
+ ```ts
936
+ {
937
+ perfil: {
938
+ isNot: {}
939
+ }
940
+ }
941
+ ```
942
+
943
+ > `Some`, `None`, `With` e `Without` (sem campo) não recebem argumento — a relação inteira é testada quanto à existência de registros (`some`/`none`) ou a ser `null`/não-`null` (`is`/`isNot`). Já as variantes `SomeField`, `EveryField`, `NoneField`, `WithField` e `WithoutField` recebem o valor do campo filtrado como argumento.
944
+
741
945
  ---
742
946
 
743
947
  ### Sufixos de paginação e ordenação
@@ -841,6 +1045,20 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
841
1045
  | `set` | Substitui completamente (remove os que não foram enviados) |
842
1046
  | `add` | Adiciona/atualiza sem remover os existentes |
843
1047
 
1048
+ > [!WARNING]
1049
+ > **`set` significa coisas diferentes dependendo do `mode` da relação — e isso pode causar perda de dados se você não prestar atenção.**
1050
+ >
1051
+ > Em relações onde o registro relacionado **pertence** ao registro pai (`oto` e `otm`), "remover os que não foram enviados" significa **deletar o registro do banco** (`delete`/`deleteMany`). Já em relações onde o registro relacionado é **independente** (`mto` e `mtm`), "remover" significa apenas **desvincular** (`disconnect`/`set: []`) — o registro relacionado continua existindo no banco, só deixa de apontar para o pai (ou de estar na tabela de junção).
1052
+ >
1053
+ > | Modo | `restriction: "set"` ao omitir um item | O item continua existindo no banco? |
1054
+ > | ----- | ---------------------------------------- | ------------------------------------ |
1055
+ > | `oto` | Passar `null` no campo → **deleta** o registro relacionado (`delete: true`) | Não |
1056
+ > | `otm` | Itens fora da lista enviada → **deletados** (`deleteMany` com `notIn`) | Não |
1057
+ > | `mto` | Passar `null` no campo (com `nullable: true`) → **desvincula** (`disconnect: true`) | Sim |
1058
+ > | `mtm` | Itens fora da lista enviada → **desvinculados** da tabela de junção (`set: []`) | Sim |
1059
+ >
1060
+ > Exemplo prático: se `postagens` é `otm` com `restriction: "set"`, um `save`/`patch` que envie o usuário com apenas 2 das 5 postagens existentes vai **apagar as outras 3 postagens do banco**, não apenas desvinculá-las do usuário. Se o comportamento esperado é só desvincular sem apagar, use `restriction: "add"` (que nunca remove nada) e gerencie a remoção manualmente.
1061
+
844
1062
  **Relação `mto` com nullable:**
845
1063
 
846
1064
  Use `nullable` (letra minúscula) para permitir a desvinculação de uma relação many-to-one:
@@ -856,8 +1074,6 @@ relations: {
856
1074
  }
857
1075
  ```
858
1076
 
859
- > **Nota:** `nullAble` (com A maiúsculo) ainda é aceito por compatibilidade, mas está **obsoleto**. Prefira `nullable`.
860
-
861
1077
  ---
862
1078
 
863
1079
  ## Transações
@@ -878,15 +1094,17 @@ await usuarioRepository.prisma.$transaction(async (tx) => {
878
1094
  });
879
1095
  ```
880
1096
 
881
- Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction` (não o cliente principal):
1097
+ Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction`:
882
1098
 
883
1099
  ```ts
884
1100
  await prisma.$transaction(async (tx) => {
885
1101
  // CORRETO: tx é uma DbTransaction
886
- await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
1102
+ const usuariosCadastrados = await usuarioRepository.saveList([{ nome: "Maria" }, { nome: "Lucas" }], { db: tx });
887
1103
 
888
- // ERRADO: não passe o prisma diretamente
889
- // await usuarioRepository.saveList([{ nome: "Maria" }], { db: prisma });
1104
+ await usuarioLogsRepository.save(
1105
+ { acao: "Cadastro de usuários", data: { usuariosCadastrados: usuariosCadastrados.map(u => u.id) } },
1106
+ { db: tx }
1107
+ );
890
1108
  });
891
1109
  ```
892
1110
 
@@ -924,12 +1142,14 @@ O VSRepository lança `VSRepoError` e suas subclasses em situações específica
924
1142
  import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
925
1143
 
926
1144
  try {
927
- const usuario = await usuarioRepository.getOrThrow(id);
1145
+ const usuario = await usuarioRepository.getOrThrow("id-que-nao-existe");
928
1146
  } catch (error) {
929
1147
  if (error instanceof VSRepoRuntimeError && error.code === "20727") {
930
- // Registro não encontrado pelo getOrThrow
1148
+ console.error("Registro não encontrado");
931
1149
  } else if (error instanceof VSRepoError) {
932
1150
  console.error("Erro no repository:", error.message);
1151
+ } else {
1152
+ console.error("Erro:", error.message)
933
1153
  }
934
1154
  }
935
1155
  ```
@@ -943,7 +1163,7 @@ try {
943
1163
  | `VSRepoExtendError` | Argumento inválido em `extend` |
944
1164
  | `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
945
1165
 
946
- `VSRepoRuntimeError` possui a propriedade `code` para identificação programática. O código `"20727"` é lançado pelo `getOrThrow` quando o registro não é encontrado.
1166
+ `VSRepoRuntimeError` possui a propriedade `code` para identificação programática. O código `"20727"` é lançado pelo `getOrThrow` quando o registro não é encontrado, por exemplo.
947
1167
 
948
1168
  ---
949
1169
 
@@ -973,6 +1193,8 @@ type SeeMode = "active" | "removed" | "all";
973
1193
  import type {
974
1194
  SelectModel,
975
1195
  SelectModels,
1196
+ IncludeModel,
1197
+ IncludeModels,
976
1198
  WhereModel,
977
1199
  OrdenationModel,
978
1200
  PaginationModel,
@@ -986,14 +1208,16 @@ import type {
986
1208
  ```ts
987
1209
  import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
988
1210
 
989
- // MethodOptions<S> — opções passadas nos métodos do repository
990
- type Opts = MethodOptions<"public" | "minimal">;
1211
+ // MethodOptions<S, IM> — opções passadas nos métodos do repository
1212
+ type Opts = MethodOptions<"public" | "minimal", "comPosts">;
991
1213
 
992
1214
  // MethodOptionsModel<TRepo> — derivado de uma instância VSRepository configurada
993
1215
  const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(config);
994
1216
  type OptsModel = MethodOptionsModel<typeof usuarioVSRepo>;
995
1217
  ```
996
1218
 
1219
+ > O segundo parâmetro de `MethodOptions` (`IM`) representa as chaves válidas de `includeModels`. Quando informado, `selectModel` e `includeModel` tornam-se mutuamente exclusivos no tipo — não é possível passar os dois na mesma chamada.
1220
+
997
1221
  ### Tipos de configuração
998
1222
 
999
1223
  ```ts
@@ -1048,9 +1272,10 @@ type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuario
1048
1272
  setupVSRepo<TPayload, TTableName>()({
1049
1273
  tableName: Uncapitalize<M>; // Nome da tabela no Prisma
1050
1274
  pkName: keyof T; // Nome da primary key
1051
- softRemovekName?: keyof T & string; // Campo DateTime para soft-delete (opcional)
1052
- selectModels?: SelectModels<M>; // Projeções de dados nomeadas
1275
+ softRemovekName?: keyof T & string; // Campo DateTime para soft-delete
1276
+ selectModels?: SelectModels<M>; // Projeções de dados nomeadas (select)
1053
1277
  defaultSelectModel?: keyof SM; // Select aplicado por padrão
1278
+ includeModels?: IncludeModels<M>; // Projeções de dados nomeadas (include) — sem default, só na chamada
1054
1279
  requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
1055
1280
  defaultOrdenation?: OrdenationModel<M>; // Ordenação padrão para queries sem Ordered/injectOrdenation
1056
1281
  relations?: RepositoryRelations<T>; // Configuração de relações
@@ -1099,6 +1324,28 @@ repo.extend((repo) => ({
1099
1324
 
1100
1325
  ---
1101
1326
 
1327
+ ## Exemplos práticos
1328
+
1329
+ Além deste README, o repositório tem uma pasta **[`examples/`](https://github.com/jaobrabo123/VSRepository/tree/main/examples)** com exemplos práticos e comentados, prontos para rodar — é o melhor lugar para ver o VSRepository sendo usado em cenários reais.
1330
+
1331
+ ```
1332
+ examples/
1333
+ ├── prisma.ts # Instância do PrismaClient usada pelos exemplos
1334
+ ├── repositories.ts # Configuração dos repositories (User, Address, Product) com setupVSRepo
1335
+ └── tests/
1336
+ ├── base-methods.test.ts # Métodos base: get, save, patch, remove, getAll, total, has...
1337
+ ├── relations.test.ts # Como configurar e usar relations no save/patch e em filtros
1338
+ ├── required-where.test.ts # Como o requiredWhere é aplicado automaticamente nas queries
1339
+ ├── dynamic-methods.test.ts # Prefixos, filtros de campo, operadores lógicos e paginação/ordenação
1340
+ ├── transactions.test.ts # Transactions com options.db e acesso à instância via repository.prisma
1341
+ ├── soft-delete.test.ts # Soft-delete: softRemove, softRemoveList, restore, restoreList e SeeMode
1342
+ └── batch-methods.test.ts # Operações em lote: getList, saveList, patchList e merge
1343
+ ```
1344
+
1345
+ Cada arquivo em `tests/` é um script independente e executável (via `tsx`) que demonstra um conjunto específico de funcionalidades, com `console.log` em cada passo para você acompanhar o resultado no terminal. A própria pasta tem um [README](https://github.com/jaobrabo123/VSRepository/blob/main/examples/README.md) explicando a ordem sugerida de leitura, como configurar o ambiente e como rodar os testes.
1346
+
1347
+ ---
1348
+
1102
1349
  ## Contribuindo
1103
1350
 
1104
1351
  Contribuições são bem-vindas! Se você encontrou um bug, tem uma ideia de melhoria ou quer ajudar com a documentação, sinta-se à vontade para participar (**[Repositório do GitHub](https://github.com/jaobrabo123/VSRepository)**):
@@ -1144,10 +1391,14 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1144
1391
 
1145
1392
  **`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
1146
1393
 
1147
- **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.
1394
+ **Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera.
1395
+
1396
+ **`selectModel` e `includeModel` juntos na mesma chamada** — Não é permitido. Escolha um ou outro: se `includeModel` for informado, o `select` (incluindo o `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
1397
+
1398
+ **`includeModel` não aparece como opção padrão do repository** — Isso é esperado. Diferente de `defaultSelectModel`, não existe `defaultIncludeModel`/`defaultInclude`. Um `includeModel` só pode ser definido na chamada do método, via `options.includeModel`.
1148
1399
 
1149
1400
  **`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.
1150
1401
 
1151
1402
  **`defaultOrdenation` não está sendo aplicada** — Verifique se o método não usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered`, e se não possui `injectOrdenation` configurado. Ambos têm prioridade sobre a ordenação padrão.
1152
1403
 
1153
- **`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.
1404
+ **`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.
@@ -50,5 +50,5 @@ export declare class VSRepoExtendError extends VSRepoError {
50
50
  */
51
51
  export declare class VSRepoRuntimeError extends VSRepoError {
52
52
  readonly type: 'VSREPO_RUNTIME';
53
- constructor(message: string, code?: string);
53
+ constructor(message: string, code: string);
54
54
  }
@@ -222,14 +222,22 @@ type PrecomputedSelects<M extends Prisma.ModelName, SelectModels> = {
222
222
  [S in keyof SelectModels]: Prisma.Result<PrismaDelegate<M>, { select: SelectModels[S] }, 'findMany'> extends Array<infer U> ? U : never
223
223
  };
224
224
 
225
- type SelectedModel<M extends Prisma.ModelName, S, SelectModels> =
226
- [S] extends [false]
227
- ? FullModelType<M>
228
- : [S] extends [never]
229
- ? FullModelType<M>
230
- : [S] extends [keyof SelectModels]
231
- ? PrecomputedSelects<M, SelectModels>[S]
232
- : FullModelType<M>;
225
+ type PrecomputedIncludes<M extends Prisma.ModelName, IncludeModels> = {
226
+ [I in keyof IncludeModels]: Prisma.Result<PrismaDelegate<M>, { include: IncludeModels[I] }, 'findMany'> extends Array<infer U> ? U : never
227
+ };
228
+
229
+ type SelectedModel<M extends Prisma.ModelName, S, SelectModels, IM = never, IncludeModels = {}> =
230
+ [IM] extends [never]
231
+ ? [S] extends [false]
232
+ ? FullModelType<M>
233
+ : [S] extends [never]
234
+ ? FullModelType<M>
235
+ : [S] extends [keyof SelectModels]
236
+ ? PrecomputedSelects<M, SelectModels>[S]
237
+ : FullModelType<M>
238
+ : IM extends keyof IncludeModels
239
+ ? PrecomputedIncludes<M, IncludeModels>[IM]
240
+ : FullModelType<M>;
233
241
 
234
242
  type CleanFields<R extends string> =
235
243
  R extends `${infer F}PaginatedAndOrdered` ? F :
@@ -242,17 +250,10 @@ type CleanFields<R extends string> =
242
250
  * Additional options accepted by repository methods.
243
251
  *
244
252
  * @template S Available keys in `selectModels`.
253
+ * @template IM Available keys in `includeModels`.
245
254
  */
246
- export type MethodOptions<S> = {
247
- /**
248
- * Select model to apply to the operation.
249
- *
250
- * @note Use `false` to return the full Prisma payload without a select.
251
- */
252
- selectModel?: S | false;
253
- /**
254
- * Prisma client or transaction to use for the operation.
255
- */
255
+ export type MethodOptions<S, IM extends PropertyKey = never> = {
256
+ /** Prisma client or transaction to use for the operation. */
256
257
  db?: ClientOrTransaction;
257
258
  /**
258
259
  * Visibility mode for records with soft-delete.
@@ -264,7 +265,17 @@ export type MethodOptions<S> = {
264
265
  * - `"all"` — returns all records, ignoring deletion status.
265
266
  */
266
267
  see?: SeeMode;
267
- };
268
+ } & (
269
+ [IM] extends [never]
270
+ ? {
271
+ /** Select model to apply to the operation. Use `false` to return the full payload. */
272
+ selectModel?: S | false;
273
+ /** Include model to apply to the operation. Cannot be used together with `selectModel`. */
274
+ includeModel?: never;
275
+ }
276
+ : | { selectModel?: S | false; includeModel?: never }
277
+ | { selectModel?: never; includeModel: IM }
278
+ );
268
279
 
269
280
  /**
270
281
  * Version of `MethodOptions` derived directly from a configured `VSRepository` instance.
@@ -277,11 +288,21 @@ export type MethodOptions<S> = {
277
288
  */
278
289
  export type MethodOptionsModel<TRepo> =
279
290
  TRepo extends VSRepository<any, any, infer Config>
280
- ? MethodOptions<keyof ExtractSelectModels<Config> | false>
291
+ ? MethodOptions<keyof ExtractSelectModels<Config> | false, keyof ExtractIncludeModels<Config>>
281
292
  : never;
282
293
 
283
- type MethodFn<MethodName extends string, T, M extends Prisma.ModelName, R extends string, SelectModels, DefaultSelect extends keyof SelectModels | false, I> =
284
- <S extends keyof SelectModels | false = DefaultSelect>(...args: [...ExtractFields<T, CleanFields<R>, I>, ...ExtraArgs<MethodName, R, I>, options?: MethodOptions<S>]) => Promise<ResolveReturnType<MethodName, SelectedModel<M, S, SelectModels>>>;
294
+ type MethodOptionsWithInclude<
295
+ S,
296
+ IM extends PropertyKey,
297
+ TIncludes,
298
+ Excluded extends keyof MethodOptions<S, keyof TIncludes> = never
299
+ > = Omit<MethodOptions<S, keyof TIncludes>, Excluded>
300
+ & ({ includeModel: IM } | { includeModel?: never });
301
+
302
+ type MethodFn<MethodName extends string, T, M extends Prisma.ModelName, R extends string, SelectModels, DefaultSelect extends keyof SelectModels | false, I, IncludeModels> =
303
+ <S extends keyof SelectModels | false = DefaultSelect, IM extends keyof IncludeModels = never>(
304
+ ...args: [...ExtractFields<T, CleanFields<R>, I>, ...ExtraArgs<MethodName, R, I>, options?: MethodOptionsWithInclude<S, IM, IncludeModels>]
305
+ ) => Promise<ResolveReturnType<MethodName, SelectedModel<M, S, SelectModels, IM, IncludeModels>>>;
285
306
 
286
307
  type GetMappedMethod<K extends string, MethodConf> =
287
308
  K extends `findBy${string}` ? (MethodConf extends { fbMode: 'one' } ? 'findByOne' : 'findByList') :
@@ -332,13 +353,13 @@ type ExtractPatternBase<K extends string> =
332
353
  K extends `updateManyAndReturnWhere${infer R}` ? R :
333
354
  K extends `deleteManyWhere${infer R}` ? R : '';
334
355
 
335
- type MethodFactory<T, M extends Prisma.ModelName, K extends string, SelectModels, DefaultSelect extends keyof SelectModels | false, I, MethodConf> =
356
+ type MethodFactory<T, M extends Prisma.ModelName, K extends string, SelectModels, DefaultSelect extends keyof SelectModels | false, I, MethodConf, IncludeModels> =
336
357
  K extends `findWhere${string}`
337
358
  ? {
338
359
  /** @deprecated Use findOneWhere instead. */
339
- <S extends keyof SelectModels | false = DefaultSelect>(...args: [...ExtractFields<T, CleanFields<ExtractPatternBase<K>>, I>, ...ExtraArgs<GetMappedMethod<K, MethodConf>, ExtractPatternBase<K>, I>, options?: MethodOptions<S>]): Promise<ResolveReturnType<GetMappedMethod<K, MethodConf>, SelectedModel<M, S, SelectModels>>>;
360
+ <S extends keyof SelectModels | false = DefaultSelect, IM extends keyof IncludeModels = never>(...args: [...ExtractFields<T, CleanFields<ExtractPatternBase<K>>, I>, ...ExtraArgs<GetMappedMethod<K, MethodConf>, ExtractPatternBase<K>, I>, options?: MethodOptionsWithInclude<S, IM, IncludeModels>]): Promise<ResolveReturnType<GetMappedMethod<K, MethodConf>, SelectedModel<M, S, SelectModels, IM, IncludeModels>>>;
340
361
  }
341
- : MethodFn<GetMappedMethod<K, MethodConf>, T, M, ExtractPatternBase<K>, SelectModels, DefaultSelect, I>;
362
+ : MethodFn<GetMappedMethod<K, MethodConf>, T, M, ExtractPatternBase<K>, SelectModels, DefaultSelect, I, IncludeModels>;
342
363
 
343
364
  type ResolveSelectModel<MethodConf, GlobalConf, SelectModels> =
344
365
  MethodConf extends { selectModel: infer S } ? (S extends false ? false : S extends keyof SelectModels ? S : never) :
@@ -346,6 +367,7 @@ type ResolveSelectModel<MethodConf, GlobalConf, SelectModels> =
346
367
 
347
368
  type ExtractPkName<T, Config> = Config extends { pkName: infer PK } ? (PK extends keyof T ? PK : never) : never;
348
369
  type ExtractSelectModels<Config> = Config extends { selectModels: infer SM } ? SM : {};
370
+ type ExtractIncludeModels<Config> = Config extends { includeModels: infer IM } ? IM : {};
349
371
  type ExtractDefaultSelect<Config> = Config extends { defaultSelectModel: infer D } ? D : never;
350
372
  type ExtractRelations<Config> = Config extends { relations: infer R } ? (R extends object ? R : {}) : {};
351
373
  type ExtractSoftRemovekName<Config> = Config extends { softRemovekName: infer S } ? S : never;
@@ -370,7 +392,7 @@ type DynamicMethods<T, M extends Prisma.ModelName, Config, I> = Config extends {
370
392
  : ResolvedKey extends 'groupBy'
371
393
  ? GroupByMethod<M>
372
394
  : ResolvedKey extends string
373
- ? MethodFactory<T, M, ResolvedKey, ExtractSelectModels<Config>, ResolveSelectModel<Methods[K], Config, ExtractSelectModels<Config>>, I, Methods[K]>
395
+ ? MethodFactory<T, M, ResolvedKey, ExtractSelectModels<Config>, ResolveSelectModel<Methods[K], Config, ExtractSelectModels<Config>>, I, Methods[K], ExtractIncludeModels<Config>>
374
396
  : never
375
397
  : never
376
398
  : never;
@@ -385,6 +407,8 @@ type DynamicMethods<T, M extends Prisma.ModelName, Config, I> = Config extends {
385
407
  export type PrismaModelInputs<M extends Prisma.ModelName> = {
386
408
  /** Type of the `select` argument used in model queries. */
387
409
  select: Prisma.TypeMap['model'][M]['operations']['findMany']['args']['select'];
410
+ /** Type of the `include` argument used in model queries. */
411
+ include: Prisma.TypeMap['model'][M]['operations']['findMany']['args']['include'];
388
412
  /** Type of the `data` used in `create`. */
389
413
  createInput: Prisma.TypeMap['model'][M]['operations']['create']['args']['data'];
390
414
  /** Type of the `data` used in `createMany`. */
@@ -415,6 +439,16 @@ export type SelectModel<M extends Prisma.ModelName> = PrismaModelInputs<M>['sele
415
439
  */
416
440
  export type SelectModels<M extends Prisma.ModelName> = Record<string, SelectModel<M>>;
417
441
 
442
+ /**
443
+ * Type of the `include` object of a Prisma model.
444
+ */
445
+ export type IncludeModel<M extends Prisma.ModelName> = PrismaModelInputs<M>['include'];
446
+
447
+ /**
448
+ * Map of named, reusable includes for a Prisma model.
449
+ */
450
+ export type IncludeModels<M extends Prisma.ModelName> = Record<string, IncludeModel<M>>;
451
+
418
452
  /**
419
453
  * Type of the `where` object of a Prisma model.
420
454
  */
@@ -452,8 +486,7 @@ export type MethodConfig<M extends Prisma.ModelName, SelectModels = any> = {
452
486
  readonly proxyTo?: ValidMethodPatterns;
453
487
  /** Adds an extra `where` on top of `requiredWhere`. */
454
488
  readonly pushWhere?: WhereModel<M>;
455
- /**
456
- * Defines whether `findBy` returns a single item (`one`) or a list (`list`).
489
+ /** * Defines whether `findBy` returns a single item (`one`) or a list (`list`).
457
490
  * @deprecated Use `findOneBy` if you want to return a single result.
458
491
  */
459
492
  readonly fbMode?: 'one' | 'list';
@@ -497,8 +530,12 @@ export type BuildConfig<TSelectKeys extends PropertyKey = string> = {
497
530
  remove?: BaseMethodConfig<TSelectKeys>;
498
531
  /** Configuration for the `save` method. */
499
532
  save?: BaseMethodConfig<TSelectKeys>;
533
+ /** Configuration for the `saveList` method (batch save via transaction). */
534
+ saveList?: BaseMethodConfig<TSelectKeys>;
500
535
  /** Configuration for the `patch` method. */
501
536
  patch?: BaseMethodConfig<TSelectKeys>;
537
+ /** Configuration for the `patchList` method (batch update via transaction). */
538
+ patchList?: BaseMethodConfig<TSelectKeys>;
502
539
  /** Configuration for the `merge` method. */
503
540
  merge?: BaseMethodConfig<TSelectKeys>;
504
541
  /** Configuration for the `removeList` method (batch deletion). Does not accept select. */
@@ -509,10 +546,6 @@ export type BuildConfig<TSelectKeys extends PropertyKey = string> = {
509
546
  total?: Omit<BaseMethodConfig<TSelectKeys>, 'defaultSelect'>;
510
547
  /** Configuration for the `has` method (existence check). Does not accept select. */
511
548
  has?: Omit<BaseMethodConfig<TSelectKeys>, 'defaultSelect'>;
512
- /** Configuration for the `saveList` method (batch save via transaction). */
513
- saveList?: BaseMethodConfig<TSelectKeys>;
514
- /** Configuration for the `patchList` method (batch update via transaction). */
515
- patchList?: BaseMethodConfig<TSelectKeys>;
516
549
  /** Configuration for the `softRemove` method. Only available if `softRemovekName` is configured. */
517
550
  softRemove?: BaseMethodConfig<TSelectKeys>;
518
551
  /** Configuration for the `softRemoveList` method. Does not accept select. Only available if `softRemovekName` is configured. */
@@ -535,12 +568,16 @@ type ResolveMethodDefaultSelect<Config, C, Method extends keyof NonNullable<Buil
535
568
  ? ExtractDefaultSelect<Config>
536
569
  : false;
537
570
 
538
- type ResolveCurrentReturn<M extends Prisma.ModelName, Models, S, D> =
539
- [S] extends [false]
540
- ? FullModelType<M>
541
- : [S] extends [never]
542
- ? ([D] extends [never] ? FullModelType<M> : SelectedModel<M, D, Models>)
543
- : SelectedModel<M, S, Models>;
571
+ type ResolveCurrentReturn<M extends Prisma.ModelName, Models, S, D, IM = never, Includes = {}> =
572
+ [IM] extends [never]
573
+ ? [S] extends [false]
574
+ ? FullModelType<M>
575
+ : [S] extends [never]
576
+ ? ([D] extends [never] ? FullModelType<M> : SelectedModel<M, D, Models>)
577
+ : SelectedModel<M, S, Models>
578
+ : IM extends keyof Includes
579
+ ? PrecomputedIncludes<M, Includes>[IM]
580
+ : FullModelType<M>;
544
581
 
545
582
  // ─── helpers reused within the mapped type ───────────────────────────────────
546
583
  // ─── relation payload types ──────────────────────────────────────────────────
@@ -603,13 +640,21 @@ type RelationUpdatePayload<TField, TRelationConfig, M extends Prisma.ModelName,
603
640
  : never;
604
641
 
605
642
  /**
606
- * Payload accepted by `patch` when the repository has configured relations.
643
+ * Payload accepted by `merge` when the repository has configured relations (uses update input).
607
644
  */
608
645
  type UpdateWithRelations<T, M extends Prisma.ModelName, TRelations> =
609
646
  DistributiveOmit<PrismaModelInputs<M>['updateInput'], keyof TRelations> & {
610
647
  [K in Extract<keyof TRelations, keyof T>]?: RelationUpdatePayload<T[K], TRelations[K], M, K>;
611
648
  };
612
649
 
650
+ /**
651
+ * Payload accepted by `patch` and `patchList` when the repository has configured relations (uses create input).
652
+ */
653
+ type PatchWithRelations<T, M extends Prisma.ModelName, TRelations> =
654
+ DistributiveOmit<PrismaModelInputs<M>['updateInput'], keyof TRelations> & {
655
+ [K in Extract<keyof TRelations, keyof T>]?: RelationPayload<T[K], TRelations[K], M, K>;
656
+ };
657
+
613
658
  /**
614
659
  * Extracts the payload type of the `save` method from a configured VSRepository instance.
615
660
  */
@@ -631,17 +676,18 @@ export type PatchObject<TInput, TRepo> =
631
676
  TRepo extends VSRepository<infer T, infer M, infer Config>
632
677
  ? (Config extends { relations: infer R } ? (R extends object ? R : {}) : {}) extends infer TRelations
633
678
  ? DistributiveOmit<TInput, keyof TRelations> & {
634
- [K in Extract<keyof TRelations, keyof T>]?: RelationUpdatePayload<T[K], TRelations[K], M, K>;
679
+ [K in Extract<keyof TRelations, keyof T>]?: RelationPayload<T[K], TRelations[K], M, K>;
635
680
  }
636
681
  : never
637
682
  : never;
638
683
 
639
684
 
640
685
  type _Pk<T, Config> = WidenField<T[ExtractPkName<T, Config> extends keyof T ? ExtractPkName<T, Config> : never]>;
641
- type _Ret<M extends Prisma.ModelName, TSelects, S, TDefault> = ResolveCurrentReturn<M, TSelects, S, TDefault>;
686
+ type _Ret<M extends Prisma.ModelName, TSelects, S, TDefault, IM = never, TIncludes = {}> = ResolveCurrentReturn<M, TSelects, S, TDefault, IM, TIncludes>;
642
687
  type _DS<Config, C, Method extends keyof NonNullable<BuildConfig['baseMethods']>, TSelects = ExtractSelectModels<Config>> =
643
688
  ResolveMethodDefaultSelect<Config, C, Method, TSelects>;
644
689
  type _Sel<Config> = ExtractSelectModels<Config>;
690
+ type _Inc<Config> = ExtractIncludeModels<Config>;
645
691
  type _Def<Config> = ExtractDefaultSelect<Config>;
646
692
  type _Rel<Config> = ExtractRelations<Config>;
647
693
  type _Soft<Config> = ExtractSoftRemovekName<Config>;
@@ -660,59 +706,60 @@ type AllBaseMethods<
660
706
  TRelations = _Rel<Config>,
661
707
  TSoftKey = _Soft<Config>,
662
708
  I = PrismaModelInputs<M>,
663
- TDefaultOrdenation = _DOrd<Config>
709
+ TDefaultOrdenation = _DOrd<Config>,
710
+ TIncludes = _Inc<Config>
664
711
  > = {
665
712
  /** Fetches a record by its primary key (PK). */
666
- get: <S extends keyof TSelects | false = _DS<Config, C, 'get', TSelects>>(
667
- pk: _Pk<T, Config>, options?: MethodOptions<S>
668
- ) => Promise<_Ret<M, TSelects, S, TDefault> | null>;
713
+ get: <S extends keyof TSelects | false = _DS<Config, C, 'get', TSelects>, IM extends keyof TIncludes = never>(
714
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
715
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes> | null>;
669
716
 
670
717
  /** Fetches a record by PK and throws `VSRepoRuntimeError` if not found. */
671
- getOrThrow: <S extends keyof TSelects | false = _DS<Config, C, 'getOrThrow', TSelects>>(
672
- pk: _Pk<T, Config>, options?: MethodOptions<S>
673
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
718
+ getOrThrow: <S extends keyof TSelects | false = _DS<Config, C, 'getOrThrow', TSelects>, IM extends keyof TIncludes = never>(
719
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
720
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
674
721
 
675
722
  /** Fetches multiple records by a list of primary keys (PKs). */
676
- getList: <S extends keyof TSelects | false = _DS<Config, C, 'getList', TSelects>>(
677
- pks: _Pk<T, Config>[], options?: MethodOptions<S>
678
- ) => Promise<_Ret<M, TSelects, S, TDefault>[]>;
723
+ getList: <S extends keyof TSelects | false = _DS<Config, C, 'getList', TSelects>, IM extends keyof TIncludes = never>(
724
+ pks: _Pk<T, Config>[], options?: MethodOptionsWithInclude<S, IM, TIncludes>
725
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
679
726
 
680
727
  /** Deletes a record identified by its primary key (PK). */
681
- remove: <S extends keyof TSelects | false = _DS<Config, C, 'remove', TSelects>>(
682
- pk: _Pk<T, Config>, options?: MethodOptions<S>
683
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
728
+ remove: <S extends keyof TSelects | false = _DS<Config, C, 'remove', TSelects>, IM extends keyof TIncludes = never>(
729
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
730
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
684
731
 
685
732
  /** Inserts or updates (upsert) a record. */
686
- save: <S extends keyof TSelects | false = _DS<Config, C, 'save', TSelects>>(
687
- obj: UpsertWithRelations<T, M, TRelations>, options?: MethodOptions<S>
688
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
733
+ save: <S extends keyof TSelects | false = _DS<Config, C, 'save', TSelects>, IM extends keyof TIncludes = never>(
734
+ obj: UpsertWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
735
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
689
736
 
690
737
  /** Saves an array of objects in a single automatic transaction. */
691
- saveList: <S extends keyof TSelects | false = _DS<Config, C, 'saveList', TSelects>>(
692
- objs: UpsertWithRelations<T, M, TRelations>[], options?: Omit<MethodOptions<S>, 'db'> & { db?: DbTransaction }
693
- ) => Promise<_Ret<M, TSelects, S, TDefault>[]>;
738
+ saveList: <S extends keyof TSelects | false = _DS<Config, C, 'saveList', TSelects>, IM extends keyof TIncludes = never>(
739
+ objs: UpsertWithRelations<T, M, TRelations>[], options?: MethodOptionsWithInclude<S, IM, TIncludes, 'db'> & { db?: DbTransaction }
740
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
694
741
 
695
742
  /** Partially updates (patch) an existing record by its primary key (PK). */
696
- patch: <S extends keyof TSelects | false = _DS<Config, C, 'patch', TSelects>>(
697
- pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>, options?: MethodOptions<S>
698
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
743
+ patch: <S extends keyof TSelects | false = _DS<Config, C, 'patch', TSelects>, IM extends keyof TIncludes = never>(
744
+ pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
745
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
699
746
 
700
747
  /** Partially updates multiple records via `[pk, obj]` tuples in an automatic transaction. */
701
- patchList: <S extends keyof TSelects | false = _DS<Config, C, 'patchList', TSelects>>(
702
- tuples: [pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>][], options?: Omit<MethodOptions<S>, 'db'> & { db?: DbTransaction }
703
- ) => Promise<_Ret<M, TSelects, S, TDefault>[]>;
748
+ patchList: <S extends keyof TSelects | false = _DS<Config, C, 'patchList', TSelects>, IM extends keyof TIncludes = never>(
749
+ tuples: [pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>][], options?: MethodOptionsWithInclude<S, IM, TIncludes, 'db'> & { db?: DbTransaction }
750
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
704
751
 
705
752
  /** Fetches a record by PK and deep-merges it with the provided object **in memory**. */
706
- merge: <S extends keyof TSelects | false = _DS<Config, C, 'merge', TSelects>>(
707
- pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>, options?: MethodOptions<S>
708
- ) => Promise<_Ret<M, TSelects, S, TDefault> | null>;
753
+ merge: <S extends keyof TSelects | false = _DS<Config, C, 'merge', TSelects>, IM extends keyof TIncludes = never>(
754
+ pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
755
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes> | null>;
709
756
 
710
757
  /** Deletes multiple records by their primary keys. */
711
758
  removeList: (pks: _Pk<T, Config>[], options?: { db?: ClientOrTransaction }) => Promise<{ count: number }>;
712
759
 
713
760
  /** Fetches all records (respects `requiredWhere` when set). */
714
- getAll: <S extends keyof TSelects | false = _DS<Config, C, 'getAll', TSelects>>(
715
- options?: MethodOptions<S> & {
761
+ getAll: <S extends keyof TSelects | false = _DS<Config, C, 'getAll', TSelects>, IM extends keyof TIncludes = never>(
762
+ options?: MethodOptionsWithInclude<S, IM, TIncludes> & {
716
763
  pagination?: PaginationOptions<I extends { cursorInput: infer Curs } ? Curs : unknown>;
717
764
  /**
718
765
  * Ordering to apply to the query.
@@ -721,7 +768,7 @@ type AllBaseMethods<
721
768
  */
722
769
  order?: I extends { orderByInput: infer OB } ? OB : OrderOptions;
723
770
  }
724
- ) => Promise<_Ret<M, TSelects, S, TDefault>[]>;
771
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
725
772
 
726
773
  /** Returns the total number of records. */
727
774
  total: (options?: { db?: ClientOrTransaction; see?: SeeMode }) => Promise<number>;
@@ -730,17 +777,17 @@ type AllBaseMethods<
730
777
  has: (pk: _Pk<T, Config>, options?: { db?: ClientOrTransaction; see?: SeeMode }) => Promise<boolean>;
731
778
 
732
779
  /** Marks a record as deleted (soft-delete). */
733
- softRemove: <S extends keyof TSelects | false = _DS<Config, C, 'softRemove', TSelects>>(
734
- pk: _Pk<T, Config>, options?: Omit<MethodOptions<S>, 'see'>
735
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
780
+ softRemove: <S extends keyof TSelects | false = _DS<Config, C, 'softRemove', TSelects>, IM extends keyof TIncludes = never>(
781
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes, 'see'>
782
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
736
783
 
737
784
  /** Marks multiple records as deleted (soft-delete) in batch. */
738
785
  softRemoveList: (pks: _Pk<T, Config>[], options?: { db?: ClientOrTransaction }) => Promise<{ count: number }>;
739
786
 
740
787
  /** Restores a record previously marked as deleted (soft-delete). */
741
- restore: <S extends keyof TSelects | false = _DS<Config, C, 'restore', TSelects>>(
742
- pk: _Pk<T, Config>, options?: Omit<MethodOptions<S>, 'see'>
743
- ) => Promise<_Ret<M, TSelects, S, TDefault>>;
788
+ restore: <S extends keyof TSelects | false = _DS<Config, C, 'restore', TSelects>, IM extends keyof TIncludes = never>(
789
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes, 'see'>
790
+ ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
744
791
 
745
792
  /** Restores multiple records previously marked as deleted (soft-delete) in batch. */
746
793
  restoreList: (pks: _Pk<T, Config>[], options?: { db?: ClientOrTransaction }) => Promise<{ count: number }>;
@@ -757,9 +804,10 @@ type InjectedBaseMethods<
757
804
  TRelations = _Rel<Config>,
758
805
  TSoftKey = _Soft<Config>,
759
806
  I = PrismaModelInputs<M>,
760
- TDefaultOrdenation = _DOrd<Config>
807
+ TDefaultOrdenation = _DOrd<Config>,
808
+ TIncludes = _Inc<Config>
761
809
  > = Pick<
762
- AllBaseMethods<T, M, Config, C, TSelects, TDefault, TPk, TRelations, TSoftKey, I, TDefaultOrdenation>,
810
+ AllBaseMethods<T, M, Config, C, TSelects, TDefault, TPk, TRelations, TSoftKey, I, TDefaultOrdenation, TIncludes>,
763
811
  | (C extends { baseMethods: { get: { active: false } } } ? never : 'get')
764
812
  | (C extends { baseMethods: { getOrThrow: { active: false } } } ? never : 'getOrThrow')
765
813
  | (C extends { baseMethods: { getList: { active: false } } } ? never : 'getList')
@@ -861,6 +909,7 @@ export type RepoConfig<T, M extends Prisma.ModelName, SM extends Record<string,
861
909
  softRemovekName?: keyof T & string;
862
910
  selectModels?: SM;
863
911
  defaultSelectModel?: Extract<keyof SM, string>;
912
+ includeModels?: IncludeModels<M>;
864
913
  requiredWhere?: WhereModel<M>;
865
914
  defaultOrdenation?: OrdenationModel<M>;
866
915
  relations?: RepositoryRelations<T>;
@@ -946,6 +995,12 @@ export type ValidateRepoConfig<T extends object, M extends Prisma.ModelName, Con
946
995
  */
947
996
  defaultSelectModel?: string;
948
997
 
998
+ /**
999
+ * Defines named and reusable include projections.
1000
+ * Similar to selectModels, but resolves Prisma `include` clauses.
1001
+ */
1002
+ includeModels?: IncludeModels<M>;
1003
+
949
1004
  /**
950
1005
  * Defines global filters that will be automatically applied to all repository queries.
951
1006
  * Useful for tenant isolation (multi-tenancy) or base restrictions (e.g., `isActive: true`).
@@ -35,6 +35,7 @@ class VSRepository {
35
35
  pkName;
36
36
  softRemovekName;
37
37
  selectModels;
38
+ includeModels;
38
39
  defaultSelectModel;
39
40
  requiredWhere;
40
41
  relations;
@@ -47,6 +48,7 @@ class VSRepository {
47
48
  this.pkName = validatedConfig.pkName;
48
49
  this.softRemovekName = validatedConfig.softRemovekName;
49
50
  this.selectModels = validatedConfig.selectModels;
51
+ this.includeModels = validatedConfig.includeModels;
50
52
  this.defaultSelectModel = validatedConfig.defaultSelectModel;
51
53
  this.relations = validatedConfig.relations;
52
54
  this.requiredWhere = validatedConfig.requiredWhere;
@@ -162,7 +164,7 @@ class VSRepository {
162
164
  const missingParams = dinamicMethodInfo.whereParams
163
165
  .concat(dinamicMethodInfo.otherParams)
164
166
  .slice(args.length);
165
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${buildInstance.tableName}: runtime) Missing parameters: ${missingParams.join(", ")}`);
167
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${buildInstance.tableName}: runtime) Missing parameters: ${missingParams.join(", ")}`, "48670");
166
168
  }
167
169
  else if (args.length > dinamicMethodInfo.argsCount) {
168
170
  const optionsArg = args[args.length - 1];
@@ -21,7 +21,7 @@ function resolveBaseMethods(instance, config) {
21
21
  if (baseMethods.get.active) {
22
22
  instance.get = async (pk, options) => {
23
23
  if (pk == undefined)
24
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
24
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
25
25
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
26
26
  instance,
27
27
  baseConfig: baseMethods.get,
@@ -41,7 +41,7 @@ function resolveBaseMethods(instance, config) {
41
41
  if (baseMethods.getOrThrow.active) {
42
42
  instance.getOrThrow = async (pk, options) => {
43
43
  if (pk == undefined)
44
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
44
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
45
45
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
46
46
  instance,
47
47
  baseConfig: baseMethods.getOrThrow,
@@ -64,7 +64,7 @@ function resolveBaseMethods(instance, config) {
64
64
  if (baseMethods.getList.active) {
65
65
  instance.getList = async (pks, options) => {
66
66
  if (!Array.isArray(pks) || pks.some(pk => pk == undefined))
67
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`);
67
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`, "65706");
68
68
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
69
69
  instance,
70
70
  baseConfig: baseMethods.getList,
@@ -84,7 +84,7 @@ function resolveBaseMethods(instance, config) {
84
84
  if (baseMethods.getAll.active) {
85
85
  instance.getAll = async (options) => {
86
86
  if (options !== undefined && !(0, is_object_validate_1.isObject)(options))
87
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'options' must be a valid object.`);
87
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'options' must be a valid object.`, "65706");
88
88
  const { pagination, order, ...restOptions } = options ?? {};
89
89
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
90
90
  instance,
@@ -107,7 +107,7 @@ function resolveBaseMethods(instance, config) {
107
107
  if (baseMethods.remove.active) {
108
108
  instance.remove = async (pk, options) => {
109
109
  if (pk == undefined)
110
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
110
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
111
111
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
112
112
  instance,
113
113
  baseConfig: baseMethods.remove,
@@ -127,7 +127,7 @@ function resolveBaseMethods(instance, config) {
127
127
  if (baseMethods.removeList.active) {
128
128
  instance.removeList = async (pks, options) => {
129
129
  if (!Array.isArray(pks) || pks.some(pk => pk == undefined))
130
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`);
130
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`, "65706");
131
131
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
132
132
  instance,
133
133
  baseConfig: baseMethods.removeList,
@@ -166,7 +166,7 @@ function resolveBaseMethods(instance, config) {
166
166
  if (baseMethods.has.active) {
167
167
  instance.has = async (pk, options) => {
168
168
  if (pk == undefined)
169
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
169
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
170
170
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
171
171
  instance,
172
172
  baseConfig: baseMethods.has,
@@ -187,9 +187,9 @@ function resolveBaseMethods(instance, config) {
187
187
  if (baseMethods.merge.active) {
188
188
  instance.merge = async (pk, obj, options) => {
189
189
  if (pk == undefined)
190
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
190
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
191
191
  if (!(0, is_object_validate_1.isObject)(obj)) {
192
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`);
192
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`, "65706");
193
193
  }
194
194
  (0, obj_with_relations_validate_1.validateObjWithRelations)(instance, obj, relationsKeys);
195
195
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
@@ -273,7 +273,7 @@ function resolveBaseMethods(instance, config) {
273
273
  if (baseMethods.softRemove.active && softRemovekName) {
274
274
  instance.softRemove = async (pk, options) => {
275
275
  if (pk == undefined)
276
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
276
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
277
277
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
278
278
  instance,
279
279
  baseConfig: baseMethods.softRemove,
@@ -295,7 +295,7 @@ function resolveBaseMethods(instance, config) {
295
295
  if (baseMethods.softRemoveList.active && softRemovekName) {
296
296
  instance.softRemoveList = async (pks, options) => {
297
297
  if (!Array.isArray(pks) || pks.some(pk => pk == undefined))
298
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`);
298
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`, "65706");
299
299
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
300
300
  instance,
301
301
  baseConfig: baseMethods.softRemoveList,
@@ -318,7 +318,7 @@ function resolveBaseMethods(instance, config) {
318
318
  if (baseMethods.restore.active && softRemovekName) {
319
319
  instance.restore = async (pk, options) => {
320
320
  if (pk == undefined)
321
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
321
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
322
322
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
323
323
  instance,
324
324
  baseConfig: baseMethods.restore,
@@ -340,7 +340,7 @@ function resolveBaseMethods(instance, config) {
340
340
  if (baseMethods.restoreList.active && softRemovekName) {
341
341
  instance.restoreList = async (pks, options) => {
342
342
  if (!Array.isArray(pks) || pks.some(pk => pk == undefined))
343
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`);
343
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pks' must an array of primary keys.`, "65706");
344
344
  const { db, prismaArgs } = (0, dbAndPrismaArgs_resolve_1.resolveDbAndPrismaArgs)({
345
345
  instance,
346
346
  baseConfig: baseMethods.restoreList,
@@ -363,7 +363,7 @@ function resolveBaseMethods(instance, config) {
363
363
  if (baseMethods.save.active) {
364
364
  instance.save = async (obj, options) => {
365
365
  if (!(0, is_object_validate_1.isObject)(obj)) {
366
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`);
366
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`, "65706");
367
367
  }
368
368
  (0, obj_with_relations_validate_1.validateObjWithRelations)(instance, obj, relationsKeys);
369
369
  const objAny = obj;
@@ -407,7 +407,7 @@ function resolveBaseMethods(instance, config) {
407
407
  if (baseMethods.saveList.active) {
408
408
  instance.saveList = async (objs, options) => {
409
409
  if (!Array.isArray(objs) || objs.some(ob => !(0, is_object_validate_1.isObject)(ob))) {
410
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'objs' must be an array of valid objects.`);
410
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'objs' must be an array of valid objects.`, "65706");
411
411
  }
412
412
  const validatedOptions = (0, method_options_validate_1.validateMethodOptions)(options, instance);
413
413
  const argsList = [];
@@ -466,9 +466,9 @@ function resolveBaseMethods(instance, config) {
466
466
  if (baseMethods.patch.active) {
467
467
  instance.patch = async (pk, obj, options) => {
468
468
  if (pk == undefined)
469
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`);
469
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'pk' must be provided.`, "65706");
470
470
  if (!(0, is_object_validate_1.isObject)(obj)) {
471
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`);
471
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'obj' must be a valid object.`, "65706");
472
472
  }
473
473
  (0, obj_with_relations_validate_1.validateObjWithRelations)(instance, obj, relationsKeys);
474
474
  const { updatePayload } = (0, create_update_payloads_with_relations_resolve_1.resolveCreateUpdatePayloadsWithRelations)(instance, obj, relationsKeys);
@@ -493,7 +493,7 @@ function resolveBaseMethods(instance, config) {
493
493
  instance.patchList = async (tuples, options) => {
494
494
  if (!Array.isArray(tuples) ||
495
495
  tuples.some(tuple => tuple[0] == undefined || !(0, is_object_validate_1.isObject)(tuple[1]))) {
496
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'tuples' must be a valid array of tuples [pk, obj].`);
496
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${tableName}: runtime) 'tuples' must be a valid array of tuples [pk, obj].`, "65706");
497
497
  }
498
498
  const validatedOptions = (0, method_options_validate_1.validateMethodOptions)(options, instance);
499
499
  const argsList = [];
@@ -6,7 +6,7 @@ const merge_wheres_resolve_1 = require("./merge-wheres.resolve");
6
6
  const select_resolve_1 = require("./select.resolve");
7
7
  function resolveDbAndPrismaArgs(data) {
8
8
  const { baseConfig, instance, options, wherePkValue, withoutSelect, withoutWhere, specificSelect, dataPayload, createPayload, updatePayload, alreadyValidatedOptions, specificWhere, pushWhere, ordenation, pagination, skipDuplicates, forceSeeMode, withOrdenationAndPagination, } = data;
9
- const validatedOptions = (alreadyValidatedOptions && options)
9
+ const validatedOptions = alreadyValidatedOptions && options
10
10
  ? options
11
11
  : (0, method_options_validate_1.validateMethodOptions)(options, instance);
12
12
  const db = validatedOptions.db ?? instance.prisma;
@@ -18,9 +18,15 @@ function resolveDbAndPrismaArgs(data) {
18
18
  }, wherePkValue != undefined ? { [instance.pkName]: wherePkValue } : specificWhere, pushWhere, instance.requiredWhere, instance.softRemovekName);
19
19
  }
20
20
  if (!withoutSelect) {
21
- prismaArgs.select =
22
- specificSelect ??
23
- (0, select_resolve_1.resolveSelect)(instance, validatedOptions.selectModel, baseConfig.defaultSelect);
21
+ if (specificSelect) {
22
+ prismaArgs.select = specificSelect;
23
+ }
24
+ else if (validatedOptions.includeModel) {
25
+ prismaArgs.include = instance.includeModels?.[validatedOptions.includeModel];
26
+ }
27
+ else {
28
+ prismaArgs.select = (0, select_resolve_1.resolveSelect)(instance, validatedOptions.selectModel, baseConfig.defaultSelect);
29
+ }
24
30
  }
25
31
  if (dataPayload) {
26
32
  prismaArgs.data = dataPayload;
@@ -14,6 +14,7 @@ function validateConstructorConfig(config) {
14
14
  pkName: schemas_util_1.stringSchema,
15
15
  softRemovekName: schemas_util_1.stringSchema.optional(),
16
16
  selectModels: zod_1.default.record(schemas_util_1.stringSchema, schemas_util_1.objectSchema).optional(),
17
+ includeModels: zod_1.default.record(schemas_util_1.stringSchema, schemas_util_1.objectSchema).optional(),
17
18
  defaultSelectModel: schemas_util_1.stringSchema.optional(),
18
19
  requiredWhere: schemas_util_1.objectSchema.optional(),
19
20
  relations: zod_1.default
@@ -8,19 +8,27 @@ const zod_1 = __importDefault(require("zod"));
8
8
  const schemas_util_1 = require("../utils/schemas.util");
9
9
  const vs_repo_error_1 = require("../errors/vs-repo.error");
10
10
  function validateMethodOptions(options, instance) {
11
- const optionsSchema = zod_1.default.strictObject({
11
+ const optionsSchema = zod_1.default
12
+ .strictObject({
12
13
  db: schemas_util_1.objectSchema.optional(),
13
14
  selectModel: zod_1.default
14
15
  .literal(false)
15
16
  .or(zod_1.default.enum(Object.keys(instance.selectModels ?? {})))
16
17
  .optional(),
18
+ includeModel: zod_1.default.enum(Object.keys(instance.includeModels ?? {})).optional(),
17
19
  see: zod_1.default.enum(["active", "removed", "all"]).default("active"),
20
+ })
21
+ .refine(data => {
22
+ return !(data.selectModel !== undefined && data.includeModel !== undefined);
23
+ }, {
24
+ message: "cannot be provided with 'selectModel'.",
25
+ path: ["includeModel"],
18
26
  });
19
27
  const optionsParsed = optionsSchema.safeParse(options ?? {});
20
28
  if (!optionsParsed.success) {
21
29
  const firstIssue = optionsParsed.error.issues[0];
22
30
  const path = firstIssue?.path.length ? firstIssue.path.join(".") : "options";
23
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) ${path}: ${firstIssue?.message}`);
31
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) ${path}: ${firstIssue?.message}`, "67542");
24
32
  }
25
33
  return optionsParsed.data;
26
34
  }
@@ -14,23 +14,23 @@ function validateObjWithRelations(instance, obj, relationsKeys) {
14
14
  const relation = relations[key];
15
15
  if (relation.mode === "mtm" || relation.mode === "otm") {
16
16
  if (obj[key] === null) {
17
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' cannot be 'null' when relation mode is "mtm" or "otm", use an empty array instead.`);
17
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' cannot be 'null' when relation mode is "mtm" or "otm", use an empty array instead.`, "91868");
18
18
  }
19
19
  else if (!Array.isArray(obj[key]) || obj[key].some(val => !(0, is_object_validate_1.isObject)(val))) {
20
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' must be a valid array of objects when relation mode is "mtm" or "otm".`);
20
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' must be a valid array of objects when relation mode is "mtm" or "otm".`, "91868");
21
21
  }
22
22
  }
23
23
  else {
24
24
  if (obj[key] === null) {
25
25
  if (relation.mode === "oto" && relation.restriction !== "set") {
26
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' can be 'null' only if the relation restriction is "set" and mode is "oto".`);
26
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' can be 'null' only if the relation restriction is "set" and mode is "oto".`, "91868");
27
27
  }
28
28
  else if (relation.mode === "mto" && !relation.nullable && !relation.nullAble) {
29
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' can be 'null' only if the relation 'nullable' is 'true' and mode is "mto".`);
29
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' can be 'null' only if the relation 'nullable' is 'true' and mode is "mto".`, "91868");
30
30
  }
31
31
  }
32
32
  else if (!(0, is_object_validate_1.isObject)(obj[key])) {
33
- throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' must be a valid object when relation mode is "oto" or "mto".`);
33
+ throw new vs_repo_error_1.VSRepoRuntimeError(`[VSRepository] (${instance.tableName}: runtime) '${key}' must be a valid object when relation mode is "oto" or "mto".`, "91868");
34
34
  }
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vsrepo",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
4
4
  "description": "Uma biblioteca de repository pattern para Prisma",
5
5
  "homepage": "https://github.com/jaobrabo123/VSRepository#readme",
6
6
  "repository": {