vsrepo 1.2.5 → 1.2.7
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 +310 -441
- package/{VSRepository → dist}/VSRepoError.d.ts +1 -1
- package/dist/VSRepoError.js +17 -0
- package/{VSRepository → dist}/VSRepository.d.ts +937 -887
- package/dist/VSRepository.js +204 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/internal/errors/types/vs-repo-error-type.type.js +2 -0
- package/dist/internal/errors/vs-repo.error.js +27 -0
- package/dist/internal/resolvers/base-methods.resolve.js +535 -0
- package/dist/internal/resolvers/create-update-payloads-with-relations.resolve.js +143 -0
- package/dist/internal/resolvers/data-payload-with-relations.resolve.js +60 -0
- package/dist/internal/resolvers/dbAndPrismaArgs.resolve.js +46 -0
- package/dist/internal/resolvers/dinamic-method-customization.resolve.js +45 -0
- package/dist/internal/resolvers/dinamic-method-info.resolve.js +265 -0
- package/dist/internal/resolvers/merge-wheres.resolve.js +22 -0
- package/dist/internal/resolvers/pretty-wheres.resolve.js +87 -0
- package/dist/internal/resolvers/select.resolve.js +7 -0
- package/dist/internal/resolvers/specific-where.resolve.js +77 -0
- package/dist/internal/resolvers/types/base-method-function.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-customization.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-info.type.js +2 -0
- package/dist/internal/resolvers/types/dinamic-method-where-ops.type.js +2 -0
- package/dist/internal/resolvers/types/pretty-where.type.js +2 -0
- package/dist/internal/resolvers/types/prisma-args.type.js +2 -0
- package/dist/internal/resolvers/types/repository-build-instance.type.js +2 -0
- package/dist/internal/resolvers/types/resolve-db-and-prisma-args-data.type.js +2 -0
- package/dist/internal/resolvers/types/ugly-where.type.js +2 -0
- package/dist/internal/resolvers/ugly-where.resolve.js +178 -0
- package/dist/internal/utils/logger.util.js +21 -0
- package/dist/internal/utils/schemas.util.js +10 -0
- package/dist/internal/utils/uncapitalize.util.js +6 -0
- package/dist/internal/validation/build-config.validate.js +84 -0
- package/dist/internal/validation/constructor-config.validate.js +68 -0
- package/dist/internal/validation/extension.validate.js +15 -0
- package/dist/internal/validation/is-object.validate.js +6 -0
- package/dist/internal/validation/method-options.validate.js +26 -0
- package/dist/internal/validation/obj-with-relations.validate.js +37 -0
- package/dist/internal/validation/prisma-client.validate.js +10 -0
- package/dist/internal/validation/types/base-methods.type.js +2 -0
- package/dist/internal/validation/types/build-config.type.js +2 -0
- package/dist/internal/validation/types/constructor-config.type.js +2 -0
- package/dist/internal/validation/types/method-options.type.js +2 -0
- package/dist/internal/validation/types/method.type.js +2 -0
- package/dist/internal/validation/types/pagination.type.js +2 -0
- package/dist/internal/validation/types/relation.type.js +2 -0
- package/dist/internal/validation/types/see-mode.type.js +2 -0
- package/package.json +72 -70
- package/scripts/configure-prisma-import.mjs +9 -8
- package/scripts/copy-types.mjs +24 -0
- package/VSRepository/VSRepoError.js +0 -33
- package/VSRepository/VSRepoUtils.js +0 -150
- package/VSRepository/VSRepository.js +0 -1394
- package/VSRepository/index.d.ts +0 -2
- package/VSRepository/index.js +0 -2
package/README.md
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
Biblioteca de repository pattern para projetos que usam **Prisma**, com suporte completo a **TypeScript** e **type inference** automático.
|
|
8
8
|
|
|
9
9
|
O VSRepository permite criar repositories fortemente tipados com:
|
|
10
10
|
|
|
11
|
-
- **Métodos base** automáticos: `get`, `getOrThrow`, `save`, `remove`, `removeList`, `getAll`, `total`, `has`
|
|
11
|
+
- **Métodos base** automáticos: `get`, `getOrThrow`, `getList`, `save`, `saveList`, `remove`, `removeList`, `patch`, `patchList`, `merge`, `getAll`, `total`, `has`
|
|
12
|
+
- **Soft-delete nativo**: `softRemove`, `softRemoveList`, `restore`, `restoreList`
|
|
12
13
|
- **Métodos dinâmicos** inferidos pelo nome: `findByEmail`, `findManyPaginated`, `updateById`, `deleteManyByIdIn`
|
|
13
14
|
- **Select models** reutilizáveis para diferentes projeções de dados
|
|
14
15
|
- **Type safety** em 100% das operações
|
|
15
|
-
- **Transações** nativas do Prisma
|
|
16
|
+
- **Transações** nativas do Prisma (automáticas em `saveList` e `patchList`)
|
|
16
17
|
- **Extensibilidade** com métodos personalizados
|
|
17
18
|
|
|
18
19
|
---
|
|
@@ -24,8 +25,13 @@ O VSRepository permite criar repositories fortemente tipados com:
|
|
|
24
25
|
- [Uso básico](#uso-básico)
|
|
25
26
|
- [Integração com NestJS](#integração-com-nestjs)
|
|
26
27
|
- [Métodos base](#métodos-base)
|
|
28
|
+
- [Soft-delete](#soft-delete)
|
|
29
|
+
- [Operações em lote](#operações-em-lote)
|
|
30
|
+
- [Merge](#merge)
|
|
31
|
+
- [Configurando os métodos base](#configurando-os-métodos-base)
|
|
27
32
|
- [Select Models](#select-models)
|
|
28
33
|
- [Required Where](#requiredwhere)
|
|
34
|
+
- [Opção `see`](#opção-see)
|
|
29
35
|
- [Métodos dinâmicos](#métodos-dinâmicos)
|
|
30
36
|
- [Prefixos disponíveis](#prefixos-disponíveis)
|
|
31
37
|
- [Filtros de campo](#filtros-de-campo)
|
|
@@ -130,7 +136,7 @@ import prisma from "../configs/db";
|
|
|
130
136
|
import { setupVSRepo } from "../../generated/vsrepo";
|
|
131
137
|
import type { Usuario } from "../../generated/prisma/client";
|
|
132
138
|
|
|
133
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
139
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
134
140
|
tableName: "usuario",
|
|
135
141
|
pkName: "id",
|
|
136
142
|
selectModels: {
|
|
@@ -183,7 +189,7 @@ import { setupVSRepo } from "../../../generated/vsrepo";
|
|
|
183
189
|
const userVSRepo = setupVSRepo<
|
|
184
190
|
UserGetPayload<{ include: { profile: true } }>,
|
|
185
191
|
"User"
|
|
186
|
-
>()({
|
|
192
|
+
>()(({
|
|
187
193
|
tableName: "user",
|
|
188
194
|
pkName: "id",
|
|
189
195
|
selectModels: {
|
|
@@ -291,31 +297,6 @@ export class UserService {
|
|
|
291
297
|
}
|
|
292
298
|
```
|
|
293
299
|
|
|
294
|
-
### Usando em um controller
|
|
295
|
-
|
|
296
|
-
```ts
|
|
297
|
-
// src/resources/user/user.controller.ts
|
|
298
|
-
import { Controller, Get, Post, Body, Param, Patch, Delete } from "@nestjs/common";
|
|
299
|
-
import { UserService } from "./user.service";
|
|
300
|
-
|
|
301
|
-
@Controller("users")
|
|
302
|
-
export class UserController {
|
|
303
|
-
constructor(private readonly userService: UserService) {}
|
|
304
|
-
|
|
305
|
-
@Get(":id")
|
|
306
|
-
async getUser(@Param("id") id: string) {
|
|
307
|
-
return this.userService.getUserById(id);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
@Post()
|
|
311
|
-
async createUser(
|
|
312
|
-
@Body() data: { email: string; password: string; name: string }
|
|
313
|
-
) {
|
|
314
|
-
return this.userService.createUser(data);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
300
|
**Benefícios desta abordagem:**
|
|
320
301
|
|
|
321
302
|
- ✅ Type-safe repositories com injeção de dependência
|
|
@@ -330,26 +311,99 @@ export class UserController {
|
|
|
330
311
|
|
|
331
312
|
Ao chamar `.build(prisma)` os métodos base abaixo são automaticamente disponibilizados:
|
|
332
313
|
|
|
333
|
-
| Método
|
|
334
|
-
|
|
|
335
|
-
| `get(pk)`
|
|
336
|
-
| `getOrThrow(pk)`
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
314
|
+
| Método | Descrição |
|
|
315
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
|
316
|
+
| `get(pk)` | Busca um registro pela primary key |
|
|
317
|
+
| `getOrThrow(pk)` | Busca um registro pela primary key; lança `VSRepoRuntimeError` (code `"20727"`) se não encontrado |
|
|
318
|
+
| `getList(pks)` | Busca múltiplos registros por uma lista de primary keys |
|
|
319
|
+
| `save(obj)` | Cria ou atualiza — se o objeto tiver a `pk` faz `upsert`, caso contrário faz `create` |
|
|
320
|
+
| `saveList(objs)` | Salva um array de objetos em uma única transação automática |
|
|
321
|
+
| `patch(pk, obj)` | Atualiza parcialmente um registro pela primary key |
|
|
322
|
+
| `patchList(tuples)` | Atualiza parcialmente múltiplos registros via array de tuplas `[pk, obj]` em transação automática |
|
|
323
|
+
| `merge(pk, obj)` | Busca um registro e faz um deep merge em memória — **não persiste**, retorna o objeto mesclado |
|
|
324
|
+
| `remove(pk)` | Remove um registro pela primary key |
|
|
325
|
+
| `removeList(pks)` | Remove vários registros pela lista de primary keys — retorna `{ count }` |
|
|
326
|
+
| `getAll()` | Retorna todos os registros (aceita `pagination` e `order` no `options`) |
|
|
327
|
+
| `total()` | Retorna o total de registros |
|
|
328
|
+
| `has(pk)` | Verifica existência de um registro pela primary key — retorna `boolean` |
|
|
344
329
|
|
|
345
330
|
Todos aceitam `options` como último argumento.
|
|
346
331
|
|
|
332
|
+
### Soft-delete
|
|
333
|
+
|
|
334
|
+
Quando `softRemovekName` está configurado no repository, os seguintes métodos adicionais ficam disponíveis:
|
|
335
|
+
|
|
336
|
+
| Método | Descrição |
|
|
337
|
+
| -------------------------- | --------------------------------------------------------------------------------- |
|
|
338
|
+
| `softRemove(pk)` | Marca um registro como removido preenchendo `softRemovekName` com a data atual |
|
|
339
|
+
| `softRemoveList(pks)` | Marca múltiplos registros como removidos em lote — retorna `{ count }` |
|
|
340
|
+
| `restore(pk)` | Restaura um registro soft-deletado, limpando o campo `softRemovekName` |
|
|
341
|
+
| `restoreList(pks)` | Restaura múltiplos registros soft-deletados em lote — retorna `{ count }` |
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
345
|
+
tableName: "usuario",
|
|
346
|
+
pkName: "id",
|
|
347
|
+
softRemovekName: "deletedAt", // deve ser um campo DateTime no schema do Prisma
|
|
348
|
+
}).build(prisma);
|
|
349
|
+
|
|
350
|
+
await usuarioRepository.softRemove(1);
|
|
351
|
+
await usuarioRepository.restore(1);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
> 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 for incorreto.
|
|
355
|
+
|
|
356
|
+
### Operações em lote
|
|
357
|
+
|
|
358
|
+
`saveList` e `patchList` executam todas as operações automaticamente dentro de uma única transação do Prisma. Se alguma falhar, todas as anteriores são revertidas.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
// saveList — cria ou atualiza múltiplos objetos em transação automática
|
|
362
|
+
const usuarios = await usuarioRepository.saveList([
|
|
363
|
+
{ nome: "Maria", email: "maria@email.com" },
|
|
364
|
+
{ id: 2, nome: "João Atualizado" },
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
// patchList — atualiza parcialmente múltiplos registros via tuplas [pk, obj]
|
|
368
|
+
const atualizados = await usuarioRepository.patchList([
|
|
369
|
+
[1, { ativo: false }],
|
|
370
|
+
[2, { nome: "Novo Nome" }],
|
|
371
|
+
]);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Quando você já está dentro de uma transação existente, passe-a em `options.db`. Nesse caso, o `db` deve ser um `DbTransaction` (não o cliente principal), pois o método não cria uma transação própria:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
await prisma.$transaction(async (tx) => {
|
|
378
|
+
await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
|
|
379
|
+
await usuarioRepository.patchList([[1, { ativo: false }]], { db: tx });
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Merge
|
|
384
|
+
|
|
385
|
+
O método `merge` busca um registro pela PK e mescla profundamente (`deepmerge`) o objeto fornecido com os dados existentes **em memória**. Ele **não persiste** as alterações — retorna o resultado mesclado para que você decida o que fazer com ele.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const existente = await usuarioRepository.get(1);
|
|
389
|
+
// existente: { id: 1, nome: "Maria", perfil: { bio: "Olá", idade: 25 } }
|
|
390
|
+
|
|
391
|
+
const mesclado = await usuarioRepository.merge(1, {
|
|
392
|
+
perfil: { bio: "Bio atualizada" },
|
|
393
|
+
});
|
|
394
|
+
// mesclado: { id: 1, nome: "Maria", perfil: { bio: "Bio atualizada", idade: 25 } }
|
|
395
|
+
|
|
396
|
+
// Para persistir, passe para save ou patch:
|
|
397
|
+
await usuarioRepository.save(mesclado);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Retorna `null` se o registro não for encontrado.
|
|
401
|
+
|
|
347
402
|
### Configurando os métodos base
|
|
348
403
|
|
|
349
404
|
```ts
|
|
350
405
|
usuarioVSRepo.build(prisma, {
|
|
351
|
-
|
|
352
|
-
showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar as queries criadas e os objetos passados para o prisma
|
|
406
|
+
showWorking: true, // Exibe logs do VSRepository no console, ótimo para debugar
|
|
353
407
|
|
|
354
408
|
baseMethods: {
|
|
355
409
|
get: {
|
|
@@ -359,17 +413,21 @@ usuarioVSRepo.build(prisma, {
|
|
|
359
413
|
remove: {
|
|
360
414
|
active: true,
|
|
361
415
|
defaultSelect: "minimal",
|
|
362
|
-
ignoreRequiredWhere: false,
|
|
416
|
+
ignoreRequiredWhere: false,
|
|
363
417
|
},
|
|
364
418
|
save: {
|
|
365
|
-
ignoreRequiredWhere: true,
|
|
419
|
+
ignoreRequiredWhere: true,
|
|
366
420
|
},
|
|
367
421
|
patch: {
|
|
368
422
|
defaultSelect: "minimal",
|
|
369
423
|
},
|
|
370
424
|
has: {
|
|
371
425
|
active: false, // Desativa o 'has' (padrão = true)
|
|
372
|
-
}
|
|
426
|
+
},
|
|
427
|
+
softRemove: {
|
|
428
|
+
active: true,
|
|
429
|
+
defaultSelect: "minimal",
|
|
430
|
+
},
|
|
373
431
|
},
|
|
374
432
|
});
|
|
375
433
|
```
|
|
@@ -423,7 +481,36 @@ const usuarios = await usuarioRepository.findMany();
|
|
|
423
481
|
const usuario = await usuarioRepository.findByEmail("joao@email.com");
|
|
424
482
|
```
|
|
425
483
|
|
|
426
|
-
Útil para soft-deletes, multi-tenancy e filtros globais de qualquer natureza.
|
|
484
|
+
Útil para soft-deletes manuais, multi-tenancy e filtros globais de qualquer natureza.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Opção `see`
|
|
489
|
+
|
|
490
|
+
Quando `softRemovekName` está configurado, todos os métodos base aceitam a opção `see` para controlar a visibilidade de registros soft-deletados:
|
|
491
|
+
|
|
492
|
+
| Valor | Comportamento |
|
|
493
|
+
| ----------- | ------------------------------------------------------------- |
|
|
494
|
+
| `"active"` | Retorna apenas registros **não** removidos (padrão) |
|
|
495
|
+
| `"removed"` | Retorna apenas registros removidos |
|
|
496
|
+
| `"all"` | Retorna todos os registros, independentemente do status |
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
// Retorna apenas usuários ativos (padrão)
|
|
500
|
+
const ativos = await usuarioRepository.getAll();
|
|
501
|
+
|
|
502
|
+
// Retorna apenas usuários removidos
|
|
503
|
+
const removidos = await usuarioRepository.getAll({ see: "removed" });
|
|
504
|
+
|
|
505
|
+
// Retorna todos
|
|
506
|
+
const todos = await usuarioRepository.getAll({ see: "all" });
|
|
507
|
+
|
|
508
|
+
// Funciona em qualquer método base
|
|
509
|
+
const usuario = await usuarioRepository.get(id, { see: "all" });
|
|
510
|
+
const existe = await usuarioRepository.has(id, { see: "removed" });
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> A opção `see` funciona independentemente do `requiredWhere` — ela é aplicada em cima do filtro de soft-delete, não o substitui.
|
|
427
514
|
|
|
428
515
|
---
|
|
429
516
|
|
|
@@ -433,10 +520,10 @@ Métodos dinâmicos são definidos na propriedade `methods` e têm seus comporta
|
|
|
433
520
|
|
|
434
521
|
```ts
|
|
435
522
|
methods: {
|
|
436
|
-
|
|
437
|
-
findManyPaginated:
|
|
438
|
-
updateById:
|
|
439
|
-
deleteManyByIdIn:
|
|
523
|
+
findOneByEmail: { map: true },
|
|
524
|
+
findManyPaginated: { map: true },
|
|
525
|
+
updateById: { map: true },
|
|
526
|
+
deleteManyByIdIn: { map: true },
|
|
440
527
|
}
|
|
441
528
|
```
|
|
442
529
|
|
|
@@ -446,34 +533,40 @@ methods: {
|
|
|
446
533
|
|
|
447
534
|
O prefixo do nome do método determina qual operação Prisma será chamada e quais argumentos serão esperados.
|
|
448
535
|
|
|
449
|
-
| Prefixo
|
|
450
|
-
|
|
|
451
|
-
| `
|
|
452
|
-
| `
|
|
453
|
-
| `
|
|
454
|
-
| `
|
|
455
|
-
| `
|
|
456
|
-
| `
|
|
457
|
-
| `
|
|
458
|
-
| `
|
|
459
|
-
| `
|
|
460
|
-
| `
|
|
461
|
-
| `
|
|
462
|
-
| `
|
|
463
|
-
| `
|
|
464
|
-
| `
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
470
|
-
| `
|
|
471
|
-
| `
|
|
472
|
-
| `
|
|
473
|
-
| `
|
|
474
|
-
| `
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
536
|
+
| Prefixo | Operação Prisma | Retorno | Observações |
|
|
537
|
+
| -------------------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------ |
|
|
538
|
+
| `findOneBy` | `findFirst` | `T \| null` | Retorno único. |
|
|
539
|
+
| `findBy` | `findMany` / `findFirst` | `T[]` ou `T \| null` | Padrão é lista; use `fbMode: "one"` para retorno único (**obsoleto**, use `findOneBy`) |
|
|
540
|
+
| `findUniqueBy` | `findUnique` | `T \| null` | |
|
|
541
|
+
| `findUniqueOrThrowBy` | `findUniqueOrThrow` | `T` | Lança erro se não encontrar |
|
|
542
|
+
| `findFirstBy` | `findFirst` | `T \| null` | Aceita campos como filtro |
|
|
543
|
+
| `findFirstOrThrowBy` | `findFirstOrThrow` | `T` | Aceita campos como filtro; lança erro se não encontrar |
|
|
544
|
+
| `findFirst` | `findFirst` | `T \| null` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
545
|
+
| `findFirstOrThrow` | `findFirstOrThrow` | `T` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere`; lança erro se não encontrar |
|
|
546
|
+
| `findManyBy` | `findMany` | `T[]` | Aceita campos como filtro |
|
|
547
|
+
| `findMany` | `findMany` | `T[]` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
548
|
+
| `findOneWhere` | `findFirst` | `T \| null` | Recebe um objeto `where` explícito como argumento |
|
|
549
|
+
| `findWhere` | `findFirst` | `T \| null` | (**Obsoleto, use `findOneWhere`**) Recebe um objeto `where` explícito |
|
|
550
|
+
| `findListWhere` | `findMany` | `T[]` | Recebe um objeto `where` explícito como argumento |
|
|
551
|
+
| `existsBy` | `findFirst` | `boolean` | Retorna `true` se encontrar, `false` caso contrário |
|
|
552
|
+
| `existsWhere` | `findFirst` | `boolean` | Recebe um objeto `where` explícito e retorna se existe |
|
|
553
|
+
| `countBy` | `count` | `number` | Aceita campos como filtro |
|
|
554
|
+
| `countWhere` | `count` | `number` | Recebe um objeto `where` explícito como argumento |
|
|
555
|
+
| `count` | `count` | `number` | Sem filtros de campo; aplica só `requiredWhere` e `pushWhere` |
|
|
556
|
+
| `create` | `create` | `T` | Recebe `data` como argumento |
|
|
557
|
+
| `createMany` | `createMany` | `{ count: number }` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
558
|
+
| `createManyAndReturn` | `createManyAndReturn` | `T[]` | Recebe `data` como argumento; suporta `SkipDuplicates` |
|
|
559
|
+
| `updateBy` | `update` | `T` | Recebe `data` como argumento |
|
|
560
|
+
| `updateManyBy` | `updateMany` | `{ count: number }` | Recebe `data` como argumento |
|
|
561
|
+
| `updateManyWhere` | `updateMany` | `{ count: number }` | Recebe um objeto `where` e um objeto `data` como argumentos |
|
|
562
|
+
| `updateManyAndReturnBy` | `updateManyAndReturn` | `T[]` | Recebe `data` como argumento |
|
|
563
|
+
| `updateManyAndReturnWhere` | `updateManyAndReturn` | `T[]` | Recebe um objeto `where` e um objeto `data` como argumentos |
|
|
564
|
+
| `upsertBy` | `upsert` | `T` | Recebe `update` e `create` como argumentos |
|
|
565
|
+
| `deleteBy` | `delete` | `T` | |
|
|
566
|
+
| `deleteManyBy` | `deleteMany` | `{ count: number }` | |
|
|
567
|
+
| `deleteManyWhere` | `deleteMany` | `{ count: number }` | Recebe um objeto `where` explícito como argumento |
|
|
568
|
+
| `aggregate` | `aggregate` | `Dinâmico` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
|
|
569
|
+
| `groupBy` | `groupBy` | `Dinâmico[]` | Nome deve ser exato; recebe args nativos do Prisma; ignora `selectModels`, `pushWhere` e `requiredWhere` |
|
|
477
570
|
|
|
478
571
|
---
|
|
479
572
|
|
|
@@ -481,29 +574,29 @@ O prefixo do nome do método determina qual operação Prisma será chamada e qu
|
|
|
481
574
|
|
|
482
575
|
Os filtros são sufixos aplicados ao nome do campo dentro do método. O campo em si vem capitalizado logo após o prefixo (ou após `By`).
|
|
483
576
|
|
|
484
|
-
| Sufixo | Operador Prisma | Argumento necessário
|
|
485
|
-
| ------------------ | --------------------- |
|
|
486
|
-
| *(sem sufixo)* | igualdade (`=`) | sim
|
|
487
|
-
| `Not` | `not` | sim
|
|
488
|
-
| `In` | `in` | sim (array)
|
|
489
|
-
| `NotIn` | `notIn` | sim (array)
|
|
490
|
-
| `Contains` | `contains` | sim
|
|
491
|
-
| `NotContains` | `not.contains` | sim
|
|
492
|
-
| `StartsWith` | `startsWith` | sim
|
|
493
|
-
| `NotStartsWith` | `not.startsWith` | sim
|
|
494
|
-
| `EndsWith` | `endsWith` | sim
|
|
495
|
-
| `NotEndsWith` | `not.endsWith` | sim
|
|
496
|
-
| `GreaterThan` | `gt` | sim
|
|
497
|
-
| `GreaterThanEqual` | `gte` | sim
|
|
498
|
-
| `LessThan` | `lt` | sim
|
|
499
|
-
| `LessThanEqual` | `lte` | sim
|
|
500
|
-
| `Between` | `gte` + `lte` | sim (tupla `[min, max]`)
|
|
501
|
-
| `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`)
|
|
502
|
-
| `IsNull` | `null` | não
|
|
503
|
-
| `IsNotNull` | `not: null` | não
|
|
504
|
-
| `IsTrue` | `true` | não
|
|
505
|
-
| `IsFalse` | `false` | não
|
|
506
|
-
| `Insensitive` | `mode: 'insensitive'` | combinador
|
|
577
|
+
| Sufixo | Operador Prisma | Argumento necessário |
|
|
578
|
+
| ------------------ | --------------------- | ------------------------- |
|
|
579
|
+
| *(sem sufixo)* | igualdade (`=`) | sim |
|
|
580
|
+
| `Not` | `not` | sim |
|
|
581
|
+
| `In` | `in` | sim (array) |
|
|
582
|
+
| `NotIn` | `notIn` | sim (array) |
|
|
583
|
+
| `Contains` | `contains` | sim |
|
|
584
|
+
| `NotContains` | `not.contains` | sim |
|
|
585
|
+
| `StartsWith` | `startsWith` | sim |
|
|
586
|
+
| `NotStartsWith` | `not.startsWith` | sim |
|
|
587
|
+
| `EndsWith` | `endsWith` | sim |
|
|
588
|
+
| `NotEndsWith` | `not.endsWith` | sim |
|
|
589
|
+
| `GreaterThan` | `gt` | sim |
|
|
590
|
+
| `GreaterThanEqual` | `gte` | sim |
|
|
591
|
+
| `LessThan` | `lt` | sim |
|
|
592
|
+
| `LessThanEqual` | `lte` | sim |
|
|
593
|
+
| `Between` | `gte` + `lte` | sim (tupla `[min, max]`) |
|
|
594
|
+
| `NotBetween` | `not.gte` + `not.lte` | sim (tupla `[min, max]`) |
|
|
595
|
+
| `IsNull` | `null` | não |
|
|
596
|
+
| `IsNotNull` | `not: null` | não |
|
|
597
|
+
| `IsTrue` | `true` | não |
|
|
598
|
+
| `IsFalse` | `false` | não |
|
|
599
|
+
| `Insensitive` | `mode: 'insensitive'` | combinador |
|
|
507
600
|
|
|
508
601
|
`Insensitive` é um combinador e pode ser usado junto com outro filtro de texto:
|
|
509
602
|
|
|
@@ -513,41 +606,24 @@ findByEmailStartsWithInsensitive // { email: { startsWith: valor, mode: 'insensi
|
|
|
513
606
|
findByNomeInsensitive // { nome: { equals: valor, mode: 'insensitive' } }
|
|
514
607
|
```
|
|
515
608
|
|
|
516
|
-
`Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]
|
|
609
|
+
`Between` e `NotBetween` recebem uma **tupla `[minValue, maxValue]`**:
|
|
517
610
|
|
|
518
611
|
```ts
|
|
519
612
|
methods: {
|
|
520
|
-
findManyByIdadeBetween:
|
|
521
|
-
findManyBySalarioNotBetween:
|
|
522
|
-
findManyByCriadoEmBetween:
|
|
613
|
+
findManyByIdadeBetween: { map: true },
|
|
614
|
+
findManyBySalarioNotBetween: { map: true },
|
|
615
|
+
findManyByCriadoEmBetween: { map: true },
|
|
523
616
|
}
|
|
524
617
|
|
|
525
|
-
// Uso
|
|
526
618
|
await usuarioRepository.findManyByIdadeBetween([18, 65]);
|
|
527
619
|
await usuarioRepository.findManyBySalarioNotBetween([1000, 5000]);
|
|
528
620
|
await usuarioRepository.findManyByCriadoEmBetween([new Date("2024-01-01"), new Date("2024-12-31")]);
|
|
529
621
|
```
|
|
530
622
|
|
|
531
|
-
Gera (`findManyByIdadeBetween`):
|
|
532
|
-
|
|
533
|
-
```ts
|
|
534
|
-
{
|
|
535
|
-
idade: { gte: 18, lte: 65 }
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
Gera (`findManyBySalarioNotBetween`):
|
|
540
|
-
|
|
541
|
-
```ts
|
|
542
|
-
{
|
|
543
|
-
salario: { not: { gte: 1000, lte: 5000 } }
|
|
544
|
-
}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
623
|
O sufixo `Optional` pode ser adicionado a qualquer campo para tornar o argumento opcional:
|
|
548
624
|
|
|
549
625
|
```ts
|
|
550
|
-
findByNomeOptionalAndEmail // nome é opcional
|
|
626
|
+
findByNomeOptionalAndEmail // nome é opcional, email é obrigatório
|
|
551
627
|
```
|
|
552
628
|
|
|
553
629
|
---
|
|
@@ -556,67 +632,32 @@ findByNomeOptionalAndEmail // nome é opcional (pode ser passado como undefined
|
|
|
556
632
|
|
|
557
633
|
| Operador | Uso no nome | Exemplo |
|
|
558
634
|
| --------- | ---------------------------- | -------------------------------- |
|
|
559
|
-
| `And` | entre dois campos | `
|
|
635
|
+
| `And` | entre dois campos | `findOneByIdAndEmail` |
|
|
560
636
|
| `Or` | entre dois campos | `findByNomeOrEmail` |
|
|
561
637
|
| `AND` | separa bloco final em `AND` | `findByEmailOrNameANDActiveStatus` |
|
|
562
638
|
|
|
563
639
|
`AND` (em capslock) tem uma regra específica:
|
|
564
640
|
|
|
565
641
|
- Só pode existir **um** `AND` por método.
|
|
566
|
-
- Todos os campos
|
|
642
|
+
- Todos os campos depois de `AND` são injetados dentro de `AND: []`.
|
|
567
643
|
- Depois de um `AND` não pode ter `Or`.
|
|
568
644
|
|
|
569
645
|
Exemplo:
|
|
570
646
|
|
|
571
647
|
```ts
|
|
572
648
|
methods: {
|
|
573
|
-
|
|
649
|
+
findOneByIdAndEmail: { map: true },
|
|
574
650
|
findByNomeOrEmail: { map: true },
|
|
575
651
|
findUniqueByIdOrEmailAndNome: { map: true },
|
|
576
652
|
findByEmailOrNameANDActiveStatusAndIdadeGreaterThan: { map: true }
|
|
577
653
|
}
|
|
578
654
|
|
|
579
|
-
|
|
580
|
-
await usuarioRepository.findByIdAndEmail(1, "joao@email.com");
|
|
655
|
+
await usuarioRepository.findOneByIdAndEmail(1, "joao@email.com");
|
|
581
656
|
await usuarioRepository.findByNomeOrEmail("Joao", "joao@email.com");
|
|
582
657
|
await usuarioRepository.findUniqueByIdOrEmailAndNome(1, "joao@email.com", "Joao");
|
|
583
658
|
await usuarioRepository.findByEmailOrNameANDActiveStatusAndIdadeGreaterThan("joao@email.com", "Joao", true, 17)
|
|
584
659
|
```
|
|
585
660
|
|
|
586
|
-
Gera (`findByIdAndEmail`):
|
|
587
|
-
|
|
588
|
-
```ts
|
|
589
|
-
{
|
|
590
|
-
id: 1,
|
|
591
|
-
email: "joao@email.com"
|
|
592
|
-
}
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
Gera (`findByNomeOrEmail`):
|
|
596
|
-
|
|
597
|
-
```ts
|
|
598
|
-
{
|
|
599
|
-
OR: [
|
|
600
|
-
{ nome: "Joao" },
|
|
601
|
-
{ email: "joao@email.com" }
|
|
602
|
-
]
|
|
603
|
-
}
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
Gera (`findUniqueByIdOrEmailAndNome`):
|
|
607
|
-
|
|
608
|
-
```ts
|
|
609
|
-
{
|
|
610
|
-
OR: [
|
|
611
|
-
{ id: 1 },
|
|
612
|
-
{
|
|
613
|
-
email: "joao@email.com",
|
|
614
|
-
nome: "Joao"
|
|
615
|
-
}
|
|
616
|
-
]
|
|
617
|
-
}
|
|
618
|
-
```
|
|
619
|
-
|
|
620
661
|
Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
|
|
621
662
|
|
|
622
663
|
```ts
|
|
@@ -638,6 +679,12 @@ Gera (`findByEmailOrNameANDActiveStatusAndIdadeGreaterThan`):
|
|
|
638
679
|
|
|
639
680
|
Permitem filtrar por campos de modelos relacionados.
|
|
640
681
|
|
|
682
|
+
> [!IMPORTANT]
|
|
683
|
+
> - **Tipagem de relação**: Para que o TypeScript reconheça os tipos dos campos de relação nos métodos dinâmicos, o tipo genérico da entidade passado no `setupVSRepo` deve incluir as relações estruturadas (ex: usando `UsuarioGetPayload<{ include: { perfil: true, postagens: true } }>` do Prisma).
|
|
684
|
+
> - **Compatibilidade de sufixos**:
|
|
685
|
+
> - Os sufixos `Some`, `Every` e `None` só funcionam para relações **to-many** (`many-to-many` e `one-to-many`).
|
|
686
|
+
> - Os sufixos `With` e `Without` só funcionam para relações **to-one** (`one-to-one` e `many-to-one`).
|
|
687
|
+
|
|
641
688
|
| Sufixo de relação | Operador Prisma | Observação |
|
|
642
689
|
| ---------------------- | --------------- | -------------------------------------------------- |
|
|
643
690
|
| `Some` | `some: {}` | Relação tem *algum* registro |
|
|
@@ -650,27 +697,11 @@ Permitem filtrar por campos de modelos relacionados.
|
|
|
650
697
|
| `Without` | `isNot: {}` | Relação não existe (é null) |
|
|
651
698
|
| `WithoutField` | `isNot.field` | Filtra campo dentro da relação com negação |
|
|
652
699
|
|
|
653
|
-
Exemplos:
|
|
654
|
-
|
|
655
|
-
```ts
|
|
656
|
-
methods: {
|
|
657
|
-
findByPostagensSomeTituloContains: { map: true }, // postagens: { some: { titulo: { contains: valor } } } >> (Busca usuários que o título de alguma postagem contém um valor)
|
|
658
|
-
findByPerfilWithDescricaoIsNotNull: { map: true }, // perfil: { is: { descricao: { not: null } } } >> (Busca usuários em que a descrição do perfil não é nula)
|
|
659
|
-
findByPerfilWithout: { map: true }, // perfil: { isNot: {} } >> (Busca usuários sem perfil)
|
|
660
|
-
findByPostagensSome: { map: true }, // postagens: { some: {} } >> (Busca usuários com alguma postagem)
|
|
661
|
-
findByPostagensEveryAtivoIsTrue: { map: true }, // postagens: { every: { ativo: true } } >> (Busca usuários que todas as postagens estão ativas)
|
|
662
|
-
findByPostagensNone: { map: true }, // postagens: { none: {} } >> (Busca usuários sem postagens)
|
|
663
|
-
}
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
> [!NOTE]
|
|
667
|
-
> Teoricamente você pode usar `Every` sem `Field` (ele geraria { every: {} }), porém isso não produz um filtro efetivo. A condição é considerada verdadeira para qualquer relação, inclusive quando não existem registros relacionados, tornando o resultado equivalente a não aplicar filtro algum.
|
|
668
|
-
|
|
669
700
|
---
|
|
670
701
|
|
|
671
702
|
### Sufixos de paginação e ordenação
|
|
672
703
|
|
|
673
|
-
Aplicados ao **final** do nome do método
|
|
704
|
+
Aplicados ao **final** do nome do método, eles injetam automaticamente os argumentos de paginação e ordenação.
|
|
674
705
|
|
|
675
706
|
| Sufixo | Argumentos adicionais |
|
|
676
707
|
| --------------------- | ----------------------------- |
|
|
@@ -685,43 +716,6 @@ Para `createMany` e `createManyAndReturn`, o sufixo `SkipDuplicates` está dispo
|
|
|
685
716
|
| ----------------- | ---------------------------------------- |
|
|
686
717
|
| `SkipDuplicates` | Ignora registros duplicados na inserção |
|
|
687
718
|
|
|
688
|
-
Exemplos completos:
|
|
689
|
-
|
|
690
|
-
```ts
|
|
691
|
-
methods: {
|
|
692
|
-
findManyPaginated: { map: true },
|
|
693
|
-
findManyByAtivoOrderedAndPaginated: { map: true },
|
|
694
|
-
findByEmailOrderedAndPaginated: { map: true },
|
|
695
|
-
createManyAndReturnSkipDuplicates: { map: true },
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Uso
|
|
699
|
-
await usuarioRepository.findManyPaginated({ skip: 0, take: 10 });
|
|
700
|
-
|
|
701
|
-
await usuarioRepository.findManyByAtivoOrderedAndPaginated(
|
|
702
|
-
true,
|
|
703
|
-
{ dataCriacao: "desc" },
|
|
704
|
-
{ skip: 0, take: 10 }
|
|
705
|
-
);
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
`PaginationOptions`:
|
|
709
|
-
|
|
710
|
-
```ts
|
|
711
|
-
type PaginationOptions<TCursor = unknown> = {
|
|
712
|
-
skip?: number;
|
|
713
|
-
take?: number;
|
|
714
|
-
cursor?: TCursor;
|
|
715
|
-
};
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
`OrderOptions`:
|
|
719
|
-
|
|
720
|
-
```ts
|
|
721
|
-
type OrderOptions = OrderPattern | OrderPattern[];
|
|
722
|
-
// Exemplo: { dataCriacao: "desc" } ou [{ dataCriacao: "desc" }, { nome: "asc" }]
|
|
723
|
-
```
|
|
724
|
-
|
|
725
719
|
---
|
|
726
720
|
|
|
727
721
|
### Configuração de métodos
|
|
@@ -731,46 +725,20 @@ Cada entrada em `methods` aceita as seguintes opções:
|
|
|
731
725
|
| Opção | Tipo | Padrão | Descrição |
|
|
732
726
|
| ------------------- | ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
733
727
|
| `map` | `boolean` | — | **Obrigatório.** Define se o método será exposto no repository. |
|
|
734
|
-
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`.
|
|
728
|
+
| `whereType` | `'extending'` \| `'overwrite'` | `extending` | `extending` combina com `requiredWhere`. `overwrite` ignora o `requiredWhere`. |
|
|
735
729
|
| `selectModel` | `keyof SelectModels \| false` | — | Sobrescreve o `defaultSelectModel` para este método. |
|
|
736
|
-
| `fbMode` | `'one'` \| `'list'` | `'list'` | Somente para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`.
|
|
737
|
-
| `proxyTo` | `Padrão de método válido`
|
|
730
|
+
| `fbMode` | `'one'` \| `'list'` | `'list'` | (**Obsoleto. Use `findOneBy`**) Somente para `findBy`. `'one'` retorna `T \| null`; `'list'` retorna `T[]`. |
|
|
731
|
+
| `proxyTo` | `Padrão de método válido` | — | Delega a lógica para outro padrão de método válido. |
|
|
738
732
|
| `pushWhere` | `WhereModel<M>` | — | Where extra adicionado à query além do `requiredWhere`. |
|
|
739
733
|
| `injectOrdenation` | `OrdenationModel<M>` | — | Ordenação fixa injetada automaticamente na query. |
|
|
740
734
|
| `injectPagination` | `PaginationModel<M>` | — | Paginação fixa injetada automaticamente na query. |
|
|
741
735
|
|
|
742
|
-
Exemplos:
|
|
743
|
-
|
|
744
|
-
```ts
|
|
745
|
-
methods: {
|
|
746
|
-
// Retorna um único resultado em vez de array
|
|
747
|
-
findByEmail: { map: true, fbMode: "one" },
|
|
748
|
-
|
|
749
|
-
// Ignora o requiredWhere
|
|
750
|
-
deleteManyByIdIn: { map: true, whereType: "overwrite" },
|
|
751
|
-
|
|
752
|
-
// Usa um select model específico neste método
|
|
753
|
-
findManyByAtivo: { map: true, selectModel: "minimal" },
|
|
754
|
-
|
|
755
|
-
// Adiciona um where extra além do requiredWhere
|
|
756
|
-
findManyByPerfil: { map: true, pushWhere: { deletedAt: null } },
|
|
757
|
-
|
|
758
|
-
// Ordenação fixa sem precisar passar como argumento
|
|
759
|
-
findManyPaginado: { map: true, injectOrdenation: { dataCriacao: "desc" } },
|
|
760
|
-
|
|
761
|
-
// Nome personalizado precisa de proxyTo
|
|
762
|
-
buscarPorEmailEPerfil: { map: true, proxyTo: "findByEmailAndPerfil" },
|
|
763
|
-
}
|
|
764
|
-
```
|
|
765
|
-
|
|
766
736
|
---
|
|
767
737
|
|
|
768
738
|
### Aggregate e GroupBy
|
|
769
739
|
|
|
770
|
-
Para utilizar operações de agrupamento e agregação do Prisma (`aggregate` e `groupBy`), você deve expô-las explicitamente na configuração dos métodos dinâmicos (`methods`):
|
|
771
|
-
|
|
772
740
|
```ts
|
|
773
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
741
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
774
742
|
tableName: "usuario",
|
|
775
743
|
pkName: "id",
|
|
776
744
|
methods: {
|
|
@@ -784,65 +752,11 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
784
752
|
> Estes métodos devem ter exatamente esses nomes (`aggregate` e `groupBy`).
|
|
785
753
|
> Ao contrário dos outros métodos dinâmicos, eles recebem argumentos nativos do Prisma e **ignoram** as configurações de `selectModels`, `pushWhere` e `requiredWhere`.
|
|
786
754
|
|
|
787
|
-
#### Exemplo de uso do `aggregate`
|
|
788
|
-
|
|
789
|
-
O método `aggregate` permite calcular valores agregados (como média, soma, mínimo, máximo, contagem) sobre os registros:
|
|
790
|
-
|
|
791
|
-
```ts
|
|
792
|
-
const resultado = await usuarioRepository.aggregate({
|
|
793
|
-
_count: {
|
|
794
|
-
_all: true,
|
|
795
|
-
},
|
|
796
|
-
_avg: {
|
|
797
|
-
idade: true,
|
|
798
|
-
},
|
|
799
|
-
_sum: {
|
|
800
|
-
saldo: true,
|
|
801
|
-
},
|
|
802
|
-
where: {
|
|
803
|
-
ativo: true,
|
|
804
|
-
},
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
console.log(resultado._count._all); // Total de usuários ativos
|
|
808
|
-
console.log(resultado._avg.idade); // Média de idade dos usuários ativos
|
|
809
|
-
console.log(resultado._sum.saldo); // Soma dos saldos dos usuários ativos
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
#### Exemplo de uso do `groupBy`
|
|
813
|
-
|
|
814
|
-
O método `groupBy` permite agrupar registros por um ou mais campos para realizar operações de agregação em cada grupo:
|
|
815
|
-
|
|
816
|
-
```ts
|
|
817
|
-
const grupos = await usuarioRepository.groupBy({
|
|
818
|
-
by: ["status"],
|
|
819
|
-
_count: {
|
|
820
|
-
status: true,
|
|
821
|
-
},
|
|
822
|
-
_avg: {
|
|
823
|
-
idade: true,
|
|
824
|
-
},
|
|
825
|
-
having: {
|
|
826
|
-
idade: {
|
|
827
|
-
_avg: {
|
|
828
|
-
gt: 18,
|
|
829
|
-
},
|
|
830
|
-
},
|
|
831
|
-
},
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
for (const grupo of grupos) {
|
|
835
|
-
console.log(`Status: ${grupo.status}`);
|
|
836
|
-
console.log(`Quantidade: ${grupo._count.status}`);
|
|
837
|
-
console.log(`Média de Idade: ${grupo._avg.idade}`);
|
|
838
|
-
}
|
|
839
|
-
```
|
|
840
|
-
|
|
841
755
|
---
|
|
842
756
|
|
|
843
757
|
## Relações no save
|
|
844
758
|
|
|
845
|
-
Configure relações para que o `save`
|
|
759
|
+
Configure relações para que o `save` e o `patch` as gerenciem automaticamente (`saveList` e `pacthList` também gerenciam as relations automaticamente).
|
|
846
760
|
|
|
847
761
|
```ts
|
|
848
762
|
import type { Prisma } from "../../generated/prisma/client";
|
|
@@ -851,7 +765,7 @@ type Usuario = Prisma.usuarioGetPayload<{
|
|
|
851
765
|
include: { perfil: true; postagens: true };
|
|
852
766
|
}>;
|
|
853
767
|
|
|
854
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
768
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
855
769
|
tableName: "usuario",
|
|
856
770
|
pkName: "id",
|
|
857
771
|
|
|
@@ -870,35 +784,6 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
870
784
|
}).build(prisma);
|
|
871
785
|
```
|
|
872
786
|
|
|
873
|
-
Exemplos de uso com relações:
|
|
874
|
-
|
|
875
|
-
```ts
|
|
876
|
-
await usuarioRepository.save({
|
|
877
|
-
nome: "Maria",
|
|
878
|
-
email: "maria@email.com",
|
|
879
|
-
senha: "password",
|
|
880
|
-
perfil: {
|
|
881
|
-
id: 1,
|
|
882
|
-
bio: "Bio da Maria",
|
|
883
|
-
},
|
|
884
|
-
postagens: [
|
|
885
|
-
{ titulo: "Primeiro post", conteudo: "Olá!" },
|
|
886
|
-
],
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
await usuarioRepository.patch(1, {
|
|
890
|
-
perfil: {
|
|
891
|
-
id: 1,
|
|
892
|
-
bio: "Bio atualizada",
|
|
893
|
-
},
|
|
894
|
-
postagens: [
|
|
895
|
-
{ id: 10, titulo: "Post revisado", conteudo: "Conteúdo ajustado" },
|
|
896
|
-
],
|
|
897
|
-
});
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
> **Dica:** Se o tipo genérico não incluir `include: { relação: true }`, o VSRepository ainda funcionará, mas o autocomplete não sugerirá as relações no `save`. O tipo recomendado é sempre `GetPayload<{ include: { /* suas relações */ } }>` para melhor experiência de desenvolvimento.
|
|
901
|
-
|
|
902
787
|
**Modos de relação:**
|
|
903
788
|
|
|
904
789
|
| Modo | Relação |
|
|
@@ -915,6 +800,23 @@ await usuarioRepository.patch(1, {
|
|
|
915
800
|
| `set` | Substitui completamente (remove os que não foram enviados) |
|
|
916
801
|
| `add` | Adiciona/atualiza sem remover os existentes |
|
|
917
802
|
|
|
803
|
+
**Relação `mto` com nullable:**
|
|
804
|
+
|
|
805
|
+
Use `nullable` (letra minúscula) para permitir a desvinculação de uma relação many-to-one:
|
|
806
|
+
|
|
807
|
+
```ts
|
|
808
|
+
relations: {
|
|
809
|
+
categoria: {
|
|
810
|
+
pk: "id",
|
|
811
|
+
mode: "mto",
|
|
812
|
+
restriction: "set",
|
|
813
|
+
nullable: true, // permite passar null para desvincular
|
|
814
|
+
},
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
> **Nota:** `nullAble` (com A maiúsculo) ainda é aceito por compatibilidade, mas está **obsoleto**. Prefira `nullable`.
|
|
819
|
+
|
|
918
820
|
---
|
|
919
821
|
|
|
920
822
|
## Transações
|
|
@@ -935,28 +837,38 @@ await usuarioRepository.prisma.$transaction(async (tx) => {
|
|
|
935
837
|
});
|
|
936
838
|
```
|
|
937
839
|
|
|
938
|
-
|
|
840
|
+
Para `saveList` e `patchList`, o campo `db` deve ser um `DbTransaction` (não o cliente principal):
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
await prisma.$transaction(async (tx) => {
|
|
844
|
+
// CORRETO: tx é uma DbTransaction
|
|
845
|
+
await usuarioRepository.saveList([{ nome: "Maria" }], { db: tx });
|
|
846
|
+
|
|
847
|
+
// ERRADO: não passe o prisma diretamente
|
|
848
|
+
// await usuarioRepository.saveList([{ nome: "Maria" }], { db: prisma });
|
|
849
|
+
});
|
|
850
|
+
```
|
|
939
851
|
|
|
940
852
|
---
|
|
941
853
|
|
|
942
854
|
## Estendendo um repository
|
|
943
855
|
|
|
944
856
|
```ts
|
|
945
|
-
const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
857
|
+
const usuarioRepository = setupVSRepo<Usuario, "usuario">()(({
|
|
946
858
|
tableName: "usuario",
|
|
947
859
|
pkName: "id",
|
|
948
860
|
methods: {
|
|
949
|
-
|
|
861
|
+
findOneByEmailEndsWith: { map: true },
|
|
950
862
|
},
|
|
951
863
|
})
|
|
952
864
|
.build(prisma)
|
|
953
865
|
.extend((repo) => ({
|
|
954
866
|
buscarAtivosPorDominio: async (dominio: string) => {
|
|
955
|
-
return repo.
|
|
867
|
+
return repo.findOneByEmailEndsWith(`@${dominio}`);
|
|
956
868
|
},
|
|
957
869
|
|
|
958
870
|
ativarMultiplos: async (ids: string[]) => {
|
|
959
|
-
return repo.
|
|
871
|
+
return repo.patchList(ids.map(id => [id, { ativo: true }]));
|
|
960
872
|
},
|
|
961
873
|
}));
|
|
962
874
|
```
|
|
@@ -965,15 +877,17 @@ const usuarioRepository = setupVSRepo<Usuario, "usuario">()({
|
|
|
965
877
|
|
|
966
878
|
## Tratamento de erros
|
|
967
879
|
|
|
968
|
-
O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (
|
|
880
|
+
O VSRepository lança `VSRepoError` e suas subclasses em situações específicas (erros do Prisma não são sobrescritos):
|
|
969
881
|
|
|
970
882
|
```ts
|
|
971
|
-
import { VSRepoError } from "../../generated/vsrepo";
|
|
883
|
+
import { VSRepoError, VSRepoRuntimeError } from "../../generated/vsrepo";
|
|
972
884
|
|
|
973
885
|
try {
|
|
974
|
-
const usuario = await usuarioRepository.
|
|
886
|
+
const usuario = await usuarioRepository.getOrThrow(id);
|
|
975
887
|
} catch (error) {
|
|
976
|
-
if (error instanceof
|
|
888
|
+
if (error instanceof VSRepoRuntimeError && error.code === "20727") {
|
|
889
|
+
// Registro não encontrado pelo getOrThrow
|
|
890
|
+
} else if (error instanceof VSRepoError) {
|
|
977
891
|
console.error("Erro no repository:", error.message);
|
|
978
892
|
}
|
|
979
893
|
}
|
|
@@ -981,29 +895,37 @@ try {
|
|
|
981
895
|
|
|
982
896
|
**Subclasses disponíveis:**
|
|
983
897
|
|
|
984
|
-
| Classe | Quando é lançada
|
|
985
|
-
| -------------------- |
|
|
986
|
-
| `VSRepoConfigError` | Configuração inválida em `setupVSRepo`
|
|
987
|
-
| `VSRepoBuildError` | Nome de método
|
|
988
|
-
| `VSRepoExtendError` | Argumento inválido em `extend`
|
|
989
|
-
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação
|
|
898
|
+
| Classe | Quando é lançada |
|
|
899
|
+
| -------------------- | ----------------------------------------------------------------- |
|
|
900
|
+
| `VSRepoConfigError` | Configuração inválida em `setupVSRepo` |
|
|
901
|
+
| `VSRepoBuildError` | Nome de método, tipo de campo ou configuração inválida no `build` |
|
|
902
|
+
| `VSRepoExtendError` | Argumento inválido em `extend` |
|
|
903
|
+
| `VSRepoRuntimeError` | Erro em tempo de execução durante uma operação |
|
|
904
|
+
|
|
905
|
+
`VSRepoRuntimeError` possui a propriedade `code` para identificação programática. O código `"20727"` é lançado pelo `getOrThrow` quando o registro não é encontrado.
|
|
990
906
|
|
|
991
907
|
---
|
|
992
908
|
|
|
993
909
|
## Tipos utilitários
|
|
994
910
|
|
|
995
|
-
O VSRepository exporta os seguintes tipos para uso nas suas aplicações:
|
|
996
|
-
|
|
997
911
|
### Tipos de cliente
|
|
998
912
|
|
|
999
913
|
```ts
|
|
1000
914
|
import type { DbClient, DbTransaction, ClientOrTransaction } from "../../generated/vsrepo";
|
|
1001
915
|
|
|
1002
|
-
type DbClient
|
|
1003
|
-
type DbTransaction
|
|
916
|
+
type DbClient = PrismaClient;
|
|
917
|
+
type DbTransaction = Prisma.TransactionClient;
|
|
1004
918
|
type ClientOrTransaction = DbClient | DbTransaction;
|
|
1005
919
|
```
|
|
1006
920
|
|
|
921
|
+
### Tipo de visibilidade soft-delete
|
|
922
|
+
|
|
923
|
+
```ts
|
|
924
|
+
import type { SeeMode } from "../../generated/vsrepo";
|
|
925
|
+
|
|
926
|
+
type SeeMode = "active" | "removed" | "all";
|
|
927
|
+
```
|
|
928
|
+
|
|
1007
929
|
### Tipos derivados do modelo Prisma
|
|
1008
930
|
|
|
1009
931
|
```ts
|
|
@@ -1014,36 +936,8 @@ import type {
|
|
|
1014
936
|
OrdenationModel,
|
|
1015
937
|
PaginationModel,
|
|
1016
938
|
ModelUpsertInput,
|
|
939
|
+
PrismaModelInputs,
|
|
1017
940
|
} from "../../generated/vsrepo";
|
|
1018
|
-
|
|
1019
|
-
// Select de um campo específico
|
|
1020
|
-
type UsuarioSelect = SelectModel<"usuario">;
|
|
1021
|
-
|
|
1022
|
-
// Mapa de selects nomeados
|
|
1023
|
-
type UsuarioSelectModels = SelectModels<"usuario">;
|
|
1024
|
-
|
|
1025
|
-
// Where clause do modelo
|
|
1026
|
-
type UsuarioWhere = WhereModel<"usuario">;
|
|
1027
|
-
|
|
1028
|
-
// OrderBy do modelo
|
|
1029
|
-
type UsuarioOrder = OrdenationModel<"usuario">;
|
|
1030
|
-
|
|
1031
|
-
// Opções de paginação com cursor tipado
|
|
1032
|
-
type UsuarioPagination = PaginationModel<"usuario">;
|
|
1033
|
-
|
|
1034
|
-
// Payload de criação do modelo (para upsert)
|
|
1035
|
-
type UsuarioUpsertInput = ModelUpsertInput<"usuario">;
|
|
1036
|
-
```
|
|
1037
|
-
|
|
1038
|
-
### Todos os inputs do modelo
|
|
1039
|
-
|
|
1040
|
-
```ts
|
|
1041
|
-
import type { PrismaModelInputs } from "../../generated/vsrepo";
|
|
1042
|
-
|
|
1043
|
-
type UsuarioInputs = PrismaModelInputs<"usuario">;
|
|
1044
|
-
// Contém:
|
|
1045
|
-
// select, createInput, createManyInput, updateInput, updateManyInput,
|
|
1046
|
-
// whereInput, orderByInput, cursorInput, upsertCreateInput, upsertUpdateInput
|
|
1047
941
|
```
|
|
1048
942
|
|
|
1049
943
|
### Tipos de opções de método
|
|
@@ -1052,10 +946,9 @@ type UsuarioInputs = PrismaModelInputs<"usuario">;
|
|
|
1052
946
|
import type { MethodOptions, MethodOptionsModel } from "../../generated/vsrepo";
|
|
1053
947
|
|
|
1054
948
|
// MethodOptions<S> — opções passadas nos métodos do repository
|
|
1055
|
-
// S = chave do select model ou false
|
|
1056
949
|
type Opts = MethodOptions<"public" | "minimal">;
|
|
1057
950
|
|
|
1058
|
-
// MethodOptionsModel<TRepo> — derivado
|
|
951
|
+
// MethodOptionsModel<TRepo> — derivado de uma instância VSRepository configurada
|
|
1059
952
|
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(config);
|
|
1060
953
|
type OptsModel = MethodOptionsModel<typeof usuarioVSRepo>;
|
|
1061
954
|
```
|
|
@@ -1070,21 +963,6 @@ import type {
|
|
|
1070
963
|
RepositoryRelations,
|
|
1071
964
|
ExtractRelationConfig,
|
|
1072
965
|
} from "../../generated/vsrepo";
|
|
1073
|
-
|
|
1074
|
-
// Configuração de um método dinâmico
|
|
1075
|
-
type MeuMethodConfig = MethodConfig<"usuario", typeof meuSelectModels>;
|
|
1076
|
-
|
|
1077
|
-
// Configuração completa do repository
|
|
1078
|
-
type MeuRepoConfig = RepoConfig<Usuario, "usuario">;
|
|
1079
|
-
|
|
1080
|
-
// Configuração do build
|
|
1081
|
-
type MeuBuildConfig = BuildConfig<"public" | "minimal">;
|
|
1082
|
-
|
|
1083
|
-
// Tipo de relações inferidas automaticamente
|
|
1084
|
-
type UsuarioRelations = RepositoryRelations<Usuario>;
|
|
1085
|
-
|
|
1086
|
-
// Configuração de relação inferida a partir de um campo
|
|
1087
|
-
type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
|
|
1088
966
|
```
|
|
1089
967
|
|
|
1090
968
|
### Tipo do repository construído
|
|
@@ -1092,7 +970,6 @@ type PerfilRelationConfig = ExtractRelationConfig<Usuario["perfil"]>;
|
|
|
1092
970
|
```ts
|
|
1093
971
|
import type { RepositoryOf } from "../../generated/vsrepo";
|
|
1094
972
|
|
|
1095
|
-
// Inferência a partir de uma instância VSRepository (útil para injeção de dependência)
|
|
1096
973
|
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({ ... });
|
|
1097
974
|
type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
|
|
1098
975
|
```
|
|
@@ -1101,42 +978,25 @@ type UsuarioRepository = RepositoryOf<typeof usuarioVSRepo>;
|
|
|
1101
978
|
|
|
1102
979
|
```ts
|
|
1103
980
|
type RepositoryOf<TRepo, C extends BuildConfig | undefined = undefined, E = unknown>
|
|
1104
|
-
// ^ BuildConfig (opcional) ^ tipo do extend (opcional)
|
|
1105
981
|
```
|
|
1106
982
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
```ts
|
|
1110
|
-
const extension = { buscarPorDominio: (dominio: string) => Promise<Usuario[]> };
|
|
1111
|
-
type UsuarioRepositoryExtended = RepositoryOf<typeof usuarioVSRepo, undefined, typeof extension>;
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
### Tipos do payload dos métodos `save` e `patch`
|
|
1115
|
-
|
|
1116
|
-
Use `SaveObject` e `PatchObject` para extrair o tipo do payload esperado pelos métodos `save` e `patch` diretamente a partir de uma instância `VSRepository` configurada. Esses tipos combinam o input base do Prisma com as relações configuradas no repository.
|
|
983
|
+
### Tipos do payload de `save` e `patch`
|
|
1117
984
|
|
|
1118
985
|
```ts
|
|
1119
986
|
import type { SaveObject, PatchObject } from "../../generated/vsrepo";
|
|
1120
|
-
import type { Prisma } from "../../generated/prisma/client";
|
|
1121
987
|
|
|
1122
|
-
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()({
|
|
988
|
+
const usuarioVSRepo = setupVSRepo<Usuario, "usuario">()(({
|
|
1123
989
|
tableName: "usuario",
|
|
1124
990
|
pkName: "id",
|
|
1125
991
|
relations: {
|
|
1126
992
|
perfil: { pk: "id", mode: "oto", restriction: "set" },
|
|
1127
|
-
postagens: { pk: "id", mode: "otm", restriction: "add" },
|
|
1128
993
|
},
|
|
1129
994
|
});
|
|
1130
995
|
|
|
1131
|
-
|
|
1132
|
-
type UsuarioSavePayload = SaveObject<Prisma.UsuarioCreateInput, typeof usuarioVSRepo>;
|
|
1133
|
-
|
|
1134
|
-
// Tipo do objeto aceito pelo .patch()
|
|
996
|
+
type UsuarioSavePayload = SaveObject<Prisma.UsuarioCreateInput, typeof usuarioVSRepo>;
|
|
1135
997
|
type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuarioVSRepo>;
|
|
1136
998
|
```
|
|
1137
999
|
|
|
1138
|
-
> Útil para tipar DTOs, funções auxiliares ou serviços que chamam `save`/`patch` e precisam do tipo correto do payload sem referenciar diretamente os tipos internos do Prisma.
|
|
1139
|
-
|
|
1140
1000
|
---
|
|
1141
1001
|
|
|
1142
1002
|
## API Reference
|
|
@@ -1145,12 +1005,13 @@ type UsuarioPatchPayload = PatchObject<Prisma.UsuarioUpdateInput, typeof usuario
|
|
|
1145
1005
|
|
|
1146
1006
|
```ts
|
|
1147
1007
|
setupVSRepo<TPayload, TTableName>()({
|
|
1148
|
-
tableName: Uncapitalize<M>;
|
|
1149
|
-
pkName: keyof T;
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1008
|
+
tableName: Uncapitalize<M>; // Nome da tabela no Prisma
|
|
1009
|
+
pkName: keyof T; // Nome da primary key
|
|
1010
|
+
softRemovekName?: keyof T & string; // Campo DateTime para soft-delete (opcional)
|
|
1011
|
+
selectModels?: SelectModels<M>; // Projeções de dados nomeadas
|
|
1012
|
+
defaultSelectModel?: keyof SM; // Select aplicado por padrão
|
|
1013
|
+
requiredWhere?: WhereModel<M>; // Filtros sempre aplicados
|
|
1014
|
+
relations?: RepositoryRelations<T>; // Configuração de relações
|
|
1154
1015
|
methods?: Record<string, MethodConfig<M, SM>>; // Métodos dinâmicos
|
|
1155
1016
|
});
|
|
1156
1017
|
```
|
|
@@ -1159,25 +1020,29 @@ setupVSRepo<TPayload, TTableName>()({
|
|
|
1159
1020
|
|
|
1160
1021
|
```ts
|
|
1161
1022
|
vsRepo.build(prisma, {
|
|
1162
|
-
freeze?: boolean; // Congela o objeto (default = true)
|
|
1163
1023
|
showWorking?: boolean; // Exibe logs internos no console (default = false)
|
|
1164
1024
|
|
|
1165
|
-
// `active` serve para definir se o método vai existir depois do build (default = true)
|
|
1166
|
-
// `defaultSelect` serve para definir qual `selectModel` esse método vai usar por default (default = `defaultSelectModel` definido no `setupVSRepo`)
|
|
1167
|
-
// `ignoreRequiredWhere` serve para definir se o método vai ignorar o `requiredWhere` definido no `setupVSRepo` (default = false)
|
|
1168
1025
|
baseMethods?: {
|
|
1169
|
-
// Métodos que podem utilizar um
|
|
1170
|
-
get?:
|
|
1171
|
-
getOrThrow?:{ active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1026
|
+
// Métodos que podem utilizar um defaultSelect
|
|
1027
|
+
get?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1028
|
+
getOrThrow?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1029
|
+
getList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1030
|
+
remove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1031
|
+
save?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1032
|
+
saveList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1033
|
+
patch?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1034
|
+
patchList?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1035
|
+
merge?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1036
|
+
getAll?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1037
|
+
softRemove?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1038
|
+
restore?: { active?: boolean; defaultSelect?: string; ignoreRequiredWhere?: boolean };
|
|
1039
|
+
|
|
1040
|
+
// Métodos que NÃO aceitam defaultSelect
|
|
1041
|
+
removeList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1042
|
+
softRemoveList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1043
|
+
restoreList?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1044
|
+
total?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1045
|
+
has?: { active?: boolean; ignoreRequiredWhere?: boolean };
|
|
1181
1046
|
};
|
|
1182
1047
|
});
|
|
1183
1048
|
```
|
|
@@ -1207,7 +1072,7 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
|
|
|
1207
1072
|
|
|
1208
1073
|
## Requisitos
|
|
1209
1074
|
|
|
1210
|
-
- Node.js
|
|
1075
|
+
- Node.js 18+ (ESM)
|
|
1211
1076
|
- Prisma
|
|
1212
1077
|
- TypeScript (opcional, mas fortemente recomendado)
|
|
1213
1078
|
- `"moduleResolution": "bundler"` ou `"nodenext"` no tsconfig
|
|
@@ -1238,3 +1103,7 @@ Para reportar problemas ou sugerir novas funcionalidades, abra uma **Issue**.
|
|
|
1238
1103
|
**`proxyTo` obrigatório** — Nomes fora dos moldes (ex.: `buscarPorEmail`) não são parseados diretamente. Use `proxyTo: "findByEmail"` nesses casos.
|
|
1239
1104
|
|
|
1240
1105
|
**Select model retorna campos inesperados** — Verifique se o select model define exatamente os campos que o seu tipo TypeScript espera. Campos com `false` não serão retornados pelo Prisma.
|
|
1106
|
+
|
|
1107
|
+
**`softRemovekName` lança erro no build** — O campo informado deve ser do tipo `DateTime` no schema do Prisma. Tipos como `Boolean` ou `String` não são aceitos.
|
|
1108
|
+
|
|
1109
|
+
**`saveList`/`patchList` com `db` inválido** — O campo `db` nestes métodos aceita apenas `DbTransaction` (retorno de `prisma.$transaction`), não o cliente principal. Passar o `PrismaClient` diretamente causará comportamento inesperado.
|