npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

spec-pattern-ts

v3.2.19

Published

Specification pattern for TypeScript

Readme

spec-pattern-ts

NPM Version License: MIT TypeScript Zero Dependencies

완벽한 조합이 가능한 TypeScript Specification Pattern 구현

API 문서 | 프로젝트 구조 | 마이그레이션 가이드 | Codex 스킬 | 예제

목차

특징

v3의 핵심 개선사항

  • 🔗 완벽한 조합성: 모든 스펙을 무제한으로 조합 가능
  • 🛡️ 타입 안전성: TypeScript의 강력한 타입 시스템 활용
  • 🚀 제로 의존성: 외부 라이브러리 없이 독립적으로 동작
  • 📦 작은 번들 크기: 최적화된 코드로 빠른 로딩
  • 🎯 직관적인 API: 비즈니스 로직을 자연스럽게 표현
  • 🔧 유연한 모듈 시스템: CommonJS, ES Modules, UMD 지원

설치

# npm
npm install spec-pattern-ts

# yarn
yarn add spec-pattern-ts

# pnpm
pnpm add spec-pattern-ts

# bun
bun add spec-pattern-ts

# gitlab registry (project-level)
# <PROJECT_ID>와 <GITLAB_NPM_TOKEN>을 자신의 값으로 변경
echo "registry=https://git.hyeon.pro/api/v4/projects/<PROJECT_ID>/packages/npm/" > .npmrc
echo "//git.hyeon.pro/api/v4/projects/<PROJECT_ID>/packages/npm/:_authToken=<GITLAB_NPM_TOKEN>" >> .npmrc
npm install spec-pattern-ts

Codex 스킬 설치

GitLab 릴리즈의 latest 스킬을 내려받아 Codex 스킬 디렉터리에 설치합니다.

# curl 사용 (latest)
curl -L -o spec-pattern-ts-dev.skill \
  "https://git.hyeon.pro/hansanghyeon/spec-pattern-ts/-/releases/latest/downloads/spec-pattern-ts-dev.skill"
mkdir -p ~/.codex/skills
unzip spec-pattern-ts-dev.skill -d ~/.codex/skills
# wget 사용 (latest)
wget -O spec-pattern-ts-dev.skill \
  "https://git.hyeon.pro/hansanghyeon/spec-pattern-ts/-/releases/latest/downloads/spec-pattern-ts-dev.skill"
mkdir -p ~/.codex/skills
unzip spec-pattern-ts-dev.skill -d ~/.codex/skills

빠른 시작

기본 사용법

import { Spec } from 'spec-pattern-ts'

// 타입 정의
interface User {
  age: number
  role: string
  status: string
}

// 스펙 정의
const isAdult = Spec('user', (u: User) => u.age >= 18)
const isMember = Spec('user', (u: User) => u.role !== 'guest')
const isActive = Spec('user', (u: User) => u.status === 'active')

// 조합
const canAccess = isAdult.and(isMember).and(isActive)

// 사용
const user = { age: 25, role: 'member', status: 'active' }

if (canAccess.is({ user })) {
  // 접근 허용
}

복잡한 비즈니스 로직

import { Spec, CompositeSpec, allOf, anyOf, not } from 'spec-pattern-ts'

// 권한 시스템
const isAdmin = Spec('user', u => u.role === 'admin')
const isOwner = CompositeSpec<{ user: User; resource: Resource }>(
  ctx => ctx.resource.ownerId === ctx.user.id
)

// 접근 권한 - 관리자이거나 소유자
const canEdit = isAdmin.or(isOwner)

// 구매 조건 - 로그인하고 성인이며 결제수단이 있어야 함
const canPurchase = allOf(
  Spec('user', u => u.isLoggedIn),
  Spec('user', u => u.age >= 18),
  Spec('payment', p => p.method !== null)
)

// 할인 조건 - VIP이거나 첫 구매이거나 특별 쿠폰 보유
const eligibleForDiscount = anyOf(
  Spec('user', u => u.tier === 'vip'),
  Spec('order', o => o.isFirstPurchase),
  Spec('coupon', c => c.type === 'special')
)

핵심 개념

1. 단일 책임 스펙

각 스펙은 하나의 비즈니스 규칙만 담당합니다:

const isAdult = Spec('age', (age: number) => age >= 18)
const hasEmail = Spec('email', (email: string) => email.includes('@'))
const isVerified = Spec('verified', (verified: boolean) => verified)

2. 스펙 조합

복잡한 규칙은 간단한 스펙들의 조합으로 표현합니다:

// AND 조합
const validUser = isAdult.and(hasEmail).and(isVerified)

// OR 조합
const canComment = isMember.or(isGuest.and(hasEmail))

// NOT 조합
const notBanned = not(isBanned)

// 복잡한 중첩
const complexRule = isAdmin.or(
  isMember.and(isVerified).and(notBanned)
)

3. 타입 안전성

TypeScript가 필요한 모든 속성을 추적합니다:

const userSpec = Spec('user', (u: User) => u.active)
const productSpec = Spec('product', (p: Product) => p.available)

const combined = userSpec.and(productSpec)
// 타입: ISpecification<{ user: User } & { product: Product }>

// ✅ 올바른 사용
combined.is({ user: myUser, product: myProduct })

// ❌ 컴파일 에러 - product 누락
combined.is({ user: myUser })

API 레퍼런스

주요 인터페이스

ISpecification

interface ISpecification<TContext> {
  isSatisfiedBy(candidate: TContext): boolean
  is(candidate: TContext): boolean  // isSatisfiedBy의 별칭
  and<TOther>(other: ISpecification<TOther>): ISpecification<TContext & TOther>
  or<TOther>(other: ISpecification<TOther>): ISpecification<TContext | TOther>
  not(): ISpecification<TContext>
}

팩토리 함수

Spec

단일 속성을 검증하는 스펙을 생성합니다:

const isExpensive = Spec('price', (price: number) => price > 100)
const isAvailable = Spec('stock', (stock: number) => stock > 0)

CompositeSpec

여러 속성을 동시에 검증하는 스펙을 생성합니다:

interface OrderContext {
  user: User
  cart: Cart
  payment: Payment
}

const canCheckout = CompositeSpec<OrderContext>(ctx => {
  return ctx.user.verified && 
         ctx.cart.total > 0 && 
         ctx.payment.authorized
})

유틸리티 함수

  • allOf(...specs) - 모든 스펙이 만족되어야 함
  • anyOf(...specs) - 하나 이상의 스펙이 만족되어야 함
  • not(spec) - 스펙이 만족되지 않아야 함
  • define(predicate).as(key) - 빌더 패턴으로 스펙 생성

전체 API 문서 보기

실전 예제

전자상거래 시스템

// 제품 구매 가능 여부
const isInStock = Spec('product', (p: Product) => p.stock > 0)
const isPublished = Spec('product', (p: Product) => p.status === 'published')
const isPriceValid = Spec('product', (p: Product) => p.price > 0)

const canBePurchased = allOf(isInStock, isPublished, isPriceValid)

// 할인 적용 조건
const isVIP = Spec('customer', (c: Customer) => c.tier === 'vip')
const isLargeOrder = Spec('order', (o: Order) => o.total > 1000)
const hasPromoCode = Spec('promo', (p: PromoCode) => p.valid)

const discountEligible = anyOf(isVIP, isLargeOrder, hasPromoCode)

권한 관리 시스템

// 역할 기반 접근 제어
const hasRole = (role: string) => 
  Spec('user', (u: User) => u.roles.includes(role))

const canViewReports = anyOf(
  hasRole('admin'),
  hasRole('manager'),
  hasRole('analyst')
)

const canEditUsers = hasRole('admin')
  .and(Spec('user', u => u.permissions.includes('user:write')))
  .and(not(Spec('user', u => u.suspended)))

콘텐츠 발행 시스템

// 콘텐츠 발행 조건
const isAuthor = Spec('user', (u: User) => u.role === 'author')
const isEditor = Spec('user', (u: User) => u.role === 'editor')
const isDraft = Spec('content', (c: Content) => c.status === 'draft')
const isReviewed = Spec('content', (c: Content) => c.reviewedAt !== null)

// 작성자는 초안만, 편집자는 검토된 콘텐츠만 발행 가능
const canPublish = isAuthor.and(isDraft)
  .or(isEditor.and(isReviewed))

더 많은 예제는 example 디렉토리를 참고하세요.

성능

  • 단축 평가: AND/OR 연산에서 불필요한 검증 스킵
  • 🎯 최소 객체 생성: 조합 시 오버헤드 최소화
  • 🔍 컴파일 타임 타입 체크: 런타임 오버헤드 없음
  • 💾 메모리 효율적: 작은 메모리 풋프린트

기여하기

기여를 환영합니다! 다음과 같은 방법으로 기여할 수 있습니다:

  1. 버그 리포트 및 기능 제안 (Issues)
  2. 코드 기여 (Pull Request)
  3. 문서 개선
  4. 예제 추가

라이센스

MIT License


⬆ 맨 위로