vsrepo 1.4.1 → 2.0.0
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 +723 -1338
- package/README.pt-BR.md +729 -1341
- package/dist/VSRepoAdapter.d.ts +71 -0
- package/dist/VSRepoAdapter.js +18 -0
- package/dist/VSRepository.d.ts +135 -1201
- package/dist/VSRepository.js +273 -237
- package/dist/decorators/dynamic-method.decorator.d.ts +25 -0
- package/dist/decorators/dynamic-method.decorator.js +38 -0
- package/dist/decorators/query-method.decorator.d.ts +27 -0
- package/dist/decorators/query-method.decorator.js +45 -0
- package/dist/errors/VSRepoAdapterError.d.ts +22 -0
- package/dist/errors/VSRepoAdapterError.js +31 -0
- package/dist/errors/VSRepoError.d.ts +15 -0
- package/dist/errors/VSRepoError.js +21 -0
- package/dist/index.d.ts +34 -1
- package/dist/index.js +28 -15
- package/dist/internal/constants/debug-arg-symbol.constant.d.ts +1 -0
- package/dist/internal/constants/debug-arg-symbol.constant.js +4 -0
- package/dist/internal/constants/dynamic-methods-key.constant.d.ts +1 -0
- package/dist/internal/constants/query-methods-key.constant.d.ts +1 -0
- package/dist/internal/constants/query-methods-key.constant.js +4 -0
- package/dist/internal/enums/adapter-error-code.enum.d.ts +125 -0
- package/dist/internal/enums/adapter-error-code.enum.js +129 -0
- package/dist/internal/enums/transaction-isolation-level.enum.d.ts +16 -0
- package/dist/internal/enums/transaction-isolation-level.enum.js +20 -0
- package/dist/internal/enums/vs-log-level.enum.d.ts +18 -0
- package/dist/internal/enums/vs-log-level.enum.js +22 -0
- package/dist/internal/enums/vsrepo-error-type.enum.d.ts +19 -0
- package/dist/internal/enums/vsrepo-error-type.enum.js +23 -0
- package/dist/internal/resolvers/dynamic-methods.resolver.d.ts +23 -0
- package/dist/internal/resolvers/dynamic-methods.resolver.js +910 -0
- package/dist/internal/resolvers/merge-wheres.resolver.d.ts +7 -0
- package/dist/internal/resolvers/merge-wheres.resolver.js +26 -0
- package/dist/internal/utils/uncapitalize.util.d.ts +1 -0
- package/dist/internal/utils/vs-logger.util.d.ts +25 -0
- package/dist/internal/utils/vs-logger.util.js +139 -0
- package/dist/internal/validators/decorators.validator.d.ts +8 -0
- package/dist/internal/validators/decorators.validator.js +75 -0
- package/dist/internal/validators/schemas/ordering.schema.d.ts +3 -0
- package/dist/internal/validators/schemas/ordering.schema.js +38 -0
- package/dist/internal/validators/schemas/pagination.schema.d.ts +6 -0
- package/dist/internal/validators/schemas/pagination.schema.js +40 -0
- package/dist/internal/validators/schemas/where.schema.d.ts +7 -0
- package/dist/internal/validators/schemas/where.schema.js +42 -0
- package/dist/internal/validators/vsrepo.validator.d.ts +33 -0
- package/dist/internal/validators/vsrepo.validator.js +160 -0
- package/dist/types/adapter/adapter-method-options.type.d.ts +24 -0
- package/dist/types/adapter/adapter-query-options.type.d.ts +5 -0
- package/dist/types/decorators/dynamic-method-options.type.d.ts +14 -0
- package/dist/types/decorators/query-method-options.type.d.ts +15 -0
- package/dist/types/dynamic-methods/dynamic-method-customization.type.d.ts +7 -0
- package/dist/types/dynamic-methods/dynamic-method-info.type.d.ts +18 -0
- package/dist/types/dynamic-methods/dynamic-method-where-ops.type.d.ts +6 -0
- package/dist/types/utils/count-result.type.d.ts +9 -0
- package/dist/types/utils/deep-partial.type.d.ts +14 -0
- package/dist/types/utils/keys-of-type.type.d.ts +20 -0
- package/dist/types/utils/methods-options.type.d.ts +23 -0
- package/dist/types/utils/ordering.type.d.ts +39 -0
- package/dist/types/utils/pagination.type.d.ts +11 -0
- package/dist/types/utils/perform-data.type.d.ts +4 -0
- package/dist/types/utils/primitive.type.d.ts +6 -0
- package/dist/types/utils/query-method-arg.type.d.ts +27 -0
- package/dist/types/utils/see-mode.type.d.ts +12 -0
- package/dist/types/vsrepo/vsrepo-args.type.d.ts +9 -0
- package/dist/types/vsrepo/vsrepo-method.type.d.ts +4 -0
- package/dist/types/vsrepo/vsrepo-options.type.d.ts +34 -0
- package/dist/types/vsrepo/vsrepo-orm-types.type.d.ts +17 -0
- package/dist/types/vsrepo/vsrepo-pretty-where.type.d.ts +7 -0
- package/dist/types/vsrepo/vsrepo-query-options.type.d.ts +17 -0
- package/dist/types/vsrepo/vsrepo-query-options.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-query.type.d.ts +5 -0
- package/dist/types/vsrepo/vsrepo-query.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-relations.type.d.ts +26 -0
- package/dist/types/vsrepo/vsrepo-relations.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-resolve-args-data.type.d.ts +19 -0
- package/dist/types/vsrepo/vsrepo-resolve-args-data.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-select.type.d.ts +15 -0
- package/dist/types/vsrepo/vsrepo-select.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-transaction-options.type.d.ts +12 -0
- package/dist/types/vsrepo/vsrepo-transaction-options.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-ugly-where.type.d.ts +9 -0
- package/dist/types/vsrepo/vsrepo-ugly-where.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-where.type.d.ts +99 -0
- package/dist/types/vsrepo/vsrepo-where.type.js +2 -0
- package/package.json +16 -37
- package/README-DynamicRepo.md +0 -625
- package/README-DynamicRepo.pt-BR.md +0 -625
- package/dist/DynamicRepository.d.ts +0 -497
- package/dist/DynamicRepository.js +0 -26
- package/dist/VSRepoError.d.ts +0 -83
- package/dist/VSRepoError.js +0 -17
- package/dist/internal/decorators/dynamic-method.decorator.js +0 -14
- package/dist/internal/decorators/query-method.decorator.js +0 -20
- package/dist/internal/entities/dynamic-method-metadata.entity.js +0 -26
- package/dist/internal/errors/vs-repo.error.js +0 -31
- package/dist/internal/resolvers/base-methods.resolve.js +0 -536
- package/dist/internal/resolvers/create-update-payloads-with-relations.resolve.js +0 -143
- package/dist/internal/resolvers/data-payload-with-relations.resolve.js +0 -60
- package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +0 -63
- package/dist/internal/resolvers/dynamic-method-customization.resolve.js +0 -57
- package/dist/internal/resolvers/dynamic-method-info.resolve.js +0 -279
- package/dist/internal/resolvers/dynamic-methods-metadata.resolve.js +0 -15
- package/dist/internal/resolvers/merge-wheres.resolve.js +0 -22
- package/dist/internal/resolvers/pretty-wheres.resolve.js +0 -87
- package/dist/internal/resolvers/select.resolve.js +0 -7
- package/dist/internal/resolvers/specific-where.resolve.js +0 -84
- package/dist/internal/resolvers/ugly-where.resolve.js +0 -178
- package/dist/internal/utils/logger.util.js +0 -21
- package/dist/internal/utils/schemas.util.js +0 -31
- package/dist/internal/validation/build-config.validate.js +0 -84
- package/dist/internal/validation/constructor-config.validate.js +0 -64
- package/dist/internal/validation/dynamic-method-config.validate.js +0 -19
- package/dist/internal/validation/extension.validate.js +0 -15
- package/dist/internal/validation/is-object.validate.js +0 -6
- package/dist/internal/validation/method-options.validate.js +0 -42
- package/dist/internal/validation/obj-with-relations.validate.js +0 -37
- package/dist/internal/validation/prisma-client.validate.js +0 -10
- package/dist/internal/validation/query-method-arg.validate.js +0 -22
- package/dist/internal/validation/query-method-options.validate.js +0 -24
- package/scripts/configure-prisma-import.mjs +0 -283
- package/scripts/copy-types.mjs +0 -24
- /package/dist/{internal/decorators/types/dynamic-method-config.type.js → types/adapter/adapter-method-options.type.js} +0 -0
- /package/dist/{internal/errors/types/vs-repo-error-type.type.js → types/adapter/adapter-query-options.type.js} +0 -0
- /package/dist/{internal/errors/types/vs-repo-runtime-error-code.type.js → types/decorators/dynamic-method-options.type.js} +0 -0
- /package/dist/{internal/validation/types → types/decorators}/query-method-options.type.js +0 -0
- /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-customization.type.js +0 -0
- /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-info.type.js +0 -0
- /package/dist/{internal/resolvers/types → types/dynamic-methods}/dynamic-method-where-ops.type.js +0 -0
- /package/dist/{internal/resolvers/types/base-method-function.type.js → types/utils/count-result.type.js} +0 -0
- /package/dist/{internal/resolvers/types/pretty-where.type.js → types/utils/deep-partial.type.js} +0 -0
- /package/dist/{internal/resolvers/types/prisma-args.type.js → types/utils/keys-of-type.type.js} +0 -0
- /package/dist/{internal/resolvers/types/repository-build-instance.type.js → types/utils/methods-options.type.js} +0 -0
- /package/dist/{internal/resolvers/types/resolve-db-and-prisma-args-data.type.js → types/utils/ordering.type.js} +0 -0
- /package/dist/{internal/validation/types → types/utils}/pagination.type.js +0 -0
- /package/dist/{internal/resolvers/types/ugly-where.type.js → types/utils/perform-data.type.js} +0 -0
- /package/dist/{internal/validation/types/base-methods.type.js → types/utils/primitive.type.js} +0 -0
- /package/dist/{internal/validation/types → types/utils}/query-method-arg.type.js +0 -0
- /package/dist/{internal/validation/types → types/utils}/see-mode.type.js +0 -0
- /package/dist/{internal/validation/types/build-config.type.js → types/vsrepo/vsrepo-args.type.js} +0 -0
- /package/dist/{internal/validation/types/constructor-config.type.js → types/vsrepo/vsrepo-method.type.js} +0 -0
- /package/dist/{internal/validation/types/method-options.type.js → types/vsrepo/vsrepo-options.type.js} +0 -0
- /package/dist/{internal/validation/types/method.type.js → types/vsrepo/vsrepo-orm-types.type.js} +0 -0
- /package/dist/{internal/validation/types/relation.type.js → types/vsrepo/vsrepo-pretty-where.type.js} +0 -0
package/README.pt-BR.md
CHANGED
|
@@ -9,139 +9,124 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
|
-
# VSRepository
|
|
12
|
+
# VSRepository v2
|
|
13
13
|
|
|
14
14
|
🇧🇷 Você está lendo a versão em português. [🇺🇸 Read in English](./README.md)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
> ✅ **Lançado.** O VSRepository v2.0.0 (o core agnóstico de ORM) e o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já foram publicados e estão prontos para uso. O Prisma 7 é o primeiro adapter totalmente suportado; outros ORMs (TypeORM, Drizzle, etc.) ainda estão em desenvolvimento — veja [Status dos adapters](#status-dos-adapters). Se você precisa da versão anterior, somente Prisma, use o código/docs da [`v1`](https://github.com/jaobrabo123/VSRepository/tree/v1).
|
|
17
17
|
|
|
18
|
-
O VSRepository
|
|
18
|
+
Biblioteca de repository pattern **agnóstica de ORM**, com suporte completo a **TypeScript** e **type inference** automático. O VSRepository v2 é uma reescrita da biblioteca [v1](./v1): em vez de falar diretamente com o Prisma, o núcleo agora delega toda operação a um **adapter** plugável, permitindo que a mesma API de repository funcione com Prisma, TypeORM ou qualquer outro ORM/banco que implemente o contrato de adapter.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
O VSRepository permite criar repositories fortemente tipados com:
|
|
21
|
+
|
|
22
|
+
- **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `merge`, `getAll`, `total`, `has`
|
|
21
23
|
- **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
|
|
22
|
-
- **Métodos dinâmicos** inferidos
|
|
23
|
-
- **
|
|
24
|
+
- **Métodos dinâmicos** inferidos a partir do nome de um campo `declare` via o decorador `@DynamicMethod`: `findByEmail`, `findManyByStatusPaginated`, `updateById`
|
|
25
|
+
- **Métodos de query SQL raw** através do novo decorador `@QueryMethod`, ignorando totalmente o engine de parsing por nome
|
|
26
|
+
- **`select`/`relations`** ad-hoc em cada chamada — sem mais projeções nomeadas pré-declaradas
|
|
24
27
|
- **Type safety** em 100% das operações
|
|
25
|
-
- **Transações** nativas do
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
> 💡 Quer ver tudo isso 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) mais abaixo.
|
|
28
|
+
- **Transações** nativas do ORM, compartilhadas entre repositories
|
|
29
|
+
- Um **núcleo agnóstico de ORM** — a mesma classe de repository funciona com qualquer implementação de `VSRepoAdapter`
|
|
29
30
|
|
|
30
31
|
---
|
|
31
32
|
|
|
32
33
|
## Sumário
|
|
33
34
|
|
|
35
|
+
- [O que mudou da v1](#o-que-mudou-da-v1)
|
|
36
|
+
- [Status dos adapters](#status-dos-adapters)
|
|
34
37
|
- [Instalação](#instalação)
|
|
35
|
-
- [Gerando os tipos](#gerando-os-tipos)
|
|
36
38
|
- [Uso básico](#uso-básico)
|
|
37
|
-
- [
|
|
38
|
-
- [Integração com NestJS](#integração-com-nestjs)
|
|
39
|
+
- [Options do construtor](#options-do-construtor)
|
|
39
40
|
- [Métodos base](#métodos-base)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- [Merge](#merge)
|
|
43
|
-
- [Configurando os métodos base](#configurando-os-métodos-base)
|
|
44
|
-
- [Select Models](#select-models)
|
|
45
|
-
- [Select bruto (options.select)](#select-bruto-optionsselect)
|
|
46
|
-
- [Include Models](#include-models)
|
|
47
|
-
- [Include bruto (options.include)](#include-bruto-optionsinclude)
|
|
48
|
-
- [Required Where](#required-where)
|
|
49
|
-
- [Ordenação padrão (Default Ordering)](#ordenação-padrão-default-ordering)
|
|
50
|
-
- [Opção `see`](#opção-see)
|
|
41
|
+
- [Soft-delete](#soft-delete)
|
|
42
|
+
- [`select` e `relations`](#select-e-relations)
|
|
51
43
|
- [Métodos dinâmicos](#métodos-dinâmicos)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- [Query Methods](#query-methods)
|
|
61
|
-
- [Relações no save](#relações-no-save)
|
|
44
|
+
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
45
|
+
- [Filtros de campo](#filtros-de-campo)
|
|
46
|
+
- [Operadores lógicos](#operadores-lógicos)
|
|
47
|
+
- [Filtros de relação](#filtros-de-relação)
|
|
48
|
+
- [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct)
|
|
49
|
+
- [Options do decorador](#options-do-decorador)
|
|
50
|
+
- [Query methods (SQL raw)](#query-methods-sql-raw)
|
|
51
|
+
- [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query)
|
|
62
52
|
- [Transações](#transações)
|
|
63
|
-
- [Estendendo um repositório](#estendendo-um-repositório)
|
|
64
|
-
- [Tratamento de erros](#tratamento-de-erros)
|
|
65
53
|
- [Tipos utilitários](#tipos-utilitários)
|
|
66
|
-
- [
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
54
|
+
- [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)
|
|
55
|
+
- [Tratamento de erros](#tratamento-de-erros)
|
|
56
|
+
- [`VSRepoAdapterError` e `AdapterErrorCode`](#vsrepoadaptererror-e-adaptererrorcode)
|
|
57
|
+
- [Logging](#logging)
|
|
58
|
+
- [Desenvolvimento](#desenvolvimento)
|
|
69
59
|
- [Requisitos](#requisitos)
|
|
70
|
-
- [
|
|
60
|
+
- [Contribuindo](#contribuindo)
|
|
71
61
|
|
|
72
62
|
---
|
|
73
63
|
|
|
74
|
-
##
|
|
64
|
+
## O que mudou da v1
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
npm i vsrepo @prisma/client
|
|
78
|
-
```
|
|
66
|
+
Se você vem do código/docs da [v1](./v1), aqui está o resumo. Veja cada seção linkada para detalhes.
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
| Área | v1 | v2 |
|
|
69
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| Acesso ao banco | Fala diretamente com o **Prisma**, embutido no pacote core | Fala com um **`VSRepoAdapter`**; o suporte a cada ORM é distribuído em pacotes separados (`@vsrepo/prisma7-adapter`, `@vsrepo/typeorm-adapter`, ...) em vez de vir embutido no pacote core `vsrepo` |
|
|
71
|
+
| Definindo um repository | `setupVSRepo<T, M>()({...}).build(prisma)` funcional, **ou** uma classe `DynamicRepository` | Uma única API **baseada em classes**: `extends VSRepository<Entity, PKType, OrmTypes>` |
|
|
72
|
+
| Métodos dinâmicos | Objeto de config `methods: { findByEmail: { map: true } }` | Decorador `@DynamicMethod()` em um campo `declare` |
|
|
73
|
+
| Projeções de dados | `selectModels` + `defaultSelectModel` nomeados e reutilizáveis | `select`/`relations` ad-hoc passados em cada chamada (sem modelos nomeados) |
|
|
74
|
+
| Eager loading | `include`/`includeModels` (específico do Prisma) | Option `relations` agnóstica de ORM |
|
|
75
|
+
| Filtros globais | `requiredWhere` (qualquer filtro arbitrário, sempre aplicado) | **Removido**; Agora aceita apenas `softRemoveKey` + `see: "active" \| "removed" \| "all"` |
|
|
76
|
+
| Sufixo de filtro case-insensitive | `Insensitive` | `IgnoreCase` |
|
|
77
|
+
| Ordenação inline no nome do método | Não suportado (`order` tinha que ser passado como argumento via `Ordered`/`Paginated`) | Cadeias `OrderBy<Campo>Asc`/`OrderBy<Campo>Desc` embutidas diretamente no nome do método |
|
|
78
|
+
| Tratamento de duplicatas no `createMany` | Sufixo `SkipDuplicates` | Sufixo `IgnoreConflicts` |
|
|
79
|
+
| `aggregate` / `groupBy` | Suportado (passthrough nativo do Prisma) | **Ainda não implementado** |
|
|
80
|
+
| Tipos de erro | `VSRepoError` + subclasses (`VSRepoConfigError`, `VSRepoBuildError`, `VSRepoExtendError`, `VSRepoRuntimeError`) | Uma classe base `VSRepoError` com um campo `type: VSRepoErrorType` (`DECORATOR`, `RESOLVER`, `DYNAMIC`, `VALIDATOR`, `BASE`, `ADAPTER`), além de uma subclasse `VSRepoAdapterError` que carrega um `AdapterErrorCode` e o erro original do ORM |
|
|
81
|
+
| Log de debug | Boolean `showWorking: true` | `logLevel: VSLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`) + `logSlowThresholdMs` para avisos de queries lentas |
|
|
82
|
+
| CLI `vsrepo generate` (etapa de geração de tipos) | Obrigatória antes de usar | Não faz parte do núcleo da v2 — os tipos vêm diretamente das suas entidades/tipos do ORM |
|
|
83
|
+
| Extras de CRUD | `patchList`, `options.select`/`options.include` raw | `select`/`relations` já são o padrão (sempre "raw"); `patch`/`merge` mantêm a mesma semântica. **`patchList` foi removido** — para uma atualização parcial em lote, use um dynamic method `updateManyBy`/`updateManyWhere` |
|
|
85
84
|
|
|
86
85
|
---
|
|
87
86
|
|
|
88
|
-
##
|
|
89
|
-
|
|
90
|
-
O VSRepository precisa saber o caminho real do seu Prisma Client para gerar as tipagens corretamente.
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
npx vsrepo generate
|
|
94
|
-
```
|
|
87
|
+
## Status dos adapters
|
|
95
88
|
|
|
96
|
-
|
|
89
|
+
O VSRepository v2 é **agnóstico de ORM por design**. O pacote core (`vsrepo`) traz apenas a classe de repository, os decoradores, o engine de parsing de nomes, o tratamento de erros e o logging — ele **não** inclui um adapter de produção. O suporte de fato a cada ORM/banco deve viver em **pacotes separados, versionados de forma independente**, um por ORM (e, quando fizer sentido, um por versão principal do ORM), por exemplo:
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
91
|
+
- `@vsrepo/prisma7-adapter`
|
|
92
|
+
- `@vsrepo/prisma8-adapter`
|
|
93
|
+
- `@vsrepo/typeorm-adapter`
|
|
94
|
+
- `@vsrepo/drizzle-adapter`
|
|
103
95
|
|
|
104
|
-
**
|
|
96
|
+
O adapter do Prisma 7 já foi publicado no npm como `@vsrepo/prisma7-adapter` — por enquanto é o **único** adapter publicado. Os adapters para os outros ORMs listados acima (Prisma 8, TypeORM, Drizzle) estão **planejados**; eles só ainda não foram publicados. Até que exista um pacote `@vsrepo/*-adapter` oficial para o seu ORM, você pode escrever o seu próprio para o seu projeto e, se quiser, publicá-lo e abrir um PR para ajudar a fazer o ecossistema crescer — contribuições nesse sentido são muito bem-vindas.
|
|
105
97
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
98
|
+
| Adapter | Status |
|
|
99
|
+
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa todo o contrato de `VSRepoAdapter` (CRUD, relations, transactions, `merge`, logging) com testes; veja o [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) para o código-fonte e docs. |
|
|
101
|
+
| TypeORM (`@vsrepo/typeorm-adapter`) | 🟡 **Planejado, ainda não publicado.** Só foi escrito um parser de referência da cláusula `where` (`parseVSRepoWhere`) para validar o design; é o ponto de partida planejado do futuro pacote `@vsrepo/typeorm-adapter`. Contribuições da comunidade nessa frente são bem-vindas. |
|
|
102
|
+
| Outros ORMs (Prisma 8, Drizzle, etc.) | 🟡 **Planejados, ainda não publicados.** Nenhum pacote oficial existe ainda — por enquanto, escreva o seu próprio adapter (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) e considere publicá-lo/contribuir de volta com o projeto. |
|
|
103
|
+
| Adapters customizados | 🟢 Totalmente suportados hoje — implemente você mesmo a classe abstrata [`VSRepoAdapter`](#escrevendo-seu-próprio-adapter) para qualquer ORM/banco que precisar, no seu próprio projeto ou pacote, seguindo o mesmo formato esperado dos `@vsrepo/*-adapter`. |
|
|
110
104
|
|
|
111
|
-
|
|
105
|
+
Resumindo: a classe de repository, os decoradores `@DynamicMethod`/`@QueryMethod`, o engine de parsing de nomes, o tratamento de erros e o logging já funcionam de ponta a ponta, e o suporte ao Prisma 7 agora é um adapter lançado e publicado. Adapters oficiais para os demais ORMs estão no roadmap e serão distribuídos como pacotes `@vsrepo/*-adapter` separados, e não como parte do pacote core `vsrepo` — mas você não precisa esperar por isso: escrever (e opcionalmente publicar) o seu próprio adapter enquanto isso é uma forma totalmente suportada de usar a v2 hoje e de contribuir de volta com o projeto.
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
generated/vsrepo/
|
|
115
|
-
├── DynamicRepository.ts
|
|
116
|
-
├── DynamicRepository.types.d.ts
|
|
117
|
-
├── VSRepoError.ts
|
|
118
|
-
├── VSRepoError.types.d.ts
|
|
119
|
-
├── VSRepository.ts
|
|
120
|
-
├── VSRepository.types.d.ts
|
|
121
|
-
└── index.ts
|
|
122
|
-
```
|
|
107
|
+
---
|
|
123
108
|
|
|
124
|
-
|
|
109
|
+
## Instalação
|
|
125
110
|
|
|
126
|
-
|
|
127
|
-
// CORRETO ✅
|
|
128
|
-
import { setupVSRepo } from "../../generated/vsrepo";
|
|
111
|
+
A v2 é instalada como o pacote core mais um pacote de adapter para o seu ORM, por exemplo:
|
|
129
112
|
|
|
130
|
-
|
|
131
|
-
|
|
113
|
+
```bash
|
|
114
|
+
npm i vsrepo @vsrepo/prisma7-adapter
|
|
132
115
|
```
|
|
133
116
|
|
|
117
|
+
> O `vsrepo` v2.0.0 e o `@vsrepo/prisma7-adapter` já foram publicados no npm e estão prontos para uso. Para qualquer ORM além do Prisma 7, ainda não existe um pacote de adapter — instale o core e escreva o seu próprio (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)).
|
|
118
|
+
|
|
134
119
|
---
|
|
135
120
|
|
|
136
121
|
## Uso básico
|
|
137
122
|
|
|
138
|
-
###
|
|
123
|
+
### Implementando/escolhendo um adapter
|
|
139
124
|
|
|
140
|
-
```
|
|
125
|
+
```typescript
|
|
141
126
|
// src/configs/db.ts
|
|
142
|
-
import { PrismaClient } from
|
|
143
|
-
import { PrismaPg } from
|
|
144
|
-
import
|
|
127
|
+
import { PrismaClient } from "../../generated/prisma/client";
|
|
128
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
129
|
+
import "dotenv/config";
|
|
145
130
|
|
|
146
131
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
147
132
|
const prisma = new PrismaClient({ adapter });
|
|
@@ -149,1459 +134,862 @@ const prisma = new PrismaClient({ adapter });
|
|
|
149
134
|
export default prisma;
|
|
150
135
|
```
|
|
151
136
|
|
|
152
|
-
### Criando um
|
|
137
|
+
### Criando um repository
|
|
153
138
|
|
|
154
|
-
```
|
|
155
|
-
// src/repositories/
|
|
139
|
+
```typescript
|
|
140
|
+
// src/repositories/user.repository.ts
|
|
141
|
+
import { VSRepository, DynamicMethod } from "vsrepo";
|
|
142
|
+
import { VSRepoPrisma7Adapter } from "@vsrepo/prisma7-adapter";
|
|
156
143
|
import prisma from "../configs/db";
|
|
157
|
-
import {
|
|
158
|
-
import type { User } from "../../generated/prisma/client";
|
|
159
|
-
|
|
160
|
-
const userRepository = setupVSRepo<User, "User">()(({
|
|
161
|
-
tableName: "user",
|
|
162
|
-
pkName: "id",
|
|
163
|
-
selectModels: {
|
|
164
|
-
public: { id: true, name: true, email: true },
|
|
165
|
-
},
|
|
166
|
-
defaultSelectModel: "public",
|
|
167
|
-
}).build(prisma);
|
|
168
|
-
|
|
169
|
-
export default userRepository;
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Usando o repositório
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
import userRepository from "./repositories/userRepository";
|
|
176
|
-
|
|
177
|
-
const user = await userRepository.save({
|
|
178
|
-
name: "John",
|
|
179
|
-
email: "john@email.com",
|
|
180
|
-
password: "password",
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const found = await userRepository.get(user.id);
|
|
184
|
-
const all = await userRepository.getAll();
|
|
185
|
-
|
|
186
|
-
user.name = "John Smith";
|
|
187
|
-
|
|
188
|
-
await userRepository.save(user);
|
|
189
|
-
await userRepository.remove(user.id);
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Abordagem baseada em classes (DynamicRepository)
|
|
195
|
-
|
|
196
|
-
Se você prefere um estilo OOP com decorators em vez da abordagem funcional `setupVSRepo`, o VSRepository também oferece `DynamicRepository` — uma classe que você pode estender com decorators `@DynamicMethod()` para definir seus métodos dinâmicos.
|
|
197
|
-
|
|
198
|
-
Veja **[README-DynamicRepo.pt-BR.md](./README-DynamicRepo.pt-BR.md)** para a documentação completa da abordagem baseada em classes, incluindo exemplos de integração com NestJS, configuração de decorators e uma comparação com o `setupVSRepo`.
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## Integração com NestJS
|
|
203
|
-
|
|
204
|
-
O VSRepository pode ser facilmente integrado a projetos NestJS através de providers. Abaixo está um exemplo completo usando o padrão de injeção de dependência do NestJS.
|
|
205
|
-
|
|
206
|
-
### Configurando o repositório como provider
|
|
144
|
+
import type { UserGetPayload } from "../../generated/prisma/models";
|
|
207
145
|
|
|
208
|
-
|
|
209
|
-
// src/modules/user/user.repository.ts
|
|
210
|
-
import { Provider } from "@nestjs/common";
|
|
211
|
-
import { PrismaService } from "../../database/prisma.service";
|
|
212
|
-
import { UserGetPayload } from "../../../generated/prisma/models";
|
|
213
|
-
import { setupVSRepo } from "../../../generated/vsrepo";
|
|
146
|
+
type User = UserGetPayload<{ include: { address: true } }>;
|
|
214
147
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
public: {
|
|
223
|
-
id: true,
|
|
224
|
-
email: true,
|
|
225
|
-
createdAt: true,
|
|
226
|
-
updatedAt: true,
|
|
227
|
-
},
|
|
228
|
-
auth: {
|
|
229
|
-
id: true,
|
|
230
|
-
email: true,
|
|
231
|
-
password: true,
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
defaultSelectModel: "public",
|
|
235
|
-
requiredWhere: {
|
|
236
|
-
deletedAt: null,
|
|
237
|
-
},
|
|
238
|
-
relations: {
|
|
239
|
-
profile: {
|
|
240
|
-
mode: "oto",
|
|
241
|
-
pk: "id",
|
|
242
|
-
restriction: "add",
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
methods: {
|
|
246
|
-
findAuthByEmail: {
|
|
247
|
-
map: true,
|
|
248
|
-
proxyTo: "findUniqueByEmail",
|
|
249
|
-
selectModel: "auth",
|
|
250
|
-
},
|
|
251
|
-
findByEmailEndsWith: {
|
|
252
|
-
map: true,
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const setupUserRepository = (prisma: PrismaService) => {
|
|
258
|
-
return userVSRepo.build(prisma);
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
export type UserRepository = ReturnType<typeof setupUserRepository>;
|
|
262
|
-
/*
|
|
263
|
-
O tipo também pode ser inferido usando o `RepositoryOf` do VSRepository, passando o tipo de `userVSRepo`:
|
|
264
|
-
|
|
265
|
-
export type UserRepository = RepositoryOf<typeof userVSRepo>;
|
|
266
|
-
|
|
267
|
-
OBS: Se você usar `.extend` para estender o repositório ou configurar os métodos base,
|
|
268
|
-
usar `ReturnType` é recomendado por ser mais simples de inferir o tipo
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
|
-
export const USER_REPOSITORY = Symbol("USER_REPOSITORY");
|
|
272
|
-
|
|
273
|
-
export const UserRepositoryProvider: Provider = {
|
|
274
|
-
provide: USER_REPOSITORY,
|
|
275
|
-
inject: [PrismaService],
|
|
276
|
-
useFactory: setupUserRepository,
|
|
277
|
-
};
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Registrando o provider no módulo
|
|
281
|
-
|
|
282
|
-
```ts
|
|
283
|
-
// src/modules/user/user.module.ts
|
|
284
|
-
import { Module } from "@nestjs/common";
|
|
285
|
-
import { UserRepositoryProvider } from "./user.repository";
|
|
286
|
-
import { UserService } from "./user.service";
|
|
287
|
-
import { UserController } from "./user.controller";
|
|
288
|
-
|
|
289
|
-
@Module({
|
|
290
|
-
imports: [DatabaseModule],
|
|
291
|
-
providers: [UserRepositoryProvider, UserService],
|
|
292
|
-
controllers: [UserController],
|
|
293
|
-
exports: [UserService],
|
|
294
|
-
})
|
|
295
|
-
export class UserModule {}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### Usando o repositório em um service
|
|
299
|
-
|
|
300
|
-
```ts
|
|
301
|
-
// src/modules/user/user.service.ts
|
|
302
|
-
import { Injectable, Inject } from "@nestjs/common";
|
|
303
|
-
import { USER_REPOSITORY, type UserRepository } from "./user.repository";
|
|
304
|
-
|
|
305
|
-
@Injectable()
|
|
306
|
-
export class UserService {
|
|
307
|
-
constructor(
|
|
308
|
-
@Inject(USER_REPOSITORY)
|
|
309
|
-
private readonly userRepository: UserRepository,
|
|
310
|
-
) {}
|
|
311
|
-
|
|
312
|
-
async getUserById(id: string) {
|
|
313
|
-
return this.userRepository.get(id);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async getUserAuthByEmail(email: string) {
|
|
317
|
-
return this.userRepository.findAuthByEmail(email);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async createUser(data: { email: string; password: string; name: string }) {
|
|
321
|
-
return this.userRepository.save({
|
|
322
|
-
email: data.email,
|
|
323
|
-
password: data.password,
|
|
324
|
-
name: data.name,
|
|
148
|
+
class UserRepository extends VSRepository<User, string> {
|
|
149
|
+
constructor() {
|
|
150
|
+
super({
|
|
151
|
+
pkName: "id",
|
|
152
|
+
adapter: new VSRepoPrisma7Adapter<User>(prisma, { tableName: "user", pkName: "id" }),
|
|
153
|
+
softRemoveKey: "deletedAt",
|
|
154
|
+
defaultOrdering: { createdAt: "desc" },
|
|
325
155
|
});
|
|
326
156
|
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**Benefícios dessa abordagem:**
|
|
331
157
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
- ✅ Isolamento da lógica de persistência
|
|
335
|
-
- ✅ Reuso do repositório em múltiplos services
|
|
336
|
-
- ✅ Suporte a transações via `PrismaService`
|
|
158
|
+
@DynamicMethod()
|
|
159
|
+
declare findByEmail: (email: string) => Promise<User[]>;
|
|
337
160
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
Ao chamar `.build(prisma)`, os métodos base abaixo ficam automaticamente disponíveis:
|
|
343
|
-
|
|
344
|
-
| Método | Descrição |
|
|
345
|
-
| ------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
346
|
-
| `get(pk)` | Busca um registro pela sua chave primária |
|
|
347
|
-
| `getOrThrow(pk)` | Busca um registro pela sua chave primária; lança `VSRepoRuntimeError` (código `"20727"`) se não for encontrado |
|
|
348
|
-
| `getList(pks)` | Busca múltiplos registros a partir de uma lista de chaves primárias |
|
|
349
|
-
| `save(obj)` | Cria ou atualiza — se o objeto tiver uma `pk`, realiza um `upsert`; caso contrário, um `create` |
|
|
350
|
-
| `saveList(objs)` | Salva um array de objetos em uma única transação automática |
|
|
351
|
-
| `patch(pk, obj)` | Atualiza parcialmente um registro pela sua chave primária |
|
|
352
|
-
| `patchList(tuples)` | Atualiza parcialmente múltiplos registros via um array de tuplas `[pk, obj]` em uma transação automática |
|
|
353
|
-
| `merge(pk, obj)` | Busca um registro e faz um deep merge em memória — **não persiste**, retorna o objeto mesclado |
|
|
354
|
-
| `remove(pk)` | Remove um registro pela sua chave primária |
|
|
355
|
-
| `removeList(pks)` | Remove vários registros a partir de uma lista de chaves primárias — retorna `{ count }` |
|
|
356
|
-
| `getAll()` | Retorna todos os registros (aceita `pagination` e `order` em `options`) |
|
|
357
|
-
| `total()` | Retorna o número total de registros |
|
|
358
|
-
| `has(pk)` | Verifica se um registro existe pela sua chave primária — retorna `boolean` |
|
|
359
|
-
|
|
360
|
-
Todos eles aceitam `options` como último argumento.
|
|
361
|
-
|
|
362
|
-
### Soft-delete
|
|
363
|
-
|
|
364
|
-
Quando `softRemovekName` está configurado no repositório, os métodos adicionais abaixo ficam disponíveis:
|
|
365
|
-
|
|
366
|
-
| Método | Descrição |
|
|
367
|
-
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
368
|
-
| `softRemove(pk)` | Marca um registro como removido, preenchendo `softRemovekName` com a data atual |
|
|
369
|
-
| `softRemoveList(pks)` | Marca múltiplos registros como removidos em lote — retorna `{ count }` |
|
|
370
|
-
| `restore(pk)` | Restaura um registro com soft-delete, limpando o campo `softRemovekName` |
|
|
371
|
-
| `restoreList(pks)` | Restaura múltiplos registros com soft-delete em lote — retorna `{ count }` |
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
const userRepository = setupVSRepo<User, "user">()(({
|
|
375
|
-
tableName: "user",
|
|
376
|
-
pkName: "id",
|
|
377
|
-
softRemovekName: "deletedAt", // deve ser um campo DateTime no schema do Prisma
|
|
378
|
-
}).build(prisma);
|
|
379
|
-
|
|
380
|
-
await userRepository.softRemove(1);
|
|
381
|
-
await userRepository.restore(1);
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
> O campo informado em `softRemovekName` **deve** ser do tipo `DateTime` no schema do Prisma. O VSRepository valida isso no momento do `build` e lança `VSRepoBuildError` se o tipo estiver incorreto.
|
|
385
|
-
|
|
386
|
-
### Operações em lote
|
|
387
|
-
|
|
388
|
-
`saveList` e `patchList` executam automaticamente todas as operações dentro de uma única transação do Prisma. Se alguma operação falhar, todas as anteriores são desfeitas (rollback).
|
|
389
|
-
|
|
390
|
-
```ts
|
|
391
|
-
// saveList — cria ou atualiza múltiplos objetos em uma transação automática
|
|
392
|
-
const users = await userRepository.saveList([
|
|
393
|
-
{ name: "Mary", email: "mary@email.com" },
|
|
394
|
-
{ id: 2, name: "John Updated", email: "john@email.com" },
|
|
395
|
-
]);
|
|
161
|
+
@DynamicMethod()
|
|
162
|
+
declare findOneByEmail: (email: string) => Promise<User | null>;
|
|
163
|
+
}
|
|
396
164
|
|
|
397
|
-
|
|
398
|
-
const updated = await userRepository.patchList([
|
|
399
|
-
[1, { active: false }],
|
|
400
|
-
[2, { name: "New Name" }],
|
|
401
|
-
]);
|
|
165
|
+
export default new UserRepository();
|
|
402
166
|
```
|
|
403
167
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
await prisma.$transaction(async (tx) => {
|
|
408
|
-
await userRepository.saveList([{ name: "Mary" }, { name: "Gus" }], { db: tx });
|
|
409
|
-
await userRepository.patchList([[1, { active: false }], [2, { active: true }]], { db: tx });
|
|
410
|
-
});
|
|
411
|
-
```
|
|
168
|
+
> A API do core (`VSRepository`, `VSRepoAdapter`, `DynamicMethod`, `QueryMethod`, `VSRepoError`, enums e tipos) é importada do entry point único `vsrepo`. O adapter concreto vem de um pacote **separado** (`@vsrepo/*-adapter`). No Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já publicado (o construtor dele recebe um objeto de config — `tableName`, `pkName`, `relations`/`logLevel` opcionais — como no exemplo acima). Adapters oficiais para outros ORMs estão planejados, mas ainda não publicados; até lá, você pode implementar o contrato `VSRepoAdapter` você mesmo (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) — e publicá-lo para ajudar o projeto é muito bem-vindo.
|
|
412
169
|
|
|
413
|
-
|
|
170
|
+
> **O terceiro generic (`OrmTypes`):** `VSRepository<Entity, PKType, OrmTypes>` aceita um terceiro type parameter opcional descrevendo os tipos de client/transaction do seu ORM, via `VSRepoOrmTypes` (`{ dbClient; dbTransaction }`). Ao fornecê-lo, `getDbClient()`, o callback de `transaction()` e a option `db` de todo método passam a ser tipados corretamente, em vez de `any`:
|
|
171
|
+
> ```typescript
|
|
172
|
+
> type PrismaOrmTypes = { dbClient: PrismaClient; dbTransaction: Prisma.TransactionClient };
|
|
173
|
+
>
|
|
174
|
+
> class UserRepository extends VSRepository<User, string, PrismaOrmTypes> {
|
|
175
|
+
> // getDbClient() agora retorna PrismaClient, e transaction(fn) tipa `tx` como Prisma.TransactionClient
|
|
176
|
+
> }
|
|
177
|
+
> ```
|
|
178
|
+
> Se omitido, o padrão é `VSRepoOrmTypes` (`dbClient`/`dbTransaction` como `any`).
|
|
414
179
|
|
|
415
|
-
|
|
180
|
+
### Usando o repository
|
|
416
181
|
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
// existing: { id: 1, name: "Mary", profile: { bio: "Hi", age: 25 } }
|
|
182
|
+
```typescript
|
|
183
|
+
import userRepository from "./repositories/user.repository";
|
|
420
184
|
|
|
421
|
-
const
|
|
422
|
-
|
|
185
|
+
const usuario = await userRepository.save({
|
|
186
|
+
name: "Joao",
|
|
187
|
+
email: "joao@email.com",
|
|
188
|
+
password: "password",
|
|
423
189
|
});
|
|
424
|
-
// merged: { id: 1, name: "Mary", profile: { bio: "Updated bio", age: 25 } }
|
|
425
190
|
|
|
426
|
-
|
|
427
|
-
await userRepository.
|
|
428
|
-
|
|
191
|
+
const encontrado = await userRepository.get(usuario.id);
|
|
192
|
+
const todos = await userRepository.getAll();
|
|
193
|
+
const porEmail = await userRepository.findByEmail("joao@email.com");
|
|
429
194
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
**O merge de relações to-many (`otm`/`mtm`) é feito por PK, não por simples concatenação.** Para relações to-one (`oto`/`mto`), o `merge` faz um deep merge comum do objeto. Para relações to-many, cada item do array enviado é comparado com o item existente que tem a mesma PK (definida em `relations[key].pk`): se a PK bater, os dois objetos são mesclados; se não bater (um item novo, sem correspondente), ele é simplesmente adicionado à lista. Itens existentes que não aparecem no array enviado são mantidos.
|
|
433
|
-
|
|
434
|
-
```ts
|
|
435
|
-
const existing = await userRepository.get(1);
|
|
436
|
-
// existing: {
|
|
437
|
-
// id: 1,
|
|
438
|
-
// posts: [
|
|
439
|
-
// { id: 10, title: "Post A", published: false },
|
|
440
|
-
// { id: 11, title: "Post B", published: true },
|
|
441
|
-
// ],
|
|
442
|
-
// }
|
|
443
|
-
|
|
444
|
-
const merged = await userRepository.merge(1, {
|
|
445
|
-
posts: [
|
|
446
|
-
{ id: 10, published: true }, // mesma PK (id: 10) → mescla com o item existente
|
|
447
|
-
{ title: "Post C" }, // sem PK → adicionado como novo item
|
|
448
|
-
],
|
|
449
|
-
});
|
|
450
|
-
// merged: {
|
|
451
|
-
// id: 1,
|
|
452
|
-
// posts: [
|
|
453
|
-
// { id: 10, title: "Post A", published: true }, // mesclado
|
|
454
|
-
// { id: 11, title: "Post B", published: true }, // mantido, não estava no array enviado
|
|
455
|
-
// { title: "Post C" }, // adicionado
|
|
456
|
-
// ],
|
|
457
|
-
// }
|
|
195
|
+
await userRepository.patch(usuario.id, { name: "Joao Pedro" });
|
|
196
|
+
await userRepository.remove(usuario.id);
|
|
458
197
|
```
|
|
459
198
|
|
|
460
|
-
> Observe que o `merge` nunca remove itens de uma relação to-many — ele apenas mescla os que batem por PK e adiciona os que não batem. Para remover itens de uma relação, use `save`/`patch` com `restriction: "set"` na configuração da relação.
|
|
461
|
-
|
|
462
|
-
### Configurando os métodos base
|
|
463
|
-
|
|
464
|
-
O segundo argumento de `.build(prisma, config)` permite ajustar o comportamento global do repositório e customizar cada método base individualmente através de `baseMethods`.
|
|
465
|
-
|
|
466
|
-
```ts
|
|
467
|
-
userVSRepo.build(prisma, {
|
|
468
|
-
// Exibe os logs internos do VSRepository no console (queries montadas, prefixo
|
|
469
|
-
// detectado, filtros aplicados, etc). Ótimo para debugar métodos dinâmicos. Padrão = false.
|
|
470
|
-
showWorking: true,
|
|
471
|
-
|
|
472
|
-
baseMethods: {
|
|
473
|
-
get: {
|
|
474
|
-
// Habilita/desabilita o método no repositório final. Se `false`, o método
|
|
475
|
-
// nem aparece no tipo do repositório (não é apenas um erro em runtime). Padrão = true.
|
|
476
|
-
active: true,
|
|
477
|
-
|
|
478
|
-
// Select model aplicado por padrão quando o método é chamado sem `options.selectModel`.
|
|
479
|
-
// Sobrescreve o `defaultSelectModel` de setupVSRepo apenas para este método.
|
|
480
|
-
defaultSelect: "public",
|
|
481
|
-
},
|
|
482
|
-
remove: {
|
|
483
|
-
active: true,
|
|
484
|
-
defaultSelect: "minimal",
|
|
485
|
-
|
|
486
|
-
// Quando `true`, ignora o `requiredWhere` configurado em setupVSRepo para
|
|
487
|
-
// este método específico — útil quando um método precisa "furar" um
|
|
488
|
-
// filtro global (ex: multi-tenancy) em um caso específico. Padrão = false.
|
|
489
|
-
ignoreRequiredWhere: false,
|
|
490
|
-
},
|
|
491
|
-
save: {
|
|
492
|
-
// Aqui apenas `ignoreRequiredWhere` é definido — `active` e `defaultSelect`
|
|
493
|
-
// mantêm seus padrões (true e o `defaultSelectModel` global).
|
|
494
|
-
ignoreRequiredWhere: true,
|
|
495
|
-
},
|
|
496
|
-
patch: {
|
|
497
|
-
// Apenas o select é sobrescrito; o método continua ativo normalmente.
|
|
498
|
-
defaultSelect: "minimal",
|
|
499
|
-
},
|
|
500
|
-
has: {
|
|
501
|
-
active: false, // Desabilita 'has' (padrão = true) — o método desaparece do repositório
|
|
502
|
-
},
|
|
503
|
-
softRemove: {
|
|
504
|
-
// Métodos de soft-delete seguem as mesmas opções (`active`, `defaultSelect`,
|
|
505
|
-
// `ignoreRequiredWhere`). Só estão disponíveis se `softRemovekName` estiver configurado.
|
|
506
|
-
active: true,
|
|
507
|
-
defaultSelect: "minimal",
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
});
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
> Métodos de lote/agregação como `removeList`, `softRemoveList`, `restoreList`, `total` e `has` **não** aceitam `defaultSelect` (eles não retornam um registro selecionável — retornam `{ count }` ou `boolean`). Nesses casos, `BaseMethodConfig` fica restrito a `active` e `ignoreRequiredWhere`.
|
|
514
|
-
|
|
515
199
|
---
|
|
516
200
|
|
|
517
|
-
##
|
|
518
|
-
|
|
519
|
-
`selectModels` define projeções de dados nomeadas e reutilizáveis.
|
|
520
|
-
|
|
521
|
-
```ts
|
|
522
|
-
selectModels: {
|
|
523
|
-
public: { id: true, name: true, email: true },
|
|
524
|
-
internal: { id: true, name: true, email: true, password: true },
|
|
525
|
-
minimal: { id: true },
|
|
526
|
-
},
|
|
527
|
-
defaultSelectModel: "public",
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
`defaultSelectModel` define qual select é usado automaticamente quando nenhum é especificado na chamada. É recomendado sempre defini-lo junto com `selectModels`.
|
|
531
|
-
|
|
532
|
-
**Usando um select específico na chamada:**
|
|
533
|
-
|
|
534
|
-
```ts
|
|
535
|
-
const user = await userRepository.get(id, { selectModel: "minimal" });
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
**Retornando o payload padrão do Prisma (sem select):**
|
|
201
|
+
## Options do construtor
|
|
539
202
|
|
|
540
|
-
|
|
541
|
-
const fullUser = await userRepository.get(id, { selectModel: false });
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### Select bruto (`options.select`)
|
|
545
|
-
|
|
546
|
-
Além do `selectModel` (nomeado, pré-configurado em `selectModels`), você pode passar um `select` bruto do Prisma diretamente na chamada, sem precisar registrá-lo antecipadamente no repositório.
|
|
547
|
-
|
|
548
|
-
```ts
|
|
549
|
-
const usuario = await usuarioRepository.get(id, {
|
|
550
|
-
select: { id: true, nome: true },
|
|
551
|
-
});
|
|
552
|
-
```
|
|
203
|
+
`VSRepoOptions<T, K>`, passado para o `super(...)` dentro do construtor do seu repository:
|
|
553
204
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
**
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
```ts
|
|
563
|
-
// CORRETO ✅ — apenas select bruto
|
|
564
|
-
await usuarioRepository.get(id, { select: { id: true, nome: true } });
|
|
565
|
-
|
|
566
|
-
// ERRADO ❌ — combinar select com selectModel/includeModel/include não é permitido
|
|
567
|
-
await usuarioRepository.get(id, { selectModel: "public", select: { id: true } });
|
|
568
|
-
await usuarioRepository.get(id, { include: { posts: true }, select: { id: true } });
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
> **Quando usar `selectModel` vs. `select`:** prefira `selectModel` para projeções reutilizadas em várias chamadas (definidas uma vez em `selectModels`); use `select` para projeções específicas e ocasionais que não precisam de um nome.
|
|
205
|
+
| Option | Tipo | Descrição |
|
|
206
|
+
| -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
207
|
+
| `adapter` | `VSRepoAdapter<T>` | **Obrigatório.** A instância do adapter que traduz as chamadas do repository em chamadas contra o ORM/banco por trás dele. |
|
|
208
|
+
| `pkName` | `keyof T` | **Obrigatório.** Nome do campo que representa a primary key da entidade. |
|
|
209
|
+
| `softRemoveKey` | `keyof T` | Opcional. Quando definido, habilita `softRemove`, `softRemoveList`, `restore` e `restoreList`. |
|
|
210
|
+
| `defaultOrdering` | `Ordering<T>` | Opcional. Ordenação padrão aplicada automaticamente em queries que aceitam `order`, a menos que seja sobrescrita em uma chamada específica. |
|
|
211
|
+
| `logLevel` | `VSLogLevel` | Opcional. Severidade mínima impressa pelo logger interno. Padrão: `VSLogLevel.WARN`. |
|
|
212
|
+
| `logSlowThresholdMs` | `number` | Opcional. Duração (ms) acima da qual uma operação concluída é logada como `WARN` em vez de `DEBUG`. Padrão: 300ms. |
|
|
572
213
|
|
|
573
214
|
---
|
|
574
215
|
|
|
575
|
-
##
|
|
576
|
-
|
|
577
|
-
`includeModels` funciona de forma parecida com `selectModels`, mas em vez de receber um `select`, recebe um `include` válido do Prisma.
|
|
578
|
-
|
|
579
|
-
```ts
|
|
580
|
-
const userRepository = setupVSRepo<User, "user">()(({
|
|
581
|
-
tableName: "user",
|
|
582
|
-
pkName: "id",
|
|
583
|
-
selectModels: {
|
|
584
|
-
public: { id: true, name: true, email: true },
|
|
585
|
-
},
|
|
586
|
-
defaultSelectModel: "public",
|
|
587
|
-
includeModels: {
|
|
588
|
-
withPosts: { posts: true },
|
|
589
|
-
withPostsAndProfile: { posts: true, profile: true },
|
|
590
|
-
},
|
|
591
|
-
}).build(prisma);
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
**Usando um `includeModel` na chamada:**
|
|
595
|
-
|
|
596
|
-
```ts
|
|
597
|
-
const user = await userRepository.get(id, { includeModel: "withPosts" });
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
Nesse caso, o `select` padrão (`selectModels`/`defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
|
|
601
|
-
|
|
602
|
-
### Diferenças em relação a `selectModels`
|
|
216
|
+
## Métodos base
|
|
603
217
|
|
|
604
|
-
|
|
605
|
-
- **`includeModel` e `selectModel` não podem ser passados juntos** na mesma chamada. Se um `includeModel` for informado, qualquer `selectModel` (incluindo o padrão) é ignorado.
|
|
218
|
+
Disponíveis automaticamente em toda subclasse de `VSRepository`:
|
|
606
219
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
220
|
+
| Método | Descrição |
|
|
221
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `get(pk, options?)` | Busca um registro pela primary key. |
|
|
223
|
+
| `getOrThrow(pk, options?)` | Busca um registro pela primary key, lançando erro se não encontrar. |
|
|
224
|
+
| `getList(pks, options?)` | Busca vários registros por uma lista de primary keys. |
|
|
225
|
+
| `getAll(options?)` | Busca todos os registros; aceita `pagination` e `order` em `options`. |
|
|
226
|
+
| `save(obj, options?)` | Cria ou atualiza (upsert) um único registro. |
|
|
227
|
+
| `saveList(objs, options?)` | Cria ou atualiza (upsert) vários registros em uma única chamada. |
|
|
228
|
+
| `patch(pk, obj, options?)` | Atualiza parcialmente um registro pela primary key. |
|
|
229
|
+
| `merge(pk, obj, options?)` | Busca um registro e o retorna mesclado (deep-merge), em memória, com o objeto informado — **não** persiste nada. |
|
|
230
|
+
| `remove(pk, options?)` | Remove um registro pela primary key. |
|
|
231
|
+
| `removeList(pks, options?)` | Remove vários registros pela primary key, retornando `{ count }`. |
|
|
232
|
+
| `total(options?)` | Retorna o total de registros. |
|
|
233
|
+
| `has(pk, options?)` | Verifica se um registro existe, retornando `boolean`. |
|
|
234
|
+
| `transaction(fn, options?)` | Executa `fn` dentro de uma transação nativa do ORM. |
|
|
235
|
+
| `getDbClient()` | Retorna a instância do client do ORM usada fora de transações. |
|
|
236
|
+
| `query<T>(query, options?)` | Executa uma instrução SQL raw diretamente contra o banco. Veja [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query). |
|
|
610
237
|
|
|
611
|
-
|
|
612
|
-
await userRepository.get(id, { selectModel: "public" });
|
|
238
|
+
Todos os métodos acima (exceto `transaction`, `query` e `getDbClient`, que recebem options proprias ou nenhuma) aceitam um objeto `MethodOptions<Entity, OrmTypes>` como último argumento (`select`, `relations`, `see`, `db`).
|
|
613
239
|
|
|
614
|
-
|
|
615
|
-
await userRepository.get(id, { selectModel: "public", includeModel: "withPosts" });
|
|
616
|
-
```
|
|
240
|
+
---
|
|
617
241
|
|
|
618
|
-
|
|
242
|
+
## Soft-delete
|
|
619
243
|
|
|
620
|
-
|
|
244
|
+
O soft-delete agora é um **conceito nativo de primeira classe**. Configure `softRemoveKey` uma vez no repository:
|
|
621
245
|
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
|
|
246
|
+
```typescript
|
|
247
|
+
super({
|
|
248
|
+
pkName: "id",
|
|
249
|
+
adapter,
|
|
250
|
+
softRemoveKey: "deletedAt",
|
|
625
251
|
});
|
|
626
252
|
```
|
|
627
253
|
|
|
628
|
-
|
|
254
|
+
Isso libera quatro métodos extras:
|
|
629
255
|
|
|
630
|
-
|
|
256
|
+
| Método | Efeito |
|
|
257
|
+
| ------------------------------- | --------------------------------------- |
|
|
258
|
+
| `softRemove(pk, options?)` | Define `deletedAt` para a data atual. |
|
|
259
|
+
| `softRemoveList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
|
|
260
|
+
| `restore(pk, options?)` | Volta `deletedAt` para `null`. |
|
|
261
|
+
| `restoreList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
|
|
631
262
|
|
|
632
|
-
|
|
633
|
-
- **Ad hoc, não reutilizável.** Diferente do `includeModel`, não precisa ser declarado em `includeModels`. Use para includes pontuais que não justificam um model nomeado.
|
|
634
|
-
- **Nenhum `selectModel` padrão é aplicado.** Assim como com `includeModel`, quando `include` é informado, o select (incluindo `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
|
|
263
|
+
Todo o restante dos métodos aceita uma option `see` que controla a visibilidade de registros com soft-delete:
|
|
635
264
|
|
|
636
|
-
```
|
|
637
|
-
//
|
|
638
|
-
await userRepository.
|
|
639
|
-
|
|
640
|
-
// ERRADO ❌ — combinar include com selectModel/includeModel não é permitido
|
|
641
|
-
await userRepository.get(id, { selectModel: "public", include: { posts: true } });
|
|
642
|
-
await userRepository.get(id, { includeModel: "withPosts", include: { posts: true } });
|
|
265
|
+
```typescript
|
|
266
|
+
await userRepository.getAll({ see: "active" }); // padrão — apenas registros não removidos
|
|
267
|
+
await userRepository.getAll({ see: "removed" }); // apenas registros com soft-delete
|
|
268
|
+
await userRepository.getAll({ see: "all" }); // todos, ignorando o soft-delete
|
|
643
269
|
```
|
|
644
270
|
|
|
645
|
-
> **Quando usar `includeModel` vs. `include`:** prefira `includeModel` para includes reutilizados em várias chamadas (definidos uma vez em `includeModels`); use `include` para includes específicos e pontuais que não precisam de um nome.
|
|
646
|
-
|
|
647
271
|
---
|
|
648
272
|
|
|
649
|
-
##
|
|
650
|
-
|
|
651
|
-
`requiredWhere` define filtros que são aplicados automaticamente em toda query do repositório.
|
|
652
|
-
|
|
653
|
-
```ts
|
|
654
|
-
requiredWhere: { active: true },
|
|
655
|
-
```
|
|
273
|
+
## `select` e `relations`
|
|
656
274
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
```ts
|
|
660
|
-
// Internamente: WHERE active = true
|
|
661
|
-
const users = await userRepository.findMany();
|
|
662
|
-
|
|
663
|
-
// Internamente: WHERE email = 'john@email.com' AND active = true
|
|
664
|
-
const user = await userRepository.findByEmail("john@email.com");
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
Útil para soft-deletes manuais, multi-tenancy e filtros globais de qualquer tipo.
|
|
668
|
-
|
|
669
|
-
---
|
|
670
|
-
|
|
671
|
-
## Ordenação padrão (Default Ordering)
|
|
672
|
-
|
|
673
|
-
`defaultOrdering` define uma ordenação padrão que é aplicada automaticamente em toda query que aceita `orderBy`, sem precisar repetir o argumento `order` em cada chamada.
|
|
674
|
-
|
|
675
|
-
```ts
|
|
676
|
-
const userRepository = setupVSRepo<User, "user">()(({
|
|
677
|
-
tableName: "user",
|
|
678
|
-
pkName: "id",
|
|
679
|
-
defaultOrdering: { createdAt: "desc" },
|
|
680
|
-
}).build(prisma);
|
|
681
|
-
```
|
|
275
|
+
Os `selectModels`/`defaultSelectModel` nomeados e reutilizáveis da v1 não existem mais. Na v2 você passa `select` e `relations` diretamente em cada chamada — não há nada para pré-registrar:
|
|
682
276
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const users = await userRepository.getAll();
|
|
688
|
-
|
|
689
|
-
// Também se aplica ao getAll com paginação
|
|
690
|
-
const paginated = await userRepository.getAll({ pagination: { take: 10 } });
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
**`defaultOrdering` é ignorado quando:**
|
|
694
|
-
|
|
695
|
-
- O método usa o sufixo `Ordered`, `OrderedAndPaginated` ou `PaginatedAndOrdered` — nesses casos, o argumento `order` passado na chamada tem prioridade.
|
|
696
|
-
- O método dinâmico tem `injectOrdering` configurado — a ordenação fixa do método tem precedência.
|
|
277
|
+
```typescript
|
|
278
|
+
const usuario = await userRepository.get(id, {
|
|
279
|
+
select: { id: true, name: true, address: { city: true } },
|
|
280
|
+
});
|
|
697
281
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
findManyByActive: { map: true }, // sem Ordered → defaultOrdering aplicado
|
|
702
|
-
findManyByStatus: {
|
|
703
|
-
map: true,
|
|
704
|
-
injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignorado
|
|
705
|
-
},
|
|
706
|
-
}
|
|
282
|
+
const usuarioComEndereco = await userRepository.get(id, {
|
|
283
|
+
relations: { address: true },
|
|
284
|
+
});
|
|
707
285
|
```
|
|
708
286
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
## Opção `see`
|
|
714
|
-
|
|
715
|
-
Quando `softRemovekName` está configurado, todo método aceita a opção `see` para controlar a visibilidade de registros com soft-delete:
|
|
716
|
-
|
|
717
|
-
| Valor | Comportamento |
|
|
718
|
-
| ----------- | -------------------------------------------------------------- |
|
|
719
|
-
| `"active"` | Retorna apenas registros que **não** foram removidos (padrão) |
|
|
720
|
-
| `"removed"` | Retorna apenas registros removidos |
|
|
721
|
-
| `"all"` | Retorna todos os registros, independente do status |
|
|
722
|
-
|
|
723
|
-
```ts
|
|
724
|
-
// Retorna apenas usuários ativos (padrão)
|
|
725
|
-
const active = await userRepository.getAll();
|
|
726
|
-
|
|
727
|
-
// Retorna apenas usuários removidos
|
|
728
|
-
const removed = await userRepository.getAll({ see: "removed" });
|
|
287
|
+
- `select` espelha o formato da entidade: campos escalares recebem um `boolean`; campos de relação recebem um `boolean` ou um `select` aninhado.
|
|
288
|
+
- `relations` carrega registros relacionados; cada campo de relação recebe um `boolean` ou um objeto `relations` aninhado.
|
|
289
|
+
- Se `select` e `relations` podem ser combinados depende do adapter (veja abaixo).
|
|
729
290
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
>
|
|
291
|
+
> ⚠️ **Comportamento de `relations` depende do adapter:**
|
|
292
|
+
>
|
|
293
|
+
> O core apenas repassa `MethodOptions.select` e `MethodOptions.relations` ao adapter — cada adapter decide como traduzi-los para o ORM subjacente:
|
|
294
|
+
>
|
|
295
|
+
> - **TypeORM (`@vsrepo/typeorm-adapter`)** — `relations` é **obrigatório** para carregar qualquer relação, mesmo quando você quer apenas uma projeção aninhada via `select`. O TypeORM não fará JOIN/carregar a relação a menos que ela esteja listada em `relations`:
|
|
296
|
+
> ```typescript
|
|
297
|
+
> // TypeORM: apenas select NÃO é suficiente
|
|
298
|
+
> await userRepository.get(id, {
|
|
299
|
+
> select: { id: true, address: { city: true } },
|
|
300
|
+
> relations: { address: true }, // ← obrigatório no TypeORM
|
|
301
|
+
> });
|
|
302
|
+
> ```
|
|
303
|
+
> - **Prisma 7 (`@vsrepo/prisma7-adapter` / `VSRepoPrisma7Adapter`)** — `relations` é convertido para `include` do Prisma (`parsePrismaInclude`). **Se `select` estiver presente, `relations` é ignorado** porque o Prisma não permite `select` + `include` na mesma query:
|
|
304
|
+
> ```typescript
|
|
305
|
+
> // Prisma7: relations é ignorado quando select existe
|
|
306
|
+
> await userRepository.get(id, {
|
|
307
|
+
> select: { id: true, name: true },
|
|
308
|
+
> relations: { address: true }, // ← ignorado, include = undefined
|
|
309
|
+
> });
|
|
310
|
+
> ```
|
|
311
|
+
>
|
|
312
|
+
> Adapters customizados podem mapear `relations` de forma diferente — consulte a documentação do adapter para a semântica exata.
|
|
735
313
|
|
|
736
314
|
---
|
|
737
315
|
|
|
738
316
|
## Métodos dinâmicos
|
|
739
317
|
|
|
740
|
-
Métodos dinâmicos são
|
|
741
|
-
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
318
|
+
Métodos dinâmicos são declarados como um campo `declare` anotado com `@DynamicMethod()`. O comportamento deles — qual método do adapter chamar, quais filtros aplicar e como os argumentos se mapeiam para eles — é inferido inteiramente a partir do **nome** do campo, seguindo a mesma filosofia de convenção sobre configuração da v1.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
class UserRepository extends VSRepository<User, string> {
|
|
322
|
+
@DynamicMethod()
|
|
323
|
+
declare findByEmail: (email: string) => Promise<User[]>;
|
|
324
|
+
|
|
325
|
+
@DynamicMethod()
|
|
326
|
+
declare findOneByEmail: (email: string) => Promise<User | null>;
|
|
327
|
+
|
|
328
|
+
@DynamicMethod()
|
|
329
|
+
declare updateById: (id: string, data: DeepPartial<User>) => Promise<User>;
|
|
330
|
+
|
|
331
|
+
// Baseado em where: VSRepoWhere<T> como primeiro parâmetro, pagination penúltimo, MethodOptions por último
|
|
332
|
+
@DynamicMethod()
|
|
333
|
+
declare findWherePaginated: (
|
|
334
|
+
where: VSRepoWhere<User>,
|
|
335
|
+
pagination: Pagination,
|
|
336
|
+
options?: MethodOptions<User>,
|
|
337
|
+
) => Promise<User[]>;
|
|
338
|
+
|
|
339
|
+
// OrderedAndPaginated: filtros de campo, depois order, depois pagination, depois MethodOptions
|
|
340
|
+
@DynamicMethod()
|
|
341
|
+
declare findByNameIgnoreCaseOrAgeBetweenOrderByCreatedAtAscPaginated: (
|
|
342
|
+
name: string,
|
|
343
|
+
age: [number, number],
|
|
344
|
+
order: Ordering<User>,
|
|
345
|
+
pagination: Pagination,
|
|
346
|
+
options?: MethodOptions<User>,
|
|
347
|
+
) => Promise<User[]>;
|
|
748
348
|
}
|
|
749
349
|
```
|
|
750
350
|
|
|
751
|
-
---
|
|
752
|
-
|
|
753
351
|
### Prefixos disponíveis
|
|
754
352
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
|
758
|
-
|
|
|
759
|
-
| `
|
|
760
|
-
| `
|
|
761
|
-
| `
|
|
762
|
-
| `
|
|
763
|
-
| `
|
|
764
|
-
| `
|
|
765
|
-
| `
|
|
766
|
-
| `
|
|
767
|
-
| `
|
|
768
|
-
| `
|
|
769
|
-
| `
|
|
770
|
-
| `
|
|
771
|
-
| `
|
|
772
|
-
| `
|
|
773
|
-
| `
|
|
774
|
-
| `
|
|
775
|
-
| `
|
|
776
|
-
| `
|
|
777
|
-
| `
|
|
778
|
-
| `
|
|
779
|
-
| `
|
|
780
|
-
| `
|
|
781
|
-
| `
|
|
782
|
-
| `
|
|
783
|
-
| `
|
|
784
|
-
| `
|
|
785
|
-
| `
|
|
786
|
-
| `
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
| `groupBy` | `groupBy` | `Dynamic[]` | O nome deve ser exato; recebe argumentos nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
|
|
790
|
-
|
|
791
|
-
---
|
|
353
|
+
| Prefixo | Método do adapter | Observações |
|
|
354
|
+
| -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| `findBy` | `findMany` | Filtros de campo seguem o prefixo. |
|
|
356
|
+
| `findOneBy` | `findOne` | Filtros de campo seguem o prefixo; resultado único. |
|
|
357
|
+
| `findOneOrThrowBy` | `findOneOrThrow` | Lança erro se não encontrar. |
|
|
358
|
+
| `findOneOrThrow` | `findOneOrThrow` | Sem filtros de campo; aplica só soft-delete/`see`. |
|
|
359
|
+
| `findOneOrThrowWhere` | `findOneOrThrow` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
360
|
+
| `findWhere` | `findMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
361
|
+
| `findOneWhere` | `findOne` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
362
|
+
| `findOne` | `findOne` | Sem filtros de campo; aplica só soft-delete/`see`. |
|
|
363
|
+
| `countBy` | `count` | Filtros de campo seguem o prefixo. |
|
|
364
|
+
| `countWhere` | `count` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
365
|
+
| `count` | `count` | Sem filtros de campo. |
|
|
366
|
+
| `existsBy` | `exists` | Retorna `boolean`. |
|
|
367
|
+
| `existsWhere` | `exists` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
368
|
+
| `create` | `create` | Recebe `data` como argumento. |
|
|
369
|
+
| `createMany` | `createMany` | Recebe `data[]` como argumento; suporta `IgnoreConflicts`. |
|
|
370
|
+
| `createManyReturning` | `createManyReturning` | Recebe `data[]` como argumento; suporta `IgnoreConflicts`; retorna os registros criados (`T[]`), em vez de `CountResult`. |
|
|
371
|
+
| `updateBy` | `update` | Filtros de campo + `data` como argumento. |
|
|
372
|
+
| `updateWhere` | `update` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`. |
|
|
373
|
+
| `updateManyBy` | `updateMany` | Filtros de campo + `data`. |
|
|
374
|
+
| `updateManyWhere` | `updateMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`. |
|
|
375
|
+
| `updateManyReturningBy` | `updateManyReturning` | Filtros de campo + `data`; retorna os registros atualizados. |
|
|
376
|
+
| `updateManyReturningWhere` | `updateManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`; retorna os registros atualizados. |
|
|
377
|
+
| `upsertBy` | `upsert` | Filtros de campo + payloads `create`/`update`. |
|
|
378
|
+
| `upsertWhere` | `upsert` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois os payloads `create`/`update`. |
|
|
379
|
+
| `deleteBy` | `delete` | Filtros de campo seguem o prefixo. |
|
|
380
|
+
| `deleteWhere` | `delete` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
381
|
+
| `deleteManyBy` | `deleteMany` | Filtros de campo seguem o prefixo. |
|
|
382
|
+
| `deleteManyWhere` | `deleteMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
383
|
+
| `deleteManyReturningBy` | `deleteManyReturning` | Filtros de campo seguem o prefixo; retorna os registros removidos. |
|
|
384
|
+
| `deleteManyReturningWhere` | `deleteManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento; retorna os registros removidos. |
|
|
385
|
+
|
|
386
|
+
> `aggregate` e `groupBy` **ainda não estão implementados** na v2 (existiam na v1). Está planejado, mas não disponível no momento.
|
|
792
387
|
|
|
793
388
|
### Filtros de campo
|
|
794
389
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
| Sufixo
|
|
798
|
-
|
|
|
799
|
-
|
|
|
800
|
-
| `Not`
|
|
801
|
-
| `In`
|
|
802
|
-
| `NotIn`
|
|
803
|
-
| `Contains`
|
|
804
|
-
| `NotContains`
|
|
805
|
-
| `StartsWith`
|
|
806
|
-
| `NotStartsWith`
|
|
807
|
-
| `EndsWith`
|
|
808
|
-
| `NotEndsWith`
|
|
809
|
-
| `GreaterThan`
|
|
810
|
-
| `GreaterThanEqual`
|
|
811
|
-
| `LessThan`
|
|
812
|
-
| `LessThanEqual`
|
|
813
|
-
| `Between`
|
|
814
|
-
| `NotBetween`
|
|
815
|
-
| `IsNull`
|
|
816
|
-
| `IsNotNull`
|
|
817
|
-
| `IsTrue`
|
|
818
|
-
| `IsFalse`
|
|
819
|
-
| `
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
390
|
+
Aplicados como sufixos ao nome do campo dentro do método (mesma ideia da v1, com um sufixo renomeado):
|
|
391
|
+
|
|
392
|
+
| Sufixo | Significado | Argumento |
|
|
393
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
394
|
+
| _(sem sufixo)_ | igualdade (`=`) | sim |
|
|
395
|
+
| `Not` | negação | sim |
|
|
396
|
+
| `In` | está em | sim (array) |
|
|
397
|
+
| `NotIn` | não está em | sim (array) |
|
|
398
|
+
| `Contains` | contém substring | sim |
|
|
399
|
+
| `NotContains` | não contém substring | sim |
|
|
400
|
+
| `StartsWith` | começa com | sim |
|
|
401
|
+
| `NotStartsWith` | não começa com | sim |
|
|
402
|
+
| `EndsWith` | termina com | sim |
|
|
403
|
+
| `NotEndsWith` | não termina com | sim |
|
|
404
|
+
| `GreaterThan` | `>` | sim |
|
|
405
|
+
| `GreaterThanEqual` | `>=` | sim |
|
|
406
|
+
| `LessThan` | `<` | sim |
|
|
407
|
+
| `LessThanEqual` | `<=` | sim |
|
|
408
|
+
| `Between` | intervalo inclusivo | sim (tupla `[min, max]`) |
|
|
409
|
+
| `NotBetween` | fora de um intervalo inclusivo | sim (tupla `[min, max]`) |
|
|
410
|
+
| `IsNull` | campo é `null` | não |
|
|
411
|
+
| `IsNotNull` | campo não é `null` | não |
|
|
412
|
+
| `IsTrue` | campo é `true` | não |
|
|
413
|
+
| `IsFalse` | campo é `false` | não |
|
|
414
|
+
| `IgnoreCase` | combinador case-insensitive para filtros de texto | não _(renomeado do `Insensitive` da v1)_ |
|
|
415
|
+
| `Optional` | **explícita** o argumento do campo como opcional — ele já é opcional por padrão, então esse sufixo é facultativo e serve apenas para deixar isso explícito | — |
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
@DynamicMethod()
|
|
419
|
+
declare findByNameContainsIgnoreCase: (name: string) => Promise<User[]>;
|
|
420
|
+
|
|
421
|
+
@DynamicMethod()
|
|
422
|
+
declare findByAgeBetween: (age: [number, number]) => Promise<User[]>;
|
|
827
423
|
```
|
|
828
424
|
|
|
829
|
-
`Between` e `NotBetween` recebem uma **tupla `[valorMinimo, valorMaximo]`**:
|
|
830
|
-
|
|
831
|
-
```ts
|
|
832
|
-
methods: {
|
|
833
|
-
findManyByAgeBetween: { map: true },
|
|
834
|
-
findManyBySalaryNotBetween: { map: true },
|
|
835
|
-
findManyByCreatedAtBetween: { map: true },
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
await userRepository.findManyByAgeBetween([18, 65]);
|
|
839
|
-
await userRepository.findManyBySalaryNotBetween([1000, 5000]);
|
|
840
|
-
await userRepository.findManyByCreatedAtBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
|
|
844
|
-
|
|
845
|
-
```ts
|
|
846
|
-
findByNameOptionalAndEmail // name é opcional, email é obrigatório
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
---
|
|
850
|
-
|
|
851
425
|
### Operadores lógicos
|
|
852
426
|
|
|
853
|
-
| Operador
|
|
854
|
-
|
|
|
855
|
-
| `And`
|
|
856
|
-
| `Or`
|
|
857
|
-
| `AND`
|
|
858
|
-
|
|
859
|
-
`AND` (em maiúsculas) tem uma regra específica:
|
|
427
|
+
| Operador | Uso no nome | Exemplo |
|
|
428
|
+
| -------- | ------------------------------ | --------------------------------------------------- |
|
|
429
|
+
| `And` | entre dois campos | `findOneByIdAndEmail` |
|
|
430
|
+
| `Or` | entre dois campos | `findByNameOrEmail` |
|
|
431
|
+
| `AND` | separa um bloco final em `AND` | `findByEmailOrNameANDActiveStatusAndAgeGreaterThan` |
|
|
860
432
|
|
|
861
|
-
|
|
862
|
-
- Todos os campos depois do `AND` são injetados dentro de `AND: []`.
|
|
863
|
-
- Depois de um `AND`, não pode haver um `Or`.
|
|
433
|
+
Regras do `AND` (em capslock), iguais às da v1: só é permitido **um** `AND` por nome de método; todo campo conectado por `And` depois dele é aninhado dentro de `AND: []`; `Or` não pode aparecer depois de um `AND`.
|
|
864
434
|
|
|
865
|
-
|
|
435
|
+
### Filtros de relação
|
|
866
436
|
|
|
867
|
-
|
|
868
|
-
methods: {
|
|
869
|
-
findOneByIdAndEmail: { map: true },
|
|
870
|
-
findByNameOrEmail: { map: true },
|
|
871
|
-
findFirstByIdOrEmailAndName: { map: true },
|
|
872
|
-
findByEmailOrNameANDActiveStatusAndAgeGreaterThan: { map: true }
|
|
873
|
-
}
|
|
437
|
+
Filtram por campos de entidades relacionadas. Internamente, mapeiam para os operadores `_some`/`_every`/`_none`/`_with`/`_without` de `VSRepoWhere` (veja [`select` e `relations`](#select-e-relations) para o equivalente de eager loading).
|
|
874
438
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
439
|
+
| Sufixo | Significado | Restrição |
|
|
440
|
+
| -------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
441
|
+
| `Some` | pelo menos um registro relacionado corresponde | apenas relações to-many |
|
|
442
|
+
| `SomeField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
443
|
+
| `Every` | todo registro relacionado corresponde | apenas relações to-many (precisa de `Field` para ser um filtro efetivo) |
|
|
444
|
+
| `EveryField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
445
|
+
| `None` | nenhum registro relacionado corresponde | apenas relações to-many |
|
|
446
|
+
| `NoneField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
447
|
+
| `With` | o registro relacionado existe | apenas relações to-one |
|
|
448
|
+
| `WithField` | filtra um campo dentro do registro relacionado | apenas relações to-one |
|
|
449
|
+
| `Without` | o registro relacionado não existe | apenas relações to-one |
|
|
450
|
+
| `WithoutField` | filtro negado em um campo do registro relacionado | apenas relações to-one |
|
|
880
451
|
|
|
881
|
-
|
|
452
|
+
```typescript
|
|
453
|
+
@DynamicMethod()
|
|
454
|
+
declare findByAddressWithCityStartsWithIgnoreCase: (city: string) => Promise<User[]>;
|
|
882
455
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
id: 1,
|
|
886
|
-
email: "john@email.com"
|
|
887
|
-
}
|
|
456
|
+
@DynamicMethod()
|
|
457
|
+
declare findByProductsSome: () => Promise<User[]>;
|
|
888
458
|
```
|
|
889
459
|
|
|
890
|
-
|
|
460
|
+
### Ordenação, paginação e distinct
|
|
891
461
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
462
|
+
| Sufixo | Efeito |
|
|
463
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
464
|
+
| `Paginated` | Injeta um argumento `pagination` (`{ limit?, offset? }`) como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
|
|
465
|
+
| `Ordered` | Injeta um argumento `order: Ordering<T>` como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
|
|
466
|
+
| `OrderedAndPaginated` | Injeta `order` como antepenúltimo, depois `pagination` como penúltimo — ambos antes do `MethodOptions`. |
|
|
467
|
+
| `PaginatedAndOrdered` | Injeta `pagination` como antepenúltimo, depois `order` como penúltimo — ambos antes do `MethodOptions`. |
|
|
468
|
+
| `OrderBy<Campo>Asc` / `OrderBy<Campo>Desc` | **Novo na v2.** Embute uma ordenação fixa diretamente no nome do método — encadeie campos com `And` (ex.: `OrderByCreatedAtAscAndNameDesc`). Não precisa de argumento `order`. |
|
|
469
|
+
| `Distinct<Campo>And<Campo>...` | Embute campos `distinct` fixos diretamente no nome do método (só válido em métodos da família `findBy`/`findWhere`). |
|
|
470
|
+
| `IgnoreConflicts` | No `createMany`/`createManyReturning`, ignora registros que violariam uma constraint única, em vez de lançar erro. _(Renomeado do `SkipDuplicates` da v1.)_ |
|
|
900
471
|
|
|
901
|
-
|
|
472
|
+
> ⚠️ **Ordem dos parâmetros:** `pagination` e `order` sempre vêm **antes** do último argumento opcional `MethodOptions<T>`. Quando `order` e `pagination` estão presentes juntos, a ordem relativa entre eles segue o nome do sufixo (`OrderedAndPaginated` → order, pagination; `PaginatedAndOrdered` → pagination, order).
|
|
902
473
|
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
email: "john@email.com",
|
|
909
|
-
name: "John"
|
|
910
|
-
}
|
|
911
|
-
]
|
|
912
|
-
}
|
|
913
|
-
```
|
|
474
|
+
```typescript
|
|
475
|
+
// Paginated: pagination é o penúltimo parâmetro (antes do MethodOptions)
|
|
476
|
+
@DynamicMethod()
|
|
477
|
+
declare findByActiveOrderByCreatedAtDescPaginated:
|
|
478
|
+
(active: boolean, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
|
|
914
479
|
|
|
915
|
-
|
|
480
|
+
// OrderedAndPaginated: order, depois pagination, depois MethodOptions
|
|
481
|
+
@DynamicMethod()
|
|
482
|
+
declare findByNameContainsIgnoreCaseOrderedAndPaginated:
|
|
483
|
+
(name: string, order: Ordering<User>, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
|
|
916
484
|
|
|
917
|
-
|
|
918
|
-
{
|
|
919
|
-
OR: [
|
|
920
|
-
{ email: "john@email.com" },
|
|
921
|
-
{ name: "John" }
|
|
922
|
-
],
|
|
923
|
-
AND: [
|
|
924
|
-
{ activeStatus: true },
|
|
925
|
-
{ age: { gt: 17 } }
|
|
926
|
-
]
|
|
927
|
-
}
|
|
928
|
-
```
|
|
485
|
+
@DynamicMethod()
|
|
486
|
+
declare createManyIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<{ count: number }>;
|
|
929
487
|
|
|
930
|
-
|
|
488
|
+
// createManyReturning: mesmo comportamento do createMany, mas retorna os registros criados
|
|
489
|
+
@DynamicMethod()
|
|
490
|
+
declare createManyReturningIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<User[]>;
|
|
931
491
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
492
|
+
// findOne sem filtro (equivalente ao findOneOrThrow sem filtro, mas retorna null em vez de lançar)
|
|
493
|
+
@DynamicMethod()
|
|
494
|
+
declare findOne: (options?: MethodOptions<User>) => Promise<User | null>;
|
|
495
|
+
```
|
|
935
496
|
|
|
936
|
-
>
|
|
497
|
+
> ⚠️ **Precedência entre `Distinct` e `OrderBy`:** quando os dois são usados no mesmo nome de método, **`Distinct` deve vir antes de `OrderBy`**:
|
|
937
498
|
>
|
|
938
|
-
>
|
|
939
|
-
>
|
|
940
|
-
>
|
|
941
|
-
>
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
| `Some` | `some: {}` | A relação tem *algum* registro |
|
|
946
|
-
| `SomeField` | `some.field` | Filtra dentro dos registros da relação |
|
|
947
|
-
| `EveryField` | `every.field` | Filtra dentro dos registros da relação |
|
|
948
|
-
| `None` | `none: {}` | A relação não tem *nenhum* registro |
|
|
949
|
-
| `NoneField` | `none.field` | Filtra dentro dos registros da relação |
|
|
950
|
-
| `With` | `is: {}` | A relação existe (não é nula) |
|
|
951
|
-
| `WithField` | `is.field` | Filtra um campo dentro da relação |
|
|
952
|
-
| `Without` | `isNot: {}` | A relação não existe (é nula) |
|
|
953
|
-
| `WithoutField` | `isNot.field` | Filtra um campo dentro da relação com negação |
|
|
954
|
-
|
|
955
|
-
Considerando `user` com uma relação to-one `profile` e uma relação to-many `posts`:
|
|
956
|
-
|
|
957
|
-
```ts
|
|
958
|
-
methods: {
|
|
959
|
-
// to-many (posts)
|
|
960
|
-
findByPostsSome: { map: true }, // tem pelo menos um post
|
|
961
|
-
findByPostsSomeTitle: { map: true }, // tem pelo menos um post com aquele título
|
|
962
|
-
findByPostsEveryPublishedIsTrue:{ map: true }, // todos os posts estão publicados
|
|
963
|
-
findByPostsNone: { map: true }, // não tem nenhum post
|
|
964
|
-
findByPostsNoneTitle: { map: true }, // nenhum post tem aquele título
|
|
965
|
-
|
|
966
|
-
// to-one (profile)
|
|
967
|
-
findByProfileWith: { map: true }, // tem um profile (não é nulo)
|
|
968
|
-
findByProfileWithBio: { map: true }, // tem um profile com aquela bio
|
|
969
|
-
findByProfileWithout: { map: true }, // não tem profile (é nulo)
|
|
970
|
-
findByProfileWithoutBio: { map: true }, // tem um profile, mas com uma bio diferente da informada
|
|
971
|
-
}
|
|
499
|
+
> ```typescript
|
|
500
|
+
> @DynamicMethod()
|
|
501
|
+
> declare findByActiveDistinctNameOrderByCreatedAtDesc:
|
|
502
|
+
> (active: boolean) => Promise<User[]>;
|
|
503
|
+
> ```
|
|
504
|
+
>
|
|
505
|
+
> Colocar `OrderBy` antes de `Distinct` (ex.: `findByActiveOrderByCreatedAtDescDistinctName`) não é um padrão válido e não será interpretado como esperado.
|
|
972
506
|
|
|
973
|
-
|
|
974
|
-
await userRepository.findByPostsSomeTitle("My first post");
|
|
975
|
-
await userRepository.findByPostsEveryPublishedIsTrue();
|
|
976
|
-
await userRepository.findByPostsNone();
|
|
977
|
-
await userRepository.findByPostsNoneTitle("Draft");
|
|
507
|
+
### Options do decorador
|
|
978
508
|
|
|
979
|
-
|
|
980
|
-
await userRepository.findByProfileWithBio("Hello, world!");
|
|
981
|
-
await userRepository.findByProfileWithout();
|
|
982
|
-
await userRepository.findByProfileWithoutBio("Old bio");
|
|
983
|
-
```
|
|
509
|
+
`@DynamicMethod<T>(options?)` aceita:
|
|
984
510
|
|
|
985
|
-
|
|
511
|
+
| Option | Tipo | Descrição |
|
|
512
|
+
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
513
|
+
| `proxyTo` | `string` | Redireciona a lógica do método para outro padrão de método dinâmico válido — útil para nomes que não seguem a convenção de nomenclatura. |
|
|
514
|
+
| `injectOrdering` | `Ordering<T>` | Ordenação fixa injetada automaticamente, sobrescrevendo o `defaultOrdering` do repository. |
|
|
986
515
|
|
|
987
|
-
```
|
|
988
|
-
{
|
|
989
|
-
|
|
990
|
-
some: { title: "My first post" }
|
|
991
|
-
}
|
|
992
|
-
}
|
|
516
|
+
```typescript
|
|
517
|
+
@DynamicMethod<User>({ injectOrdering: { createdAt: "desc" } })
|
|
518
|
+
declare findByStatus: (status: string) => Promise<User[]>;
|
|
993
519
|
```
|
|
994
520
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
```ts
|
|
998
|
-
{
|
|
999
|
-
posts: {
|
|
1000
|
-
every: { published: true }
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
```
|
|
521
|
+
---
|
|
1004
522
|
|
|
1005
|
-
|
|
523
|
+
## Query methods (SQL raw)
|
|
1006
524
|
|
|
1007
|
-
|
|
1008
|
-
{
|
|
1009
|
-
profile: {
|
|
1010
|
-
is: { bio: "Hello, world!" }
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
```
|
|
525
|
+
`@QueryMethod` ignora totalmente o engine de parsing por nome e executa uma instrução SQL raw através do método `query()` do adapter. Use placeholders `$1`, `$2`, ... — nunca interpole valores diretamente na string SQL.
|
|
1014
526
|
|
|
1015
|
-
|
|
527
|
+
```typescript
|
|
528
|
+
class UserRepository extends VSRepository<User, string> {
|
|
529
|
+
@QueryMethod('SELECT * FROM "user" WHERE email = $1')
|
|
530
|
+
declare findByEmailRaw: (arg: QueryMethodArg<[email: string]>) => Promise<User[]>;
|
|
1016
531
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
profile: {
|
|
1020
|
-
isNot: {}
|
|
1021
|
-
}
|
|
532
|
+
@QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
533
|
+
declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
1022
534
|
}
|
|
1023
535
|
```
|
|
1024
536
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
### Sufixos de paginação e ordenação
|
|
1030
|
-
|
|
1031
|
-
Aplicados no **final** do nome do método, injetam automaticamente os argumentos de paginação e ordenação.
|
|
537
|
+
| Option | Tipo | Padrão | Descrição |
|
|
538
|
+
| ----------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
539
|
+
| `modifying` | `boolean` | `false` | Quando `true`, executa como `INSERT`/`UPDATE`/`DELETE` e o método resolve para o número de linhas afetadas. Quando `false`, executa como query de leitura e resolve para o tipo de retorno declarado. |
|
|
1032
540
|
|
|
1033
|
-
|
|
1034
|
-
| ------------------------ | ---------------------------- |
|
|
1035
|
-
| `Paginated` | `(pagination)` |
|
|
1036
|
-
| `Ordered` | `(order)` |
|
|
1037
|
-
| `OrderedAndPaginated` | `(order, pagination)` |
|
|
1038
|
-
| `PaginatedAndOrdered` | `(pagination, order)` |
|
|
541
|
+
Query methods aceitam `{ args, db? }` na chamada — `db` permite que participem de um bloco `transaction()`, assim como os métodos base e dinâmicos.
|
|
1039
542
|
|
|
1040
|
-
|
|
543
|
+
### Queries raw pontuais com `query()`
|
|
1041
544
|
|
|
1042
|
-
|
|
1043
|
-
| ---------------------| ---------------------------------------------- |
|
|
1044
|
-
| `SkipDuplicates` | Ignora registros duplicados durante a inserção |
|
|
545
|
+
Para SQL raw pontual que não justifica declarar um `@QueryMethod` na classe do repository, chame `query()` diretamente — ele está disponível em toda instância de `VSRepository` e passa pela mesma implementação de `query()` do adapter por baixo dos panos:
|
|
1045
546
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
### Distinct
|
|
1049
|
-
|
|
1050
|
-
O sufixo `Distinct` permite obter apenas registros únicos com base em um ou mais campos, equivalente à opção `distinct` do Prisma.
|
|
1051
|
-
|
|
1052
|
-
Para usá-lo, coloque `Distinct` no nome do método (depois dos filtros de campo, se houver), seguido dos campos desejados separados por `And`. A primeira letra de cada campo deve ser maiúscula, assim como nos filtros de campo comuns.
|
|
1053
|
-
|
|
1054
|
-
```ts
|
|
1055
|
-
methods: {
|
|
1056
|
-
// Retorna usuários únicos combinando "age" e "role" (sem filtro de campo)
|
|
1057
|
-
findManyDistinctAgeAndRole: { map: true },
|
|
1058
|
-
|
|
1059
|
-
// Distinct combinado com o sufixo Paginated
|
|
1060
|
-
findManyDistinctNamePaginated: { map: true },
|
|
1061
|
-
|
|
1062
|
-
// Distinct combinado com um filtro de campo (name) — filtra por name e depois aplica distinct em role
|
|
1063
|
-
findManyByNameDistinctRole: { map: true },
|
|
1064
|
-
},
|
|
547
|
+
```typescript
|
|
548
|
+
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean }): Promise<T>;
|
|
1065
549
|
```
|
|
1066
550
|
|
|
1067
|
-
```
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
// O argumento de paginação continua funcionando normalmente
|
|
1072
|
-
await userRepository.findManyDistinctNamePaginated({ take: 10, skip: 0 });
|
|
551
|
+
```typescript
|
|
552
|
+
const users = await userRepository.query<User[]>('SELECT * FROM "user" WHERE email = $1', {
|
|
553
|
+
args: ["maria@email.com"],
|
|
554
|
+
});
|
|
1073
555
|
|
|
1074
|
-
|
|
1075
|
-
|
|
556
|
+
const linhasAfetadas = await userRepository.query<number>(
|
|
557
|
+
'UPDATE "user" SET active = true WHERE id = $1',
|
|
558
|
+
{ args: ["123"], modifying: true },
|
|
559
|
+
);
|
|
1076
560
|
```
|
|
1077
561
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
`
|
|
562
|
+
| Option | Tipo | Padrão | Descrição |
|
|
563
|
+
| ----------- | --------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
564
|
+
| `args` | `any[]` | `undefined` | Parâmetros posicionais injetados nos placeholders `$1`, `$2`, ... Nunca interpole valores diretamente na string SQL. |
|
|
565
|
+
| `db` | `any` | Client padrão do repository | Client ou transação do banco em que essa query deve rodar. |
|
|
566
|
+
| `modifying` | `boolean` | `false` | Quando `true`, trata a instrução como `INSERT`/`UPDATE`/`DELETE`. |
|
|
1081
567
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
### Configuração de método
|
|
1085
|
-
|
|
1086
|
-
Cada entrada em `methods` aceita as seguintes opções:
|
|
1087
|
-
|
|
1088
|
-
| Opção | Tipo | Padrão | Descrição |
|
|
1089
|
-
| --------------------- | ---------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
1090
|
-
| `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repositório. |
|
|
1091
|
-
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora `requiredWhere`. |
|
|
1092
|
-
| `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
|
|
1093
|
-
| `fbMode` | `'one'` \| `'list'` | `'list'` | (**Deprecated. Use `findOneBy`**) Apenas para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
|
|
1094
|
-
| `proxyTo` | `Padrão de método válido` | — | Delega a lógica para outro padrão de método válido. |
|
|
1095
|
-
| `pushWhere` | `WhereModel<M>` | — | `where` extra adicionado à query além do `requiredWhere`. |
|
|
1096
|
-
| `injectOrdering` | `OrderingModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
|
|
1097
|
-
| `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
|
|
1098
|
-
| `query` | `{ value: string; modifying?: boolean }` | — | Transforma o método em um **Query Method** (SQL bruto). Ignora todas as outras opções acima — veja [Query Methods](#query-methods). |
|
|
568
|
+
Assim como os métodos base, dinâmicos e query, `query()` aceita `db` em `options` para participar de um bloco `transaction()`.
|
|
1099
569
|
|
|
1100
570
|
---
|
|
1101
571
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
```ts
|
|
1105
|
-
const userRepository = setupVSRepo<User, "user">()(({
|
|
1106
|
-
tableName: "user",
|
|
1107
|
-
pkName: "id",
|
|
1108
|
-
methods: {
|
|
1109
|
-
aggregate: { map: true },
|
|
1110
|
-
groupBy: { map: true },
|
|
1111
|
-
},
|
|
1112
|
-
}).build(prisma);
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
> [!NOTE]
|
|
1116
|
-
> Esses métodos devem ter exatamente esses nomes (`aggregate` e `groupBy`).
|
|
1117
|
-
> Diferente dos demais métodos dinâmicos, eles recebem argumentos nativos do Prisma e **ignoram** as configurações `selectModels`, `pushWhere` e `requiredWhere`.
|
|
1118
|
-
|
|
1119
|
-
---
|
|
1120
|
-
|
|
1121
|
-
### Query Methods
|
|
1122
|
-
|
|
1123
|
-
Query Methods permitem que um método execute **SQL bruto** diretamente, contornando totalmente o parser de nomes dos métodos dinâmicos. São úteis para consultas complexas (joins pesados, CTEs, funções específicas do banco) que não são práticas de expressar com os prefixos/sufixos padrão.
|
|
1124
|
-
|
|
1125
|
-
Internamente, o VSRepository executa a SQL através do Prisma usando `$queryRawUnsafe` (para leitura) ou `$executeRawUnsafe` (para escrita), e os valores do array `args` são passados como **parâmetros posicionais** (`$1`, `$2`, ...) — a mesma técnica de *prepared statements* usada pelo próprio Prisma. Isso significa que os valores nunca são concatenados na string SQL, o que é o que efetivamente previne SQL Injection.
|
|
1126
|
-
|
|
1127
|
-
> [!WARNING]
|
|
1128
|
-
> `$1`, `$2`, ... na sua SQL devem representar sempre **valores** (parâmetros de dados), nunca nomes de colunas, tabelas ou trechos de SQL dinâmicos. Nomes de identificadores (colunas/tabelas) não podem ser passados como parâmetro posicional — se seu método precisar variar isso, monte o SQL a partir de um conjunto fixo e conhecido de opções no seu próprio código, nunca a partir de entrada não confiável.
|
|
1129
|
-
|
|
1130
|
-
```ts
|
|
1131
|
-
const userRepository = setupVSRepo<User, "user">()({
|
|
1132
|
-
tableName: "user",
|
|
1133
|
-
pkName: "id",
|
|
1134
|
-
methods: {
|
|
1135
|
-
// Query method de leitura (não-modifying)
|
|
1136
|
-
findActiveUsersRaw: {
|
|
1137
|
-
map: true,
|
|
1138
|
-
query: {
|
|
1139
|
-
value: 'SELECT * FROM "user" WHERE active = $1',
|
|
1140
|
-
},
|
|
1141
|
-
},
|
|
1142
|
-
|
|
1143
|
-
// Query method de escrita (modifying: true)
|
|
1144
|
-
deactivateUsersOlderThanRaw: {
|
|
1145
|
-
map: true,
|
|
1146
|
-
query: {
|
|
1147
|
-
value: 'UPDATE "user" SET active = false WHERE "createdAt" < $1',
|
|
1148
|
-
modifying: true,
|
|
1149
|
-
},
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
}).build(prisma);
|
|
1153
|
-
```
|
|
572
|
+
## Transações
|
|
1154
573
|
|
|
1155
|
-
|
|
574
|
+
Todos os métodos (base, dinâmicos e de query) aceitam `options.db` para participar de uma transação compartilhada:
|
|
1156
575
|
|
|
1157
|
-
|
|
576
|
+
```typescript
|
|
577
|
+
await userRepository.transaction(async tx => {
|
|
578
|
+
const usuario = await userRepository.save(
|
|
579
|
+
{ name: "Maria", email: "maria@email.com" },
|
|
580
|
+
{ db: tx },
|
|
581
|
+
);
|
|
1158
582
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
args: [true],
|
|
1164
|
-
});
|
|
1165
|
-
|
|
1166
|
-
// Modifying: sempre retorna 'number' (quantidade de linhas afetadas)
|
|
1167
|
-
const afetados = await userRepository.deactivateUsersOlderThanRaw({
|
|
1168
|
-
args: [new Date("2024-01-01")],
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
// Participando de uma transação, via 'db'
|
|
1172
|
-
await userRepository.prisma.$transaction(async (tx) => {
|
|
1173
|
-
await userRepository.deactivateUsersOlderThanRaw({
|
|
1174
|
-
args: [new Date("2024-01-01")],
|
|
1175
|
-
db: tx,
|
|
1176
|
-
});
|
|
583
|
+
await userLogsRepository.save(
|
|
584
|
+
{ action: "Usuário criado", data: { userId: usuario.id } },
|
|
585
|
+
{ db: tx },
|
|
586
|
+
);
|
|
1177
587
|
});
|
|
1178
588
|
```
|
|
1179
589
|
|
|
1180
|
-
|
|
1181
|
-
| ------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1182
|
-
| `value` | `string` | — | **Obrigatório.** SQL bruto a ser executado. Use `$1`, `$2`, ... para os placeholders dos valores em `args`. |
|
|
1183
|
-
| `modifying` | `boolean` | `false` | Quando `true`, executa via `$executeRawUnsafe` e o método sempre resolve para `number`. Quando `false`, executa via `$queryRawUnsafe` e o método resolve para `TReturn` (`any` por padrão, inferível via generic na chamada). |
|
|
1184
|
-
|
|
1185
|
-
> [!NOTE]
|
|
1186
|
-
> Diferente dos demais métodos dinâmicos, Query Methods **ignoram completamente** `selectModels`, `requiredWhere`, `pushWhere`, `whereType`, `injectOrdering`, `injectPagination` e `proxyTo` — nada disso se aplica, já que não há parsing de nome nem montagem de `where`/`select` pelo VSRepository. Nomes de métodos livres (fora dos padrões de `findBy`, `updateBy`, etc.) também **não** exigem `proxyTo`.
|
|
1187
|
-
|
|
1188
|
-
A mesma funcionalidade está disponível na abordagem baseada em classes através do decorator `@QueryMethod` — veja o [README-DynamicRepo.pt-BR.md](./README-DynamicRepo.pt-BR.md#o-decorator-querymethod).
|
|
1189
|
-
|
|
1190
|
-
---
|
|
1191
|
-
|
|
1192
|
-
## Relações no save
|
|
590
|
+
Repositories diferentes podem compartilhar a mesma transação, desde que seus adapters apontem para a mesma conexão do ORM por trás deles.
|
|
1193
591
|
|
|
1194
|
-
|
|
592
|
+
`transaction()` aceita um `VSRepoTransactionOptions` opcional como segundo argumento:
|
|
1195
593
|
|
|
1196
|
-
```
|
|
1197
|
-
import
|
|
594
|
+
```typescript
|
|
595
|
+
import { TransactionIsolationLevel } from "vsrepo";
|
|
1198
596
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
const userRepository = setupVSRepo<User, "user">()(({
|
|
1204
|
-
tableName: "user",
|
|
1205
|
-
pkName: "id",
|
|
1206
|
-
|
|
1207
|
-
relations: {
|
|
1208
|
-
profile: {
|
|
1209
|
-
pk: "id",
|
|
1210
|
-
mode: "oto",
|
|
1211
|
-
restriction: "set",
|
|
1212
|
-
},
|
|
1213
|
-
posts: {
|
|
1214
|
-
pk: "id",
|
|
1215
|
-
mode: "otm",
|
|
1216
|
-
restriction: "add",
|
|
597
|
+
await userRepository.transaction(
|
|
598
|
+
async tx => {
|
|
599
|
+
await userRepository.save({ name: "Maria", email: "maria@email.com" }, { db: tx });
|
|
1217
600
|
},
|
|
1218
|
-
|
|
1219
|
-
|
|
601
|
+
{ isolationLevel: TransactionIsolationLevel.SERIALIZABLE, timeoutMs: 5000 },
|
|
602
|
+
);
|
|
1220
603
|
```
|
|
1221
604
|
|
|
1222
|
-
|
|
605
|
+
| Option | Type | Descrição |
|
|
606
|
+
| ----------------- | -------------------------- | -------------------------------------------------------------------------------- |
|
|
607
|
+
| `isolationLevel` | `TransactionIsolationLevel` | Nível de isolamento usado na transação. O padrão é o default do ORM por trás dela. |
|
|
608
|
+
| `timeoutMs` | `number` | Tempo máximo (em ms) que a transação pode rodar antes de ser abortada. |
|
|
1223
609
|
|
|
1224
|
-
|
|
1225
|
-
| ----- | ------------------ |
|
|
1226
|
-
| `oto` | um-para-um |
|
|
1227
|
-
| `otm` | um-para-muitos |
|
|
1228
|
-
| `mto` | muitos-para-um |
|
|
1229
|
-
| `mtm` | muitos-para-muitos |
|
|
610
|
+
`TransactionIsolationLevel` espelha os níveis de isolamento SQL padrão: `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, `SERIALIZABLE`. O suporte a um determinado nível depende do adapter/ORM e do banco de dados por trás dele.
|
|
1230
611
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
| Restrição | Comportamento na atualização |
|
|
1234
|
-
| ----------- | ----------------------------------------------------------- |
|
|
1235
|
-
| `set` | Substitui totalmente (remove os que não foram enviados) |
|
|
1236
|
-
| `add` | Adiciona/atualiza sem remover os existentes |
|
|
612
|
+
---
|
|
1237
613
|
|
|
1238
|
-
|
|
1239
|
-
> **`set` significa coisas diferentes dependendo do `mode` da relação — e isso pode causar perda de dados se você não tomar cuidado.**
|
|
1240
|
-
>
|
|
1241
|
-
> Em relações onde o registro relacionado **pertence** ao registro pai (`oto` e `otm`), "remover os que não foram enviados" significa **apagar o registro do banco de dados** (`delete`/`deleteMany`). Em relações onde o registro relacionado é **independente** (`mto` e `mtm`), "remover" significa apenas **desvincular** (`disconnect`/`set: []`) — o registro relacionado continua existindo no banco, apenas deixa de apontar para o pai (ou de estar na tabela de junção).
|
|
1242
|
-
>
|
|
1243
|
-
> | Modo | `restriction: "set"` quando um item é omitido | O item continua existindo no banco de dados? |
|
|
1244
|
-
> | ----- | ------------------------------------------------ | ----------------------------------------------------- |
|
|
1245
|
-
> | `oto` | Passar `null` no campo → **apaga** o registro relacionado (`delete: true`) | Não |
|
|
1246
|
-
> | `otm` | Itens fora da lista enviada → **apagados** (`deleteMany` com `notIn`) | Não |
|
|
1247
|
-
> | `mto` | Passar `null` no campo (com `nullable: true`) → **desvincula** (`disconnect: true`) | Sim |
|
|
1248
|
-
> | `mtm` | Itens fora da lista enviada → **desvinculados** da tabela de junção (`set: []`) | Sim |
|
|
1249
|
-
>
|
|
1250
|
-
> Exemplo prático: se `posts` for `otm` com `restriction: "set"`, um `save`/`patch` que envia o usuário com apenas 2 dos 5 posts existentes vai **apagar os outros 3 posts do banco de dados**, não apenas desvinculá-los do usuário. Se o comportamento esperado for apenas desvincular sem apagar, use `restriction: "add"` (que nunca remove nada) e trate a remoção manualmente.
|
|
614
|
+
## Tipos utilitários
|
|
1251
615
|
|
|
1252
|
-
|
|
616
|
+
Além dos tipos que descrevem o formato da entidade já vistos acima (`VSRepoSelect`, `VSRepoRelations`, `VSRepoWhere`), o VSRepository exporta um conjunto de tipos utilitários. Eles aparecem ao longo de várias seções anteriores, mas aqui está uma referência consolidada. Todos fazem parte da API pública e podem ser importados diretamente:
|
|
1253
617
|
|
|
1254
|
-
|
|
618
|
+
```typescript
|
|
619
|
+
import type {
|
|
620
|
+
MethodOptions,
|
|
621
|
+
Pagination,
|
|
622
|
+
Ordering,
|
|
623
|
+
OrderByField,
|
|
624
|
+
SortDirection,
|
|
625
|
+
SeeMode,
|
|
626
|
+
DeepPartial,
|
|
627
|
+
CountResult,
|
|
628
|
+
QueryMethodArg,
|
|
629
|
+
KeysOfType,
|
|
630
|
+
Primitive,
|
|
631
|
+
VSRepoWhere,
|
|
632
|
+
VSRepoOrmTypes,
|
|
633
|
+
VSRepoTransactionOptions,
|
|
634
|
+
TransactionIsolationLevel,
|
|
635
|
+
} from "vsrepo";
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
| Tipo | Descrição | Usado por |
|
|
639
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
640
|
+
| `MethodOptions<T, K>` | Options aceitas como último argumento por todo método base e dinâmico: `select`, `relations`, `see`, `db`. | [Métodos base](#métodos-base), [Métodos Dinâmicos](#métodos-dinâmicos). |
|
|
641
|
+
| `Pagination` | `{ limit?, offset? }` aceito por `getAll` e pelos métodos dinâmicos com `Paginated`. | [Métodos base](#métodos-base), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
|
|
642
|
+
| `Ordering<T>` / `OrderByField<T>` / `SortDirection` | Formato de ordenação aceito por `getAll`, `defaultOrdering` e `injectOrdering`, e pelos métodos dinâmicos com `Ordered`. Pode ser um único objeto ou um array encadeado; objetos aninhados ordenam relações to-one. | [Options do construtor](#options-do-construtor), [Options do decorador](#options-do-decorador), [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct). |
|
|
643
|
+
| `SeeMode` | `"active" \| "removed" \| "all"` — controla a visibilidade de registros com soft-delete. | [Soft-delete](#soft-delete). |
|
|
644
|
+
| `DeepPartial<T>` | Torna todas as propriedades de `T` opcionais recursivamente, incluindo objetos aninhados e elementos de array. | `save`, `saveList`, `patch`, `merge`, e todo método de escrita do `VSRepoAdapter`. |
|
|
645
|
+
| `CountResult` | `{ count: number }` — o formato retornado por operações em lote. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
646
|
+
| `QueryMethodArg<T>` | `{ args?: T, db? }` — parâmetros posicionais do SQL (`$1`, `$2`, ...) e cliente de transação para o `@QueryMethod`. | [Query methods (SQL raw)](#query-methods-sql-raw). |
|
|
647
|
+
| `KeysOfType<T, K>` | Extrai as chaves de `T` cujo tipo de valor é atribuível a `K`. | Restringe `pkName`, em [Options do construtor](#options-do-construtor), aos campos da entidade compatíveis com o tipo de chave primária configurado. |
|
|
648
|
+
| `Primitive` | União de tipos escalares (`string \| number \| boolean \| bigint \| symbol \| undefined \| null \| Date`) tratados como valores-folha — e não relações — ao percorrer o formato de uma entidade. | Usado por `Ordering<T>` para distinguir campos escalares de campos de relação. |
|
|
649
|
+
| `VSRepoWhere<T>` | Tipo de filtro agnóstico de ORM aceito pelos métodos dinâmicos `*Where` (ex.: `findWhere`, `findOneWhere`, `updateWhere`). Suporta filtros de campo, operadores lógicos (`AND`/`OR`/`NOT`) e filtros de relação. | [Prefixos `findWhere`, `findOneWhere` e demais `*Where`](#prefixos-disponíveis). |
|
|
650
|
+
| `VSRepoOrmTypes` | `{ dbClient; dbTransaction }` — descreve os tipos de client/transaction do seu ORM. Passado como terceiro generic de `VSRepository<Entity, PKType, OrmTypes>` para tipar `getDbClient()`, `transaction()` e a option `db` em vez de `any`. | [Criando um repository](#criando-um-repository). |
|
|
651
|
+
| `VSRepoTransactionOptions` | `{ isolationLevel?, timeoutMs? }` — options aceitas como segundo argumento de `transaction()`. | [Transações](#transações). |
|
|
652
|
+
| `TransactionIsolationLevel` | Enum dos níveis de isolamento SQL padrão (`READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, `SERIALIZABLE`) aceitos por `VSRepoTransactionOptions.isolationLevel`. | [Transações](#transações). |
|
|
653
|
+
|
|
654
|
+
### `DeepPartial<T>`
|
|
655
|
+
|
|
656
|
+
Torna todas as propriedades opcionais recursivamente, percorrendo objetos aninhados e elementos de array — diferente do `Partial<T>` nativo do TypeScript, que só torna o nível superior opcional:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
type User = { id: string; name: string; address: { city: string; zip: string } };
|
|
660
|
+
|
|
661
|
+
const patch: DeepPartial<User> = {
|
|
662
|
+
address: { city: "São Paulo" }, // zip pode ser omitido; city mantém seu tipo
|
|
663
|
+
};
|
|
1255
664
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
665
|
+
await userRepository.patch(id, patch);
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### `KeysOfType<T, K>`
|
|
669
|
+
|
|
670
|
+
Filtra um tipo de objeto para as chaves cujo valor corresponde a um tipo dado — é isso que permite que `pkName` aceite apenas campos da entidade que sejam de fato atribuíveis ao tipo de chave primária do repository:
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
type User = { id: string; age: number; name: string };
|
|
674
|
+
type StringKeys = KeysOfType<User, string>; // "id" | "name"
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### `Ordering<T>`
|
|
678
|
+
|
|
679
|
+
Aceita um único objeto de ordenação ou um array deles, aplicados na ordem declarada:
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
const order: Ordering<User> = { createdAt: "desc" };
|
|
683
|
+
const chained: Ordering<User> = [{ name: "asc" }, { createdAt: "desc" }];
|
|
684
|
+
|
|
685
|
+
await userRepository.getAll({ order: chained });
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## Escrevendo seu próprio adapter
|
|
691
|
+
|
|
692
|
+
Como o núcleo é agnóstico de ORM e é distribuído sem um adapter embutido, adicionar suporte a um ORM/banco — seja como solução provisória para o seu próprio projeto, seja como candidato a um futuro pacote `@vsrepo/*-adapter` — significa implementar a classe abstrata `VSRepoAdapter<T>`:
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
export abstract class VSRepoAdapter<T> {
|
|
696
|
+
abstract runInTransaction<R>(
|
|
697
|
+
fn: (tx: any) => Promise<R>,
|
|
698
|
+
options?: VSRepoTransactionOptions,
|
|
699
|
+
): Promise<R>;
|
|
700
|
+
abstract getDbClient(): any;
|
|
701
|
+
abstract query<T = any>(query: string, options?: AdapterQueryOptions): Promise<T>;
|
|
702
|
+
abstract findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T | null>;
|
|
703
|
+
abstract findOneOrThrow(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
704
|
+
abstract findMany(
|
|
705
|
+
where: VSRepoWhere<T>,
|
|
706
|
+
options?: AdapterMethodOptions<T> & { distinct?: (keyof T)[] },
|
|
707
|
+
): Promise<T[]>;
|
|
708
|
+
abstract save(obj: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
709
|
+
abstract saveMany(objs: DeepPartial<T>[], options?: AdapterMethodOptions<T>): Promise<T[]>;
|
|
710
|
+
abstract create(objs: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
711
|
+
abstract createMany(
|
|
712
|
+
objs: DeepPartial<T>[],
|
|
713
|
+
options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
|
|
714
|
+
): Promise<CountResult>;
|
|
715
|
+
abstract createManyReturning(
|
|
716
|
+
objs: DeepPartial<T>[],
|
|
717
|
+
options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
|
|
718
|
+
): Promise<T[]>;
|
|
719
|
+
abstract delete(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
720
|
+
abstract deleteMany(
|
|
721
|
+
where: VSRepoWhere<T>,
|
|
722
|
+
options?: AdapterMethodOptions<T>,
|
|
723
|
+
): Promise<CountResult>;
|
|
724
|
+
abstract deleteManyReturning(
|
|
725
|
+
where: VSRepoWhere<T>,
|
|
726
|
+
options?: AdapterMethodOptions<T>,
|
|
727
|
+
): Promise<T[]>;
|
|
728
|
+
abstract update(
|
|
729
|
+
where: VSRepoWhere<T>,
|
|
730
|
+
obj: DeepPartial<T>,
|
|
731
|
+
options?: AdapterMethodOptions<T>,
|
|
732
|
+
): Promise<T>;
|
|
733
|
+
abstract updateMany(
|
|
734
|
+
where: VSRepoWhere<T>,
|
|
735
|
+
obj: DeepPartial<T>,
|
|
736
|
+
options?: AdapterMethodOptions<T>,
|
|
737
|
+
): Promise<CountResult>;
|
|
738
|
+
abstract updateManyReturning(
|
|
739
|
+
where: VSRepoWhere<T>,
|
|
740
|
+
obj: DeepPartial<T>,
|
|
741
|
+
options?: AdapterMethodOptions<T>,
|
|
742
|
+
): Promise<T[]>;
|
|
743
|
+
abstract count(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number>;
|
|
744
|
+
abstract exists(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<boolean>;
|
|
745
|
+
abstract merge<K>(
|
|
746
|
+
where: VSRepoWhere<T>,
|
|
747
|
+
obj: DeepPartial<T>,
|
|
748
|
+
options?: AdapterMethodOptions<T>,
|
|
749
|
+
): Promise<K & T>;
|
|
750
|
+
abstract upsert(
|
|
751
|
+
where: VSRepoWhere<T>,
|
|
752
|
+
create: DeepPartial<T>,
|
|
753
|
+
update: DeepPartial<T>,
|
|
754
|
+
options?: AdapterMethodOptions<T>,
|
|
755
|
+
): Promise<T>;
|
|
1264
756
|
}
|
|
1265
757
|
```
|
|
1266
758
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
## Transações
|
|
1270
|
-
|
|
1271
|
-
Todos os métodos aceitam `options.db` para participar de uma transação:
|
|
759
|
+
O `VSRepository` nunca fala diretamente com o ORM — ele só chama esses métodos com um `VSRepoWhere<T>` e um `AdapterMethodOptions<T>` já resolvidos. Uma vez que um adapter implemente esse contrato, todo método base, método dinâmico e query method passa a funcionar com ele automaticamente. Pra uma implementação completa e funcional, veja o repositório externo [`VSRepoPrisma7Adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter).
|
|
1272
760
|
|
|
1273
|
-
|
|
1274
|
-
await userRepository.prisma.$transaction(async (tx) => {
|
|
1275
|
-
const user = await userRepository.save(
|
|
1276
|
-
{ name: "Mary", email: "mary@email.com", password: "password" },
|
|
1277
|
-
{ db: tx }
|
|
1278
|
-
);
|
|
761
|
+
### Logging a partir do seu adapter
|
|
1279
762
|
|
|
1280
|
-
|
|
1281
|
-
{ action: "User registration", data: { registeredUser: user.id } },
|
|
1282
|
-
{ db: tx }
|
|
1283
|
-
);
|
|
1284
|
-
});
|
|
1285
|
-
```
|
|
763
|
+
O `vsrepo` exporta a mesma classe `VSLogger` usada internamente pelo core, então seu adapter pode logar no mesmo formato/estilo (timestamps, labels de nível coloridos, avisos de operação lenta) em vez de implementar o seu próprio:
|
|
1286
764
|
|
|
1287
|
-
|
|
765
|
+
```typescript
|
|
766
|
+
import { VSLogger, VSLogLevel } from "vsrepo";
|
|
1288
767
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// CORRETO: tx é um DbTransaction
|
|
1292
|
-
const registeredUsers = await userRepository.saveList([{ name: "Mary" }, { name: "Lucas" }], { db: tx });
|
|
768
|
+
export class MyOrmAdapter<T> extends VSRepoAdapter<T> {
|
|
769
|
+
private readonly logger = new VSLogger(VSLogLevel.WARN, "MyOrmAdapterLogger");
|
|
1293
770
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
771
|
+
async findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>) {
|
|
772
|
+
const start = this.logger.startPerformLog("adapter findOne");
|
|
773
|
+
try {
|
|
774
|
+
// ... fala com o ORM ...
|
|
775
|
+
this.logger.endPerformLog(start);
|
|
776
|
+
return result;
|
|
777
|
+
} catch (err) {
|
|
778
|
+
this.logger.endPerformLog(start);
|
|
779
|
+
this.logger.logError("adapter findOne falhou", err);
|
|
780
|
+
throw err;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
1299
784
|
```
|
|
1300
785
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
pkName: "id",
|
|
1309
|
-
methods: {
|
|
1310
|
-
findOneByEmailEndsWith: { map: true },
|
|
1311
|
-
},
|
|
1312
|
-
})
|
|
1313
|
-
.build(prisma)
|
|
1314
|
-
.extend((repo) => ({
|
|
1315
|
-
findActiveByDomain: async (domain: string) => {
|
|
1316
|
-
return repo.findOneByEmailEndsWith(`@${domain}`);
|
|
1317
|
-
},
|
|
786
|
+
| Método | Descrição |
|
|
787
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
788
|
+
| `new VSLogger(logLevel, name, slowThresholdMs?)` | Cria um logger; `name` prefixa cada linha, `slowThresholdMs` tem default 300. |
|
|
789
|
+
| `logDebug/logInfo/logWarn(text, obj?)` | Loga no nível dado se `logLevel` permitir; `obj` é anexado como JSON formatado. |
|
|
790
|
+
| `logError(text, err?)` | Loga em `ERROR`; se `err` for uma `Error`, só `name`/`message`/`stack`/`cause` são logados. |
|
|
791
|
+
| `startPerformLog(operation)` / `endPerformLog(data)` | Envolve um trecho de código para logar sua duração, escalando pra `WARN` se ultrapassar `slowThresholdMs`. |
|
|
792
|
+
| `getLogLevel()` | Retorna o `VSLogLevel` configurado do logger. |
|
|
1318
793
|
|
|
1319
|
-
|
|
1320
|
-
return repo.patchList(ids.map(id => [id, { active: true }]));
|
|
1321
|
-
},
|
|
1322
|
-
}));
|
|
1323
|
-
```
|
|
794
|
+
Isso é puramente uma conveniência para autores de adapters — nada no core exige que seu adapter o utilize.
|
|
1324
795
|
|
|
1325
796
|
---
|
|
1326
797
|
|
|
1327
798
|
## Tratamento de erros
|
|
1328
799
|
|
|
1329
|
-
|
|
800
|
+
A v2 simplifica a hierarquia de erros da v1: em vez de várias subclasses, existe uma classe base `VSRepoError` carregando um campo `type: VSRepoErrorType`, além de uma subclasse dedicada `VSRepoAdapterError` (veja abaixo) para falhas vindas do ORM/banco subjacente.
|
|
1330
801
|
|
|
1331
|
-
```
|
|
1332
|
-
import { VSRepoError
|
|
802
|
+
```typescript
|
|
803
|
+
import { VSRepoError } from "vsrepo";
|
|
1333
804
|
|
|
1334
805
|
try {
|
|
1335
|
-
|
|
806
|
+
await userRepository.get(id);
|
|
1336
807
|
} catch (error) {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
console.error("Repository error:", error.message);
|
|
1341
|
-
} else {
|
|
1342
|
-
console.error("Error:", error.message)
|
|
1343
|
-
}
|
|
808
|
+
if (error instanceof VSRepoError) {
|
|
809
|
+
console.error(`[${error.type}] ${error.message}`);
|
|
810
|
+
}
|
|
1344
811
|
}
|
|
1345
812
|
```
|
|
1346
813
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
|
1350
|
-
|
|
|
1351
|
-
| `
|
|
1352
|
-
| `
|
|
1353
|
-
| `
|
|
1354
|
-
| `
|
|
1355
|
-
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
|
|
1356
|
-
|
|
1357
|
-
`VSRepoRuntimeError` tem uma propriedade `code: VSRepoRuntimeErrorCode` para identificação programática, sem precisar fazer parsing da mensagem (legível por humanos, e possivelmente localizada):
|
|
1358
|
-
|
|
1359
|
-
| Código | Significado |
|
|
1360
|
-
| ------ | ----------- |
|
|
1361
|
-
| `"65706"` | Um argumento obrigatório está ausente ou tem formato inválido — ex.: `pk` ausente, `pks`/`objs`/`tuples` que não são um array, ou `options`/`obj` que não são um objeto válido. |
|
|
1362
|
-
| `"20727"` | Nenhum registro foi encontrado para a primary key informada (`getOrThrow` ao buscar o registro base). |
|
|
1363
|
-
| `"67542"` | A validação (zod) de `options` de um método, ou de um argumento de `@QueryMethod`, falhou. |
|
|
1364
|
-
| `"91868"` | Uma relação passada em `save`/`patch`/`merge` tem formato inválido para o `mode`/`restriction` configurados (ex.: `null` numa relação `mtm`/`otm`, ou um array numa relação `oto`/`mto`). |
|
|
1365
|
-
| `"48670"` | Um método dinâmico (`config.methods`) foi chamado com menos argumentos posicionais do que os campos do `where` exigem. |
|
|
1366
|
-
|
|
1367
|
-
---
|
|
1368
|
-
|
|
1369
|
-
## Tipos utilitários
|
|
814
|
+
| `VSRepoErrorType` | Quando é lançado |
|
|
815
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
816
|
+
| `DECORATOR` | Argumentos inválidos foram passados para `@DynamicMethod` ou `@QueryMethod`. |
|
|
817
|
+
| `RESOLVER` | A biblioteca falhou ao resolver a configuração de um método dinâmico/de query em um método chamável (ex.: um nome de método desconhecido). |
|
|
818
|
+
| `DYNAMIC` | Um método dinâmico já resolvido falhou em tempo de execução (ex.: argumentos faltando). |
|
|
819
|
+
| `VALIDATOR` | Options ou argumentos de método inválidos foram detectados durante a validação. |
|
|
820
|
+
| `BASE` | Uso inválido de um método base (`get`, `save`, `remove`, etc). |
|
|
821
|
+
| `ADAPTER` | Um `VSRepoAdapter` falhou ao falar com o ORM/banco subjacente — sempre é lançado como `VSRepoAdapterError`. |
|
|
1370
822
|
|
|
1371
|
-
###
|
|
823
|
+
### `VSRepoAdapterError` e `AdapterErrorCode`
|
|
1372
824
|
|
|
1373
|
-
|
|
1374
|
-
import type { DbClient, DbTransaction, ClientOrTransaction } from "../../generated/vsrepo";
|
|
1375
|
-
|
|
1376
|
-
type DbClient = PrismaClient;
|
|
1377
|
-
type DbTransaction = Prisma.TransactionClient;
|
|
1378
|
-
type ClientOrTransaction = DbClient | DbTransaction;
|
|
1379
|
-
```
|
|
825
|
+
Quando um adapter fala com o ORM/banco subjacente e essa operação falha, o adapter encapsula a falha em um `VSRepoAdapterError` — uma subclasse de `VSRepoError` com `type: VSRepoErrorType.ADAPTER`. Ele carrega um `code: AdapterErrorCode` **estável e agnóstico de adapter** além do erro bruto lançado pelo ORM/driver, para que quem chama possa reagir às falhas sem depender do formato de erro de nenhum ORM específico:
|
|
1380
826
|
|
|
1381
|
-
|
|
827
|
+
```typescript
|
|
828
|
+
import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
|
|
1382
829
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
### Tipos derivados do modelo Prisma
|
|
830
|
+
try {
|
|
831
|
+
await userRepository.save({ name: "Maria" });
|
|
832
|
+
} catch (error) {
|
|
833
|
+
if (error instanceof VSRepoAdapterError) {
|
|
834
|
+
console.error(`[${error.code}] ${error.message}`, error.originalError);
|
|
1390
835
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
IncludeModels,
|
|
1397
|
-
WhereModel,
|
|
1398
|
-
OrderingModel,
|
|
1399
|
-
PaginationModel,
|
|
1400
|
-
ModelUpsertInput,
|
|
1401
|
-
PrismaModelInputs,
|
|
1402
|
-
} from "../../generated/vsrepo";
|
|
836
|
+
if (error.code === AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION) {
|
|
837
|
+
// tratar chave duplicada, ex.: retornar uma mensagem amigável
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
1403
841
|
```
|
|
1404
842
|
|
|
1405
|
-
|
|
843
|
+
| Propriedade | Tipo | Descrição |
|
|
844
|
+
| --------------- | ------------------ | --------------------------------------------------------------------------------- |
|
|
845
|
+
| `code` | `AdapterErrorCode` | Código estável e agnóstico que classifica a falha. |
|
|
846
|
+
| `originalError` | `unknown` | O erro bruto (ou `null`/`undefined`) lançado pelo driver do ORM/banco subjacente. |
|
|
847
|
+
| `message` | `string` | Descrição legível da falha do adapter. |
|
|
848
|
+
| `type` | `VSRepoErrorType` | Sempre `VSRepoErrorType.ADAPTER`. |
|
|
849
|
+
| `cause` | `unknown` | Causa raiz opcional da qual o erro foi encadeado. |
|
|
1406
850
|
|
|
1407
|
-
|
|
1408
|
-
import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
|
|
851
|
+
As implementações de adapter o constroem diretamente ao mapear uma falha do ORM:
|
|
1409
852
|
|
|
1410
|
-
|
|
1411
|
-
|
|
853
|
+
```typescript
|
|
854
|
+
import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
|
|
1412
855
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
856
|
+
throw new VSRepoAdapterError(
|
|
857
|
+
"falha ao criar o usuário",
|
|
858
|
+
AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION,
|
|
859
|
+
originalError, // erro bruto do banco/driver
|
|
860
|
+
);
|
|
1416
861
|
```
|
|
1417
862
|
|
|
1418
|
-
|
|
1419
|
-
>
|
|
1420
|
-
> `MethodOptions` também aceita mais dois parâmetros genéricos, `RI` e `RS`, para tipar `include` e `select` brutos respectivamente (ambos com padrão `never`, ou seja, não são aceitos a menos que tipados explicitamente): `MethodOptions<"public", "withPosts", "usuario", IncludeModel<"usuario">, SelectModel<"usuario">>`. O `MethodOptionsModel`, derivado diretamente de um repositório configurado, não expõe `RI`/`RS` — use `MethodOptions` diretamente se precisar tipar as opções de `include`/`select` brutos.
|
|
863
|
+
#### `AdapterErrorCode`
|
|
1421
864
|
|
|
1422
|
-
|
|
865
|
+
`AdapterErrorCode` é um enum de códigos granulares e agnósticos que um adapter pode lançar através de `VSRepoAdapterError`. Eles espelham as falhas mais comuns lançadas por ORMs e drivers de banco, para que erros de qualquer ORM possam ser mapeados para o mesmo código estável:
|
|
1423
866
|
|
|
1424
|
-
```
|
|
1425
|
-
import
|
|
1426
|
-
MethodConfig,
|
|
1427
|
-
RepoConfig,
|
|
1428
|
-
BuildConfig,
|
|
1429
|
-
RepositoryRelations,
|
|
1430
|
-
ExtractRelationConfig,
|
|
1431
|
-
} from "../../generated/vsrepo";
|
|
1432
|
-
```
|
|
1433
|
-
|
|
1434
|
-
### Tipo do repositório construído
|
|
1435
|
-
|
|
1436
|
-
```ts
|
|
1437
|
-
import type { RepositoryOf } from "../../generated/vsrepo";
|
|
867
|
+
```typescript
|
|
868
|
+
import { AdapterErrorCode } from "vsrepo";
|
|
1438
869
|
|
|
1439
|
-
|
|
1440
|
-
type UserRepository = RepositoryOf<typeof userVSRepo>;
|
|
870
|
+
console.log(AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION); // "UNIQUE_CONSTRAINT_VIOLATION"
|
|
1441
871
|
```
|
|
1442
872
|
|
|
1443
|
-
|
|
873
|
+
| Código | Significado |
|
|
874
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
875
|
+
| `UNKNOWN` | Erro não classificado/desconhecido; o fallback quando nenhum código mais específico corresponde. |
|
|
876
|
+
| `MISSING_DB_CLIENT` | Cliente de banco (ou pool de conexões) não fornecido ou que não pôde ser resolvido. |
|
|
877
|
+
| `CONNECTION_FAILED` | Não foi possível alcançar/conectar ao banco, ou uma conexão estabelecida foi perdida/terminada. |
|
|
878
|
+
| `CONNECTION_POOL_EXHAUSTED` | Pool de conexões esgotado/depletado — nenhuma conexão disponível, todas ocupadas ou o limite foi atingido. |
|
|
879
|
+
| `TIMEOUT` | O banco não respondeu a tempo; uma query excedeu o timeout permitido. |
|
|
880
|
+
| `UNIQUE_CONSTRAINT_VIOLATION` | Violação de constraint unique (chave duplicada). Ex.: Postgres/SQLite `23505`, MySQL `1062`. |
|
|
881
|
+
| `FOREIGN_KEY_VIOLATION` | Violação de constraint de foreign key (linha referenciada não existe). |
|
|
882
|
+
| `NOT_NULL_VIOLATION` | Violação de constraint NOT NULL. |
|
|
883
|
+
| `CHECK_VIOLATION` | Violação de constraint CHECK. |
|
|
884
|
+
| `CONSTRAINT_VIOLATION` | Violação geral de integridade/constraint não coberta por um código mais específico. |
|
|
885
|
+
| `NOT_FOUND` | Registro solicitado não encontrado (ex.: uma operação tipo `findOneOrThrow`). |
|
|
886
|
+
| `INVALID_DATA` | Valor de campo inválido para o tipo/tamanho, ou um valor obrigatório ausente. |
|
|
887
|
+
| `VALUE_TOO_LONG` | Valor fornecido excede o limite de tamanho da coluna/campo. |
|
|
888
|
+
| `CONVERSION_ERROR` | Um valor não pôde ser convertido/convertido para o tipo alvo. Ex.: Postgres `22P02`, MySQL `1366`. |
|
|
889
|
+
| `INVALID_QUERY` | A query/stored procedure SQL está malformada ou é inválida. |
|
|
890
|
+
| `TABLE_OR_COLUMN_NOT_FOUND` | A tabela/coluna/relação referenciada não existe. |
|
|
891
|
+
| `DEADLOCK` | Operação abortada por timeout de lock ou deadlock entre transações concorrentes. |
|
|
892
|
+
| `LOCK_TIMEOUT` | Não foi possível adquirir um lock de banco obrigatório a tempo. |
|
|
893
|
+
| `LOCKED` | O registro está travado e não pode ser modificado. |
|
|
894
|
+
| `ACCESS_DENIED` | O usuário/role atual não tem permissão para a operação. |
|
|
895
|
+
| `INVALID_CREDENTIALS` | Credenciais de conexão inválidas (host/usuário/senha). |
|
|
896
|
+
| `ROW_NOT_ALLOWED` | O usuário autenticado não é dono do registro / a segurança em nível de linha rejeitou. |
|
|
897
|
+
| `MODEL_NOT_FOUND` | Entidade/modelo ou tabela não definida/mapeada no ORM, ou o adapter não tem os metadados do modelo. |
|
|
898
|
+
| `FIELD_NOT_FOUND` | Nome de campo/coluna nos dados ou no `where` não existe na entidade/modelo. |
|
|
899
|
+
| `TRANSACTION_CLOSED` | Transação usada depois de commit/rollback. |
|
|
900
|
+
| `TRANSACTION_ALREADY_STARTED` | Uma transação aninhada não pôde ser aberta (ex.: chamadas `transaction()` aninhadas). |
|
|
901
|
+
| `TRANSACTION_CONFLICT` | Uma transação falhou ao commitar e foi desfeita. |
|
|
902
|
+
| `TRANSACTION_NOT_STARTED` | Nenhuma transação ativa quando uma era obrigatória. |
|
|
903
|
+
| `CONNECTION_CLOSED` | Conexão fechada/terminada enquanto uma transação ou query estava em andamento. |
|
|
904
|
+
| `INVALID_PARTIAL` | `merge`/`upsert`/`update` recebeu um objeto parcial inválido ou faltando chaves obrigatórias. |
|
|
905
|
+
| `NOT_SUPPORTED` | Feature/operação não suportada solicitada ao adapter (ex.: `query()` bruto não suportado). |
|
|
906
|
+
| `INVALID_ADAPTER_CONFIG` | Configuração do adapter inválida ou incompleta (options obrigatórias ausentes, ou com tipo/valor inválido). |
|
|
907
|
+
| `INTERNAL` | Bug interno do adapter ou estado irrecuperável; deve raramente ser usado — prefira um código mais específico. |
|
|
1444
908
|
|
|
1445
|
-
|
|
1446
|
-
type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
|
|
1447
|
-
```
|
|
909
|
+
#### `VSRepoError` vs. erros brutos do ORM
|
|
1448
910
|
|
|
1449
|
-
|
|
911
|
+
Erros de uso/configuração fora do adapter lançam o `VSRepoError` base. Falhas lançadas _pelo ORM subjacente_ enquanto um método do adapter roda são **encapsuladas** em `VSRepoAdapterError` (classificadas por um `AdapterErrorCode`, com o erro original preservado em `originalError`) em vez de se propagarem cruas — é isso que torna quem chama independente do formato de erro de qualquer ORM específico.
|
|
1450
912
|
|
|
1451
|
-
|
|
1452
|
-
import type { SaveObject, PatchObject } from "../../generated/vsrepo";
|
|
913
|
+
---
|
|
1453
914
|
|
|
1454
|
-
|
|
1455
|
-
tableName: "user",
|
|
1456
|
-
pkName: "id",
|
|
1457
|
-
relations: {
|
|
1458
|
-
profile: { pk: "id", mode: "oto", restriction: "set" },
|
|
1459
|
-
},
|
|
1460
|
-
});
|
|
915
|
+
## Logging
|
|
1461
916
|
|
|
1462
|
-
|
|
1463
|
-
type UserPatchPayload = PatchObject<Prisma.UserUpdateInput, typeof userVSRepo>;
|
|
1464
|
-
```
|
|
917
|
+
Todo repository tem um logger interno, configurado via `logLevel` e `logSlowThresholdMs` nas options do construtor:
|
|
1465
918
|
|
|
1466
|
-
|
|
919
|
+
```typescript
|
|
920
|
+
import { VSLogLevel } from "vsrepo";
|
|
1467
921
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
setupVSRepo<TPayload, TTableName>()({
|
|
1474
|
-
tableName: Uncapitalize<M>; // Nome da tabela no Prisma
|
|
1475
|
-
pkName: keyof T; // Nome da chave primária
|
|
1476
|
-
softRemovekName?: keyof T & string; // Campo DateTime para soft-delete
|
|
1477
|
-
selectModels?: SelectModels<M>; // Projeções de dados nomeadas (select)
|
|
1478
|
-
defaultSelectModel?: keyof SM; // Select aplicado por padrão
|
|
1479
|
-
includeModels?: IncludeModels<M>; // Projeções de dados nomeadas (include) — sem padrão, apenas na chamada
|
|
1480
|
-
requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
|
|
1481
|
-
defaultOrdering?: OrderingModel<M>; // Ordenação padrão para queries sem Ordered/injectOrdering
|
|
1482
|
-
relations?: RepositoryRelations<T>; // Configuração de relações
|
|
1483
|
-
methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
|
|
922
|
+
super({
|
|
923
|
+
pkName: "id",
|
|
924
|
+
adapter,
|
|
925
|
+
logLevel: VSLogLevel.DEBUG,
|
|
926
|
+
logSlowThresholdMs: 200,
|
|
1484
927
|
});
|
|
1485
928
|
```
|
|
1486
929
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
baseMethods?: {
|
|
1494
|
-
// Métodos que podem usar um defaultSelect
|
|
1495
|
-
get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1496
|
-
getOrThrow?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1497
|
-
getList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1498
|
-
remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1499
|
-
save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1500
|
-
saveList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1501
|
-
patch?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1502
|
-
patchList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1503
|
-
merge?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1504
|
-
getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1505
|
-
softRemove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1506
|
-
restore?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1507
|
-
|
|
1508
|
-
// Métodos que NÃO aceitam defaultSelect
|
|
1509
|
-
removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1510
|
-
softRemoveList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1511
|
-
restoreList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1512
|
-
total?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1513
|
-
has?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1514
|
-
};
|
|
1515
|
-
});
|
|
1516
|
-
```
|
|
930
|
+
| Nível | Significado |
|
|
931
|
+
| --------------- | ------------------------------------------------------------------------------------------------------- |
|
|
932
|
+
| `DEBUG` | Detalhes internos verbosos, incluindo toda query resolvida — muito útil para debugar métodos dinâmicos. |
|
|
933
|
+
| `INFO` | Eventos de alto nível do ciclo de vida, como a inicialização do repository. |
|
|
934
|
+
| `WARN` (padrão) | Problemas recuperáveis e operações lentas (veja `logSlowThresholdMs`, padrão de 300ms). |
|
|
935
|
+
| `ERROR` | Falhas lançadas durante a execução de uma operação. |
|
|
1517
936
|
|
|
1518
|
-
|
|
937
|
+
---
|
|
1519
938
|
|
|
1520
|
-
|
|
1521
|
-
repo.extend((repo) => ({
|
|
1522
|
-
myMethod: () => { ... }
|
|
1523
|
-
}));
|
|
1524
|
-
```
|
|
939
|
+
## Desenvolvimento
|
|
1525
940
|
|
|
1526
|
-
|
|
941
|
+
O core da v2 é compilado e empacotado a partir desta branch como um pacote npm padrão:
|
|
1527
942
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
```text
|
|
1533
|
-
examples/
|
|
1534
|
-
├── prisma.ts # Instância do PrismaClient usada pelos exemplos
|
|
1535
|
-
├── repositories.ts # Configuração dos repositórios (User, Address, Product) com setupVSRepo
|
|
1536
|
-
└── tests/
|
|
1537
|
-
├── base-methods.test.ts # Métodos base: get, save, patch, remove, getAll, total, has...
|
|
1538
|
-
├── relations.test.ts # Como configurar e usar relações em save/patch e em filtros
|
|
1539
|
-
├── required-where.test.ts # Como requiredWhere é aplicado automaticamente às queries
|
|
1540
|
-
├── dynamic-methods.test.ts # Prefixos, filtros de campo, operadores lógicos e paginação/ordenação
|
|
1541
|
-
├── transactions.test.ts # Transações com options.db e acesso à instância via repository.prisma
|
|
1542
|
-
├── soft-delete.test.ts # Soft-delete: softRemove, softRemoveList, restore, restoreList e SeeMode
|
|
1543
|
-
└── batch-methods.test.ts # Operações em lote: getList, saveList, patchList e merge
|
|
1544
|
-
```
|
|
943
|
+
```bash
|
|
944
|
+
# 1. Instalar as dependências
|
|
945
|
+
pnpm install
|
|
1545
946
|
|
|
1546
|
-
|
|
947
|
+
# 2. Compilar os fontes TypeScript em dist/ (remove um dist/ anterior primeiro)
|
|
948
|
+
pnpm build
|
|
1547
949
|
|
|
1548
|
-
|
|
950
|
+
# 3. (Opcional) Inspecionar o que seria publicado sem gerar um tarball
|
|
951
|
+
npm pack --dry-run
|
|
1549
952
|
|
|
1550
|
-
|
|
953
|
+
# 4. Gerar o tarball instalável (roda `prepack` -> `pnpm build` automaticamente)
|
|
954
|
+
npm pack
|
|
1551
955
|
|
|
1552
|
-
|
|
956
|
+
# 5. Consumir localmente em outro projeto
|
|
957
|
+
npm install ../caminho/vsrepo-1.4.0.tgz
|
|
958
|
+
```
|
|
1553
959
|
|
|
1554
|
-
|
|
1555
|
-
2. Crie uma nova branch com sua alteração: `git checkout -b fixing-bug`.
|
|
1556
|
-
3. Envie para sua branch: `git push origin fixing-bug`.
|
|
1557
|
-
4. Abra um **Pull Request**.
|
|
960
|
+
Observações:
|
|
1558
961
|
|
|
1559
|
-
|
|
962
|
+
- `pnpm build` executa `tsc -p tsconfig.build.json`, que gera o JS compilado e as declarações de tipo em `dist/` com `rootDir: src`.
|
|
963
|
+
- O pacote publicado contém **apenas** a pasta `dist/` além dos READMEs e da `LICENSE` (veja `files` no `package.json`). Fontes, testes, a pasta `v1/` e `generated/` **não** são enviados — os adapters viverão em seus próprios pacotes `@vsrepo/*-adapter`.
|
|
964
|
+
- O core é ORM-agnóstico e não tem dependência peer de `@prisma/client`.
|
|
1560
965
|
|
|
1561
966
|
---
|
|
1562
967
|
|
|
1563
968
|
## Requisitos
|
|
1564
969
|
|
|
1565
|
-
- Node.js 18+
|
|
1566
|
-
-
|
|
1567
|
-
- TypeScript (opcional, mas fortemente recomendado)
|
|
1568
|
-
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
1569
|
-
|
|
1570
|
-
`tsconfig.json` recomendado:
|
|
970
|
+
- Node.js 18+
|
|
971
|
+
- TypeScript, com **decorators legacy/experimentais** habilitados (necessário para `@DynamicMethod`/`@QueryMethod`):
|
|
1571
972
|
|
|
1572
973
|
```json
|
|
1573
974
|
{
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
"moduleResolution": "NodeNext",
|
|
1578
|
-
"strict": true,
|
|
1579
|
-
"skipLibCheck": true,
|
|
1580
|
-
"lib": ["ES2020"]
|
|
1581
|
-
}
|
|
975
|
+
"compilerOptions": {
|
|
976
|
+
"experimentalDecorators": true
|
|
977
|
+
}
|
|
1582
978
|
}
|
|
1583
979
|
```
|
|
1584
980
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
## Solução de problemas
|
|
1588
|
-
|
|
1589
|
-
**Tipos genéricos não são inferidos** — Verifique se `strict: true` e `moduleResolution: "bundler"` ou `"nodenext"` estão configurados no `tsconfig.json`.
|
|
1590
|
-
|
|
1591
|
-
**Método dinâmico não existe em tempo de execução** — O campo referenciado no nome do método deve existir no modelo do Prisma. Ex.: `findByEmail` requer que o modelo tenha um campo `email`.
|
|
981
|
+
- `reflect-metadata` (já incluso como dependência, importado internamente — você não precisa importá-lo você mesmo)
|
|
982
|
+
- Pelo menos um `VSRepoAdapter` funcional para o seu banco — no Prisma 7, instale o [`@vsrepo/prisma7-adapter`](https://github.com/jaobrabo123/VSRepoPrisma7Adapter) já publicado (veja [Status dos adapters](#status-dos-adapters)); adapters oficiais para outros ORMs estão planejados, mas ainda não publicados, então por enquanto isso significa escrever o seu próprio (veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)) — e, se publicá-lo, contribuir de volta com o projeto é bem-vindo
|
|
1592
983
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
**Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera.
|
|
1596
|
-
|
|
1597
|
-
**`selectModel`, `includeModel` e `include` juntos na mesma chamada** — Não é permitido. Apenas um dos três pode ser informado por chamada: se `includeModel` ou `include` for informado, o `select` (incluindo `defaultSelectModel`) é ignorado e apenas o `include` é enviado ao Prisma.
|
|
1598
|
-
|
|
1599
|
-
**`includeModel` não aparece como opção padrão do repositório** — Isso é esperado. Diferente do `defaultSelectModel`, não existe `defaultIncludeModel`/`defaultInclude`. Um `includeModel` só pode ser definido na chamada do método, via `options.includeModel`. Um include bruto e ad hoc pode ser definido via `options.include`, sem precisar ser registrado em `includeModels`.
|
|
984
|
+
---
|
|
1600
985
|
|
|
1601
|
-
|
|
986
|
+
## Contribuindo
|
|
1602
987
|
|
|
1603
|
-
|
|
988
|
+
Contribuições são bem-vindas, especialmente para finalizar os adapters do Prisma e do TypeORM! (**[Repositório do GitHub](https://github.com/jaobrabo123/VSRepository)**):
|
|
1604
989
|
|
|
1605
|
-
|
|
990
|
+
1. Faça um **Fork** do projeto.
|
|
991
|
+
2. Crie uma branch a partir de `v2` para sua alteração: `git checkout -b v2-minha-alteracao`.
|
|
992
|
+
3. Faça o push da sua branch: `git push origin v2-minha-alteracao`.
|
|
993
|
+
4. Abra um **Pull Request** contra a `v2`.
|
|
1606
994
|
|
|
1607
|
-
|
|
995
|
+
Para reportar problemas ou sugerir funcionalidades, abra uma **Issue**.
|