option-t

基本API

import { createOk, createErr, isOk, isErr, unwrapOk } from "option-t/plain_result";
import { mapForResult } from "option-t/plain_result/map";
import { andThenForResult } from "option-t/plain_result/and_then";
import { andThenAsyncForResult } from "option-t/plain_result/and_then_async";
import { mapErrForResult } from "option-t/plain_result/map_err";
import { orElseForResult } from "option-t/plain_result/or_else";

または、named import で公開される namespace-style API を使用します:

import { Result } from "option-t/plain_result/namespace";
// Result.createOk, Result.map, Result.andThen, etc.
関数/型 説明
Result<T, E> Result 型(Ok<T> \| Err<E> の判別共用体、プレーンオブジェクト)
createOk(value) 成功値を生成({ ok: true, val: T, err: null })
createErr(error) 失敗値を生成({ ok: false, val: null, err: E })

neverthrow との主な違いは次のとおりです。

  • クラスではなくプレーンオブジェクト(discriminant は ok フィールド)
  • メソッドチェーンではなくスタンドアロン関数で合成
  • 非同期は *Async バリアント関数を使用(戻り値は Promise<Result<T, E>>)

関数による合成

import { mapForResult } from "option-t/plain_result/map";
import { mapErrForResult } from "option-t/plain_result/map_err";
import { andThenForResult } from "option-t/plain_result/and_then";
import { orElseForResult } from "option-t/plain_result/or_else";

const mapped = mapForResult(result, (value) => transform(value));
const mappedErr = mapErrForResult(result, (error) => transformErr(error));
const chained = andThenForResult(result, (value) => nextResult(value));
const recovered = orElseForResult(result, (error) => recover(error));

// 分岐は型ガードまたは ok フィールドで判定
if (isOk(result)) {
  console.log(result.val);
} else {
  console.log(result.err);
}

コード例: 状態遷移パイプライン

RequestResolver / RequestStore の設計と、状態とドメインイベントを同一トランザクションで永続化する方法は state-modeling.md#ドメインイベント を参照してください。

import { createOk, createErr, isErr, type Result } from "option-t/plain_result";

// --- Branded Types ---

declare const RequestIdBrand: unique symbol;
type RequestId = string & { readonly [RequestIdBrand]: never };

declare const DriverIdBrand: unique symbol;
type DriverId = string & { readonly [DriverIdBrand]: never };

declare const PassengerIdBrand: unique symbol;
type PassengerId = string & { readonly [PassengerIdBrand]: never };

// --- State Types ---

type Waiting = Readonly<{
  kind: "Waiting";
  requestId: RequestId;
  passengerId: PassengerId;
}>;

type EnRoute = Readonly<{
  kind: "EnRoute";
  requestId: RequestId;
  passengerId: PassengerId;
  driverId: DriverId;
}>;

// --- Repository Types ---

type RequestResolver = Readonly<{
  findById: (id: RequestId) => Promise<Waiting | undefined>;
}>;

type RequestStore = Readonly<{
  save: (state: EnRoute) => Promise<void>;
}>;

// --- Error Types ---

type AssignDriverError =
  | Readonly<{ kind: "RequestNotFound"; requestId: RequestId }>
  | Readonly<{ kind: "DriverNotAvailable"; driverId: DriverId }>;

// --- Use Case ---

const assignDriverUseCase =
  (requestResolver: RequestResolver, requestStore: RequestStore) =>
  async (
    requestId: RequestId,
    driverId: DriverId,
    isDriverAvailable: boolean,
  ): Promise<Result<EnRoute, AssignDriverError>> => {
    const request = await requestResolver.findById(requestId);

    const waitingResult = request !== undefined
      ? createOk(request)
      : createErr({ kind: "RequestNotFound" as const, requestId });

    if (isErr(waitingResult)) return waitingResult;

    const waiting = waitingResult.val;

    if (!isDriverAvailable) {
      return createErr({ kind: "DriverNotAvailable" as const, driverId });
    }

    const enRoute: EnRoute = {
      kind: "EnRoute",
      requestId: waiting.requestId,
      passengerId: waiting.passengerId,
      driverId,
    };

    await requestStore.save(enRoute);

    return createOk(enRoute);
  };

回復可能な外部障害

ワークフローが回復判断を行える場合に限り、名前付きの外部エラーを Result に含めます。

type PaymentAuthorizationError = {
  readonly kind: "AuthorizationTemporarilyUnavailable";
  readonly retryAfter: RetryAfter;
};

例えば、呼び出し側はこのエラー後に認可を延期または再試行できます。任意の通信障害を包むラッパーではありません。


This site uses Just the Docs, a documentation theme for Jekyll.