funkcia

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Funkcia Adoption

采用Funkcia

Migrate existing error handling and validation into explicit, typed, chainable flows using Funkcia.
将现有的错误处理和验证迁移为使用Funkcia的显式、类型化、可链式调用的流程。

Workflow

工作流程

  1. Start at boundaries, not internals.
    • Migrate I/O edges first: API handlers, DB repositories, queue consumers, file readers.
    • Keep module internals stable until boundary contracts return
      Option
      /
      Result
      .
  2. Classify failure shape before coding.
    • Use
      Option
      /
      OptionAsync
      for expected absence without an error payload.
    • Use
      Result
      /
      ResultAsync
      for expected failure with explicit error semantics.
  3. Define domain errors with
    TaggedError
    before refactoring call chains.
    • Model application failures (auth, validation, not-found, rate limit, external dependency).
    • Preserve causes when wrapping infrastructure failures.
  4. Transform imperative control flow.
    • Replace
      try/catch
      with
      Result.try
      or
      ResultAsync.try
      .
    • Replace null/falsy branching with
      fromNullable
      /
      fromFalsy
      .
    • Prefer generator style for multi-step flows; use
      map
      ,
      andThen
      ,
      filter
      ,
      or
      ,
      match
      for focused one-step transforms.
  5. Resolve once at the boundary.
    • Use
      match
      in handlers/controllers to map outcomes to transport responses.
    • For tagged error unions (
      _tag
      ), prefer
      exhaustive(error, { ... })
      to enforce full case handling.
    • Avoid
      unwrap
      in business logic.
  6. Verify behavior and type contracts.
    • Add runtime +
      expectTypeOf
      tests.
    • Run repository checks before completion.
  1. 从边界开始,而非内部。
    • 优先迁移I/O边界:API处理器、数据库仓库、队列消费者、文件读取器。
    • 在边界契约返回
      Option
      /
      Result
      之前,保持模块内部稳定。
  2. 编码前先分类失败形态。
    • 当预期无错误负载的缺失场景时,使用
      Option
      /
      OptionAsync
    • 当预期有明确错误语义的失败场景时,使用
      Result
      /
      ResultAsync
  3. 在重构调用链之前,使用
    TaggedError
    定义领域错误。
    • 为应用层失败建模(认证、验证、未找到、速率限制、外部依赖)。
    • 包装基础设施层失败时保留根因。
  4. 转换命令式控制流。
    • 使用
      Result.try
      ResultAsync.try
      替代
      try/catch
    • 使用
      fromNullable
      /
      fromFalsy
      替代null/假值分支判断。
    • 多步骤流程优先使用生成器风格;单步骤转换优先使用
      map
      andThen
      filter
      or
      match
  5. 在边界处一次性解析结果。
    • 在处理器/控制器中使用
      match
      将结果映射为传输层响应。
    • 对于带
      _tag
      的标签化错误联合类型,优先使用
      exhaustive(error, { ... })
      来强制覆盖所有场景。
    • 业务逻辑中避免使用
      unwrap
  6. 验证行为与类型契约。
    • 添加运行时测试 +
      expectTypeOf
      类型测试。
    • 完成前运行仓库检查。

Migration Strategy

迁移策略

  • Migrate incrementally by vertical slices (feature or module), not by type across the whole codebase.
  • Keep adapters during migration when legacy callers still expect nullable or throwing APIs.
  • Preserve behavior first, then improve ergonomics.
  • Recommend wrapping shared dependencies (database clients, API SDKs, queues) with
    ResultAsync.resource
    and mapping each dependency to pre-defined resource error types before wiring business flows.
  • 按垂直切片(功能或模块)逐步迁移,而非跨整个代码库按类型迁移。
  • 迁移期间保留适配器,以兼容仍期望可空或抛出异常API的遗留调用方。
  • 先保留原有行为,再优化易用性。
  • 建议使用
    ResultAsync.resource
    包装共享依赖(数据库客户端、API SDK、队列),并在连接业务流程前将每个依赖映射为预定义的资源错误类型。

Pattern Transformations

模式转换

Throwing parse to
Result

将抛出异常的解析转换为
Result

ts
import { Result } from 'funkcia';

function parseWebhook(raw: string) {
  return Result.try(
    () => JSON.parse(raw),
    () => new Error('Invalid webhook payload'),
  );
}
ts
import { Result } from 'funkcia';

function parseWebhook(raw: string) {
  return Result.try(
    () => JSON.parse(raw),
    () => new Error('Invalid webhook payload'),
  );
}

Nullable lookup to
Option

将可空查找转换为
Option

ts
import { Option } from 'funkcia';

function findPrimaryEmail(user: User) {
  return Option.fromNullable(user.emails.find((x) => x.primary))
    .map((email) => email.value.toLowerCase());
}
ts
import { Option } from 'funkcia';

function findPrimaryEmail(user: User) {
  return Option.fromNullable(user.emails.find((x) => x.primary))
    .map((email) => email.value.toLowerCase());
}

Rejecting async call to
ResultAsync

将拒绝的异步调用转换为
ResultAsync

ts
import { ResultAsync } from 'funkcia';

function fetchCheckoutSession(sessionId: string) {
  return ResultAsync.try(
    () => payments.getSession(sessionId),
    (cause) => new Error(`Failed to fetch session ${sessionId}: ${String(cause)}`),
  );
}
ts
import { ResultAsync } from 'funkcia';

function fetchCheckoutSession(sessionId: string) {
  return ResultAsync.try(
    () => payments.getSession(sessionId),
    (cause) => new Error(`Failed to fetch session ${sessionId}: ${String(cause)}`),
  );
}

Callback Hell vs Generators

回调地狱 vs 生成器

Callback hell / nested async handling

回调地狱 / 嵌套异步处理

ts
async function buildCheckoutSummary(userId: string): Promise<Result<CheckoutSummary, CheckoutError>> {
  try {
    const user = await usersApi.getById(userId);

    if (!user.defaultPaymentMethodId) {
      return Result.error(new MissingPaymentMethodError(user.id));
    }

    const paymentMethod = await paymentsApi.getMethod(user.defaultPaymentMethodId);
    const cart = await cartApi.getActiveCart(user.id);

    if (!cart) {
      return Result.error(new CartNotFoundError(user.id));
    }

    return Result.ok({
      userEmail: user.email,
      paymentMethod: paymentMethod.brand,
      total: cart.total,
    });
  } catch (cause) {
    return Result.error(new CheckoutInfrastructureError(cause));
  }
}
ts
async function buildCheckoutSummary(userId: string): Promise<Result<CheckoutSummary, CheckoutError>> {
  try {
    const user = await usersApi.getById(userId);

    if (!user.defaultPaymentMethodId) {
      return Result.error(new MissingPaymentMethodError(user.id));
    }

    const paymentMethod = await paymentsApi.getMethod(user.defaultPaymentMethodId);
    const cart = await cartApi.getActiveCart(user.id);

    if (!cart) {
      return Result.error(new CartNotFoundError(user.id));
    }

    return Result.ok({
      userEmail: user.email,
      paymentMethod: paymentMethod.brand,
      total: cart.total,
    });
  } catch (cause) {
    return Result.error(new CheckoutInfrastructureError(cause));
  }
}

Preferred generator style

推荐的生成器风格

ts
function buildCheckoutSummary(userId: string): ResultAsync<CheckoutSummary, CheckoutError> {
  return ResultAsync.use(async function* () {
    const user = yield* ResultAsync.try(
      () => usersApi.getById(userId),
      (cause) => new CheckoutInfrastructureError(cause),
    );

    const paymentMethodId = yield* Result.fromNullable(
      user.defaultPaymentMethodId,
      () => new MissingPaymentMethodError(user.id),
    );

    const paymentMethod = yield* ResultAsync.try(
      () => paymentsApi.getMethod(paymentMethodId),
      (cause) => new CheckoutInfrastructureError(cause),
    );

    const cart = yield* Result.fromNullable(
      yield* ResultAsync.try(
        () => cartApi.getActiveCart(user.id),
        (cause) => new CheckoutInfrastructureError(cause),
      ),
      () => new CartNotFoundError(user.id),
    );

    return ResultAsync.ok({
      userEmail: user.email,
      paymentMethod: paymentMethod.brand,
      total: cart.total,
    });
  });
}
ts
function buildCheckoutSummary(userId: string): ResultAsync<CheckoutSummary, CheckoutError> {
  return ResultAsync.use(async function* () {
    const user = yield* ResultAsync.try(
      () => usersApi.getById(userId),
      (cause) => new CheckoutInfrastructureError(cause),
    );

    const paymentMethodId = yield* Result.fromNullable(
      user.defaultPaymentMethodId,
      () => new MissingPaymentMethodError(user.id),
    );

    const paymentMethod = yield* ResultAsync.try(
      () => paymentsApi.getMethod(paymentMethodId),
      (cause) => new CheckoutInfrastructureError(cause),
    );

    const cart = yield* Result.fromNullable(
      yield* ResultAsync.try(
        () => cartApi.getActiveCart(user.id),
        (cause) => new CheckoutInfrastructureError(cause),
      ),
      () => new CartNotFoundError(user.id),
    );

    return ResultAsync.ok({
      userEmail: user.email,
      paymentMethod: paymentMethod.brand,
      total: cart.total,
    });
  });
}

Reference Map

参考映射

  • references/tagged-errors.md
    : Model real application failures with
    TaggedError
    .
  • references/exhaustive.md
    : Use
    exhaustive
    and
    corrupt
    for type-safe branching.
  • references/brand.md
    : Build branded domain primitives and safe parsers.
  • references/exceptions.md
    : Use built-in exception types and
    panic
    .
  • references/safe-functions.md
    : Safely parse JSON and normalize URLs/URIs.
  • references/generators.md
    : Preferred generator-based style for
    Option
    /
    Result
    and async variants.
  • references/do-notation.md
    : Context-accumulation style with
    Do
    ,
    bind
    ,
    let
    , and
    bindTo
    .
  • references/resources.md
    : Wrap shared resources with
    ResultAsync.resource
    so operations return expected values or pre-defined resource errors.
  • references/tagged-errors.md
    : 使用
    TaggedError
    为真实应用层失败建模。
  • references/exhaustive.md
    : 使用
    exhaustive
    corrupt
    实现类型安全的分支处理。
  • references/brand.md
    : 构建带品牌标识的领域原语与安全解析器。
  • references/exceptions.md
    : 使用内置异常类型与
    panic
  • references/safe-functions.md
    : 安全解析JSON并标准化URL/URI。
  • references/generators.md
    :
    Option
    /
    Result
    及其异步变体的推荐生成器风格。
  • references/do-notation.md
    : 使用
    Do
    bind
    let
    bindTo
    的上下文累积风格。
  • references/resources.md
    : 使用
    ResultAsync.resource
    包装共享资源,使操作返回预期值或预定义的资源错误。