1

is-kit chegou a 100 stars ⭐🎉 O que mudou desde os 50?

Hoi hoi!

YAY! O is-kit acabou de alcançar 100 stars no GitHub⭐ 🎉🎉🎉

Quando chegou a 50 stars, escrevi sobre como usamos a biblioteca em uma aplicação TypeScript em produção que atende mais de 100.000 usuários. 👇

https://dev.to/nyaomaru/is-kit-reached-50-stars-heres-how-we-use-it-in-production-2i5b

Eu poderia contar a mesma história outra vez. Mas seria sem graça. 😹

Muita coisa aconteceu desde então.

A biblioteca ganhou novas APIs, algumas APIs existentes ficaram mais rigorosas, comecei a usá-la em outros projetos OSS meus, e um caso de uso real chegou a revelar um caso-limite de inferência de tipos no Effect.

Então, em vez de explicar novamente seu uso em produção, desta vez quero falar sobre:

  • O que mudou entre 50 e 100 stars
  • Algumas das APIs mais importantes adicionadas nesse período
  • Como estou usando is-kit fora da aplicação original em produção
  • Um bug real envolvendo Effect.catchIf
  • O que 100 stars significam para mim hoje

Vamos lá! 🔧

is-kit 100 stars


⭐ Primeiro: o que significavam 50 stars?

Quando o is-kit chegou a 50 stars, a principal coisa que eu queria mostrar era que ele não era apenas um projeto de brincadeira.

Já estava sendo usado em uma aplicação real em produção. 🚀

A aplicação repetia verificações de runtime como:

typeof value === "string";
typeof value === "number";
value === null || value === undefined;

e também tinha vários type guards maiores, definidos pela aplicação, para coisas como erros HTTP, uniões de literais, arrays, valores de API e objetos comuns.

O interessante não era simplesmente o is-kit deixar essas verificações mais curtas.
O interessante era dar a elas uma forma reutilizável.

Pequenos guards podiam ser compostos.

Helpers da própria aplicação podiam ficar entre a biblioteca e o código das funcionalidades. E o resultado continuava funcionando com o fluxo de controle normal do TypeScript.

Isso não mudou.

Mas, desde os 50 stars, a própria biblioteca avançou bastante. 👀


🔑 Refinamento de propriedades virou um caso de uso de primeira classe

Eu vivia esbarrando nesta situação:

Eu já tenho um bom type guard.
Só quero aplicá-lo a uma propriedade.

Por exemplo:

declare const input: unknown;

Talvez eu já saiba validar uma string:

import { isString } from "is-kit";

Mas agora quero expressar:

“Este objeto tem uma propriedade name, e essa propriedade é uma string.”

Parece simples, mas existem alguns casos distintos.

Uma propriedade obrigatória não é a mesma coisa que uma propriedade opcional.
Um índice de array não é a mesma coisa que uma propriedade de objeto.

E uma propriedade opcional que sabemos estar definida deve ter um narrowing diferente do de uma que continua opcional.

Por isso, is-kit adicionou três helpers de refinamento de propriedades:

  • refineKey
  • refineDefinedKey
  • refineIndex

A ideia é simples:

Pegue um type guard em que você já confia e aplique-o a uma propriedade ou elemento de array.

Por exemplo:

import { isString, refineKey } from "is-kit";

const hasStringName = refineKey("name", isString);

Agora o guard de string pode ser reutilizado em um nível mais profundo, em vez de ser reescrito dentro de outro predicate customizado.

É uma API pequena, mas gosto dela porque segue o mesmo objetivo de design do restante de is-kit.

Não construir um validador gigante.

Construir pequenas verificações que possam ser reutilizadas no código TypeScript comum.


🔍 Depois, discriminatedUnion ficou muito mais robusto

Provavelmente a maior novidade desde o artigo dos 50 stars foi a API exaustiva de discriminated unions, introduzida na v1.15.0.

Considere este tipo comum de TypeScript:

type Result = { ok: true; value: string } | { ok: false; error: string };

Em runtime, validá-lo não é difícil.

Podemos verificar ok e então validar os campos correspondentes.

Mas existe outra pergunta:

O TypeScript consegue verificar se o próprio validador cobre toda a união?

É aí que entra discriminatedUnion.

import { discriminatedUnion, isString, oneOfValues, typedStruct } from "is-kit";

type Result = { ok: true; value: string } | { ok: false; error: string };

const resultUnion = discriminatedUnion<Result>();

export const isResult = resultUnion("ok", [true, false], {
  true: typedStruct<Extract<Result, { ok: true }>>()({
    ok: oneOfValues(true),
    value: isString,
  }),
  false: typedStruct<Extract<Result, { ok: false }>>()({
    ok: oneOfValues(false),
    error: isString,
  }),
});

O comportamento em runtime é útil.

Mas a parte mais interessante é o contrato em tempo de compilação.

O builder verifica coisas como:

  • Branches de discriminante ausentes
  • Branches extras
  • Guards incompatíveis com seus branches
  • Tuplas de discriminantes incompletas

Assim, se a união mudar, o guard não poderá continuar incompleto silenciosamente.

Isso importa porque uma das maneiras mais fáceis de tornar um type guard escrito à mão perigoso é o drift.

O tipo TypeScript evolui.
O runtime guard não.
E normalmente o compilador não consegue avisar que eles deixaram de corresponder.

discriminatedUnion é uma tentativa de dificultar esse tipo de erro.


💪 A biblioteca também ficou mais rigorosa

Nem toda melhoria entre 50 e 100 stars foi uma funcionalidade nova.

Boa parte do trabalho consistiu em tornar o comportamento existente mais preciso.

Por exemplo, releases recentes trouxeram correções para:

  • Narrowing de chaves literais em recordOf
  • Verificações de marca de Error nativo
  • Guards de array com iterators sobrescritos
  • Validação exata com struct
  • Schemas struct com chaves symbol
  • Schemas struct entre realms diferentes

Também aumentou o número de testes no nível do pacote.

Agora is-kit verifica o comportamento do pacote publicado tanto com consumers TypeScript ESM quanto CommonJS, e também conta com smoke tests para Deno / JSR.

Essa parte é menos empolgante de mostrar num snippet.

Mas, para uma biblioteca pequena, acho importante.

É garantir que as coisas funcionem corretamente em ambientes JavaScript e TypeScript esquisitos que torna tudo real.


🤖 Também comecei a usar is-kit em outros projetos OSS

Desde os 50 stars, comecei a usar is-kit com mais naturalidade em outros projetos OSS que mantenho.

Por exemplo, nas ferramentas de changelog do changelog-bot, uso define para manter pequenos predicates reutilizáveis e combináveis, em vez de transformar cada verificação de runtime em um helper isolado.

Um exemplo simplificado é este 👇

import { define, oneOfValues } from "is-kit";

const CHANGELOG_SECTIONS = ["Added", "Changed", "Fixed", "Removed"] as const;

const isChangelogSection = define<(typeof CHANGELOG_SECTIONS)[number]>(
  oneOfValues(CHANGELOG_SECTIONS),
);

O código exato não é o mais importante.

O que mudou para mim foi deixar de pensar:

“Eu deveria usar minha biblioteca aqui.”

Agora simplesmente pego o guard de que preciso.

Uma biblioteca começa a ser útil até para quem a mantém quando deixa de parecer uma demonstração de si mesma.


🚀 Então um guard de is-kit encontrou um bug de tipos no Effect

Foi uma das coisas mais interessantes que aconteceram recentemente.

Um usuário de is-kit estava usando um type guard com catchIf do Effect.

Conceitualmente, o refinamento era assim 👇

(error: unknown): error is SomeError => ...

Isso é completamente normal para is-kit.

Na verdade, unknown é exatamente o ponto de partida para o qual a biblioteca foi projetada.

Mas, com Effect.catchIf, esse refinamento podia afetar a inferência de tipos genéricos de forma inesperada.

O canal de erro podia acabar ampliado para unknown.

O caso reportado levou a este PR no Effect 👏 E ele foi aceito e integrado! 🚀

https://github.com/Effect-TS/effect/pull/9252

O tipo relevante originalmente era aproximadamente este:

refinement: Predicate.Refinement<E, EB>;

e a correção mudou para:

refinement: Predicate.Refinement<NoInfer<E>, EB>;

A ideia central é que o tipo de entrada do refinamento não deveria poder redefinir o tipo de erro já estabelecido pelo Effect.

O PR também adicionou testes de tipo para casos como:

const result = Effect.catchIf(
  mixedEffect,
  (e: unknown): e is AiError => e instanceof AiError,
  () => Effect.succeed("ok"),
);

Sem a correção, a entrada do refinamento podia participar da inferência e ampliar o canal de erro.

Com NoInfer, o tipo original de erro continua sendo a fonte da verdade.

O mesmo padrão foi corrigido para Channel e Stream.

Também houve um post no Bluesky sobre o problema:

https://bsky.app/profile/evadeva.bsky.social/post/3mxwiqfeo3s2j

Gostei muito desse bug porque ele surgiu de uma interação que eu provavelmente nunca descobriria testando apenas o is-kit.

is-kit não “quebrou o Effect”. E o Effect também não estava fazendo algo absurdo.

Foi uma interação sutil de inferência do TypeScript entre uma API genérica e um refinamento cuja entrada era intencionalmente ampla.

É exatamente o tipo de edge case que aparece quando bibliotecas começam a se encontrar em código real.


👍 Era esse tipo de feedback que eu queria

Quando is-kit chegou a 50 stars, escrevi:

“Alguém achou esta ideia útil.”

Isso já era suficiente para me deixar feliz.

100 stars ainda não significam que a biblioteca seja enorme.

Uma star continua não sendo um benchmark.
Também não é prova de correção.
E definitivamente não significa que todo projeto TypeScript deveria instalar is-kit.

Mas alguma coisa mudou.

As pessoas não estão apenas dando stars no repositório.

Elas estão:

  • Usando a biblioteca
  • Fazendo perguntas
  • Encontrando edge cases
  • Reportando bugs
  • Comparando com outras abordagens de validação
  • Usando seus guards com outras bibliotecas TypeScript
  • Fazendo-me repensar APIs que eu considerava prontas

Isso vale muito mais que o número em si. 💖


💬 O projeto também ficou mais fácil de explicar

Outra coisa mudou entre 50 e 100 stars.

Ficou mais claro para mim o que is-kit não é.

Ele não tenta substituir todas as schema libraries.
Ele não tenta transformar todo valor de runtime em um workflow centrado em schema.
E não tenta esconder o fluxo de controle normal do TypeScript.

A pergunta central continua pequena:

“Este valor unknown pode entrar com segurança neste branch?”

Às vezes uma schema library é a escolha certa.

Por exemplo, se eu preciso de:

  • Erros de validação ricos
  • Transformações
  • Validação de formulários
  • Um contrato compartilhado entre sistemas

Talvez eu prefira Zod, Valibot, ArkType ou outra opção.

Mas às vezes já tenho código TypeScript normal e só preciso de uma função reutilizável de narrowing.

É aí que quero que is-kit seja útil.

if (isUser(value)) {
  value.id;
}

if (isHttpClientError(error)) {
  // Handle HTTP client errors
}

items.filter(isString);

result.catch(isHttpClientError);

const isMaybeNumber = nullish(isNumber);

Pequenas decisões em runtime.
Funções normais.
TypeScript normal.


🐱 O que 100 stars significam para mim

100 stars ainda é pouco perto dos maiores projetos OSS de TypeScript.

Sem problema.

Para mim, o importante é que o projeto está ficando mais real de maneiras que não têm nada a ver com o contador de stars.

Desde o marco de 50 stars:

  • Novas APIs foram adicionadas
  • APIs existentes ficaram mais rigorosas
  • Edge cases de runtime foram corrigidos
  • Os testes de compatibilidade do pacote melhoraram
  • Passei a usar is-kit naturalmente em outros projetos OSS
  • O uso externo trouxe feedback que eu não conseguiria produzir sozinho
  • Um refinamento baseado em unknown até ajudou a revelar um edge case de inferência no Effect

Essa última parte provavelmente é minha favorita.

Porque o objetivo nunca foi criar uma biblioteca que só fica bonita no próprio README!!!

O objetivo é fazer com que o runtime narrowing reutilizável funcione naturalmente em código TypeScript real.

E agora estou começando a ver isso acontecer fora do meu próprio repositório também.

Obrigado a todos que deram uma star, experimentaram o projeto, relataram algo, fizeram uma pergunta, abriram uma issue ou simplesmente leram o código. 🙏

Ainda há muita coisa que quero melhorar.

Mas chegar a 100 é uma sensação muito boa. 😸⭐

Se pequenos type guards combináveis fazem sentido para seu estilo de TypeScript, experimente!

https://github.com/nyaomaru/is-kit

Obrigado pela leitura! 🤲

Carregando publicação patrocinada...