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 +229 -33
- package/dist/VSRepository.d.ts +22 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# VSRepository
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-

|
|
4
|
+

|
|
5
5
|

|
|
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: `
|
|
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, "
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
935
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
package/dist/VSRepository.d.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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. */
|