vsrepo 1.4.2 → 2.1.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 +827 -1322
- package/README.pt-BR.md +833 -1325
- package/dist/VSRepoAdapter.d.ts +109 -0
- package/dist/VSRepoAdapter.js +18 -0
- package/dist/VSRepository.d.ts +166 -1201
- package/dist/VSRepository.js +327 -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 +38 -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 +40 -0
- package/dist/internal/validators/vsrepo.validator.js +182 -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/decimal-like.type.d.ts +23 -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/numeric-keys.type.d.ts +24 -0
- package/dist/types/utils/numeric-like.type.d.ts +10 -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 +7 -0
- package/dist/types/utils/query-method-arg.type.d.ts +27 -0
- package/dist/types/utils/restrict-method-options.type.d.ts +14 -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-method.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-options.type.d.ts +34 -0
- package/dist/types/vsrepo/vsrepo-options.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-orm-types.type.d.ts +17 -0
- package/dist/types/vsrepo/vsrepo-orm-types.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-pretty-where.type.d.ts +7 -0
- package/dist/types/vsrepo/vsrepo-pretty-where.type.js +2 -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 -541
- 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/decimal-like.type.js} +0 -0
- /package/dist/{internal/resolvers/types/prisma-args.type.js → types/utils/deep-partial.type.js} +0 -0
- /package/dist/{internal/resolvers/types/repository-build-instance.type.js → types/utils/keys-of-type.type.js} +0 -0
- /package/dist/{internal/resolvers/types/resolve-db-and-prisma-args-data.type.js → types/utils/methods-options.type.js} +0 -0
- /package/dist/{internal/resolvers/types/ugly-where.type.js → types/utils/numeric-keys.type.js} +0 -0
- /package/dist/{internal/validation/types/base-methods.type.js → types/utils/numeric-like.type.js} +0 -0
- /package/dist/{internal/validation/types/build-config.type.js → types/utils/ordering.type.js} +0 -0
- /package/dist/{internal/validation/types → types/utils}/pagination.type.js +0 -0
- /package/dist/{internal/validation/types/constructor-config.type.js → types/utils/perform-data.type.js} +0 -0
- /package/dist/{internal/validation/types/method-options.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/method.type.js → types/utils/restrict-method-options.type.js} +0 -0
- /package/dist/{internal/validation/types → types/utils}/see-mode.type.js +0 -0
- /package/dist/{internal/validation/types/relation.type.js → types/vsrepo/vsrepo-args.type.js} +0 -0
package/README.pt-BR.md
CHANGED
|
@@ -9,139 +9,127 @@
|
|
|
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](https://github.com/jaobrabo123/VSRepository/tree/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
|
-
|
|
43
|
-
|
|
44
|
-
- [
|
|
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
|
+
- [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)
|
|
43
|
+
- [Quais campos são elegíveis](#quais-campos-são-elegíveis)
|
|
44
|
+
- [Escrevendo um adapter](#escrevendo-um-adapter)
|
|
45
|
+
- [`select` e `relations`](#select-e-relations)
|
|
51
46
|
- [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)
|
|
47
|
+
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
48
|
+
- [Filtros de campo](#filtros-de-campo)
|
|
49
|
+
- [Operadores lógicos](#operadores-lógicos)
|
|
50
|
+
- [Filtros de relação](#filtros-de-relação)
|
|
51
|
+
- [Ordenação, paginação e distinct](#ordenação-paginação-e-distinct)
|
|
52
|
+
- [Options do decorador](#options-do-decorador)
|
|
53
|
+
- [Query methods (SQL raw)](#query-methods-sql-raw)
|
|
54
|
+
- [Queries raw pontuais com `query()`](#queries-raw-pontuais-com-query)
|
|
62
55
|
- [Transações](#transações)
|
|
63
|
-
- [Estendendo um repositório](#estendendo-um-repositório)
|
|
64
|
-
- [Tratamento de erros](#tratamento-de-erros)
|
|
65
56
|
- [Tipos utilitários](#tipos-utilitários)
|
|
66
|
-
- [
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
57
|
+
- [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)
|
|
58
|
+
- [Tratamento de erros](#tratamento-de-erros)
|
|
59
|
+
- [`VSRepoAdapterError` e `AdapterErrorCode`](#vsrepoadaptererror-e-adaptererrorcode)
|
|
60
|
+
- [Logging](#logging)
|
|
61
|
+
- [Desenvolvimento](#desenvolvimento)
|
|
69
62
|
- [Requisitos](#requisitos)
|
|
70
|
-
- [
|
|
63
|
+
- [Contribuindo](#contribuindo)
|
|
71
64
|
|
|
72
65
|
---
|
|
73
66
|
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
## O que mudou da v1
|
|
68
|
+
|
|
69
|
+
Se você vem do código/docs da [v1](https://github.com/jaobrabo123/VSRepository/tree/v1), aqui está o resumo. Veja cada seção linkada para detalhes.
|
|
70
|
+
|
|
71
|
+
| Área | v1 | v2 |
|
|
72
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
73
|
+
| 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` |
|
|
74
|
+
| Definindo um repository | `setupVSRepo<T, M>()({...}).build(prisma)` funcional, **ou** uma classe `DynamicRepository` | Uma única API **baseada em classes**: `extends VSRepository<Entity, PKType, OrmTypes>` |
|
|
75
|
+
| Métodos dinâmicos | Objeto de config `methods: { findByEmail: { map: true } }` | Decorador `@DynamicMethod()` em um campo `declare` |
|
|
76
|
+
| Projeções de dados | `selectModels` + `defaultSelectModel` nomeados e reutilizáveis | `select`/`relations` ad-hoc passados em cada chamada (sem modelos nomeados) |
|
|
77
|
+
| Eager loading | `include`/`includeModels` (específico do Prisma) | Option `relations` agnóstica de ORM |
|
|
78
|
+
| Filtros globais | `requiredWhere` (qualquer filtro arbitrário, sempre aplicado) | **Removido**; Agora aceita apenas `softRemoveKey` + `see: "active" \| "removed" \| "all"` |
|
|
79
|
+
| Sufixo de filtro case-insensitive | `Insensitive` | `IgnoreCase` |
|
|
80
|
+
| 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 |
|
|
81
|
+
| Tratamento de duplicatas no `createMany` | Sufixo `SkipDuplicates` | Sufixo `IgnoreConflicts` |
|
|
82
|
+
| `aggregate` / `groupBy` | Suportado (passthrough nativo do Prisma) | **Ainda não implementado** |
|
|
83
|
+
| 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 |
|
|
84
|
+
| Log de debug | Boolean `showWorking: true` | `logLevel: VSLogLevel` (`DEBUG`/`INFO`/`WARN`/`ERROR`) + `logSlowThresholdMs` para avisos de queries lentas |
|
|
85
|
+
| 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 |
|
|
86
|
+
| 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
87
|
|
|
86
88
|
---
|
|
87
89
|
|
|
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
|
-
```
|
|
90
|
+
## Status dos adapters
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
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
93
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
94
|
+
- `@vsrepo/prisma7-adapter`
|
|
95
|
+
- `@vsrepo/prisma8-adapter`
|
|
96
|
+
- `@vsrepo/typeorm-adapter`
|
|
97
|
+
- `@vsrepo/drizzle-adapter`
|
|
103
98
|
|
|
104
|
-
**
|
|
99
|
+
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
100
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
101
|
+
| Adapter | Status |
|
|
102
|
+
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| Prisma 7 (`@vsrepo/prisma7-adapter`) | 🟢 **Lançado** — publicado no npm, implementa 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. **Nota:** os métodos atômicos/de agregação (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação)) foram adicionados ao contrato do `VSRepoAdapter` depois do último release desse adapter; confirme no changelog/versão dele se já implementam esses métodos antes de depender de `increment`/`sum`/etc. contra o Prisma 7. |
|
|
104
|
+
| 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. |
|
|
105
|
+
| 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. |
|
|
106
|
+
| 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
107
|
|
|
111
|
-
|
|
108
|
+
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
109
|
|
|
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
|
-
```
|
|
110
|
+
---
|
|
123
111
|
|
|
124
|
-
|
|
112
|
+
## Instalação
|
|
125
113
|
|
|
126
|
-
|
|
127
|
-
// CORRETO ✅
|
|
128
|
-
import { setupVSRepo } from "../../generated/vsrepo";
|
|
114
|
+
A v2 é instalada como o pacote core mais um pacote de adapter para o seu ORM, por exemplo:
|
|
129
115
|
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
```bash
|
|
117
|
+
npm i vsrepo @vsrepo/prisma7-adapter
|
|
132
118
|
```
|
|
133
119
|
|
|
120
|
+
> 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)).
|
|
121
|
+
|
|
134
122
|
---
|
|
135
123
|
|
|
136
124
|
## Uso básico
|
|
137
125
|
|
|
138
|
-
###
|
|
126
|
+
### Implementando/escolhendo um adapter
|
|
139
127
|
|
|
140
|
-
```
|
|
128
|
+
```typescript
|
|
141
129
|
// src/configs/db.ts
|
|
142
|
-
import { PrismaClient } from
|
|
143
|
-
import { PrismaPg } from
|
|
144
|
-
import
|
|
130
|
+
import { PrismaClient } from "../../generated/prisma/client";
|
|
131
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
132
|
+
import "dotenv/config";
|
|
145
133
|
|
|
146
134
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
147
135
|
const prisma = new PrismaClient({ adapter });
|
|
@@ -149,1459 +137,979 @@ const prisma = new PrismaClient({ adapter });
|
|
|
149
137
|
export default prisma;
|
|
150
138
|
```
|
|
151
139
|
|
|
152
|
-
### Criando um
|
|
140
|
+
### Criando um repository
|
|
153
141
|
|
|
154
|
-
```
|
|
155
|
-
// src/repositories/
|
|
142
|
+
```typescript
|
|
143
|
+
// src/repositories/user.repository.ts
|
|
144
|
+
import { VSRepository, DynamicMethod } from "vsrepo";
|
|
145
|
+
import { VSRepoPrisma7Adapter } from "@vsrepo/prisma7-adapter";
|
|
156
146
|
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
|
-
---
|
|
147
|
+
import type { UserGetPayload } from "../../generated/prisma/models";
|
|
201
148
|
|
|
202
|
-
|
|
149
|
+
type User = UserGetPayload<{ include: { address: true } }>;
|
|
203
150
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
import { PrismaService } from "../../database/prisma.service";
|
|
212
|
-
import { UserGetPayload } from "../../../generated/prisma/models";
|
|
213
|
-
import { setupVSRepo } from "../../../generated/vsrepo";
|
|
214
|
-
|
|
215
|
-
const userVSRepo = setupVSRepo<
|
|
216
|
-
UserGetPayload<{ include: { profile: true } }>,
|
|
217
|
-
"User"
|
|
218
|
-
>()(({
|
|
219
|
-
tableName: "user",
|
|
220
|
-
pkName: "id",
|
|
221
|
-
selectModels: {
|
|
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,
|
|
151
|
+
class UserRepository extends VSRepository<User, string> {
|
|
152
|
+
constructor() {
|
|
153
|
+
super({
|
|
154
|
+
pkName: "id",
|
|
155
|
+
adapter: new VSRepoPrisma7Adapter<User>(prisma, { tableName: "user", pkName: "id" }),
|
|
156
|
+
softRemoveKey: "deletedAt",
|
|
157
|
+
defaultOrdering: { createdAt: "desc" },
|
|
325
158
|
});
|
|
326
159
|
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**Benefícios dessa abordagem:**
|
|
331
|
-
|
|
332
|
-
- ✅ Repositórios type-safe com injeção de dependência
|
|
333
|
-
- ✅ Fácil de testar (mock do `USER_REPOSITORY`)
|
|
334
|
-
- ✅ Isolamento da lógica de persistência
|
|
335
|
-
- ✅ Reuso do repositório em múltiplos services
|
|
336
|
-
- ✅ Suporte a transações via `PrismaService`
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Métodos base
|
|
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
160
|
|
|
386
|
-
|
|
161
|
+
@DynamicMethod()
|
|
162
|
+
declare findByEmail: (email: string) => Promise<User[]>;
|
|
387
163
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
]);
|
|
164
|
+
@DynamicMethod()
|
|
165
|
+
declare findOneByEmail: (email: string) => Promise<User | null>;
|
|
166
|
+
}
|
|
396
167
|
|
|
397
|
-
|
|
398
|
-
const updated = await userRepository.patchList([
|
|
399
|
-
[1, { active: false }],
|
|
400
|
-
[2, { name: "New Name" }],
|
|
401
|
-
]);
|
|
168
|
+
export default new UserRepository();
|
|
402
169
|
```
|
|
403
170
|
|
|
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
|
-
```
|
|
171
|
+
> 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
172
|
|
|
413
|
-
|
|
173
|
+
> **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`:
|
|
174
|
+
> ```typescript
|
|
175
|
+
> type PrismaOrmTypes = { dbClient: PrismaClient; dbTransaction: Prisma.TransactionClient };
|
|
176
|
+
>
|
|
177
|
+
> class UserRepository extends VSRepository<User, string, PrismaOrmTypes> {
|
|
178
|
+
> // getDbClient() agora retorna PrismaClient, e transaction(fn) tipa `tx` como Prisma.TransactionClient
|
|
179
|
+
> }
|
|
180
|
+
> ```
|
|
181
|
+
> Se omitido, o padrão é `VSRepoOrmTypes` (`dbClient`/`dbTransaction` como `any`).
|
|
414
182
|
|
|
415
|
-
|
|
183
|
+
### Usando o repository
|
|
416
184
|
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
// existing: { id: 1, name: "Mary", profile: { bio: "Hi", age: 25 } }
|
|
185
|
+
```typescript
|
|
186
|
+
import userRepository from "./repositories/user.repository";
|
|
420
187
|
|
|
421
|
-
const
|
|
422
|
-
|
|
188
|
+
const usuario = await userRepository.save({
|
|
189
|
+
name: "Joao",
|
|
190
|
+
email: "joao@email.com",
|
|
191
|
+
password: "password",
|
|
423
192
|
});
|
|
424
|
-
// merged: { id: 1, name: "Mary", profile: { bio: "Updated bio", age: 25 } }
|
|
425
193
|
|
|
426
|
-
|
|
427
|
-
await userRepository.
|
|
428
|
-
|
|
194
|
+
const encontrado = await userRepository.get(usuario.id);
|
|
195
|
+
const todos = await userRepository.getAll();
|
|
196
|
+
const porEmail = await userRepository.findByEmail("joao@email.com");
|
|
429
197
|
|
|
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
|
-
// }
|
|
198
|
+
await userRepository.patch(usuario.id, { name: "Joao Pedro" });
|
|
199
|
+
await userRepository.remove(usuario.id);
|
|
458
200
|
```
|
|
459
201
|
|
|
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
202
|
---
|
|
516
203
|
|
|
517
|
-
##
|
|
204
|
+
## Options do construtor
|
|
518
205
|
|
|
519
|
-
`
|
|
206
|
+
`VSRepoOptions<T, K>`, passado para o `super(...)` dentro do construtor do seu repository:
|
|
520
207
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
208
|
+
| Option | Tipo | Descrição |
|
|
209
|
+
| -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| `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. |
|
|
211
|
+
| `pkName` | `keyof T` | **Obrigatório.** Nome do campo que representa a primary key da entidade. |
|
|
212
|
+
| `softRemoveKey` | `keyof T` | Opcional. Quando definido, habilita `softRemove`, `softRemoveList`, `restore` e `restoreList`. |
|
|
213
|
+
| `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. |
|
|
214
|
+
| `logLevel` | `VSLogLevel` | Opcional. Severidade mínima impressa pelo logger interno. Padrão: `VSLogLevel.WARN`. |
|
|
215
|
+
| `logSlowThresholdMs` | `number` | Opcional. Duração (ms) acima da qual uma operação concluída é logada como `WARN` em vez de `DEBUG`. Padrão: 300ms. |
|
|
529
216
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
**Usando um select específico na chamada:**
|
|
217
|
+
---
|
|
533
218
|
|
|
534
|
-
|
|
535
|
-
const user = await userRepository.get(id, { selectModel: "minimal" });
|
|
536
|
-
```
|
|
219
|
+
## Métodos base
|
|
537
220
|
|
|
538
|
-
|
|
221
|
+
Disponíveis automaticamente em toda subclasse de `VSRepository`:
|
|
222
|
+
|
|
223
|
+
| Método | Descrição |
|
|
224
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| `get(pk, options?)` | Busca um registro pela primary key. |
|
|
226
|
+
| `getOrThrow(pk, options?)` | Busca um registro pela primary key, lançando erro se não encontrar. |
|
|
227
|
+
| `getList(pks, options?)` | Busca vários registros por uma lista de primary keys. |
|
|
228
|
+
| `getAll(options?)` | Busca todos os registros; aceita `pagination` e `order` em `options`. |
|
|
229
|
+
| `save(obj, options?)` | Cria ou atualiza (upsert) um único registro. |
|
|
230
|
+
| `saveList(objs, options?)` | Cria ou atualiza (upsert) vários registros em uma única chamada. |
|
|
231
|
+
| `patch(pk, obj, options?)` | Atualiza parcialmente um registro pela primary key. |
|
|
232
|
+
| `merge(pk, obj, options?)` | Busca um registro e o retorna mesclado (deep-merge), em memória, com o objeto informado — **não** persiste nada. |
|
|
233
|
+
| `remove(pk, options?)` | Remove um registro pela primary key. |
|
|
234
|
+
| `removeList(pks, options?)` | Remove vários registros pela primary key, retornando `{ count }`. |
|
|
235
|
+
| `total(options?)` | Retorna o total de registros. |
|
|
236
|
+
| `has(pk, options?)` | Verifica se um registro existe, retornando `boolean`. |
|
|
237
|
+
| `increment(pk, field, value, options?)` | Adiciona `value` a um campo numérico de forma atômica. Veja [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
238
|
+
| `decrement(pk, field, value, options?)` | Subtrai `value` de um campo numérico de forma atômica. |
|
|
239
|
+
| `multiply(pk, field, value, options?)` | Multiplica um campo numérico por `value` de forma atômica. |
|
|
240
|
+
| `divide(pk, field, value, options?)` | Divide um campo numérico por `value` de forma atômica. |
|
|
241
|
+
| `sum(field, where?, options?)` | Soma um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
242
|
+
| `average(field, where?, options?)` | Média aritmética de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
243
|
+
| `min(field, where?, options?)` | Valor mínimo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
244
|
+
| `max(field, where?, options?)` | Valor máximo de um campo numérico em todos os registros que baterem no filtro; `null` se nenhum bater. |
|
|
245
|
+
| `transaction(fn, options?)` | Executa `fn` dentro de uma transação nativa do ORM. |
|
|
246
|
+
| `getDbClient()` | Retorna a instância do client do ORM usada fora de transações. |
|
|
247
|
+
| `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). |
|
|
248
|
+
|
|
249
|
+
A maioria dos métodos acima aceita um objeto `MethodOptions<Entity, OrmTypes>` como último argumento (`select`, `relations`, `see`, `db`). Alguns — `total`, `has`, `removeList`, `sum`, `average`, `min`, `max`, e os métodos em lote de soft-delete (`softRemoveList`/`restoreList`) — não retornam/moldam uma `Entity`, então aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db`; sem `select`/`relations`). `transaction`, `query` e `getDbClient` recebem options próprias ou nenhuma.
|
|
539
250
|
|
|
540
|
-
|
|
541
|
-
const fullUser = await userRepository.get(id, { selectModel: false });
|
|
542
|
-
```
|
|
251
|
+
---
|
|
543
252
|
|
|
544
|
-
|
|
253
|
+
## Soft-delete
|
|
545
254
|
|
|
546
|
-
|
|
255
|
+
O soft-delete agora é um **conceito nativo de primeira classe**. Configure `softRemoveKey` uma vez no repository:
|
|
547
256
|
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
|
|
257
|
+
```typescript
|
|
258
|
+
super({
|
|
259
|
+
pkName: "id",
|
|
260
|
+
adapter,
|
|
261
|
+
softRemoveKey: "deletedAt",
|
|
551
262
|
});
|
|
552
263
|
```
|
|
553
264
|
|
|
554
|
-
|
|
265
|
+
Isso libera quatro métodos extras:
|
|
555
266
|
|
|
556
|
-
|
|
267
|
+
| Método | Efeito |
|
|
268
|
+
| ------------------------------- | --------------------------------------- |
|
|
269
|
+
| `softRemove(pk, options?)` | Define `deletedAt` para a data atual. |
|
|
270
|
+
| `softRemoveList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
|
|
271
|
+
| `restore(pk, options?)` | Volta `deletedAt` para `null`. |
|
|
272
|
+
| `restoreList(pks, options?)` | O mesmo, em lote — retorna `{ count }`. |
|
|
557
273
|
|
|
558
|
-
|
|
559
|
-
- **Ad hoc, não reutilizável.** Diferente do `selectModel`, não precisa ser declarado em `selectModels`. Use-o para projeções pontuais que não justificam um select model nomeado.
|
|
560
|
-
- **Nenhum `defaultSelectModel` é aplicado.** Quando `select` é fornecido, o select padrão (`defaultSelectModel`) é ignorado e apenas o `select` bruto é enviado ao Prisma.
|
|
274
|
+
Todo o restante dos métodos aceita uma option `see` que controla a visibilidade de registros com soft-delete:
|
|
561
275
|
|
|
562
|
-
```
|
|
563
|
-
//
|
|
564
|
-
await
|
|
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 } });
|
|
276
|
+
```typescript
|
|
277
|
+
await userRepository.getAll({ see: "active" }); // padrão — apenas registros não removidos
|
|
278
|
+
await userRepository.getAll({ see: "removed" }); // apenas registros com soft-delete
|
|
279
|
+
await userRepository.getAll({ see: "all" }); // todos, ignorando o soft-delete
|
|
569
280
|
```
|
|
570
281
|
|
|
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.
|
|
572
|
-
|
|
573
282
|
---
|
|
574
283
|
|
|
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`
|
|
284
|
+
## Métodos atômicos e de agregação
|
|
603
285
|
|
|
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.
|
|
286
|
+
Toda subclasse de `VSRepository` ganha 8 métodos extras para trabalhar com campos numéricos, divididos em dois grupos:
|
|
606
287
|
|
|
607
|
-
|
|
608
|
-
// CORRETO ✅ — apenas includeModel
|
|
609
|
-
await userRepository.get(id, { includeModel: "withPosts" });
|
|
288
|
+
**Updates atômicos** — avaliados no servidor contra o valor *atual* da linha (`UPDATE ... SET field = field + value`), não um read-modify-write feito no client:
|
|
610
289
|
|
|
611
|
-
|
|
612
|
-
await userRepository.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
await userRepository.
|
|
290
|
+
```typescript
|
|
291
|
+
await userRepository.increment("user-1", "balance", 50); // balance = balance + 50
|
|
292
|
+
await userRepository.decrement("user-1", "balance", 50); // balance = balance - 50
|
|
293
|
+
await userRepository.multiply("user-1", "balance", 2); // balance = balance * 2
|
|
294
|
+
await userRepository.divide("user-1", "balance", 4); // balance = balance / 4
|
|
616
295
|
```
|
|
617
296
|
|
|
618
|
-
|
|
297
|
+
Os quatro retornam a `Entity` atualizada e aceitam o `MethodOptions<Entity, OrmTypes>` completo (`select`, `relations`, `see`, `db`) como último argumento, igual `get`/`save`/`patch`.
|
|
619
298
|
|
|
620
|
-
|
|
299
|
+
**Agregações** — calculadas sobre todos os registros que baterem num `where` (opcional):
|
|
621
300
|
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
301
|
+
```typescript
|
|
302
|
+
await userRepository.sum("balance"); // soma do saldo de todos os registros ativos
|
|
303
|
+
await userRepository.sum("balance", { active: true }); // ...restrito por um where
|
|
304
|
+
await userRepository.average("balance");
|
|
305
|
+
await userRepository.min("balance");
|
|
306
|
+
await userRepository.max("balance");
|
|
626
307
|
```
|
|
627
308
|
|
|
628
|
-
`
|
|
309
|
+
Os quatro retornam `number | null` — `null` quando nenhum registro bate no filtro, espelhando o comportamento de `SUM()`/`AVG()`/`MIN()`/`MAX()` do SQL, que retornam `NULL` (não `0`) sobre um conjunto vazio. Diferente dos métodos atômicos, eles aceitam o tipo mais restrito `RestrictMethodOptions<Entity, OrmTypes>` (só `see`, `db` — sem `select`/`relations`, já que o resultado é um número simples, não uma `Entity` moldada).
|
|
629
310
|
|
|
630
|
-
|
|
311
|
+
Os dois grupos respeitam `softRemoveKey`/`see` do mesmo jeito que todo outro método base — `sum("balance")` só soma registros não removidos por padrão; passe `{ see: "all" }` ou `{ see: "removed" }` para mudar isso.
|
|
631
312
|
|
|
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.
|
|
313
|
+
### Quais campos são elegíveis
|
|
635
314
|
|
|
636
|
-
|
|
637
|
-
// CORRETO ✅ — apenas include bruto
|
|
638
|
-
await userRepository.get(id, { include: { posts: true } });
|
|
315
|
+
`field` é restrito a `NumericKeys<Entity>` — chaves cujo valor (ignorando `null`/`undefined`) é um `number`, um `bigint`, ou um objeto `DecimalLike` (qualquer coisa que exponha `toNumber()` e `decimalPlaces()`, como o `Prisma.Decimal` do Prisma):
|
|
639
316
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
await userRepository.get(id, { includeModel: "withPosts", include: { posts: true } });
|
|
643
|
-
```
|
|
644
|
-
|
|
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
|
-
---
|
|
648
|
-
|
|
649
|
-
## Required Where
|
|
650
|
-
|
|
651
|
-
`requiredWhere` define filtros que são aplicados automaticamente em toda query do repositório.
|
|
652
|
-
|
|
653
|
-
```ts
|
|
654
|
-
requiredWhere: { active: true },
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
Agora toda query vai incluir automaticamente `active: true`:
|
|
658
|
-
|
|
659
|
-
```ts
|
|
660
|
-
// Internamente: WHERE active = true
|
|
661
|
-
const users = await userRepository.findMany();
|
|
317
|
+
```typescript
|
|
318
|
+
type Product = { id: string; name: string; price: Decimal; stock: number | null };
|
|
662
319
|
|
|
663
|
-
|
|
664
|
-
|
|
320
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok — Decimal-like
|
|
321
|
+
await productRepository.increment(id, "stock", 5); // ok — campos numéricos nullable são incluídos
|
|
322
|
+
await productRepository.increment(id, "name", 1); // erro de compilação — "name" não é numérico
|
|
665
323
|
```
|
|
666
324
|
|
|
667
|
-
|
|
325
|
+
`value` é tipado como `NonNullable<Entity[Field]>` — precisa bater exatamente com o tipo do próprio campo. Um campo `Decimal` espera uma instância de `Decimal`, não um `number`/`string` puro:
|
|
668
326
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
```
|
|
682
|
-
|
|
683
|
-
Com isso, toda query de listagem já virá ordenada por `createdAt` decrescente:
|
|
684
|
-
|
|
685
|
-
```ts
|
|
686
|
-
// Internamente: ORDER BY createdAt DESC
|
|
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 } });
|
|
327
|
+
```typescript
|
|
328
|
+
await productRepository.increment(id, "price", new Decimal(10.5)); // ok
|
|
329
|
+
await productRepository.increment(id, "price", 10.5); // erro de compilação — envolva: new Decimal(10.5)
|
|
691
330
|
```
|
|
692
331
|
|
|
693
|
-
|
|
332
|
+
Vale notar que vários ORMs (Drizzle, MikroORM, TypeORM) representam colunas `decimal`/`numeric` como `string` pura por padrão, para evitar perda de precisão de ponto flutuante — um campo `string` **não** satisfaz `NumericKeys<Entity>` por padrão. Configure a coluna em modo numérico (ou um transformer) nesses ORMs se quiser que o campo fique disponível para esses 8 métodos.
|
|
694
333
|
|
|
695
|
-
|
|
696
|
-
- O método dinâmico tem `injectOrdering` configurado — a ordenação fixa do método tem precedência.
|
|
697
|
-
|
|
698
|
-
```ts
|
|
699
|
-
methods: {
|
|
700
|
-
findManyPaginatedAndOrdered: { map: true }, // order vem do argumento → defaultOrdering ignorado
|
|
701
|
-
findManyByActive: { map: true }, // sem Ordered → defaultOrdering aplicado
|
|
702
|
-
findManyByStatus: {
|
|
703
|
-
map: true,
|
|
704
|
-
injectOrdering: { name: "asc" }, // injectOrdering → defaultOrdering ignorado
|
|
705
|
-
},
|
|
706
|
-
}
|
|
707
|
-
```
|
|
334
|
+
### Escrevendo um adapter
|
|
708
335
|
|
|
709
|
-
|
|
336
|
+
O `VSRepoAdapter` espelha as mesmas 8 operações (`incrementOne`, `decrementOne`, `multiplyOne`, `divideOne`, `sum`, `average`, `min`, `max` — veja [Escrevendo seu próprio adapter](#escrevendo-seu-próprio-adapter)). Cada adapter traduz isso para o que o ORM/banco considera "nativo": o Prisma tem um formato de update embutido (`{ field: { increment: value } }`) e uma chamada `aggregate()`; outros ORMs em geral precisam de um `QueryBuilder`/expressão `sql` raw (ex.: `SET field = field * :value`, `SELECT SUM(field) ...`). Os métodos atômicos precisam retornar o registro refletindo o estado *depois* do write — se a API de update atômico do ORM só retorna a quantidade de linhas afetadas, faça uma leitura extra em vez de devolver uma cópia desatualizada que já estava em memória.
|
|
710
337
|
|
|
711
338
|
---
|
|
712
339
|
|
|
713
|
-
##
|
|
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 |
|
|
340
|
+
## `select` e `relations`
|
|
722
341
|
|
|
723
|
-
|
|
724
|
-
// Retorna apenas usuários ativos (padrão)
|
|
725
|
-
const active = await userRepository.getAll();
|
|
342
|
+
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:
|
|
726
343
|
|
|
727
|
-
|
|
728
|
-
const
|
|
344
|
+
```typescript
|
|
345
|
+
const usuario = await userRepository.get(id, {
|
|
346
|
+
select: { id: true, name: true, address: { city: true } },
|
|
347
|
+
});
|
|
729
348
|
|
|
730
|
-
|
|
731
|
-
|
|
349
|
+
const usuarioComEndereco = await userRepository.get(id, {
|
|
350
|
+
relations: { address: true },
|
|
351
|
+
});
|
|
732
352
|
```
|
|
733
353
|
|
|
734
|
-
|
|
354
|
+
- `select` espelha o formato da entidade: campos escalares recebem um `boolean`; campos de relação recebem um `boolean` ou um `select` aninhado.
|
|
355
|
+
- `relations` carrega registros relacionados; cada campo de relação recebe um `boolean` ou um objeto `relations` aninhado.
|
|
356
|
+
- Se `select` e `relations` podem ser combinados depende do adapter (veja abaixo).
|
|
357
|
+
|
|
358
|
+
> ⚠️ **Comportamento de `relations` depende do adapter:**
|
|
359
|
+
>
|
|
360
|
+
> O core apenas repassa `MethodOptions.select` e `MethodOptions.relations` ao adapter — cada adapter decide como traduzi-los para o ORM subjacente:
|
|
361
|
+
>
|
|
362
|
+
> - **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`:
|
|
363
|
+
> ```typescript
|
|
364
|
+
> // TypeORM: apenas select NÃO é suficiente
|
|
365
|
+
> await userRepository.get(id, {
|
|
366
|
+
> select: { id: true, address: { city: true } },
|
|
367
|
+
> relations: { address: true }, // ← obrigatório no TypeORM
|
|
368
|
+
> });
|
|
369
|
+
> ```
|
|
370
|
+
> - **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:
|
|
371
|
+
> ```typescript
|
|
372
|
+
> // Prisma7: relations é ignorado quando select existe
|
|
373
|
+
> await userRepository.get(id, {
|
|
374
|
+
> select: { id: true, name: true },
|
|
375
|
+
> relations: { address: true }, // ← ignorado, include = undefined
|
|
376
|
+
> });
|
|
377
|
+
> ```
|
|
378
|
+
>
|
|
379
|
+
> Adapters customizados podem mapear `relations` de forma diferente — consulte a documentação do adapter para a semântica exata.
|
|
735
380
|
|
|
736
381
|
---
|
|
737
382
|
|
|
738
383
|
## Métodos dinâmicos
|
|
739
384
|
|
|
740
|
-
Métodos dinâmicos são
|
|
741
|
-
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
385
|
+
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.
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
class UserRepository extends VSRepository<User, string> {
|
|
389
|
+
@DynamicMethod()
|
|
390
|
+
declare findByEmail: (email: string) => Promise<User[]>;
|
|
391
|
+
|
|
392
|
+
@DynamicMethod()
|
|
393
|
+
declare findOneByEmail: (email: string) => Promise<User | null>;
|
|
394
|
+
|
|
395
|
+
@DynamicMethod()
|
|
396
|
+
declare updateById: (id: string, data: DeepPartial<User>) => Promise<User>;
|
|
397
|
+
|
|
398
|
+
// Baseado em where: VSRepoWhere<T> como primeiro parâmetro, pagination penúltimo, MethodOptions por último
|
|
399
|
+
@DynamicMethod()
|
|
400
|
+
declare findWherePaginated: (
|
|
401
|
+
where: VSRepoWhere<User>,
|
|
402
|
+
pagination: Pagination,
|
|
403
|
+
options?: MethodOptions<User>,
|
|
404
|
+
) => Promise<User[]>;
|
|
405
|
+
|
|
406
|
+
// OrderedAndPaginated: filtros de campo, depois order, depois pagination, depois MethodOptions
|
|
407
|
+
@DynamicMethod()
|
|
408
|
+
declare findByNameIgnoreCaseOrAgeBetweenOrderByCreatedAtAscPaginated: (
|
|
409
|
+
name: string,
|
|
410
|
+
age: [number, number],
|
|
411
|
+
order: Ordering<User>,
|
|
412
|
+
pagination: Pagination,
|
|
413
|
+
options?: MethodOptions<User>,
|
|
414
|
+
) => Promise<User[]>;
|
|
748
415
|
}
|
|
749
416
|
```
|
|
750
417
|
|
|
751
|
-
---
|
|
752
|
-
|
|
753
418
|
### Prefixos disponíveis
|
|
754
419
|
|
|
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
|
-
---
|
|
420
|
+
| Prefixo | Método do adapter | Observações |
|
|
421
|
+
| -------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
422
|
+
| `findBy` | `findMany` | Filtros de campo seguem o prefixo. |
|
|
423
|
+
| `findOneBy` | `findOne` | Filtros de campo seguem o prefixo; resultado único. |
|
|
424
|
+
| `findOneOrThrowBy` | `findOneOrThrow` | Lança erro se não encontrar. |
|
|
425
|
+
| `findOneOrThrow` | `findOneOrThrow` | Sem filtros de campo; aplica só soft-delete/`see`. |
|
|
426
|
+
| `findOneOrThrowWhere` | `findOneOrThrow` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
427
|
+
| `findWhere` | `findMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
428
|
+
| `findOneWhere` | `findOne` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
429
|
+
| `findOne` | `findOne` | Sem filtros de campo; aplica só soft-delete/`see`. |
|
|
430
|
+
| `countBy` | `count` | Filtros de campo seguem o prefixo. |
|
|
431
|
+
| `countWhere` | `count` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
432
|
+
| `count` | `count` | Sem filtros de campo. |
|
|
433
|
+
| `existsBy` | `exists` | Retorna `boolean`. |
|
|
434
|
+
| `existsWhere` | `exists` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
435
|
+
| `create` | `create` | Recebe `data` como argumento. |
|
|
436
|
+
| `createMany` | `createMany` | Recebe `data[]` como argumento; suporta `IgnoreConflicts`. |
|
|
437
|
+
| `createManyReturning` | `createManyReturning` | Recebe `data[]` como argumento; suporta `IgnoreConflicts`; retorna os registros criados (`T[]`), em vez de `CountResult`. |
|
|
438
|
+
| `updateBy` | `update` | Filtros de campo + `data` como argumento. |
|
|
439
|
+
| `updateWhere` | `update` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`. |
|
|
440
|
+
| `updateManyBy` | `updateMany` | Filtros de campo + `data`. |
|
|
441
|
+
| `updateManyWhere` | `updateMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`. |
|
|
442
|
+
| `updateManyReturningBy` | `updateManyReturning` | Filtros de campo + `data`; retorna os registros atualizados. |
|
|
443
|
+
| `updateManyReturningWhere` | `updateManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois `data`; retorna os registros atualizados. |
|
|
444
|
+
| `upsertBy` | `upsert` | Filtros de campo + payloads `create`/`update`. |
|
|
445
|
+
| `upsertWhere` | `upsert` | Recebe um `VSRepoWhere<T>` como primeiro argumento, depois os payloads `create`/`update`. |
|
|
446
|
+
| `deleteBy` | `delete` | Filtros de campo seguem o prefixo. |
|
|
447
|
+
| `deleteWhere` | `delete` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
448
|
+
| `deleteManyBy` | `deleteMany` | Filtros de campo seguem o prefixo. |
|
|
449
|
+
| `deleteManyWhere` | `deleteMany` | Recebe um `VSRepoWhere<T>` como primeiro argumento. |
|
|
450
|
+
| `deleteManyReturningBy` | `deleteManyReturning` | Filtros de campo seguem o prefixo; retorna os registros removidos. |
|
|
451
|
+
| `deleteManyReturningWhere` | `deleteManyReturning` | Recebe um `VSRepoWhere<T>` como primeiro argumento; retorna os registros removidos. |
|
|
452
|
+
|
|
453
|
+
> `aggregate` e `groupBy` **ainda não estão implementados** na v2 (existiam na v1). Está planejado, mas não disponível no momento.
|
|
792
454
|
|
|
793
455
|
### Filtros de campo
|
|
794
456
|
|
|
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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")]);
|
|
457
|
+
Aplicados como sufixos ao nome do campo dentro do método (mesma ideia da v1, com um sufixo renomeado):
|
|
458
|
+
|
|
459
|
+
| Sufixo | Significado | Argumento |
|
|
460
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
461
|
+
| _(sem sufixo)_ | igualdade (`=`) | sim |
|
|
462
|
+
| `Not` | negação | sim |
|
|
463
|
+
| `In` | está em | sim (array) |
|
|
464
|
+
| `NotIn` | não está em | sim (array) |
|
|
465
|
+
| `Contains` | contém substring | sim |
|
|
466
|
+
| `NotContains` | não contém substring | sim |
|
|
467
|
+
| `StartsWith` | começa com | sim |
|
|
468
|
+
| `NotStartsWith` | não começa com | sim |
|
|
469
|
+
| `EndsWith` | termina com | sim |
|
|
470
|
+
| `NotEndsWith` | não termina com | sim |
|
|
471
|
+
| `GreaterThan` | `>` | sim |
|
|
472
|
+
| `GreaterThanEqual` | `>=` | sim |
|
|
473
|
+
| `LessThan` | `<` | sim |
|
|
474
|
+
| `LessThanEqual` | `<=` | sim |
|
|
475
|
+
| `Between` | intervalo inclusivo | sim (tupla `[min, max]`) |
|
|
476
|
+
| `NotBetween` | fora de um intervalo inclusivo | sim (tupla `[min, max]`) |
|
|
477
|
+
| `IsNull` | campo é `null` | não |
|
|
478
|
+
| `IsNotNull` | campo não é `null` | não |
|
|
479
|
+
| `IsTrue` | campo é `true` | não |
|
|
480
|
+
| `IsFalse` | campo é `false` | não |
|
|
481
|
+
| `IgnoreCase` | combinador case-insensitive para filtros de texto | não _(renomeado do `Insensitive` da v1)_ |
|
|
482
|
+
| `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 | — |
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
@DynamicMethod()
|
|
486
|
+
declare findByNameContainsIgnoreCase: (name: string) => Promise<User[]>;
|
|
487
|
+
|
|
488
|
+
@DynamicMethod()
|
|
489
|
+
declare findByAgeBetween: (age: [number, number]) => Promise<User[]>;
|
|
841
490
|
```
|
|
842
491
|
|
|
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
492
|
### Operadores lógicos
|
|
852
493
|
|
|
853
|
-
| Operador
|
|
854
|
-
|
|
|
855
|
-
| `And`
|
|
856
|
-
| `Or`
|
|
857
|
-
| `AND`
|
|
494
|
+
| Operador | Uso no nome | Exemplo |
|
|
495
|
+
| -------- | ------------------------------ | --------------------------------------------------- |
|
|
496
|
+
| `And` | entre dois campos | `findOneByIdAndEmail` |
|
|
497
|
+
| `Or` | entre dois campos | `findByNameOrEmail` |
|
|
498
|
+
| `AND` | separa um bloco final em `AND` | `findByEmailOrNameANDActiveStatusAndAgeGreaterThan` |
|
|
858
499
|
|
|
859
|
-
`AND` (em
|
|
500
|
+
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`.
|
|
860
501
|
|
|
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`.
|
|
502
|
+
### Filtros de relação
|
|
864
503
|
|
|
865
|
-
|
|
504
|
+
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).
|
|
866
505
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
506
|
+
| Sufixo | Significado | Restrição |
|
|
507
|
+
| -------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
508
|
+
| `Some` | pelo menos um registro relacionado corresponde | apenas relações to-many |
|
|
509
|
+
| `SomeField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
510
|
+
| `Every` | todo registro relacionado corresponde | apenas relações to-many (precisa de `Field` para ser um filtro efetivo) |
|
|
511
|
+
| `EveryField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
512
|
+
| `None` | nenhum registro relacionado corresponde | apenas relações to-many |
|
|
513
|
+
| `NoneField` | filtra dentro dos registros relacionados | apenas relações to-many |
|
|
514
|
+
| `With` | o registro relacionado existe | apenas relações to-one |
|
|
515
|
+
| `WithField` | filtra um campo dentro do registro relacionado | apenas relações to-one |
|
|
516
|
+
| `Without` | o registro relacionado não existe | apenas relações to-one |
|
|
517
|
+
| `WithoutField` | filtro negado em um campo do registro relacionado | apenas relações to-one |
|
|
874
518
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
await userRepository.findByEmailOrNameANDActiveStatusAndAgeGreaterThan("john@email.com", "John", true, 17)
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
Gera (`findOneByIdAndEmail`):
|
|
519
|
+
```typescript
|
|
520
|
+
@DynamicMethod()
|
|
521
|
+
declare findByAddressWithCityStartsWithIgnoreCase: (city: string) => Promise<User[]>;
|
|
882
522
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
id: 1,
|
|
886
|
-
email: "john@email.com"
|
|
887
|
-
}
|
|
523
|
+
@DynamicMethod()
|
|
524
|
+
declare findByProductsSome: () => Promise<User[]>;
|
|
888
525
|
```
|
|
889
526
|
|
|
890
|
-
|
|
527
|
+
### Ordenação, paginação e distinct
|
|
891
528
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
529
|
+
| Sufixo | Efeito |
|
|
530
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
531
|
+
| `Paginated` | Injeta um argumento `pagination` (`{ limit?, offset? }`) como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
|
|
532
|
+
| `Ordered` | Injeta um argumento `order: Ordering<T>` como **penúltimo** parâmetro (antes do `MethodOptions` opcional). |
|
|
533
|
+
| `OrderedAndPaginated` | Injeta `order` como antepenúltimo, depois `pagination` como penúltimo — ambos antes do `MethodOptions`. |
|
|
534
|
+
| `PaginatedAndOrdered` | Injeta `pagination` como antepenúltimo, depois `order` como penúltimo — ambos antes do `MethodOptions`. |
|
|
535
|
+
| `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`. |
|
|
536
|
+
| `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`). |
|
|
537
|
+
| `IgnoreConflicts` | No `createMany`/`createManyReturning`, ignora registros que violariam uma constraint única, em vez de lançar erro. _(Renomeado do `SkipDuplicates` da v1.)_ |
|
|
900
538
|
|
|
901
|
-
|
|
539
|
+
> ⚠️ **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
540
|
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
email: "john@email.com",
|
|
909
|
-
name: "John"
|
|
910
|
-
}
|
|
911
|
-
]
|
|
912
|
-
}
|
|
913
|
-
```
|
|
541
|
+
```typescript
|
|
542
|
+
// Paginated: pagination é o penúltimo parâmetro (antes do MethodOptions)
|
|
543
|
+
@DynamicMethod()
|
|
544
|
+
declare findByActiveOrderByCreatedAtDescPaginated:
|
|
545
|
+
(active: boolean, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
|
|
914
546
|
|
|
915
|
-
|
|
547
|
+
// OrderedAndPaginated: order, depois pagination, depois MethodOptions
|
|
548
|
+
@DynamicMethod()
|
|
549
|
+
declare findByNameContainsIgnoreCaseOrderedAndPaginated:
|
|
550
|
+
(name: string, order: Ordering<User>, pagination: Pagination, options?: MethodOptions<User>) => Promise<User[]>;
|
|
916
551
|
|
|
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
|
-
```
|
|
552
|
+
@DynamicMethod()
|
|
553
|
+
declare createManyIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<{ count: number }>;
|
|
929
554
|
|
|
930
|
-
|
|
555
|
+
// createManyReturning: mesmo comportamento do createMany, mas retorna os registros criados
|
|
556
|
+
@DynamicMethod()
|
|
557
|
+
declare createManyReturningIgnoreConflicts: (data: DeepPartial<User>[]) => Promise<User[]>;
|
|
931
558
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
559
|
+
// findOne sem filtro (equivalente ao findOneOrThrow sem filtro, mas retorna null em vez de lançar)
|
|
560
|
+
@DynamicMethod()
|
|
561
|
+
declare findOne: (options?: MethodOptions<User>) => Promise<User | null>;
|
|
562
|
+
```
|
|
935
563
|
|
|
936
|
-
>
|
|
564
|
+
> ⚠️ **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
565
|
>
|
|
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
|
-
}
|
|
566
|
+
> ```typescript
|
|
567
|
+
> @DynamicMethod()
|
|
568
|
+
> declare findByActiveDistinctNameOrderByCreatedAtDesc:
|
|
569
|
+
> (active: boolean) => Promise<User[]>;
|
|
570
|
+
> ```
|
|
571
|
+
>
|
|
572
|
+
> Colocar `OrderBy` antes de `Distinct` (ex.: `findByActiveOrderByCreatedAtDescDistinctName`) não é um padrão válido e não será interpretado como esperado.
|
|
972
573
|
|
|
973
|
-
|
|
974
|
-
await userRepository.findByPostsSomeTitle("My first post");
|
|
975
|
-
await userRepository.findByPostsEveryPublishedIsTrue();
|
|
976
|
-
await userRepository.findByPostsNone();
|
|
977
|
-
await userRepository.findByPostsNoneTitle("Draft");
|
|
574
|
+
### Options do decorador
|
|
978
575
|
|
|
979
|
-
|
|
980
|
-
await userRepository.findByProfileWithBio("Hello, world!");
|
|
981
|
-
await userRepository.findByProfileWithout();
|
|
982
|
-
await userRepository.findByProfileWithoutBio("Old bio");
|
|
983
|
-
```
|
|
576
|
+
`@DynamicMethod<T>(options?)` aceita:
|
|
984
577
|
|
|
985
|
-
|
|
578
|
+
| Option | Tipo | Descrição |
|
|
579
|
+
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
580
|
+
| `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. |
|
|
581
|
+
| `injectOrdering` | `Ordering<T>` | Ordenação fixa injetada automaticamente, sobrescrevendo o `defaultOrdering` do repository. |
|
|
986
582
|
|
|
987
|
-
```
|
|
988
|
-
{
|
|
989
|
-
|
|
990
|
-
some: { title: "My first post" }
|
|
991
|
-
}
|
|
992
|
-
}
|
|
583
|
+
```typescript
|
|
584
|
+
@DynamicMethod<User>({ injectOrdering: { createdAt: "desc" } })
|
|
585
|
+
declare findByStatus: (status: string) => Promise<User[]>;
|
|
993
586
|
```
|
|
994
587
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
```ts
|
|
998
|
-
{
|
|
999
|
-
posts: {
|
|
1000
|
-
every: { published: true }
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
```
|
|
588
|
+
---
|
|
1004
589
|
|
|
1005
|
-
|
|
590
|
+
## Query methods (SQL raw)
|
|
1006
591
|
|
|
1007
|
-
|
|
1008
|
-
{
|
|
1009
|
-
profile: {
|
|
1010
|
-
is: { bio: "Hello, world!" }
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
```
|
|
592
|
+
`@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
593
|
|
|
1015
|
-
|
|
594
|
+
```typescript
|
|
595
|
+
class UserRepository extends VSRepository<User, string> {
|
|
596
|
+
@QueryMethod('SELECT * FROM "user" WHERE email = $1')
|
|
597
|
+
declare findByEmailRaw: (arg: QueryMethodArg<[email: string]>) => Promise<User[]>;
|
|
1016
598
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
profile: {
|
|
1020
|
-
isNot: {}
|
|
1021
|
-
}
|
|
599
|
+
@QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
600
|
+
declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
1022
601
|
}
|
|
1023
602
|
```
|
|
1024
603
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
### Sufixos de paginação e ordenação
|
|
604
|
+
| Option | Tipo | Padrão | Descrição |
|
|
605
|
+
| ----------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
606
|
+
| `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. |
|
|
1030
607
|
|
|
1031
|
-
|
|
608
|
+
Query methods aceitam `{ args, db? }` na chamada — `db` permite que participem de um bloco `transaction()`, assim como os métodos base e dinâmicos.
|
|
1032
609
|
|
|
1033
|
-
|
|
1034
|
-
| ------------------------ | ---------------------------- |
|
|
1035
|
-
| `Paginated` | `(pagination)` |
|
|
1036
|
-
| `Ordered` | `(order)` |
|
|
1037
|
-
| `OrderedAndPaginated` | `(order, pagination)` |
|
|
1038
|
-
| `PaginatedAndOrdered` | `(pagination, order)` |
|
|
610
|
+
### Queries raw pontuais com `query()`
|
|
1039
611
|
|
|
1040
|
-
Para `
|
|
612
|
+
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:
|
|
1041
613
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
| `SkipDuplicates` | Ignora registros duplicados durante a inserção |
|
|
1045
|
-
|
|
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
|
-
},
|
|
614
|
+
```typescript
|
|
615
|
+
query<T = any>(query: string, options?: { args?: any[]; db?: any; modifying?: boolean }): Promise<T>;
|
|
1065
616
|
```
|
|
1066
617
|
|
|
1067
|
-
```
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
// O argumento de paginação continua funcionando normalmente
|
|
1072
|
-
await userRepository.findManyDistinctNamePaginated({ take: 10, skip: 0 });
|
|
618
|
+
```typescript
|
|
619
|
+
const users = await userRepository.query<User[]>('SELECT * FROM "user" WHERE email = $1', {
|
|
620
|
+
args: ["maria@email.com"],
|
|
621
|
+
});
|
|
1073
622
|
|
|
1074
|
-
|
|
1075
|
-
|
|
623
|
+
const linhasAfetadas = await userRepository.query<number>(
|
|
624
|
+
'UPDATE "user" SET active = true WHERE id = $1',
|
|
625
|
+
{ args: ["123"], modifying: true },
|
|
626
|
+
);
|
|
1076
627
|
```
|
|
1077
628
|
|
|
1078
|
-
|
|
629
|
+
| Option | Tipo | Padrão | Descrição |
|
|
630
|
+
| ----------- | --------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
631
|
+
| `args` | `any[]` | `undefined` | Parâmetros posicionais injetados nos placeholders `$1`, `$2`, ... Nunca interpole valores diretamente na string SQL. |
|
|
632
|
+
| `db` | `any` | Client padrão do repository | Client ou transação do banco em que essa query deve rodar. |
|
|
633
|
+
| `modifying` | `boolean` | `false` | Quando `true`, trata a instrução como `INSERT`/`UPDATE`/`DELETE`. |
|
|
1079
634
|
|
|
1080
|
-
|
|
635
|
+
Assim como os métodos base, dinâmicos e query, `query()` aceita `db` em `options` para participar de um bloco `transaction()`.
|
|
1081
636
|
|
|
1082
637
|
---
|
|
1083
638
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
Cada entrada em `methods` aceita as seguintes opções:
|
|
639
|
+
## Transações
|
|
1087
640
|
|
|
1088
|
-
|
|
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). |
|
|
641
|
+
Todos os métodos (base, dinâmicos e de query) aceitam `options.db` para participar de uma transação compartilhada:
|
|
1099
642
|
|
|
1100
|
-
|
|
643
|
+
```typescript
|
|
644
|
+
await userRepository.transaction(async tx => {
|
|
645
|
+
const usuario = await userRepository.save(
|
|
646
|
+
{ name: "Maria", email: "maria@email.com" },
|
|
647
|
+
{ db: tx },
|
|
648
|
+
);
|
|
1101
649
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
pkName: "id",
|
|
1108
|
-
methods: {
|
|
1109
|
-
aggregate: { map: true },
|
|
1110
|
-
groupBy: { map: true },
|
|
1111
|
-
},
|
|
1112
|
-
}).build(prisma);
|
|
650
|
+
await userLogsRepository.save(
|
|
651
|
+
{ action: "Usuário criado", data: { userId: usuario.id } },
|
|
652
|
+
{ db: tx },
|
|
653
|
+
);
|
|
654
|
+
});
|
|
1113
655
|
```
|
|
1114
656
|
|
|
1115
|
-
|
|
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.
|
|
657
|
+
Repositories diferentes podem compartilhar a mesma transação, desde que seus adapters apontem para a mesma conexão do ORM por trás deles.
|
|
1124
658
|
|
|
1125
|
-
|
|
659
|
+
`transaction()` aceita um `VSRepoTransactionOptions` opcional como segundo argumento:
|
|
1126
660
|
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
},
|
|
661
|
+
```typescript
|
|
662
|
+
import { TransactionIsolationLevel } from "vsrepo";
|
|
1142
663
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
query: {
|
|
1147
|
-
value: 'UPDATE "user" SET active = false WHERE "createdAt" < $1',
|
|
1148
|
-
modifying: true,
|
|
1149
|
-
},
|
|
664
|
+
await userRepository.transaction(
|
|
665
|
+
async tx => {
|
|
666
|
+
await userRepository.save({ name: "Maria", email: "maria@email.com" }, { db: tx });
|
|
1150
667
|
},
|
|
1151
|
-
|
|
1152
|
-
|
|
668
|
+
{ isolationLevel: TransactionIsolationLevel.SERIALIZABLE, timeoutMs: 5000 },
|
|
669
|
+
);
|
|
1153
670
|
```
|
|
1154
671
|
|
|
1155
|
-
|
|
672
|
+
| Option | Type | Descrição |
|
|
673
|
+
| ----------------- | -------------------------- | -------------------------------------------------------------------------------- |
|
|
674
|
+
| `isolationLevel` | `TransactionIsolationLevel` | Nível de isolamento usado na transação. O padrão é o default do ORM por trás dela. |
|
|
675
|
+
| `timeoutMs` | `number` | Tempo máximo (em ms) que a transação pode rodar antes de ser abortada. |
|
|
1156
676
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
```ts
|
|
1160
|
-
// Não-modifying: retorna 'any' por padrão, mas aceita um generic para
|
|
1161
|
-
// inferir/afirmar o tipo de retorno na própria chamada
|
|
1162
|
-
const usuariosAtivos = await userRepository.findActiveUsersRaw<User[]>({
|
|
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
|
-
});
|
|
1177
|
-
});
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
| Opção | Tipo | Padrão | Descrição |
|
|
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).
|
|
677
|
+
`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.
|
|
1189
678
|
|
|
1190
679
|
---
|
|
1191
680
|
|
|
1192
|
-
##
|
|
1193
|
-
|
|
1194
|
-
Configure as relações para que `save` e `patch` as gerenciem automaticamente (`saveList` e `patchList` também gerenciam relações automaticamente).
|
|
1195
|
-
|
|
1196
|
-
```ts
|
|
1197
|
-
import type { Prisma } from "../../generated/prisma/client";
|
|
681
|
+
## Tipos utilitários
|
|
1198
682
|
|
|
1199
|
-
|
|
1200
|
-
include: { profile: true; posts: true };
|
|
1201
|
-
}>;
|
|
683
|
+
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:
|
|
1202
684
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
685
|
+
```typescript
|
|
686
|
+
import type {
|
|
687
|
+
MethodOptions,
|
|
688
|
+
RestrictMethodOptions,
|
|
689
|
+
Pagination,
|
|
690
|
+
Ordering,
|
|
691
|
+
OrderByField,
|
|
692
|
+
SortDirection,
|
|
693
|
+
SeeMode,
|
|
694
|
+
DeepPartial,
|
|
695
|
+
CountResult,
|
|
696
|
+
QueryMethodArg,
|
|
697
|
+
KeysOfType,
|
|
698
|
+
NumericKeys,
|
|
699
|
+
NumericLike,
|
|
700
|
+
DecimalLike,
|
|
701
|
+
Primitive,
|
|
702
|
+
VSRepoWhere,
|
|
703
|
+
VSRepoOrmTypes,
|
|
704
|
+
VSRepoTransactionOptions,
|
|
705
|
+
TransactionIsolationLevel,
|
|
706
|
+
} from "vsrepo";
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
| Tipo | Descrição | Usado por |
|
|
710
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
711
|
+
| `MethodOptions<T, K>` | Options aceitas como último argumento pela maioria dos métodos base e dinâmicos: `select`, `relations`, `see`, `db`. | [Métodos base](#métodos-base), [Métodos Dinâmicos](#métodos-dinâmicos). |
|
|
712
|
+
| `RestrictMethodOptions<T, K>` | `MethodOptions<T, K>` restrito, expondo só `see`/`db` — usado pelos métodos que não retornam/moldam uma `Entity` (`total`, `has`, `sum`, `average`, `min`, `max`, `removeList`, `softRemoveList`, `restoreList`). | [Métodos base](#métodos-base), [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
713
|
+
| `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). |
|
|
714
|
+
| `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). |
|
|
715
|
+
| `SeeMode` | `"active" \| "removed" \| "all"` — controla a visibilidade de registros com soft-delete. | [Soft-delete](#soft-delete). |
|
|
716
|
+
| `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`. |
|
|
717
|
+
| `CountResult` | `{ count: number }` — o formato retornado por operações em lote. | `removeList`, `softRemoveList`, `restoreList`, `createManyIgnoreConflicts`. |
|
|
718
|
+
| `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). |
|
|
719
|
+
| `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. |
|
|
720
|
+
| `NumericKeys<T>` | Extrai as chaves de `T` cujo tipo de valor (ignorando `null`/`undefined`) é atribuível a `NumericLike`. Campos numéricos nullable (`number \| null`) são incluídos. | Restringe `field` em [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação) (`increment`, `sum`, etc). |
|
|
721
|
+
| `NumericLike` | `number \| bigint \| DecimalLike`. | [Métodos atômicos e de agregação](#métodos-atômicos-e-de-agregação). |
|
|
722
|
+
| `DecimalLike` | Formato estrutural de um valor decimal de precisão arbitrária (`{ toNumber(): number; decimalPlaces(): number }`), compatível com o `Prisma.Decimal` do Prisma sem precisar importá-lo diretamente. | [Quais campos são elegíveis](#quais-campos-são-elegíveis). |
|
|
723
|
+
| `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. |
|
|
724
|
+
| `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). |
|
|
725
|
+
| `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). |
|
|
726
|
+
| `VSRepoTransactionOptions` | `{ isolationLevel?, timeoutMs? }` — options aceitas como segundo argumento de `transaction()`. | [Transações](#transações). |
|
|
727
|
+
| `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). |
|
|
728
|
+
|
|
729
|
+
### `DeepPartial<T>`
|
|
730
|
+
|
|
731
|
+
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:
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
type User = { id: string; name: string; address: { city: string; zip: string } };
|
|
735
|
+
|
|
736
|
+
const patch: DeepPartial<User> = {
|
|
737
|
+
address: { city: "São Paulo" }, // zip pode ser omitido; city mantém seu tipo
|
|
738
|
+
};
|
|
1206
739
|
|
|
1207
|
-
|
|
1208
|
-
profile: {
|
|
1209
|
-
pk: "id",
|
|
1210
|
-
mode: "oto",
|
|
1211
|
-
restriction: "set",
|
|
1212
|
-
},
|
|
1213
|
-
posts: {
|
|
1214
|
-
pk: "id",
|
|
1215
|
-
mode: "otm",
|
|
1216
|
-
restriction: "add",
|
|
1217
|
-
},
|
|
1218
|
-
},
|
|
1219
|
-
}).build(prisma);
|
|
740
|
+
await userRepository.patch(id, patch);
|
|
1220
741
|
```
|
|
1221
742
|
|
|
1222
|
-
|
|
743
|
+
### `KeysOfType<T, K>`
|
|
1223
744
|
|
|
1224
|
-
|
|
1225
|
-
| ----- | ------------------ |
|
|
1226
|
-
| `oto` | um-para-um |
|
|
1227
|
-
| `otm` | um-para-muitos |
|
|
1228
|
-
| `mto` | muitos-para-um |
|
|
1229
|
-
| `mtm` | muitos-para-muitos |
|
|
745
|
+
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:
|
|
1230
746
|
|
|
1231
|
-
|
|
747
|
+
```typescript
|
|
748
|
+
type User = { id: string; age: number; name: string };
|
|
749
|
+
type StringKeys = KeysOfType<User, string>; // "id" | "name"
|
|
750
|
+
```
|
|
1232
751
|
|
|
1233
|
-
|
|
1234
|
-
| ----------- | ----------------------------------------------------------- |
|
|
1235
|
-
| `set` | Substitui totalmente (remove os que não foram enviados) |
|
|
1236
|
-
| `add` | Adiciona/atualiza sem remover os existentes |
|
|
752
|
+
### `Ordering<T>`
|
|
1237
753
|
|
|
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.
|
|
1251
|
-
|
|
1252
|
-
**Relação `mto` com nullable:**
|
|
754
|
+
Aceita um único objeto de ordenação ou um array deles, aplicados na ordem declarada:
|
|
1253
755
|
|
|
1254
|
-
|
|
756
|
+
```typescript
|
|
757
|
+
const order: Ordering<User> = { createdAt: "desc" };
|
|
758
|
+
const chained: Ordering<User> = [{ name: "asc" }, { createdAt: "desc" }];
|
|
1255
759
|
|
|
1256
|
-
|
|
1257
|
-
relations: {
|
|
1258
|
-
category: {
|
|
1259
|
-
pk: "id",
|
|
1260
|
-
mode: "mto",
|
|
1261
|
-
restriction: "set",
|
|
1262
|
-
nullable: true, // permite passar null para desvincular
|
|
1263
|
-
},
|
|
1264
|
-
}
|
|
760
|
+
await userRepository.getAll({ order: chained });
|
|
1265
761
|
```
|
|
1266
762
|
|
|
1267
763
|
---
|
|
1268
764
|
|
|
1269
|
-
##
|
|
765
|
+
## Escrevendo seu próprio adapter
|
|
766
|
+
|
|
767
|
+
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>`:
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
export abstract class VSRepoAdapter<T> {
|
|
771
|
+
abstract runInTransaction<R>(
|
|
772
|
+
fn: (tx: any) => Promise<R>,
|
|
773
|
+
options?: VSRepoTransactionOptions,
|
|
774
|
+
): Promise<R>;
|
|
775
|
+
abstract getDbClient(): any;
|
|
776
|
+
abstract query<T = any>(query: string, options?: AdapterQueryOptions): Promise<T>;
|
|
777
|
+
abstract findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T | null>;
|
|
778
|
+
abstract findOneOrThrow(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
779
|
+
abstract findMany(
|
|
780
|
+
where: VSRepoWhere<T>,
|
|
781
|
+
options?: AdapterMethodOptions<T> & { distinct?: (keyof T)[] },
|
|
782
|
+
): Promise<T[]>;
|
|
783
|
+
abstract save(obj: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
784
|
+
abstract saveMany(objs: DeepPartial<T>[], options?: AdapterMethodOptions<T>): Promise<T[]>;
|
|
785
|
+
abstract create(objs: DeepPartial<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
786
|
+
abstract createMany(
|
|
787
|
+
objs: DeepPartial<T>[],
|
|
788
|
+
options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
|
|
789
|
+
): Promise<CountResult>;
|
|
790
|
+
abstract createManyReturning(
|
|
791
|
+
objs: DeepPartial<T>[],
|
|
792
|
+
options?: AdapterMethodOptions<T> & { ignoreConflicts?: boolean },
|
|
793
|
+
): Promise<T[]>;
|
|
794
|
+
abstract delete(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<T>;
|
|
795
|
+
abstract deleteMany(
|
|
796
|
+
where: VSRepoWhere<T>,
|
|
797
|
+
options?: AdapterMethodOptions<T>,
|
|
798
|
+
): Promise<CountResult>;
|
|
799
|
+
abstract deleteManyReturning(
|
|
800
|
+
where: VSRepoWhere<T>,
|
|
801
|
+
options?: AdapterMethodOptions<T>,
|
|
802
|
+
): Promise<T[]>;
|
|
803
|
+
abstract update(
|
|
804
|
+
where: VSRepoWhere<T>,
|
|
805
|
+
obj: DeepPartial<T>,
|
|
806
|
+
options?: AdapterMethodOptions<T>,
|
|
807
|
+
): Promise<T>;
|
|
808
|
+
abstract updateMany(
|
|
809
|
+
where: VSRepoWhere<T>,
|
|
810
|
+
obj: DeepPartial<T>,
|
|
811
|
+
options?: AdapterMethodOptions<T>,
|
|
812
|
+
): Promise<CountResult>;
|
|
813
|
+
abstract updateManyReturning(
|
|
814
|
+
where: VSRepoWhere<T>,
|
|
815
|
+
obj: DeepPartial<T>,
|
|
816
|
+
options?: AdapterMethodOptions<T>,
|
|
817
|
+
): Promise<T[]>;
|
|
818
|
+
abstract count(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<number>;
|
|
819
|
+
abstract exists(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>): Promise<boolean>;
|
|
820
|
+
abstract merge<K>(
|
|
821
|
+
where: VSRepoWhere<T>,
|
|
822
|
+
obj: DeepPartial<T>,
|
|
823
|
+
options?: AdapterMethodOptions<T>,
|
|
824
|
+
): Promise<K & T>;
|
|
825
|
+
abstract upsert(
|
|
826
|
+
where: VSRepoWhere<T>,
|
|
827
|
+
create: DeepPartial<T>,
|
|
828
|
+
update: DeepPartial<T>,
|
|
829
|
+
options?: AdapterMethodOptions<T>,
|
|
830
|
+
): Promise<T>;
|
|
831
|
+
|
|
832
|
+
abstract incrementOne<K extends NumericKeys<T>>(
|
|
833
|
+
field: K,
|
|
834
|
+
value: NonNullable<T[K]>,
|
|
835
|
+
where: VSRepoWhere<T>,
|
|
836
|
+
options?: AdapterMethodOptions<T>,
|
|
837
|
+
): Promise<T>;
|
|
838
|
+
abstract decrementOne<K extends NumericKeys<T>>(
|
|
839
|
+
field: K,
|
|
840
|
+
value: NonNullable<T[K]>,
|
|
841
|
+
where: VSRepoWhere<T>,
|
|
842
|
+
options?: AdapterMethodOptions<T>,
|
|
843
|
+
): Promise<T>;
|
|
844
|
+
abstract multiplyOne<K extends NumericKeys<T>>(
|
|
845
|
+
field: K,
|
|
846
|
+
value: NonNullable<T[K]>,
|
|
847
|
+
where: VSRepoWhere<T>,
|
|
848
|
+
options?: AdapterMethodOptions<T>,
|
|
849
|
+
): Promise<T>;
|
|
850
|
+
abstract divideOne<K extends NumericKeys<T>>(
|
|
851
|
+
field: K,
|
|
852
|
+
value: NonNullable<T[K]>,
|
|
853
|
+
where: VSRepoWhere<T>,
|
|
854
|
+
options?: AdapterMethodOptions<T>,
|
|
855
|
+
): Promise<T>;
|
|
856
|
+
abstract sum(
|
|
857
|
+
field: NumericKeys<T>,
|
|
858
|
+
where?: VSRepoWhere<T>,
|
|
859
|
+
options?: AdapterMethodOptions<T>,
|
|
860
|
+
): Promise<number | null>;
|
|
861
|
+
abstract average(
|
|
862
|
+
field: NumericKeys<T>,
|
|
863
|
+
where?: VSRepoWhere<T>,
|
|
864
|
+
options?: AdapterMethodOptions<T>,
|
|
865
|
+
): Promise<number | null>;
|
|
866
|
+
abstract min(
|
|
867
|
+
field: NumericKeys<T>,
|
|
868
|
+
where?: VSRepoWhere<T>,
|
|
869
|
+
options?: AdapterMethodOptions<T>,
|
|
870
|
+
): Promise<number | null>;
|
|
871
|
+
abstract max(
|
|
872
|
+
field: NumericKeys<T>,
|
|
873
|
+
where?: VSRepoWhere<T>,
|
|
874
|
+
options?: AdapterMethodOptions<T>,
|
|
875
|
+
): Promise<number | null>;
|
|
876
|
+
}
|
|
877
|
+
```
|
|
1270
878
|
|
|
1271
|
-
|
|
879
|
+
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
880
|
|
|
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
|
-
);
|
|
881
|
+
### Logging a partir do seu adapter
|
|
1279
882
|
|
|
1280
|
-
|
|
1281
|
-
{ action: "User registration", data: { registeredUser: user.id } },
|
|
1282
|
-
{ db: tx }
|
|
1283
|
-
);
|
|
1284
|
-
});
|
|
1285
|
-
```
|
|
883
|
+
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
884
|
|
|
1287
|
-
|
|
885
|
+
```typescript
|
|
886
|
+
import { VSLogger, VSLogLevel } from "vsrepo";
|
|
1288
887
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// CORRETO: tx é um DbTransaction
|
|
1292
|
-
const registeredUsers = await userRepository.saveList([{ name: "Mary" }, { name: "Lucas" }], { db: tx });
|
|
888
|
+
export class MyOrmAdapter<T> extends VSRepoAdapter<T> {
|
|
889
|
+
private readonly logger = new VSLogger(VSLogLevel.WARN, "MyOrmAdapterLogger");
|
|
1293
890
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
891
|
+
async findOne(where: VSRepoWhere<T>, options?: AdapterMethodOptions<T>) {
|
|
892
|
+
const start = this.logger.startPerformLog("adapter findOne");
|
|
893
|
+
try {
|
|
894
|
+
// ... fala com o ORM ...
|
|
895
|
+
this.logger.endPerformLog(start);
|
|
896
|
+
return result;
|
|
897
|
+
} catch (err) {
|
|
898
|
+
this.logger.endPerformLog(start);
|
|
899
|
+
this.logger.logError("adapter findOne falhou", err);
|
|
900
|
+
throw err;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
1299
904
|
```
|
|
1300
905
|
|
|
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
|
-
},
|
|
906
|
+
| Método | Descrição |
|
|
907
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
908
|
+
| `new VSLogger(logLevel, name, slowThresholdMs?)` | Cria um logger; `name` prefixa cada linha, `slowThresholdMs` tem default 300. |
|
|
909
|
+
| `logDebug/logInfo/logWarn(text, obj?)` | Loga no nível dado se `logLevel` permitir; `obj` é anexado como JSON formatado. |
|
|
910
|
+
| `logError(text, err?)` | Loga em `ERROR`; se `err` for uma `Error`, só `name`/`message`/`stack`/`cause` são logados. |
|
|
911
|
+
| `startPerformLog(operation)` / `endPerformLog(data)` | Envolve um trecho de código para logar sua duração, escalando pra `WARN` se ultrapassar `slowThresholdMs`. |
|
|
912
|
+
| `getLogLevel()` | Retorna o `VSLogLevel` configurado do logger. |
|
|
1318
913
|
|
|
1319
|
-
|
|
1320
|
-
return repo.patchList(ids.map(id => [id, { active: true }]));
|
|
1321
|
-
},
|
|
1322
|
-
}));
|
|
1323
|
-
```
|
|
914
|
+
Isso é puramente uma conveniência para autores de adapters — nada no core exige que seu adapter o utilize.
|
|
1324
915
|
|
|
1325
916
|
---
|
|
1326
917
|
|
|
1327
918
|
## Tratamento de erros
|
|
1328
919
|
|
|
1329
|
-
|
|
920
|
+
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
921
|
|
|
1331
|
-
```
|
|
1332
|
-
import { VSRepoError
|
|
922
|
+
```typescript
|
|
923
|
+
import { VSRepoError } from "vsrepo";
|
|
1333
924
|
|
|
1334
925
|
try {
|
|
1335
|
-
|
|
926
|
+
await userRepository.get(id);
|
|
1336
927
|
} catch (error) {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
console.error("Repository error:", error.message);
|
|
1341
|
-
} else {
|
|
1342
|
-
console.error("Error:", error.message)
|
|
1343
|
-
}
|
|
928
|
+
if (error instanceof VSRepoError) {
|
|
929
|
+
console.error(`[${error.type}] ${error.message}`);
|
|
930
|
+
}
|
|
1344
931
|
}
|
|
1345
932
|
```
|
|
1346
933
|
|
|
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
|
|
1370
|
-
|
|
1371
|
-
### Tipos de client
|
|
1372
|
-
|
|
1373
|
-
```ts
|
|
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
|
-
```
|
|
1380
|
-
|
|
1381
|
-
### Tipo de visibilidade do soft-delete
|
|
1382
|
-
|
|
1383
|
-
```ts
|
|
1384
|
-
import type { SeeMode } from "../../generated/vsrepo";
|
|
1385
|
-
|
|
1386
|
-
type SeeMode = "active" | "removed" | "all";
|
|
1387
|
-
```
|
|
1388
|
-
|
|
1389
|
-
### Tipos derivados do modelo Prisma
|
|
1390
|
-
|
|
1391
|
-
```ts
|
|
1392
|
-
import type {
|
|
1393
|
-
SelectModel,
|
|
1394
|
-
SelectModels,
|
|
1395
|
-
IncludeModel,
|
|
1396
|
-
IncludeModels,
|
|
1397
|
-
WhereModel,
|
|
1398
|
-
OrderingModel,
|
|
1399
|
-
PaginationModel,
|
|
1400
|
-
ModelUpsertInput,
|
|
1401
|
-
PrismaModelInputs,
|
|
1402
|
-
} from "../../generated/vsrepo";
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
### Tipos de opções de método
|
|
1406
|
-
|
|
1407
|
-
```ts
|
|
1408
|
-
import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
|
|
1409
|
-
|
|
1410
|
-
// MethodOptions<S, IM> — opções passadas para os métodos do repositório
|
|
1411
|
-
type Opts = MethodOptions<"public" | "minimal", "withPosts">;
|
|
1412
|
-
|
|
1413
|
-
// MethodOptionsModel<TRepo> — derivado de uma instância configurada do VSRepository
|
|
1414
|
-
const userVSRepo = setupVSRepo<User, "user">()(config);
|
|
1415
|
-
type OptsModel = MethodOptionsModel<typeof userVSRepo>;
|
|
1416
|
-
```
|
|
1417
|
-
|
|
1418
|
-
> O segundo parâmetro de `MethodOptions` (`IM`) representa as chaves válidas de `includeModels`. Quando informado, `selectModel` e `includeModel` se tornam mutuamente exclusivos no tipo — não é possível passar os dois na mesma chamada.
|
|
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.
|
|
1421
|
-
|
|
1422
|
-
### Tipos de configuração
|
|
934
|
+
| `VSRepoErrorType` | Quando é lançado |
|
|
935
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
936
|
+
| `DECORATOR` | Argumentos inválidos foram passados para `@DynamicMethod` ou `@QueryMethod`. |
|
|
937
|
+
| `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). |
|
|
938
|
+
| `DYNAMIC` | Um método dinâmico já resolvido falhou em tempo de execução (ex.: argumentos faltando). |
|
|
939
|
+
| `VALIDATOR` | Options ou argumentos de método inválidos foram detectados durante a validação. |
|
|
940
|
+
| `BASE` | Uso inválido de um método base (`get`, `save`, `remove`, etc). |
|
|
941
|
+
| `ADAPTER` | Um `VSRepoAdapter` falhou ao falar com o ORM/banco subjacente — sempre é lançado como `VSRepoAdapterError`. |
|
|
1423
942
|
|
|
1424
|
-
|
|
1425
|
-
import type {
|
|
1426
|
-
MethodConfig,
|
|
1427
|
-
RepoConfig,
|
|
1428
|
-
BuildConfig,
|
|
1429
|
-
RepositoryRelations,
|
|
1430
|
-
ExtractRelationConfig,
|
|
1431
|
-
} from "../../generated/vsrepo";
|
|
1432
|
-
```
|
|
943
|
+
### `VSRepoAdapterError` e `AdapterErrorCode`
|
|
1433
944
|
|
|
1434
|
-
|
|
945
|
+
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:
|
|
1435
946
|
|
|
1436
|
-
```
|
|
1437
|
-
import
|
|
947
|
+
```typescript
|
|
948
|
+
import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
|
|
1438
949
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
`
|
|
950
|
+
try {
|
|
951
|
+
await userRepository.save({ name: "Maria" });
|
|
952
|
+
} catch (error) {
|
|
953
|
+
if (error instanceof VSRepoAdapterError) {
|
|
954
|
+
console.error(`[${error.code}] ${error.message}`, error.originalError);
|
|
1444
955
|
|
|
1445
|
-
|
|
1446
|
-
|
|
956
|
+
if (error.code === AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION) {
|
|
957
|
+
// tratar chave duplicada, ex.: retornar uma mensagem amigável
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
1447
961
|
```
|
|
1448
962
|
|
|
1449
|
-
|
|
963
|
+
| Propriedade | Tipo | Descrição |
|
|
964
|
+
| --------------- | ------------------ | --------------------------------------------------------------------------------- |
|
|
965
|
+
| `code` | `AdapterErrorCode` | Código estável e agnóstico que classifica a falha. |
|
|
966
|
+
| `originalError` | `unknown` | O erro bruto (ou `null`/`undefined`) lançado pelo driver do ORM/banco subjacente. |
|
|
967
|
+
| `message` | `string` | Descrição legível da falha do adapter. |
|
|
968
|
+
| `type` | `VSRepoErrorType` | Sempre `VSRepoErrorType.ADAPTER`. |
|
|
969
|
+
| `cause` | `unknown` | Causa raiz opcional da qual o erro foi encadeado. |
|
|
970
|
+
|
|
971
|
+
As implementações de adapter o constroem diretamente ao mapear uma falha do ORM:
|
|
972
|
+
|
|
973
|
+
```typescript
|
|
974
|
+
import { VSRepoAdapterError, AdapterErrorCode } from "vsrepo";
|
|
975
|
+
|
|
976
|
+
throw new VSRepoAdapterError(
|
|
977
|
+
"falha ao criar o usuário",
|
|
978
|
+
AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION,
|
|
979
|
+
originalError, // erro bruto do banco/driver
|
|
980
|
+
);
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
#### `AdapterErrorCode`
|
|
984
|
+
|
|
985
|
+
`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:
|
|
986
|
+
|
|
987
|
+
```typescript
|
|
988
|
+
import { AdapterErrorCode } from "vsrepo";
|
|
989
|
+
|
|
990
|
+
console.log(AdapterErrorCode.UNIQUE_CONSTRAINT_VIOLATION); // "UNIQUE_CONSTRAINT_VIOLATION"
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
| Código | Significado |
|
|
994
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
995
|
+
| `UNKNOWN` | Erro não classificado/desconhecido; o fallback quando nenhum código mais específico corresponde. |
|
|
996
|
+
| `MISSING_DB_CLIENT` | Cliente de banco (ou pool de conexões) não fornecido ou que não pôde ser resolvido. |
|
|
997
|
+
| `CONNECTION_FAILED` | Não foi possível alcançar/conectar ao banco, ou uma conexão estabelecida foi perdida/terminada. |
|
|
998
|
+
| `CONNECTION_POOL_EXHAUSTED` | Pool de conexões esgotado/depletado — nenhuma conexão disponível, todas ocupadas ou o limite foi atingido. |
|
|
999
|
+
| `TIMEOUT` | O banco não respondeu a tempo; uma query excedeu o timeout permitido. |
|
|
1000
|
+
| `UNIQUE_CONSTRAINT_VIOLATION` | Violação de constraint unique (chave duplicada). Ex.: Postgres/SQLite `23505`, MySQL `1062`. |
|
|
1001
|
+
| `FOREIGN_KEY_VIOLATION` | Violação de constraint de foreign key (linha referenciada não existe). |
|
|
1002
|
+
| `NOT_NULL_VIOLATION` | Violação de constraint NOT NULL. |
|
|
1003
|
+
| `CHECK_VIOLATION` | Violação de constraint CHECK. |
|
|
1004
|
+
| `CONSTRAINT_VIOLATION` | Violação geral de integridade/constraint não coberta por um código mais específico. |
|
|
1005
|
+
| `NOT_FOUND` | Registro solicitado não encontrado (ex.: uma operação tipo `findOneOrThrow`). |
|
|
1006
|
+
| `INVALID_DATA` | Valor de campo inválido para o tipo/tamanho, ou um valor obrigatório ausente. |
|
|
1007
|
+
| `VALUE_TOO_LONG` | Valor fornecido excede o limite de tamanho da coluna/campo. |
|
|
1008
|
+
| `CONVERSION_ERROR` | Um valor não pôde ser convertido/convertido para o tipo alvo. Ex.: Postgres `22P02`, MySQL `1366`. |
|
|
1009
|
+
| `INVALID_QUERY` | A query/stored procedure SQL está malformada ou é inválida. |
|
|
1010
|
+
| `TABLE_OR_COLUMN_NOT_FOUND` | A tabela/coluna/relação referenciada não existe. |
|
|
1011
|
+
| `DEADLOCK` | Operação abortada por timeout de lock ou deadlock entre transações concorrentes. |
|
|
1012
|
+
| `LOCK_TIMEOUT` | Não foi possível adquirir um lock de banco obrigatório a tempo. |
|
|
1013
|
+
| `LOCKED` | O registro está travado e não pode ser modificado. |
|
|
1014
|
+
| `ACCESS_DENIED` | O usuário/role atual não tem permissão para a operação. |
|
|
1015
|
+
| `INVALID_CREDENTIALS` | Credenciais de conexão inválidas (host/usuário/senha). |
|
|
1016
|
+
| `ROW_NOT_ALLOWED` | O usuário autenticado não é dono do registro / a segurança em nível de linha rejeitou. |
|
|
1017
|
+
| `MODEL_NOT_FOUND` | Entidade/modelo ou tabela não definida/mapeada no ORM, ou o adapter não tem os metadados do modelo. |
|
|
1018
|
+
| `FIELD_NOT_FOUND` | Nome de campo/coluna nos dados ou no `where` não existe na entidade/modelo. |
|
|
1019
|
+
| `TRANSACTION_CLOSED` | Transação usada depois de commit/rollback. |
|
|
1020
|
+
| `TRANSACTION_ALREADY_STARTED` | Uma transação aninhada não pôde ser aberta (ex.: chamadas `transaction()` aninhadas). |
|
|
1021
|
+
| `TRANSACTION_CONFLICT` | Uma transação falhou ao commitar e foi desfeita. |
|
|
1022
|
+
| `TRANSACTION_NOT_STARTED` | Nenhuma transação ativa quando uma era obrigatória. |
|
|
1023
|
+
| `CONNECTION_CLOSED` | Conexão fechada/terminada enquanto uma transação ou query estava em andamento. |
|
|
1024
|
+
| `INVALID_PARTIAL` | `merge`/`upsert`/`update` recebeu um objeto parcial inválido ou faltando chaves obrigatórias. |
|
|
1025
|
+
| `NOT_SUPPORTED` | Feature/operação não suportada solicitada ao adapter (ex.: `query()` bruto não suportado). |
|
|
1026
|
+
| `INVALID_ADAPTER_CONFIG` | Configuração do adapter inválida ou incompleta (options obrigatórias ausentes, ou com tipo/valor inválido). |
|
|
1027
|
+
| `INTERNAL` | Bug interno do adapter ou estado irrecuperável; deve raramente ser usado — prefira um código mais específico. |
|
|
1028
|
+
|
|
1029
|
+
#### `VSRepoError` vs. erros brutos do ORM
|
|
1030
|
+
|
|
1031
|
+
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
1032
|
|
|
1451
|
-
|
|
1452
|
-
import type { SaveObject, PatchObject } from "../../generated/vsrepo";
|
|
1033
|
+
---
|
|
1453
1034
|
|
|
1454
|
-
|
|
1455
|
-
tableName: "user",
|
|
1456
|
-
pkName: "id",
|
|
1457
|
-
relations: {
|
|
1458
|
-
profile: { pk: "id", mode: "oto", restriction: "set" },
|
|
1459
|
-
},
|
|
1460
|
-
});
|
|
1035
|
+
## Logging
|
|
1461
1036
|
|
|
1462
|
-
|
|
1463
|
-
type UserPatchPayload = PatchObject<Prisma.UserUpdateInput, typeof userVSRepo>;
|
|
1464
|
-
```
|
|
1037
|
+
Todo repository tem um logger interno, configurado via `logLevel` e `logSlowThresholdMs` nas options do construtor:
|
|
1465
1038
|
|
|
1466
|
-
|
|
1039
|
+
```typescript
|
|
1040
|
+
import { VSLogLevel } from "vsrepo";
|
|
1467
1041
|
|
|
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
|
|
1042
|
+
super({
|
|
1043
|
+
pkName: "id",
|
|
1044
|
+
adapter,
|
|
1045
|
+
logLevel: VSLogLevel.DEBUG,
|
|
1046
|
+
logSlowThresholdMs: 200,
|
|
1484
1047
|
});
|
|
1485
1048
|
```
|
|
1486
1049
|
|
|
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
|
-
```
|
|
1050
|
+
| Nível | Significado |
|
|
1051
|
+
| --------------- | ------------------------------------------------------------------------------------------------------- |
|
|
1052
|
+
| `DEBUG` | Detalhes internos verbosos, incluindo toda query resolvida — muito útil para debugar métodos dinâmicos. |
|
|
1053
|
+
| `INFO` | Eventos de alto nível do ciclo de vida, como a inicialização do repository. |
|
|
1054
|
+
| `WARN` (padrão) | Problemas recuperáveis e operações lentas (veja `logSlowThresholdMs`, padrão de 300ms). |
|
|
1055
|
+
| `ERROR` | Falhas lançadas durante a execução de uma operação. |
|
|
1517
1056
|
|
|
1518
|
-
|
|
1057
|
+
---
|
|
1519
1058
|
|
|
1520
|
-
|
|
1521
|
-
repo.extend((repo) => ({
|
|
1522
|
-
myMethod: () => { ... }
|
|
1523
|
-
}));
|
|
1524
|
-
```
|
|
1059
|
+
## Desenvolvimento
|
|
1525
1060
|
|
|
1526
|
-
|
|
1061
|
+
O core da v2 é compilado e empacotado a partir desta branch como um pacote npm padrão:
|
|
1527
1062
|
|
|
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
|
-
```
|
|
1063
|
+
```bash
|
|
1064
|
+
# 1. Instalar as dependências
|
|
1065
|
+
pnpm install
|
|
1545
1066
|
|
|
1546
|
-
|
|
1067
|
+
# 2. Compilar os fontes TypeScript em dist/ (remove um dist/ anterior primeiro)
|
|
1068
|
+
pnpm build
|
|
1547
1069
|
|
|
1548
|
-
|
|
1070
|
+
# 3. (Opcional) Inspecionar o que seria publicado sem gerar um tarball
|
|
1071
|
+
npm pack --dry-run
|
|
1549
1072
|
|
|
1550
|
-
|
|
1073
|
+
# 4. Gerar o tarball instalável (roda `prepack` -> `pnpm build` automaticamente)
|
|
1074
|
+
npm pack
|
|
1551
1075
|
|
|
1552
|
-
|
|
1076
|
+
# 5. Consumir localmente em outro projeto
|
|
1077
|
+
npm install ../caminho/vsrepo-1.4.0.tgz
|
|
1078
|
+
```
|
|
1553
1079
|
|
|
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**.
|
|
1080
|
+
Observações:
|
|
1558
1081
|
|
|
1559
|
-
|
|
1082
|
+
- `pnpm build` executa `tsc -p tsconfig.build.json`, que gera o JS compilado e as declarações de tipo em `dist/` com `rootDir: src`.
|
|
1083
|
+
- O pacote publicado contém **apenas** a pasta `dist/` além dos READMEs e da `LICENSE` (veja `files` no `package.json`). Os adapters viverão em seus próprios pacotes `@vsrepo/*-adapter`.
|
|
1084
|
+
- O core é ORM-agnóstico e não tem dependência peer de `@prisma/client`.
|
|
1560
1085
|
|
|
1561
1086
|
---
|
|
1562
1087
|
|
|
1563
1088
|
## Requisitos
|
|
1564
1089
|
|
|
1565
|
-
- Node.js 18+
|
|
1566
|
-
-
|
|
1567
|
-
- TypeScript (opcional, mas fortemente recomendado)
|
|
1568
|
-
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
1569
|
-
|
|
1570
|
-
`tsconfig.json` recomendado:
|
|
1090
|
+
- Node.js 18+
|
|
1091
|
+
- TypeScript, com **decorators legacy/experimentais** habilitados (necessário para `@DynamicMethod`/`@QueryMethod`):
|
|
1571
1092
|
|
|
1572
1093
|
```json
|
|
1573
1094
|
{
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
"moduleResolution": "NodeNext",
|
|
1578
|
-
"strict": true,
|
|
1579
|
-
"skipLibCheck": true,
|
|
1580
|
-
"lib": ["ES2020"]
|
|
1581
|
-
}
|
|
1095
|
+
"compilerOptions": {
|
|
1096
|
+
"experimentalDecorators": true
|
|
1097
|
+
}
|
|
1582
1098
|
}
|
|
1583
1099
|
```
|
|
1584
1100
|
|
|
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`.
|
|
1101
|
+
- `reflect-metadata` (já incluso como dependência, importado internamente — você não precisa importá-lo você mesmo)
|
|
1102
|
+
- 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
1103
|
|
|
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`.
|
|
1104
|
+
---
|
|
1600
1105
|
|
|
1601
|
-
|
|
1106
|
+
## Contribuindo
|
|
1602
1107
|
|
|
1603
|
-
|
|
1108
|
+
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
1109
|
|
|
1605
|
-
|
|
1110
|
+
1. Faça um **Fork** do projeto.
|
|
1111
|
+
2. Crie uma branch a partir de `v2` para sua alteração: `git checkout -b v2-minha-alteracao`.
|
|
1112
|
+
3. Faça o push da sua branch: `git push origin v2-minha-alteracao`.
|
|
1113
|
+
4. Abra um **Pull Request** contra a `v2`.
|
|
1606
1114
|
|
|
1607
|
-
|
|
1115
|
+
Para reportar problemas ou sugerir funcionalidades, abra uma **Issue**.
|