vsrepo 1.3.0 → 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
@@ -48,6 +50,7 @@ O VSRepository permite criar repositories fortemente tipados com:
48
50
  - [Tratamento de erros](#tratamento-de-erros)
49
51
  - [Tipos utilitários](#tipos-utilitários)
50
52
  - [API Reference](#api-reference)
53
+ - [Exemplos práticos](#exemplos-práticos)
51
54
  - [Contribuindo](#contribuindo)
52
55
  - [Requisitos](#requisitos)
53
56
  - [Troubleshooting](#troubleshooting)
@@ -138,21 +141,18 @@ import prisma from "../configs/db";
138
141
  import { setupVSRepo } from "../../generated/vsrepo";
139
142
  import type { Usuario } from "../../generated/prisma/client";
140
143
 
141
- const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
144
+ const usuarioRepository = setupVSRepo<Usuario, "Usuario">()(({
142
145
  tableName: "usuario",
143
146
  pkName: "id",
144
147
  selectModels: {
145
148
  public: { id: true, nome: true, email: true },
146
149
  },
147
150
  defaultSelectModel: "public",
148
- requiredWhere: { ativo: true },
149
151
  }).build(prisma);
150
152
 
151
153
  export default usuarioRepository;
152
154
  ```
153
155
 
154
- > `selectModels` e `requiredWhere` podem ser declarados fora do `setupVSRepo` se você precisar exportá-los para uso em outros arquivos.
155
-
156
156
  ### Usando o repository
157
157
 
158
158
  ```ts
@@ -231,14 +231,18 @@ const userVSRepo = setupVSRepo<
231
231
  });
232
232
 
233
233
  const setupUserRepository = (prisma: PrismaService) => {
234
- return userVSRepo.build(prisma).extend((repo) => ({
235
- buscarPorDominio: async (dominio: string) => {
236
- return repo.findByEmailEndsWith(`@${dominio}`);
237
- },
238
- }));
234
+ return userVSRepo.build(prisma);
239
235
  };
240
236
 
241
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
+ */
242
246
 
243
247
  export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
244
248
 
@@ -363,7 +367,7 @@ await usuarioRepository.restore(1);
363
367
  // saveList — cria ou atualiza múltiplos objetos em transação automática
364
368
  const usuarios = await usuarioRepository.saveList([
365
369
  { nome: "Maria", email: "maria@email.com" },
366
- { id: 2, nome: "João Atualizado" },
370
+ { id: 2, nome: "João Atualizado", email: "joao@email.com" },
367
371
  ]);
368
372
 
369
373
  // patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
@@ -401,32 +405,80 @@ await usuarioRepository.save(mesclado);
401
405
 
402
406
  Retorna `null` se o registro não for encontrado.
403
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
+
404
438
  ### Configurando os métodos base
405
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
+
406
442
  ```ts
407
443
  usuarioVSRepo.build(prisma, {
408
- 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,
409
447
 
410
448
  baseMethods: {
411
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.
412
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.
413
456
  defaultSelect: "public",
414
457
  },
415
458
  remove: {
416
459
  active: true,
417
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.
418
465
  ignoreRequiredWhere: false,
419
466
  },
420
467
  save: {
468
+ // Aqui só `ignoreRequiredWhere` é definido — `active` e `defaultSelect`
469
+ // continuam com seus padrões (true e o `defaultSelectModel` global).
421
470
  ignoreRequiredWhere: true,
422
471
  },
423
472
  patch: {
473
+ // Somente o select é sobrescrito; o método continua ativo normalmente.
424
474
  defaultSelect: "minimal",
425
475
  },
426
476
  has: {
427
- active: false, // Desativa o 'has' (padrão = true)
477
+ active: false, // Desativa o 'has' (padrão = true) — o método some do repository
428
478
  },
429
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.
430
482
  active: true,
431
483
  defaultSelect: "minimal",
432
484
  },
@@ -434,6 +486,8 @@ usuarioVSRepo.build(prisma, {
434
486
  });
435
487
  ```
436
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
+
437
491
  ---
438
492
 
439
493
  ## Select Models
@@ -574,7 +628,9 @@ methods: {
574
628
 
575
629
  ---
576
630
 
577
- 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:
578
634
 
579
635
  | Valor | Comportamento |
580
636
  | ----------- | ------------------------------------------------------------- |
@@ -591,10 +647,6 @@ const removidos = await usuarioRepository.getAll({ see: "removed" });
591
647
 
592
648
  // Retorna todos
593
649
  const todos = await usuarioRepository.getAll({ see: "all" });
594
-
595
- // Funciona em qualquer método base
596
- const usuario = await usuarioRepository.get(id, { see: "all" });
597
- const existe = await usuarioRepository.has(id, { see: "removed" });
598
650
  ```
599
651
 
600
652
  > A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não o substitui.
@@ -735,16 +787,50 @@ Exemplo:
735
787
  methods: {
736
788
  findOneByIdAndEmail: { map: true },
737
789
  findByNomeOrEmail: { map: true },
738
- findUniqueByIdOrEmailAndNome: { map: true },
790
+ findFirstByIdOrEmailAndNome: { map: true },
739
791
  findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
740
792
  }
741
793
 
742
794
  await usuarioRepository.findOneByIdAndEmail(1, "joao@email.com");
743
795
  await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
744
- await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "joao@email.com", "Joao");
796
+ await usuarioRepository.findFirstByIdOrEmailAndNome(1, "joao@email.com", "Joao");
745
797
  await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("joao@email.com", "Joao", true, 17)
746
798
  ```
747
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
+
748
834
  Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
749
835
 
750
836
  ```ts
@@ -784,6 +870,78 @@ Permitem filtrar por campos de modelos relacionados.
784
870
  | `Without` | `isNot: {}` | Relação não existe (é null) |
785
871
  | `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
786
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
+
787
945
  ---
788
946
 
789
947
  ### Sufixos de paginação e ordenação
@@ -887,6 +1045,20 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
887
1045
  | `set` | Substitui completamente (remove os que não foram enviados) |
888
1046
  | `add` | Adiciona/atualiza sem remover os existentes |
889
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
+
890
1062
  **Relação `mto` com nullable:**
891
1063
 
892
1064
  Use `nullable` (letra minúscula) para permitir a desvinculação de uma relação many-to-one:
@@ -902,8 +1074,6 @@ relations: {
902
1074
  }
903
1075
  ```
904
1076
 
905
- > **Nota:** `nullAble` (com A maiúsculo) ainda é aceito por compatibilidade, mas está **obsoleto**. Prefira `nullable`.
906
-
907
1077
  ---
908
1078
 
909
1079
  ## Transações
@@ -924,15 +1094,17 @@ await usuarioRepository.prisma.$transaction(async (tx) => {
924
1094
  });
925
1095
  ```
926
1096
 
927
- 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`:
928
1098
 
929
1099
  ```ts
930
1100
  await prisma.$transaction(async (tx) => {
931
1101
  // CORRETO: tx é uma DbTransaction
932
- await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
1102
+ const usuariosCadastrados = await usuarioRepository.saveList([{ nome: "Maria" }, { nome: "Lucas" }], { db: tx });
933
1103
 
934
- // ERRADO: não passe o prisma diretamente
935
- // 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
+ );
936
1108
  });
937
1109
  ```
938
1110
 
@@ -970,12 +1142,14 @@ O VSRepository lança `VSRepoError` e suas subclasses em situações específica
970
1142
  import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
971
1143
 
972
1144
  try {
973
- const usuario = await usuarioRepository.getOrThrow(id);
1145
+ const usuario = await usuarioRepository.getOrThrow("id-que-nao-existe");
974
1146
  } catch (error) {
975
1147
  if (error instanceof VSRepoRuntimeError && error.code === "20727") {
976
- // Registro não encontrado pelo getOrThrow
1148
+ console.error("Registro não encontrado");
977
1149
  } else if (error instanceof VSRepoError) {
978
1150
  console.error("Erro no repository:", error.message);
1151
+ } else {
1152
+ console.error("Erro:", error.message)
979
1153
  }
980
1154
  }
981
1155
  ```
@@ -989,7 +1163,7 @@ try {
989
1163
  | `VSRepoExtendError` | Argumento inválido em `extend` |
990
1164
  | `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
991
1165
 
992
- `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.
993
1167
 
994
1168
  ---
995
1169
 
@@ -1098,7 +1272,7 @@ type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuario
1098
1272
  setupVSRepo<TPayload, TTableName>()({
1099
1273
  tableName: Uncapitalize<M>; // Nome da tabela no Prisma
1100
1274
  pkName: keyof T; // Nome da primary key
1101
- softRemovekName?: keyof T & string; // Campo DateTime para soft-delete (opcional)
1275
+ softRemovekName?: keyof T & string; // Campo DateTime para soft-delete
1102
1276
  selectModels?: SelectModels<M>; // Projeções de dados nomeadas (select)
1103
1277
  defaultSelectModel?: keyof SM; // Select aplicado por padrão
1104
1278
  includeModels?: IncludeModels<M>; // Projeções de dados nomeadas (include) — sem default, só na chamada
@@ -1150,6 +1324,28 @@ repo.extend((repo) => ({
1150
1324
 
1151
1325
  ---
1152
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
+
1153
1349
  ## Contribuindo
1154
1350
 
1155
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)**):
@@ -1195,7 +1391,7 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1195
1391
 
1196
1392
  **`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
1197
1393
 
1198
- **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.
1199
1395
 
1200
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.
1201
1397
 
@@ -1205,4 +1401,4 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
1205
1401
 
1206
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.
1207
1403
 
1208
- **`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.
@@ -291,9 +291,17 @@ export type MethodOptionsModel<TRepo> =
291
291
  ? MethodOptions<keyof ExtractSelectModels<Config> | false, keyof ExtractIncludeModels<Config>>
292
292
  : never;
293
293
 
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
+
294
302
  type MethodFn<MethodName extends string, T, M extends Prisma.ModelName, R extends string, SelectModels, DefaultSelect extends keyof SelectModels | false, I, IncludeModels> =
295
303
  <S extends keyof SelectModels | false = DefaultSelect, IM extends keyof IncludeModels = never>(
296
- ...args: [...ExtractFields<T, CleanFields<R>, I>, ...ExtraArgs<MethodName, R, I>, options?: MethodOptions<S, keyof IncludeModels> & ({ includeModel: IM } | { includeModel?: never })]
304
+ ...args: [...ExtractFields<T, CleanFields<R>, I>, ...ExtraArgs<MethodName, R, I>, options?: MethodOptionsWithInclude<S, IM, IncludeModels>]
297
305
  ) => Promise<ResolveReturnType<MethodName, SelectedModel<M, S, SelectModels, IM, IncludeModels>>>;
298
306
 
299
307
  type GetMappedMethod<K extends string, MethodConf> =
@@ -349,7 +357,7 @@ type MethodFactory<T, M extends Prisma.ModelName, K extends string, SelectModels
349
357
  K extends `findWhere${string}`
350
358
  ? {
351
359
  /** @deprecated Use findOneWhere instead. */
352
- <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?: MethodOptions<S, keyof IncludeModels> & ({ includeModel: IM } | { includeModel?: never })]): Promise<ResolveReturnType<GetMappedMethod<K, MethodConf>, SelectedModel<M, S, SelectModels, IM, IncludeModels>>>;
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>>>;
353
361
  }
354
362
  : MethodFn<GetMappedMethod<K, MethodConf>, T, M, ExtractPatternBase<K>, SelectModels, DefaultSelect, I, IncludeModels>;
355
363
 
@@ -703,47 +711,47 @@ type AllBaseMethods<
703
711
  > = {
704
712
  /** Fetches a record by its primary key (PK). */
705
713
  get: <S extends keyof TSelects | false = _DS<Config, C, 'get', TSelects>, IM extends keyof TIncludes = never>(
706
- pk: _Pk<T, Config>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
714
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
707
715
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes> | null>;
708
716
 
709
717
  /** Fetches a record by PK and throws `VSRepoRuntimeError` if not found. */
710
718
  getOrThrow: <S extends keyof TSelects | false = _DS<Config, C, 'getOrThrow', TSelects>, IM extends keyof TIncludes = never>(
711
- pk: _Pk<T, Config>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
719
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
712
720
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
713
721
 
714
722
  /** Fetches multiple records by a list of primary keys (PKs). */
715
723
  getList: <S extends keyof TSelects | false = _DS<Config, C, 'getList', TSelects>, IM extends keyof TIncludes = never>(
716
- pks: _Pk<T, Config>[], options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
724
+ pks: _Pk<T, Config>[], options?: MethodOptionsWithInclude<S, IM, TIncludes>
717
725
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
718
726
 
719
727
  /** Deletes a record identified by its primary key (PK). */
720
728
  remove: <S extends keyof TSelects | false = _DS<Config, C, 'remove', TSelects>, IM extends keyof TIncludes = never>(
721
- pk: _Pk<T, Config>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
729
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
722
730
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
723
731
 
724
732
  /** Inserts or updates (upsert) a record. */
725
733
  save: <S extends keyof TSelects | false = _DS<Config, C, 'save', TSelects>, IM extends keyof TIncludes = never>(
726
- obj: UpsertWithRelations<T, M, TRelations>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
734
+ obj: UpsertWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
727
735
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
728
736
 
729
737
  /** Saves an array of objects in a single automatic transaction. */
730
738
  saveList: <S extends keyof TSelects | false = _DS<Config, C, 'saveList', TSelects>, IM extends keyof TIncludes = never>(
731
- objs: UpsertWithRelations<T, M, TRelations>[], options?: Omit<MethodOptions<S, keyof TIncludes>, 'db'> & ({ includeModel: IM } | { includeModel?: never }) & { db?: DbTransaction }
739
+ objs: UpsertWithRelations<T, M, TRelations>[], options?: MethodOptionsWithInclude<S, IM, TIncludes, 'db'> & { db?: DbTransaction }
732
740
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
733
741
 
734
742
  /** Partially updates (patch) an existing record by its primary key (PK). */
735
743
  patch: <S extends keyof TSelects | false = _DS<Config, C, 'patch', TSelects>, IM extends keyof TIncludes = never>(
736
- pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
744
+ pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
737
745
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
738
746
 
739
747
  /** Partially updates multiple records via `[pk, obj]` tuples in an automatic transaction. */
740
748
  patchList: <S extends keyof TSelects | false = _DS<Config, C, 'patchList', TSelects>, IM extends keyof TIncludes = never>(
741
- tuples: [pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>][], options?: Omit<MethodOptions<S, keyof TIncludes>, 'db'> & ({ includeModel: IM } | { includeModel?: never }) & { db?: DbTransaction }
749
+ tuples: [pk: _Pk<T, Config>, obj: PatchWithRelations<T, M, TRelations>][], options?: MethodOptionsWithInclude<S, IM, TIncludes, 'db'> & { db?: DbTransaction }
742
750
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>[]>;
743
751
 
744
752
  /** Fetches a record by PK and deep-merges it with the provided object **in memory**. */
745
753
  merge: <S extends keyof TSelects | false = _DS<Config, C, 'merge', TSelects>, IM extends keyof TIncludes = never>(
746
- pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>, options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never })
754
+ pk: _Pk<T, Config>, obj: UpdateWithRelations<T, M, TRelations>, options?: MethodOptionsWithInclude<S, IM, TIncludes>
747
755
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes> | null>;
748
756
 
749
757
  /** Deletes multiple records by their primary keys. */
@@ -751,7 +759,7 @@ type AllBaseMethods<
751
759
 
752
760
  /** Fetches all records (respects `requiredWhere` when set). */
753
761
  getAll: <S extends keyof TSelects | false = _DS<Config, C, 'getAll', TSelects>, IM extends keyof TIncludes = never>(
754
- options?: MethodOptions<S, keyof TIncludes> & ({ includeModel: IM } | { includeModel?: never }) & {
762
+ options?: MethodOptionsWithInclude<S, IM, TIncludes> & {
755
763
  pagination?: PaginationOptions<I extends { cursorInput: infer Curs } ? Curs : unknown>;
756
764
  /**
757
765
  * Ordering to apply to the query.
@@ -770,7 +778,7 @@ type AllBaseMethods<
770
778
 
771
779
  /** Marks a record as deleted (soft-delete). */
772
780
  softRemove: <S extends keyof TSelects | false = _DS<Config, C, 'softRemove', TSelects>, IM extends keyof TIncludes = never>(
773
- pk: _Pk<T, Config>, options?: Omit<MethodOptions<S, keyof TIncludes>, 'see'> & ({ includeModel: IM } | { includeModel?: never })
781
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes, 'see'>
774
782
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
775
783
 
776
784
  /** Marks multiple records as deleted (soft-delete) in batch. */
@@ -778,7 +786,7 @@ type AllBaseMethods<
778
786
 
779
787
  /** Restores a record previously marked as deleted (soft-delete). */
780
788
  restore: <S extends keyof TSelects | false = _DS<Config, C, 'restore', TSelects>, IM extends keyof TIncludes = never>(
781
- pk: _Pk<T, Config>, options?: Omit<MethodOptions<S, keyof TIncludes>, 'see'> & ({ includeModel: IM } | { includeModel?: never })
789
+ pk: _Pk<T, Config>, options?: MethodOptionsWithInclude<S, IM, TIncludes, 'see'>
782
790
  ) => Promise<_Ret<M, TSelects, S, TDefault, IM, TIncludes>>;
783
791
 
784
792
  /** Restores multiple records previously marked as deleted (soft-delete) in batch. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vsrepo",
3
- "version": "1.3.0",
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": {