Executando verificação de segurança...
0
lucax
1 min de leitura ·

[TypeScript][Ajuda]

Poderiam me ajudar a melhorar esse código?

O meu objetivo com essa função é poder fazer um get em qualquer collection do banco de dados a partir de uma Unique-Key.

O meu maior problema foi com a tipagem dessa função.

(Os nomes das variaveis tbm n são os melhores)

import { db } from "@/db"
import { TransactionInterface, UserInterface } from "@/interfaces"

type CollectionsData = {
	users: UserInterface
	transactions: TransactionInterface
}

type UniqueUserKeys = Pick<UserInterface, "_id" | "email">
type UniqueTransactionKeys = Pick<TransactionInterface, "_id" | "userId">

type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<
	T,
	Exclude<keyof T, Keys>
> &
	{
		[K in Keys]-?: Required<Pick<T, K>> &
			Partial<Record<Exclude<Keys, K>, undefined>>
	}[Keys]

type UniqueCollectionsKeys = {
	users: RequireOnlyOne<UniqueUserKeys>
	transactions: RequireOnlyOne<UniqueTransactionKeys>
}

type UniqueCollectionsKeysData = {
	[Key in keyof UniqueCollectionsKeys]: {
		collectionName: Key
		uniqueKey: UniqueCollectionsKeys[Key]
	}
}[keyof UniqueCollectionsKeys]

const getByUnique = async <T extends keyof CollectionsData>(
	collection: UniqueCollectionsKeysData
): Promise<CollectionsData[T] | null> => {
	const result = await db
		.collection(collection.collectionName)
		.findOne(collection.uniqueKey)
	return result || null
}

export default {
	getByUnique,
}

Carregando publicação patrocinada...
1

Qual específicamente foi o seu problema? Foi na parte de exigir apenas um campo de usuário? Eu já tive um problema com isso e resolvi utilizando libs de validação como o zod.

1

Eu diria que, tanto na parte de, por exemplo, ao passar uma coleção X, ela terá Y e Z como chaves únicas válidas. Já ao passar uma coleção Y, essas chaves seriam diferentes. Não sei se haveria uma maneira mais limpa de fazer isso do que eu fiz utilizando o UniqueCollectionsKeysData. Além disso, o que você mencionou também vejo como um problema cuja solução ficou bem extensa, porque quero que apenas seja possível passar uma chave das várias possíveis dentro de .findOne(collection.uniqueKey), e a única maneira que encontrei de fazer isso foi com o RequireOnlyOne.

1

Eu te diria que esse esforço é inútil. No final das contas tudo será javascript e os tipos irão desaparecer no momento que a aplicação estiver rodando. A tipagem do Typescript não faz nenhuma validação de dados e é perigoso acreditar nisso.

Basicamente o que você quer é garantir que se a função receber um parâmetro X, só sejam válidas as chaves B e, se receber um parâmetro Y, só serão válidas as chaves C. Contudo fazer isso com typescript é um esforço inútil, já que no momento em que o programa estiver rodando você poderá enviar o Parâmetro X com uma chave B sem nenhum tipo de erro ou problema.

Por isso falei em utilizar libs de validação como o zod. A solução que você quer ficaria mais ou menos assim com o zod:

import { z } from 'zod'

const schema = z.object({
    // campos do objeto aqui
}).superRefine(() => {
    // lógica que você quer aqui
)

type seuTipo = z.infer<typeof schema>

Para ficar mais simples de entender vou dar como exemplo o caso onde eu utilizei essa solução:

import { z } from 'zod'

export const EditUserDTOSchema = z
  .object({
    id: z.string({ required_error: 'user id is required' }).uuid(),
    name: z
      .string()
      .nonempty({ message: 'user name cannot be empty' })
      .max(30, { message: 'user name max length is 30' })
      .optional(),
    lastName: z
      .string()
      .nonempty({ message: 'user name cannot be empty' })
      .max(100, { message: 'user name max length is 100' })
      .optional(),
    password: z
      .string()
      .min(8, { message: 'user password min length is 8' })
      .max(60, 'user password max length is 60')
      .optional()
  })
  .superRefine((val, ctx) => {
    if (!val.name && !val.lastName && !val.password)
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'At least one user optional key must be provided',
        fatal: true
      })
  })
  
  export type EditUserDTO = z.infer<typeof EditUserDTOSchema>

Nesse caso eu queria fazer com que pelo menos um dos parâmetros opcionais fossem obrigatórios na hora de receber o objeto, parecido com o que você está querendo. O ChatGPT me deu uma solução idêntica a que você utiliza no tipo RequireOnlyOne, mas não funcionava.

A vantagem é que dessa forma eu não tenho apenas o tipo, mas também uma forma de validar diretamente esses erros, onde, caso eu não receba nenhum dos três parâmetros opcionais, um erro será retornado, que é o comportamento que eu quero.

Talvez exista uma forma mais simples de resolver o problema da tipagem com intersection types ou union tipes, mas é como eu disse no início, no final esse esforço vai ser inútil.

1

Não diria que é inútil, pois esses dados não serão recebidos diretamente do usuário, mas sim de um desenvolvedor que utilizará a função. Limitar as opções que o desenvolvedor pode passar como parâmetro pode reduzir significativamente o número de erros.

Entendo as preocupações levantadas sobre o TypeScript e suas limitações em runtime, mas é importante lembrar que a tipagem está mais voltada para o desenvolvimento e manutenção do código. Ela ajuda a tornar o código mais legível e seguro durante a fase de desenvolvimento, permitindo que o dev saiba quais tipos de dados esperar e como utilizá-los corretamente.

Além disso, a introdução de uma nova biblioteca para uma única função de validação pode aumentar a complexidade do projeto, gerar dependências desnecessárias e potencialmente dificultar a manutenção do código a longo prazo. Não sei se seria a melhor abordagem nesse caso!

1

se não quer adicionar a biblioteca pode fazer uma função que faça essa validação. A maior vantagem do Zod nesse caso seria para reduzir a complexidade na validação. Se esse for um caso crítico (como no meu exemplo), deve-se retornar um erro se o desenvolvedor adicionar os parâmetros errados na função.

Para reduzir significativamente o tipo de erros não teria jeito, apenas com testes unitários seria possível fazer isso efetivamente, partindo de uma boa cobertura desses testes. A complexidade envolvida em desenvolver essa tipagem não é justificada por nenhum ganho na legibilidade do código, até porque diminui ela.

Nesse caso específico uma biblioteca como zod garantiria muito mais a legibilidade e facilitaria a manutenção do código, além de que poderia ser utilizada em outras partes do projeto. Eventualmente uma aplicação que lida diretamente com IO precisa de validação e fazer tudo na mão é aumentar muito a complexidade.