[Node Library] @tool-chain/core
強力な非同期チェーン実行ライブラリ - エレガントな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();
実行フロー:
- 関数を実行
- 失敗時 → retryDelay待機 → リトライ
- 成功またはリトライ回数消費まで繰り返し
スマートなリトライ条件
特定のエラーのみリトライ、他はすぐに失敗:
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ファイルを参照
🤝 サポートとフィードバック
- GitHubリポジトリ - hu-shukang/toolchain_core
- イシュートラッカー - イシュー報告
- ディスカッション - GitHubディスカッション
- 著者 - HU SHUKANG
このライブラリが役に立つなら、⭐ Starをお願いします!
更新ログ
バージョン履歴と更新内容についてはCHANGELOG.mdを参照。
最終更新: v1.1.2 | GitHubリポジトリ