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-kitfora da aplicação original em produção - Um bug real envolvendo
Effect.catchIf - O que 100 stars significam para mim hoje
Vamos lá! 🔧

⭐ 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:
refineKeyrefineDefinedKeyrefineIndex
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
Errornativo - Guards de array com iterators sobrescritos
- Validação exata com
struct - Schemas
structcom chavessymbol - Schemas
structentre 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
unknownpode 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-kitnaturalmente em outros projetos OSS - O uso externo trouxe feedback que eu não conseguiria produzir sozinho
- Um refinamento baseado em
unknownaté 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! 🤲