prisma-power-types
v1.2.1
Published
Uma coleção de TS Utility Types para transformar de forma prática os modelos do Prisma em DTOs seguros, dinâmicos e legíveis.
Downloads
43
Readme
Prisma Power Types 🚀
Uma coleção de utilitários de TypeScript de alto nível para transformar de forma prática os modelos do Prisma em DTOs (Data Transfer Objects) seguros, dinâmicos e legíveis.
📋 Pré-requisitos
Apesar de esta biblioteca não utilizar diretamente nenhuma dependência, seu uso não faz muito sentido fora de ecossistemas que não utilizem o Prisma. Portanto, você vai precisar:
TypeScript 4.5+: Necessário para o suporte a Template Literal Types.
Prisma: Instalado e gerado no projeto (confira a documentação oficial do Prisma).
✨ Motivação
O Prisma gera tipos excelentes, mas criar interfaces de Criação, Atualização ou Filtros manualmente geralmente resulta em código repetitivo ou tipos "sujos" no IntelliSense. Esta biblioteca fornece utilitários que:
Automatizam metadados: Removem
ide timestamps (created_at,updated_at,deleted_at).Refinam a tipagem: Transformam campos anuláveis em opcionais ou obrigatórios sob demanda.
Melhoram a DX: Utilizam o utilitário
Prettifypara que você veja o objeto real ao passar o mouse, e não uma colcha de retalhos deOmitePick.
📦 Instalação
npm i prisma-power-types
# OU
yarn add prisma-power-types🛠️ Principais Utilitários
Vamos primeiro definir o seguinte schema para os exemplos:
model Sector {
id String @id @default(uuid) @db.Uuid
name String @unique @db.VarChar(100)
acronym String? @unique @db.VarChar(16)
is_active Boolean @default(true)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
users User[] // Relacionamento de 1:N
}
enum UserRole {
administrator
moderator
user
}
model User {
id String @id @default(uuid()) @db.Uuid
sector_id String @db.Uuid
cpf String @unique @db.Char(11)
email String? @unique @db.VarChar(255)
phone String? @db.VarChar(16)
password String @db.Char(60)
role UserRole @default(user)
is_active Boolean @default(true)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
sector Sector @relation(fields: [sector_id], reference: [id])
}
// Os timestamps precisam terminar com '_at' ou 'At' para que a omissão automática funcione (e.g. 'created_at', 'createdAt'). Particularmente eu prefiro o formato 'snake_case' para Banco de Dados.💡 Aqui definimos
PrismaElementCreate
Este utilitário gera um tipo para criação de registros, removendo metadados e permitindo ajustes finos de obrigatoriedade.
import { PrismaElementCreate } from 'prisma-power-types';
import { User } from 'generated/prisma/client';
/**
* @template T - O elemento gerado pelo Prisma;
* @template O - Omite propriedades do elemento. (padrão: never);
* @template R - Torna propriedades obrigatórias. (padrão: never);
* @template P - Torna propriedades opcionais. (padrão: never);
*/
// PrismaElementCreate<T, O, R, P>
export type IUserCreate = PrismaElementCreate<
User,
'is_active', // (opcional) Chaves que DEVEM ser para omitidas
'email', // (opcional) Chaves que DEVEM ser obrigatórias
'role' // (opcional) Chaves que DEVEM ser opcionais
>;
/**
* O resultado é:
*
* type IUserCreate = {
* cpf: string;
* email: string; // Definimos como obrigatório;
* phone?: string; // Propriedades `null` são tornadas opcionais;
* password: string;
* role?: UserRole; // Definimos como opcional;
* };
*
* As chaves `id` e os timestamps são omitidas automaticamente.
*/OBS: O
PrismaElementCreatesubstitui o tiponullporundefinedde todas as propriedades restantes, tornando-as opcionais.
PrismaElementIdentifier
Este utilitário gera um tipo que garante que uma busca seja feita exatamente pelo id OU por um campo único (como CPF ou email), mas nunca ambos ou nenhum.
import { PrismaElementIdentifier } from 'prisma-power-types';
import { User } from 'generated/prisma/client';
/**
* @template T - O elemento gerado pelo Prisma;
* @template K - As chaves únicas do elemento;
*/
// PrismaElementIdentifier<T, K>
export type IUserIdentifier = PrismaElementIdentifier<User, 'cpf' | 'email'>;
/**
* O resultado fica assim:
*
* type IUserIdentifier =
* | { id: string; cpf?: never; email?: never }
* | { id?: never; cpf: string; email?: never }
* | { id?: never; cpf?: never; email: string };
*/💡 O mais legal aqui é que é totalmente seguro a transformação de
IUserIdentifieremPrisma.UserWhereUniqueInputvia Type Assertion.
PrismaElementUpdate
Gera um tipo para atualização onde todos os campos são opcionais por padrão. Permite especificar quais campos não podem ser nulos, removendo o tipo null da união, mas mantendo o campo como opcional (?).
import { PrismaElementUpdate } from 'prisma-power-types';
import { User } from 'generated/prisma/client';
/**
* @template T - Elemento gerado pelo Prisma;
* @template O - Omite propriedades do elemento (padrão: never)
* @template N - Torna propriedades não nulas (padrão: never)
*/
// PrismaElementUpdate<T, O, N>
export type IUserUpdate = PrismaElementUpdate<
User,
'is_active', // Chaves para omitir
'email' // Chaves que NÃO podem ser nulas
>;
/**
* O resultado final fica assim:
*
* type IUserUpdate = {
* cpf?: string;
* email?: string; // `null` foi removido, mas permanece opcional.
* phone?: string | null;
* password?: string;
* role?: UserRole;
* };
*/PrismaElementOrderBy & PrismaOrderByRelation (v1.2.0)
Este utilitário transforma seus modelos em estruturas de ordenação dinâmicas. A partir da v1.2.0, ele suporta a definição de relacionamentos aninhados, permitindo ordenar uma entidade por campos de suas relações (ex: ordenar Usuários pelo nome do Setor).
PrismaOrderByRelation
Mapeia um relacionamento do Prisma associando o nome da propriedade de navegação à sua respectiva entidade e às chaves permitidas para ordenação.
/**
* @template R - Nome da propriedade de relacionamento no modelo Prisma.
* @template T - Tipo da entidade relacionada (PrismaElement).
* @template K - Chaves da entidade relacionada permitidas para ordenação.
*/
type SectorRelation = PrismaOrderByRelation<
'sector',
Sector,
'name' | 'acronym'
>;PrismaElementOrderBy
Define a estrutura de ordenação final. Ele trata automaticamente campos que aceitam null usando SortOrderInput, garantindo compatibilidade total com o motor do Prisma.
// Definindo a relação
type SectorRelation = PrismaOrderByRelation<
'sector',
Sector,
'name' | 'acronym'
>;
/**
* @template T - Elemento gerado pelo Prisma;
* @template K - As chaves ordenáveis do elemento;
* @template R - Ordenação pelas propriedades de um relacionamento (padrão: []);
*/
export type IUserOrderBy = PrismaElementOrderBy<
User,
'cpf' | 'email', // Campos locais da tabela User
[SectorRelation] // Injeção de relacionamentos (opcional)
>;
/**
* O resultado aqui seria:
* type IUserOrderBy = {
* cpf?: SortOrder;
* email?: SortOrderInput | SortOrder; // email pode ser null;
* sector?: {
* name?: SortOrder;
* acronym?: SortOrderInput | SortOrder; // acronym pode ser null;
* }
* };
*/O
Prisma.SortOrderé um Enum que aceita apenas os valores 'asc' e 'desc' (crescente e descrescente). Já oPrisma.SortOrderInputé uma interface:interface SortOrderInput { sort: SortOrder; nulls?: NullsOrder; }Ela permite definir tanto a ordenação, quanto a posição dos elementos cujo parametro de ordenamento é
null. ONullsOrdertambém é um Enum que aceita apenas 'first' ou 'last'.;
PrismaPagination
Esta é uma interface simples com page e limit pré-definidos, criada apenas para facilitar a inclusão de paginadores em filtros.
export interface IUserFilters extends PrismaPagination {
orderBy?: IUserOrderBy;
}
/**
* O IUserFilters fica assim:
*
* interface IUserFilters {
* orderBy: IUserOrderBy;
* page?: number;
* limit?: number;
* }
*/🚀 Recomendações de Uso
Para manter um projeto escalável e organizado, recomendo centralizar as transformações de tipos em arquivos dedicados e utilizá-los como contratos para seus DTOs.
Estrutura de Pastas Sugerida
Organize seus tipos por domínio dentro de cada rota. Isso evita referências circulares e facilita a localização de definições:
src/
└─ routes/
└─ users/
├─ dto/
│ ├─ users-create.dto.ts
│ └─ users-update.dto.ts
├─ types/
│ └─ users.types.ts
├─ users.service.ts
└─ users.controller.ts1. Centralize as Definições (users.types.ts)
Neste arquivo, você consome os modelos do Prisma e exporta as interfaces processadas pela biblioteca:
import { User } from 'generated/prisma/client';
import { PrismaElementCreate, PrismaElementUpdate } from 'prisma-power-types';
export type IUserCreate = PrismaElementCreate<
User,
'is_active',
'email',
'role'
>;
export type IUserUpdate = PrismaElementUpdate<User, 'is_active', 'email'>;2. Implemente nos DTOs (users-create.dto.ts)
Ao implementar os tipos gerados no seu DTO, o TypeScript garantirá que sua classe de validação (no exemplo estou usando o class-validator) esteja sempre em sincronia com as regras de negócio definidas nos seus tipos.
import {
IsEmail,
IsEnum,
IsString,
IsOptional,
IsPhoneNumber,
IsStrongPassword
Length,
} from 'class-validator';
import { UserRole } from 'generated/prisma/client';
import { IUserCreate } from '../types/users.types';
export class CreateUserDto implements IUserCreate {
@IsString()
@Length(11, 11)
cpf: string;
@IsEmail()
email: string; // O TS exigirá que seja obrigatório conforme IUserCreate
@IsOptional()
@IsPhoneNumber('BR')
phone?: string;
@IsStrongPassword()
password: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole; // O TS exigirá que seja opcional conforme IUserCreate
}Vantagem desta abordagem: Se você alterar a obrigatoriedade de um campo no arquivo de tipos, o TypeScript apontará imediatamente um erro no seu DTO, evitando que você esqueça de atualizar as validações de entrada da API.
🧬 Estrutura Base
Para utilizar os utilitários, seus modelos devem ser compatíveis com a interface PrismaElement:
export interface PrismaElement {
id: string | number;
[key: string]: unknown;
}🔍 Utilitários de String (Low-level)
Filtros por Nome de Chave
Utilizam Template Literal Types para filtrar propriedades do objeto dinamicamente.
| Utilitário | Descrição |
| ----------------------- | -------------------------------------------------------------- |
| PickByPrefix<T, S> | Seleciona propriedades cujas chaves começam com o prefixo S. |
| PickBySubstring<T, S> | Seleciona propriedades cujas chaves contêm a substring S. |
| PickBySuffix<T, S> | Seleciona propriedades cujas chaves terminam com o sufixo S. |
| OmitByPrefix<T, S> | Remove propriedades cujas chaves começam com o prefixo S. |
| OmitBySubstring<T, S> | Remove propriedades cujas chaves contêm a substring S. |
| OmitBySuffix<T, S> | Remove propriedades cujas chaves terminam com o sufixo S. |
Todos seguem o padrão:
<T extends object, S extends string>.
type OnlyTimestamps = PickBySuffix<User, '_at'>;
/**
* type OnlyTimestamps = {
* created_at: Date;
* updated_at: Date;
* deleted_at: Date | null;
* }Filtros por Tipo de Valor
| Utilitário | Descrição |
| --------------------- | ---------------------------------------------------------------- |
| PickByType<T, Type> | Seleciona propriedades onde o valor é atribuível ao tipo Type. |
| OmitByType<T, Type> | Remove propriedades onde o valor é atribuível ao tipo Type. |
Os dois seguem o padrão:
<T extends object, Type>.
// Seleciona apenas campos que podem ser nulos
type NullableFields = PickByType<User, null>;
/**
* type NullableFields = {
* email: string | null;
* deleted_at: Date | null;
* }
*/Utilitários de Composição
| Utilitário | Descrição |
| ----------------- | ------------------------------------------------------------------------------------------ |
| PickOneOf<T, K> | Cria um tipo onde exatamente uma das chaves em K é obrigatória, proibindo as outras. |
| PickReq<T, K> | Seleciona as chaves K e as torna obrigatórias. |
| PickOpt<T, K> | Seleciona as chaves K e as torna opcionais. |
Prettify
Esse tipo merece um destaque especial por ser o segredo por trás da experiência do desenvolvedor (DX) nesta biblioteca.
Ao trabalhar com utilitários complexos como Omit, Pick e interseções (&), o TypeScript tende a exibir o "processo" e não o "resultado" no IntelliSense da sua IDE. Em vez de ver as propriedades reais do objeto, você acaba vendo algo como Pick<User, "name"> & { role?: UserRole }. O Prettify resolve isso "achatando" o tipo em um objeto literal único e legível.
O que o torna único:
Preservação de Tipos Nativos: Diferente de versões simplificadas, nossa implementação não tenta "expandir" objetos nativos como
Date,Map,Set,PromiseouRegExp. Isso evita que sua IDE exiba centenas de propriedades internas globais do JavaScript.Recursividade Inteligente: Ele percorre toda a árvore de objetos, limpando tipos aninhados para que a legibilidade seja mantida em qualquer profundidade.
Controle de Exclusão (Template E): Este é o grande diferencial. O parâmetro genérico
Epermite definir tipos customizados que não devem ser expandidos.
Exemplo: Se você tem um tipo Decimal (muito comum no Prisma para campos monetários), você pode passar Prettify<SeuTipo, Decimal>. Isso instrui o utilitário a tratar o Decimal como um valor atômico, impedindo que a IDE tente detalhar sua estrutura interna complexa.
💡 Dicas de Uso
Otimizando a Visualização (DX)
O TypeScript tende a mostrar tipos complexos como Pick<User, "name"> & Omit<...> ao passar o mouse. Todos os utilitários de alto nível desta biblioteca já utilizam o Prettify internamente para garantir que você veja a estrutura final do objeto.
🤝 Contribuição
Contribuições são muito bem-vindas! Se você encontrou um bug ou tem uma ideia para um novo utilitário:
- Faça um Fork do projeto.
- Crie uma Branch para sua feature (
git checkout -b feature/minha-feature). - Faça o Commit das alterações (
git commit -m 'Adicionando nova funcionalidade'). - Envie o Push para a branch (
git push origin feature/minha-feature). - Abra um Pull Request.
📄 Licença
Distribuído sob a licença MIT.
- Você é livre para utilizar essa biblioteca em projetos pessoais ou empresariais;
- Fique a vontade para modificar os códigos de acordo com suas necessidades;
- Você também pode distribuir versões modificadas desse projeto, só peço que mencione este repositório.
Criado por Bianca Maxine.
Se este pacote te ajudou, considere dar uma ⭐️ no Repositório do GitHub!
