Files
2nd/10_Wiki/Topics/Frontend/Effect TS 및 ts-brand 라이브러리 활용.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

6.9 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-effect-ts-및-ts-brand-라이브러리-활용 Effect TS 및 ts-brand 라이브러리 활용 10_Wiki/Topics verified self
Effect-TS
ts-brand
Branded Types
Effect.gen
none A 0.9 applied
typescript
functional
effect-system
branded-types
2026-05-10 pending
language framework
TypeScript Effect

Effect TS 및 ts-brand 라이브러리 활용

매 한 줄

"매 typed effect system + nominal type 의 — TypeScript 의 의 ZIO/Cats 의 imported". Effect 의 의 async/error/dependency 의 의 single 의 Effect<A, E, R> 의 의 unify, ts-brand 의 의 structural type 의 의 nominal flavor 의 의 add. 2026 의 매 enterprise TS codebase 의 의 fast 의 emerging.

매 핵심

매 Effect 의 type

  • Effect<A, E, R> — 의 success A, 의 failure E, 의 requirement R.
  • 매 lazy — 매 Effect.runPromise / Effect.runSync 의 의 actual 의 execute.
  • 의 composable — pipe, Effect.gen 의 의 chain.

매 ts-brand 의 nominal type

  • 의 TypeScript structural — string === string 의 의 distinguishable X.
  • Brand 의 의 string & { __brand: "UserId" } 의 의 phantom tag.
  • 의 runtime cost zero — type-level only.

매 응용

  1. Domain ID (UserId, OrderId 의 의 mix-up 의 prevent).
  2. Validated value (Email, NonEmptyString).
  3. Async pipeline 의 typed error.
  4. Dependency injection (Effect Layer).
  5. Retry/timeout/concurrency 의 declarative.

💻 패턴

ts-brand 기본

import type { Brand } from "ts-brand";

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

const makeUserId = (s: string): UserId => s as UserId;

function getUser(id: UserId) { /* ... */ }

const uid = makeUserId("u_123");
const oid = "o_456" as OrderId;

getUser(uid);
// getUser(oid);  // 매 compile error — UserId 의 X
// getUser("u_123");  // 매 compile error — raw string

Validated brand (smart constructor)

type Email = Brand<string, "Email">;

function parseEmail(s: string): Email | null {
  return /^[^@]+@[^@]+\.[^@]+$/.test(s) ? (s as Email) : null;
}

function sendMail(to: Email, subject: string) { /* ... */ }

const e = parseEmail(input);
if (e) sendMail(e, "hi");  // 매 type-narrowed 의 valid 의 only

Effect 기본

import { Effect, pipe } from "effect";

const fetchUser = (id: UserId): Effect.Effect<User, NetworkError | NotFoundError> =>
  Effect.tryPromise({
    try: () => fetch(`/api/users/${id}`).then((r) => {
      if (r.status === 404) throw new NotFoundError(id);
      return r.json();
    }),
    catch: (e) => e instanceof NotFoundError ? e : new NetworkError(e),
  });

const program = pipe(
  fetchUser(uid),
  Effect.map((u) => u.email),
  Effect.tap((email) => Effect.log(`Got: ${email}`)),
);

Effect.runPromise(program).then(console.log);

Effect.gen (do-notation)

import { Effect } from "effect";

const program = Effect.gen(function* () {
  const user = yield* fetchUser(uid);
  const orders = yield* fetchOrders(user.id);
  const valid = orders.filter((o) => o.status === "paid");
  return { user, orderCount: valid.length };
});

Typed retry / timeout

import { Effect, Schedule, Duration } from "effect";

const robust = pipe(
  fetchUser(uid),
  Effect.retry(Schedule.exponential(Duration.millis(100)).pipe(Schedule.compose(Schedule.recurs(3)))),
  Effect.timeout(Duration.seconds(5)),
);

Layer / dependency injection

import { Context, Effect, Layer } from "effect";

class Database extends Context.Tag("Database")<Database, {
  query: (sql: string) => Effect.Effect<unknown[], DBError>;
}>() {}

const DatabaseLive = Layer.succeed(Database, {
  query: (sql) => Effect.tryPromise({ try: () => pool.query(sql), catch: (e) => new DBError(e) }),
});

const program = Effect.gen(function* () {
  const db = yield* Database;
  const rows = yield* db.query("SELECT * FROM users");
  return rows;
});

Effect.runPromise(program.pipe(Effect.provide(DatabaseLive)));

Schema (Effect's Zod-equivalent)

import { Schema } from "effect";

const User = Schema.Struct({
  id: Schema.String.pipe(Schema.brand("UserId")),
  email: Schema.String.pipe(Schema.pattern(/^[^@]+@[^@]+$/), Schema.brand("Email")),
  age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
});

type User = Schema.Schema.Type<typeof User>;
const decoded = Schema.decodeUnknownSync(User)(input);

의 Concurrency

import { Effect } from "effect";

const fetchAll = Effect.all(
  [fetchUser(u1), fetchUser(u2), fetchUser(u3)],
  { concurrency: 2 },
);

의 Either (sync error)

import { Either } from "effect";

const safeParse = (s: string): Either.Either<number, "not_a_number"> => {
  const n = Number(s);
  return Number.isNaN(n) ? Either.left("not_a_number") : Either.right(n);
};

Effect 의 React (effect-rx)

import { useRxSuspense } from "@effect-rx/rx-react";

const userRx = Rx.make((get) => fetchUser(get(userIdRx)));

function UserProfile() {
  const user = useRxSuspense(userRx);
  return <div>{user.email}</div>;
}

매 결정 기준

상황 Approach
Domain ID 의 의 mix-up 의 prevent ts-brand 의만
Validated value (email, URL) ts-brand 의 + smart constructor
Complex async pipeline Effect TS
Typed error + retry + timeout Effect TS
DI 의 의 typed Effect Layer
Simple fetch + try/catch 매 plain async — Effect 의 X

기본값: ts-brand 의 의 always, Effect 의 의 의 complex pipeline 의만.

🔗 Graph

🤖 LLM 활용

언제: typed pipeline 의 design, domain ID safety, layer-based DI, schema-driven decode. 언제 X: 의 매 simple CRUD — 매 Effect 의 learning curve 의 의 not worth.

안티패턴

  • as UserId 의 의 raw string 의 의 cast: 의 brand 의 의 bypass — 의 smart constructor 의 의 always.
  • Effect 의 의 entire codebase 의 의 force: 의 team 의 의 buy-in 의 X 의 시 의 — 매 friction.
  • Effect.runSync 의 의 async 의: 의 throw 의 의 — runPromise 의 의 사용.
  • Mutable state 의 의 Effect 의: 의 referential transparency 의 의 violation — Ref/Layer 의 의 사용.
  • Brand 의 의 nested 의 reuse: Brand<Brand<string, "A">, "B"> 의 의 confusing — 매 single brand 의 keep.

🧪 검증 / 중복

  • Verified (Effect-TS docs effect.website, ts-brand npm, ZIO inspiration, Effect Schema docs).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — Effect.gen + Layer + Schema + ts-brand smart constructor 추가