dev.to

is-kit Reached 100 Stars ⭐🎉 Here’s What Changed Since 50

is-kit GitHub 별 100개 달성, 50개 이후 달라진 점

TypeScript 타입 가드 라이브러리 is-kit이 GitHub 별 100개를 달성했습니다. 글에서는 속성 좁히기 API와 판별 유니온 검사를 추가하고 런타임 예외를 보완한 과정, 외부 사용이 Effect의 타입 추론 문제를 드러낸 사례를 소개합니다.

에디터 노트

이 라이브러리의 차별점은 검증 스키마를 새로 세우기보다 기존 TypeScript 코드에 작은 타입 가드를 끼워 넣는 데 있습니다. 속성 단위 보조 함수와 discriminatedUnion은 각각 재사용성과 타입 정의 변경에 따른 가드 누락을 겨냥합니다. 특히 분기 누락을 컴파일 단계에서 잡는다는 설명은 타입과 런타임 검사가 어긋나는 구체적인 위험에 대응합니다. Effect 사례도 두 라이브러리가 실제 코드에서 만날 때 타입 추론 문제가 생길 수 있음을 보여줍니다. 다만 해결은 is-kit의 가드 자체가 아니라 Effect API의 NoInfer 적용이며, 글은 수정 테스트 범위와 재현 조건도 제시합니다. 댓글은 이 상호작용과 분기 검사의 필요성에 공감하지만, 별 100개나 댓글 반응이 정확성의 증거가 되지는 않습니다. 글도 별 수와 정확성을 구분하며, 실제 사용에서 나온 제보가 테스트만으로는 찾기 어려운 문제를 드러냈다는 점을 성과로 제시합니다.

AI 요약

is-kit은 런타임 값을 검사하는 작은 TypeScript 타입 가드를 조합해 재사용하는 라이브러리입니다. 50개 별을 달성했을 때는 10만 명이 넘는 사용자가 있는 프로덕션 TypeScript 애플리케이션에서 사용한다는 점을 소개했습니다. 이번 글은 그 뒤 추가된 API와 실제 사용 사례, Effect와 함께 쓰며 발견한 타입 추론 문제를 정리합니다.

속성 단위로 기존 가드 재사용

새 API refineKey, refineDefinedKey, refineIndex는 이미 있는 타입 가드를 객체 속성이나 배열 요소에 적용합니다. 예를 들어 refineKey("name", isString)으로 name 속성이 문자열인지 검사하는 가드를 만들 수 있습니다. 필수 속성과 선택 속성은 좁혀지는 방식이 다르고, 배열 인덱스와 객체 속성도 같지 않으므로 각각에 맞는 API를 둡니다. 선택 속성이 정의된 경우와 여전히 선택적인 경우의 타입 구분도 반영합니다. 목적은 큰 검증기 하나를 만드는 대신 작은 검사를 기존 TypeScript 코드 흐름 안에서 조합하는 것입니다.

판별 유니온의 누락된 분기를 컴파일 단계에서 검사

v1.15.0에서 추가한 discriminatedUnion은 판별 유니온의 각 분기를 검증하는 가드를 구성합니다. 글의 예시는 ok가 true이면 value가 문자열이고, false이면 error가 문자열인 Result 타입입니다. 빌더는 판별자별 분기 누락이나 추가, 분기 가드와 타입의 불일치, 불완전한 판별자 튜플을 검사합니다. 타입이 바뀌어도 직접 작성한 가드가 예전 상태로 남으면 런타임 검증과 타입 정의가 어긋날 수 있는데, 컴파일 단계에서 이 불일치를 드러내려는 설계입니다.

런타임 경계 사례와 패키지 테스트

기존 API도 더 엄격하게 다듬었습니다. recordOf의 리터럴 키 좁히기, 네이티브 Error의 브랜드 검사, 반복자가 재정의된 배열 가드, 정확한 struct 검증, 심볼 키와 다른 realm의 struct 스키마 처리 등을 수정했습니다. 패키지는 ESM과 CommonJS TypeScript 소비자 환경에서 테스트하고 Deno와 JSR 스모크 테스트도 추가했습니다.

작성자는 changelog-bot에서도 define과 oneOfValues를 사용해 changelog 항목 검사 함수를 재사용합니다. 라이브러리를 따로 시연하는 예제가 아니라 다른 오픈소스 프로젝트의 일반 코드에서 필요한 가드를 골라 쓰게 된 점을 사용 방식의 변화로 설명합니다.

Effect catchIf에서 드러난 추론 문제

사용자가 is-kit 가드를 Effect의 catchIf에 전달했을 때, 입력 타입을 unknown으로 받는 refinement가 Effect의 오류 타입까지 unknown으로 넓히는 문제가 발견됐습니다. 원인은 refinement 입력 타입이 이미 정해진 오류 타입의 추론에 끼어들 수 있었던 점입니다. Effect 쪽 수정은 Predicate.Refinement<E, EB>에서 입력 타입에 NoInfer<E>를 적용해 refinement가 기존 오류 타입을 다시 추론하지 않도록 했습니다. 같은 방식이 Channel과 Stream에도 적용됐습니다.

수정 검증에서는 TypeScript 5.9.3과 6.0.3에서 Channel, Effect, Stream 타입 테스트를 통과했고, 구현을 되돌리면 특정 사용 방식에서 오류 채널이 unknown으로 넓어지는 현상을 재현했습니다. 글은 이를 is-kit이나 Effect 하나의 독립적인 결함이라기보다 두 라이브러리가 실제 코드에서 만날 때 드러난 TypeScript 추론 경계 사례로 설명합니다.

100개 별의 의미

작성자는 별 수가 정확성이나 성능의 증거는 아니라고 선을 긋습니다. 대신 사용자가 라이브러리를 쓰고 질문과 버그를 제보하면서, 작성자가 혼자 테스트할 때는 찾기 어려운 상호작용이 드러났다는 점을 의미 있게 봅니다. is-kit은 풍부한 검증 오류, 변환, 폼 검증, 시스템 간 공유 계약이 필요한 경우 Zod, Valibot, ArkType 같은 스키마 라이브러리를 대체하려는 도구가 아닙니다. 이미 있는 TypeScript 코드에서 unknown 값이 특정 분기로 들어가도 안전한지 검사하고 싶을 때 작은 가드를 조합하는 용도를 지향합니다.

dev.to 반응

  • @sylwia-lask — 100개가 넘는 별을 받은 저장소를 늘 꿈꿨습니다. 정말 멋집니다. 축하합니다.
    • @nyaomaru — 감사합니다. 별 100개를 달성한 건 처음이라 정말 기쁩니다.
  • @technogamerz — 축하합니다, 친구!
    • @nyaomaru — 감사합니다, 친구!
    • @technogamerz — 다음 목표는 200개 별입니다. 행운을 빕니다.
  • @akhourianmolkumar — 축하합니다, GitHub의 달인이시군요.
  • @shouvik12 — 앞으로도 계속 잘되길 바랍니다. 축하합니다.
    • @shouvik12 — Dev.to의 유명 인사가 되어가시네요.
  • @johnnylemonny — 축하합니다.
  • @francistrdev — 별 100개라니 대단합니다. 잘하셨고 글 표지 GIF도 마음에 듭니다.
  • @amintacccp — 제가 기억할 부분은 Effect catchIf 사례입니다. refinement가 오류 채널을 조용히 unknown으로 넓히는 문제는 타입 가드가 막아야 할 실패 방식인데, 어느 한쪽 라이브러리의 테스트가 아니라 두 라이브러리가 실제 코드에서 만났을 때 드러났습니다. NoInfer 수정도 적절합니다. 앞 단계에서 정해진 타입을 refinement 입력이 다시 결정하게 두면 안 됩니다. discriminatedUnion의 컴파일 단계 검사도 같은 문제를 다른 쪽에서 다룹니다. 타입은 바뀌는데 직접 작성한 가드는 그대로 남고 컴파일러는 보통 알려주지 않습니다. 분기 누락이나 추가를 거부하는 빌더가 필요한 이유입니다. 실제 경계 사례가 들어오기 시작하는 시점에 100개 별을 달성한 것을 축하합니다.

원문: dev.to / 번역·요약: Trawling