[Node Library] @tool-chain/core

公開日 2025年12月11日
39 分で読める
TypeScript

npm version npm downloads License: MIT Node.js Version

強力な非同期チェーン実行ライブラリ - エレガントなAPIで複雑な非同期ワークフローを構築します。ESMとCommonJS双方のモジュール形式に対応し、完全なTypeScriptタイプセーフと柔軟なエラー処理を提供します。

非同期操作を簡潔にして、コードをより優雅で読みやすく保守しやすくします。

🚀 クイックスタート

インストール

好みのパッケージマネージャーでインストール:

# npm
npm install @tool-chain/core

# yarn
yarn add @tool-chain/core

# pnpm
pnpm add @tool-chain/core

必要な環境

  • Node.js: >= 12.0
  • TypeScript: >= 4.5(オプション、完全なタイプサポートの場合は推奨)
  • ブラウザ: モダンブラウザ(Webpack/Rollupなどのビルドツールが必要)

30秒クイック例

ライブラリは非同期ワークフロー管理用の2つの強力なツールを提供します:

オプション1:Chains - 全ての前のステップ結果にアクセス

複数の履歴結果にアクセスする必要がある場合はChainsを使用:

import { Chains } from '@tool-chain/core';

const result = await new Chains()
  .chain(() => 10) // ステップ1: 10を返す、r1 = 10
  .chain((r) => r.r1 * 2) // ステップ2: r1=10、20を返す、r2 = 20
  .chain((r) => r.r2 + 5) // ステップ3: r2=20、25を返す、r3 = 25
  .invoke();

console.log(result); // 出力: 25

// 重要な概念:
// - 各ステップは全ての前のステップの結果にアクセスできます(r1, r2, r3...)
// - r1はステップ1の結果、r2はステップ2の結果など
// - .invoke()はチェーン全体を実行して最後のステップの結果を返します

オプション2:Tasks - ステップ間の清潔なパラメータ伝達

パラメータベースのフロー制御を好む場合はTasksを使用:

import { Tasks } from '@tool-chain/core';

await new Tasks()
  .addTask(async ({ next }) => {
    const data = 10;
    await next(data * 2); // 次のタスクに20を渡す
  })
  .addTask(async ({ param, next }) => {
    // param = 20(前のタスクから)
    await next(param + 5); // 次のタスクに25を渡す
  })
  .addTask(async ({ param }) => {
    console.log(param); // 出力: 25
  })
  .invoke();

これだけです!基本を習得しました。続けてもっと強力な機能を学び、どのツールを使うべきか理解しましょう。

✨ 主な機能

🔗 2つの強力な実行パターン

Chains - 複数結果へのアクセス

  • .chain()メソッドで非同期ワークフローを構築
  • 全ての履歴結果にアクセス(r1, r2, r3…)
  • 複数の中間結果が必要なパイプラインに最適

Tasks - パラメータ伝達

  • .addTask()メソッドで順序実行タスク
  • next(param)でステップ間のクリーンなパラメータ伝達
  • 線形データフローのワークフローに最適

🛡️ 柔軟なエラー処理

  • デフォルトモード: エラーは直接スロー、チェーンを中断
  • キャプチャモード: { withoutThrow: true }でステップごとにエラーをキャプチャ(Chainsのみ)
  • ミックスモード: 同じチェーン内で両方のエラー処理戦略を柔軟に混合(Chainsのみ)
  • タスクエラー: エラーは即座に伝播、後続タスク停止(Tasks)
  • try-catchで全体をラップする必要なし

🎯 スマートなリトライとタイムアウト

  • 失敗時の自動リトライ(リトライ回数をカスタマイズ可能)
  • 柔軟なリトライ条件(エラータイプ、メッセージ、または正規表現)
  • 設定可能なリトライ遅延(即座のリトライによるリソース浪費を防止)
  • 実行タイムアウト制御(長時間のハング防止)
  • 注記: リトライとタイムアウト機能はChainsで利用可能;Tasksはシンプルさに注力

📊 初期データと状態管理

  • Chains: 構成時に初期データを渡す、チェーン内でr1として利用可能
  • Tasks: next(param)でタスク間のパラメータ伝達
  • 全ステップの実行結果を自動保存
  • 柔軟なデータフローと状態管理

完全なTypeScriptサポート

  • 完全なタイプ定義ファイル(.d.ts
  • 厳格モードのコンパイル設定
  • スマートなタイプ推論(Chainsで20+ステップをサポート;Tasksで完全なタイプセーフ)
  • 完璧なIDE自動補完とタイプチェック
  • Tasksでのタイプセーフパラメータ伝達はコンパイル時安全を保証

📦 デュアルモジュールサポート

  • ESM (ES Modules) - モダンJavaScriptモジュール形式
  • CommonJS - Node.js標準モジュール形式
  • 自動モジュール検出、手動設定不要

📚 詳細な使用ガイド

基本的なチェーン

シンプルなチェーンフローを作成してステップごとにデータを処理:

import { Chains } from '@tool-chain/core';

const result = await new Chains()
  .chain(() => 10)
  .chain((r) => r.r1 * 2) // ステップ1の結果にアクセス
  .chain((r) => r.r2 + 5) // ステップ2の結果にアクセス
  .chain((r) => r.r3.toString()) // 文字列に変換
  .invoke();

console.log(result); // "25"

重要ポイント:

  • .chain()ステップは全ての前の結果にアクセス可能
  • r1はステップ1の結果、r2はステップ2の結果など
  • チェーン全体を実行するために最後に.invoke()を呼ぶ必要があります

ファクトリ関数

createChains()ファクトリ関数でより簡潔なコードを記述:

import { createChains } from '@tool-chain/core';

const result = await createChains()
  .chain(() => 'hello')
  .chain((r) => r.r1.toUpperCase())
  .invoke();

console.log(result); // "HELLO"

初期データの使用

構成時に初期データを渡す、チェーン内でr1として利用可能:

const result = await new Chains(100) // 初期データ: 100
  .chain((r) => r.r1 * 2) // r1 = 100、200を返す
  .chain((r) => r.r2 - 50) // r2 = 200、150を返す
  .invoke();

console.log(result); // 150

使用ケース:

  • 関数パラメータから渡される初期データの処理
  • 再利用可能なデータ処理フローの構築
  • クラスメソッド内でのチェーン初期化

エラー処理 - 深掘り

方法1: デフォルトスロー模式

エラーが直接スロー、チェーン実行を中断。try-catchで処理:

try {
  const result = await new Chains()
    .chain(() => JSON.parse('invalid')) // SyntaxErrorをスロー
    .chain((r) => r.r1.name) // 実行されない
    .invoke();
} catch (error) {
  console.error('チェーン失敗:', error.message);
  // エラー処理
}

利点: シンプル明確、エラーは即座に中断 欠点: チェーン全体をtry-catchでラップ必要

方法2: キャプチャ模式(推奨)

{ withoutThrow: true }でエラーをキャプチャしてオブジェクトとしてラップ、チェーン継続:

const result = await new Chains()
  .chain(() => JSON.parse('invalid'), { withoutThrow: true })
  // 返す: { error: SyntaxError, data: undefined }
  .chain((r) => {
    if (r.r1.error) {
      console.log('解析失敗、デフォルト値を使用');
      return { name: 'default', age: 0 };
    }
    return r.r1.data;
  })
  .chain((r) => {
    // r.r1は安全に使用可能
    return r.r1.name.toUpperCase();
  })
  .invoke();

console.log(result); // "DEFAULT"

返却値の構造:

{
  data?: T;      // 成功時のデータ
  error?: Error; // 失敗時のエラーオブジェクト
}

利点: 柔軟なエラー処理、チェーン継続、フォールバック可能 欠点: 各ステップでエラーをチェック必要

方法3: ミックス模式

同じチェーン内で両方のエラー処理戦略を混合:

const result = await new Chains()
  // ステップ1: ネットワークエラーをキャプチャ
  .chain(
    async () => {
      const resp = await fetch('/api/users');
      return resp.json();
    },
    { withoutThrow: true },
  )
  // ステップ2: エラーをチェック、必要ならフォールバック
  .chain((r) => {
    if (r.r1.error) {
      console.log('API呼び出し失敗、キャッシュデータを使用');
      return [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' },
      ];
    }
    return r.r1.data;
  })
  // ステップ3: データ変換(デフォルト模式、エラーをスロー)
  .chain((r) => {
    return r.r1.map((user) => user.name.toUpperCase());
  })
  // ステップ4: 再度エラーをキャプチャ
  .chain((r) => saveToDatabase(r.r1), { withoutThrow: true })
  .invoke();

重要な概念:

  • 各ステップは独立してエラー処理戦略を設定可能
  • キャプチャ模式のエラーオブジェクトは一貫した形式
  • ミックス使用は重要なポイントで守備的プログラミング、他の場所では素早く失敗

リトライとタイムアウト - 深掘り

基本的なリトライ

失敗時に指定回数の自動リトライ:

const result = await new Chains()
  .chain(
    async () => {
      return fetch('/api/unstable').then((r) => r.json());
    },
    {
      retry: 3, // 最大3回リトライ(合計4回の試行)
      retryDelay: 1000, // リトライ間の待機時間 1秒
    },
  )
  .invoke();

実行フロー:

  1. 関数を実行
  2. 失敗時 → retryDelay待機 → リトライ
  3. 成功またはリトライ回数消費まで繰り返し
スマートなリトライ条件

特定のエラーのみリトライ、他はすぐに失敗:

const result = await new Chains()
  .chain(
    async () => {
      const res = await fetch('/api/data');
      if (res.status === 429) throw new Error('Rate limited');
      if (res.status === 404) throw new Error('Not found');
      return res.json();
    },
    {
      retry: 5,
      // 方法1: 文字列 - エラーメッセージにこの文字列を含む場合リトライ
      retryWhen: 'Rate limited',
      retryDelay: 2000,
    },
  )
  .invoke();

retryWhenパラメータタイプ:

// 1. エラータイプ - そのエラータイプのみリトライ
retryWhen: TypeError; // TypeErrorのみリトライ

// 2. 文字列 - エラーメッセージがこの文字列を含む場合リトライ
retryWhen: 'timeout'; // エラーメッセージに'timeout'を含む場合リトライ

// 3. 正規表現 - エラーメッセージが正規表現にマッチする場合リトライ
retryWhen: /timeout|Rate limited/; // どちらかにマッチする場合リトライ
タイムアウト制御

リクエストが無期限にハングするのを防止:

const result = await new Chains()
  .chain(
    async () => {
      return fetch('/api/slow-endpoint').then((r) => r.json());
    },
    {
      timeout: 5000, // 5秒タイムアウト、超過するとエラー
      retry: 2, // タイムアウト時に2回リトライ
      retryWhen: /timeout/, // タイムアウト時のみリトライ
    },
  )
  .chain((r) => {
    // 5秒以内にデータ取得を保証
    return r.r1;
  })
  .invoke();

タイムアウトエラー例:

// タイムアウトはエラーをスロー(withoutThrow未設定の場合)
// Error: Timeout exceeded: operation took longer than 5000ms

非同期操作チェーン

複数の非同期操作をチェーン、自動Promise処理:

const result = await new Chains()
  // ステップ1: ユーザーリストを取得
  .chain(async () => {
    const res = await fetch('/api/users');
    return res.json();
  })
  // ステップ2: ユーザー詳細を取得
  .chain(async (r) => {
    const userIds = r.r1.map((u) => u.id);
    const res = await fetch(`/api/user-details?ids=${userIds.join(',')}`);
    return res.json();
  })
  // ステップ3: 結果を結合
  .chain((r) => {
    return {
      users: r.r1, // ステップ1の結果
      details: r.r2, // ステップ2の結果
    };
  })
  .invoke();

同期と非同期の混合

同期と非同期の操作を自由に混合:

const result = await new Chains()
  .chain(() => 10) // 同期: 初期値
  .chain(async (r) => {
    // 非同期: API呼び出しをシミュレート
    await new Promise((resolve) => setTimeout(resolve, 100));
    return r.r1 * 2; // 20
  })
  .chain((r) => {
    // 同期: データ変換
    return r.r2 + 5; // 25
  })
  .chain(async (r) => {
    // 非同期: データ保存
    await saveToDatabase({ value: r.r1 });
    return r.r1;
  })
  .invoke();

console.log(result); // 25

Tasks 使用

Tasksクラスは、タスク間のパラメータ伝達をサポートする軽量な順序実行方法を提供します。全ての前のステップ結果へのアクセスに最適化されたChainsとは異なり、Tasksは明確なパラメータベースのフロー制御に焦点を当てています。

基本的なタスク実行

複数のタスクを順序で実行し、各タスクがnext()を呼び出してデータを次のタスクに渡すことができます:

import { Tasks, createTasks } from '@tool-chain/core';

await new Tasks()
  .addTask(async ({ next }) => {
    console.log('タスク 1');
    await next(); // 次のタスクに続行
  })
  .addTask(async ({ next }) => {
    console.log('タスク 2');
    await next(42); // 次のタスクにパラメータを渡す
  })
  .addTask<number>(async ({ param }) => {
    console.log(`タスク 3 受け取った: ${param}`); // 42
  })
  .invoke();

タスク間のパラメータ伝達

前のタスクからパラメータを受け取ると、完全なタイプセーフで処理できます:

import { createTasks } from '@tool-chain/core';

await createTasks()
  .addTask<string>(async ({ next }) => {
    // このタスクは次のタスクに文字列を渡す
    await next('hello');
  })
  .addTask(async ({ param, next }) => {
    // param は string として推論される(オプションではない)
    console.log(param); // "hello"
    await next({ id: 1, name: 'Alice' });
  })
  .addTask<{ id: number; name: string }>(async ({ param }) => {
    // param は { id: number; name: string } として推論される
    console.log(param.name); // "Alice"
  })
  .invoke();

finish() による早期終了

いつでもfinish()メソッドを使用してタスク実行を停止できます:

await new Tasks()
  .addTask(async ({ next }) => {
    console.log('タスク 1');
    await next();
  })
  .addTask(async ({ finish }) => {
    console.log('タスク 2');
    await finish(); // ここで停止、タスク 3 は実行されない
  })
  .addTask(async () => {
    console.log('タスク 3'); // 実行されない
  })
  .invoke();

自動タスク終了

タスクがnext()またはfinish()を呼び出さない場合、後続のタスクは自動的に停止します:

await new Tasks()
  .addTask(async ({ next }) => {
    console.log('タスク 1');
    await next();
  })
  .addTask(async () => {
    console.log('タスク 2');
    // next() または finish() 呼び出しなし - 自動停止
  })
  .addTask(async () => {
    console.log('タスク 3'); // 実行されない
  })
  .invoke();

Tasks でのエラー処理

任意のタスクでスローされたエラーは実行を停止して伝播します:

try {
  await new Tasks()
    .addTask(async ({ next }) => {
      console.log('タスク 1');
      await next();
    })
    .addTask(async () => {
      throw new Error('タスク 2 失敗');
    })
    .addTask(async () => {
      console.log('タスク 3'); // 実行されない
    })
    .invoke();
} catch (error) {
  console.error('エラー:', error.message); // "タスク 2 失敗"
}

ファクトリ関数

createTasks()を使用してより簡潔なコードを書くことができます:

import { createTasks } from '@tool-chain/core';

await createTasks()
  .addTask(async ({ next }) => {
    await next('データ');
  })
  .addTask(async ({ param }) => {
    console.log(param);
  })
  .invoke();

🎯 一般的な使用ケース

シナリオ1: データ処理パイプライン

// 検証 → 変換 → クリーニング → 保存
const result = await new Chains(rawUserData)
  // データ検証
  .chain((r) => {
    const valid = r.r1.every((u) => u.id && u.name && u.email);
    if (!valid) throw new Error('Invalid data');
    return r.r1;
  })
  // データ変換
  .chain((r) => {
    return r.r1.map((u) => ({
      id: u.id,
      name: u.name.trim(),
      email: u.email.toLowerCase(),
    }));
  })
  // データクリーニング(重複削除)
  .chain((r) => {
    const seen = new Set();
    return r.r1.filter((u) => {
      if (seen.has(u.email)) return false;
      seen.add(u.email);
      return true;
    });
  })
  // データベース保存
  .chain(async (r) => {
    return db.users.insertMany(r.r1);
  })
  .invoke();

シナリオ2: API呼び出しチェーン

// ユーザー取得 → 権限取得 → 設定取得 → 結合
const userData = await new Chains()
  // ユーザー情報取得
  .chain(
    async () => {
      const res = await fetch(`/api/user/${userId}`);
      return res.json();
    },
    { timeout: 5000 },
  )
  // ユーザー権限取得
  .chain(
    async (r) => {
      const res = await fetch(`/api/permissions/${r.r1.id}`);
      return res.json();
    },
    { timeout: 5000 },
  )
  // ユーザー設定取得
  .chain(
    async (r) => {
      const res = await fetch(`/api/config/${r.r1.userId}`);
      return res.json();
    },
    { timeout: 5000 },
  )
  // 全てのデータを結合
  .chain((r) => {
    return {
      user: r.r1,
      permissions: r.r2,
      config: r.r3,
    };
  })
  .invoke();

📖 完全なAPI参照

クラス: Chains<TResults>

チェーン実行のメインクラス、メソッドチェーンをサポート。

コンストラクタ

constructor(initialData?: T)

パラメータ:

  • initialData (オプション) - 初期データ、チェーン内でr1として利用可能

例:

// 初期データなし
const chain = new Chains();

// 初期データ付き
const chain = new Chains(100);
const chain = new Chains({ name: 'John', age: 30 });
const chain = new Chains([1, 2, 3]);

メソッド: chain<R>(fn, options?)

チェーンに実行ステップを追加。

パラメータ:

  • fn - 実行関数、署名は(results: ResultsObject) => T | Promise<T>
  • options (オプション) - ChainOptions<T>型のオプションオブジェクト

返却値:

  • メソッドチェーンのため新しいChainsインスタンスを返す

例:

const chain = new Chains()
  .chain(() => getValue())
  .chain((r) => r.r1 + 10)
  .chain(
    async (r) => {
      const data = await fetchData();
      return data;
    },
    { timeout: 5000 },
  );

メソッド: invoke()

チェーン全体を実行して最終結果を返す。

返却値:

  • Promise<T> - 最後のチェーンステップの戻り値

例:

const result = await new Chains()
  .chain(() => 10)
  .chain((r) => r.r1 * 2)
  .invoke();

console.log(result); // 20

関数: createChains<T>(initialData?)

新しいChainsインスタンスを作成するファクトリ関数。

パラメータ:

  • initialData (オプション) - 初期データ

返却値:

  • Chains<[T]> - Chainsインスタンス

例:

const result = await createChains(100)
  .chain((r) => r.r1 * 2)
  .invoke();

console.log(result); // 200

オプションオブジェクト: ChainOptions<T>

chain()メソッドのオプションオブジェクト。

プロパティテーブル:

プロパティ タイプ デフォルト 説明
withoutThrow boolean false エラーを{ data?: T; error?: Error }としてキャプチャ(スロー代わり)
retry number 0 失敗時のリトライ回数(0はリトライなし)
retryWhen Error \| string \| RegExp なし リトライ条件: マッチ時のみリトライ
retryDelay number 0 リトライ間の遅延(ミリ秒)
timeout number なし 実行タイムアウト(ミリ秒)、超過するとTimeoutErrorをスロー

📄 ライセンス

MIT License - 詳細はLICENSEファイルを参照

🤝 サポートとフィードバック

このライブラリが役に立つなら、⭐ Starをお願いします!

更新ログ

バージョン履歴と更新内容についてはCHANGELOG.mdを参照。


最終更新: v1.1.2 | GitHubリポジトリ

概要

技術的洞察、経験、思考を共有する個人ブログ

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 CODE赤兎. 無断転載禁止