@d-zero/dealer 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,6 +38,31 @@ await deal(
38
38
  );
39
39
  ```
40
40
 
41
+ ### キャンセル
42
+
43
+ `AbortSignal`を渡すことで、処理を途中で中断できます。実行中のワーカーは完了まで待機し、新しいワーカーの起動のみが停止されます。
44
+
45
+ ```ts
46
+ const controller = new AbortController();
47
+
48
+ // 外部イベント(タイムアウトなど)でキャンセル
49
+ setTimeout(() => controller.abort(), 30_000);
50
+
51
+ await deal(
52
+ items,
53
+ (item, update, index) => {
54
+ return async () => {
55
+ update(`Processing item ${index}...`);
56
+ await item.process();
57
+ };
58
+ },
59
+ {
60
+ limit: 10,
61
+ signal: controller.signal,
62
+ },
63
+ );
64
+ ```
65
+
41
66
  ### deal関数
42
67
 
43
68
  コレクションを並列処理し、ログを順次出力します。
@@ -95,6 +120,7 @@ type DealOptions<T = unknown> = DealerOptions<T> &
95
120
 
96
121
  - `limit?: number`: 同時実行数の制限(デフォルト: 10)
97
122
  - `onPush?: (item: T) => boolean`: `push()`時のフィルタ関数。`false`を返すとそのアイテムは拒否される(例: 重複排除)
123
+ - `signal?: AbortSignal`: 処理のキャンセルに使用する`AbortSignal`。シグナルがabortされると新しいワーカーの起動を停止し、実行中のワーカーの完了を待ってから終了する
98
124
  - `header?: DealHeader`: 進捗ヘッダーを生成する関数
99
125
  - `debug?: boolean`: デバッグログを表示するかどうか
100
126
  - `interval?: number | DelayOptions`: 各処理の間隔(ミリ秒またはDelayOptions)
@@ -141,6 +167,7 @@ constructor(items: readonly T[], options?: DealerOptions<T>)
141
167
  - `items`: 処理対象のアイテム
142
168
  - `options.limit`: 同時実行数の制限(デフォルト: 10)
143
169
  - `options.onPush`: `push()`時のフィルタ関数
170
+ - `options.signal`: 処理のキャンセルに使用する`AbortSignal`
144
171
 
145
172
  #### メソッド
146
173
 
@@ -184,7 +211,7 @@ dealer.progress((progress, done, total, limit) => {
184
211
 
185
212
  ##### async push(...items: T[])
186
213
 
187
- 実行中にアイテムをキューに追加します。追加されたアイテムには`setup()`で設定した初期化関数が自動適用されます。完了後の呼び出しは無視されます。
214
+ 実行中にアイテムをキューに追加します。追加されたアイテムには`setup()`で設定した初期化関数が自動適用されます。処理完了後または`signal`がabort済みの場合、呼び出しは無視されます。
188
215
 
189
216
  ```ts
190
217
  await dealer.push(newItem1, newItem2);
package/dist/deal.d.ts CHANGED
@@ -1,11 +1,26 @@
1
1
  import type { DealerOptions } from './dealer.js';
2
2
  import type { LanesOptions } from './lanes.js';
3
3
  import type { DelayOptions } from '@d-zero/shared/delay';
4
+ /**
5
+ * {@link deal} 関数のオプション。
6
+ * {@link DealerOptions} と {@link LanesOptions} を合成し、
7
+ * ヘッダー・デバッグ・インターバルの設定を追加したもの。
8
+ * @template T - 処理対象アイテムの型
9
+ */
4
10
  export type DealOptions<T = unknown> = DealerOptions<T> & LanesOptions & {
5
11
  readonly header?: DealHeader;
6
12
  readonly debug?: boolean;
7
13
  readonly interval?: number | DelayOptions;
8
14
  };
15
+ /**
16
+ * 進捗情報をヘッダー文字列に変換するコールバック型。
17
+ * 返却文字列にはアニメーション変数(`%earth%`, `%dots%` など)を含めることができる。
18
+ * @param progress - 進捗率(0〜1)
19
+ * @param done - 完了したアイテム数
20
+ * @param total - 総アイテム数
21
+ * @param limit - 同時実行数制限
22
+ * @returns ヘッダーとして表示する文字列
23
+ */
9
24
  export type DealHeader = (progress: number, done: number, total: number, limit: number) => string;
10
25
  /**
11
26
  * Processes items in parallel with coordinated logging and optional interval delays.
@@ -36,9 +51,20 @@ export type DealHeader = (progress: number, done: number, total: number, limit:
36
51
  * - All wait logs (including interval delay) are output via `delay()` callback
37
52
  * - This ensures the determined interval (even for random delays) is used for countdown
38
53
  * - The `%countdown()` function displays remaining time based on the actual delay duration
39
- * @param items - Collection of items to process
40
- * @param setup - Function that initializes each item and returns a start function
41
- * @param options - Configuration options including interval delay
42
- * @returns Promise that resolves when all items are processed
54
+ *
55
+ * ### Cancellation via AbortSignal
56
+ * - Pass `signal` option with an `AbortSignal` to enable cancellation
57
+ * - When the signal is aborted:
58
+ * 1. No new workers will be launched
59
+ * 2. Currently running workers will continue until they complete
60
+ * 3. `push()` calls after abort are silently ignored
61
+ * 4. The returned Promise resolves after all running workers finish
62
+ * - If the signal is already aborted before `play()`, the Promise resolves immediately
63
+ * without processing any items
64
+ * @template T - 処理対象アイテムの型(WeakKey 制約)
65
+ * @param items - 処理対象のアイテムのコレクション
66
+ * @param setup - 各アイテムを初期化し、開始関数を返すコールバック
67
+ * @param options - 並列処理・ログ出力・インターバルの設定オプション
68
+ * @returns 全アイテムの処理完了またはキャンセル完了時に解決する Promise
43
69
  */
44
70
  export declare function deal<T extends WeakKey>(items: readonly T[], setup: (process: T, update: (log: string) => void, index: number, setLineHeader: (lineHeader: string) => void, push: (...items: T[]) => Promise<void>) => Promise<() => void | Promise<void>> | (() => void | Promise<void>), options?: DealOptions<T>): Promise<void>;
package/dist/deal.js CHANGED
@@ -31,10 +31,21 @@ const DEBUG_ID = Number.MIN_SAFE_INTEGER;
31
31
  * - All wait logs (including interval delay) are output via `delay()` callback
32
32
  * - This ensures the determined interval (even for random delays) is used for countdown
33
33
  * - The `%countdown()` function displays remaining time based on the actual delay duration
34
- * @param items - Collection of items to process
35
- * @param setup - Function that initializes each item and returns a start function
36
- * @param options - Configuration options including interval delay
37
- * @returns Promise that resolves when all items are processed
34
+ *
35
+ * ### Cancellation via AbortSignal
36
+ * - Pass `signal` option with an `AbortSignal` to enable cancellation
37
+ * - When the signal is aborted:
38
+ * 1. No new workers will be launched
39
+ * 2. Currently running workers will continue until they complete
40
+ * 3. `push()` calls after abort are silently ignored
41
+ * 4. The returned Promise resolves after all running workers finish
42
+ * - If the signal is already aborted before `play()`, the Promise resolves immediately
43
+ * without processing any items
44
+ * @template T - 処理対象アイテムの型(WeakKey 制約)
45
+ * @param items - 処理対象のアイテムのコレクション
46
+ * @param setup - 各アイテムを初期化し、開始関数を返すコールバック
47
+ * @param options - 並列処理・ログ出力・インターバルの設定オプション
48
+ * @returns 全アイテムの処理完了またはキャンセル完了時に解決する Promise
38
49
  */
39
50
  export async function deal(items, setup, options) {
40
51
  const dealer = new Dealer(items, options);
package/dist/dealer.d.ts CHANGED
@@ -1,15 +1,67 @@
1
1
  import type { ProcessInitializer } from './types.js';
2
+ /**
3
+ * {@link Dealer} のコンストラクタオプション。
4
+ * @template T - 処理対象アイテムの型
5
+ */
2
6
  export interface DealerOptions<T = unknown> {
7
+ /** 同時実行ワーカー数の上限。デフォルトは `10`。 */
3
8
  limit?: number;
9
+ /**
10
+ * {@link Dealer.push} 時に呼ばれるフィルタ関数。
11
+ * `false` を返すとそのアイテムはキューに追加されない。
12
+ * @param item - push されたアイテム
13
+ * @returns アイテムを受け入れる場合は `true`
14
+ */
4
15
  onPush?: (item: T) => boolean;
16
+ /**
17
+ * 処理のキャンセルに使用する `AbortSignal`。
18
+ * シグナルが abort されると新しいワーカーの起動を停止し、
19
+ * 実行中のワーカーの完了を待ってから終了する。
20
+ */
21
+ signal?: AbortSignal;
5
22
  }
23
+ /**
24
+ * アイテムを並列処理するワーカープールマネージャー。
25
+ *
26
+ * 典型的な使用順序: {@link setup} → {@link finish} / {@link progress} → {@link play}。
27
+ * 処理中に {@link push} で動的にアイテムを追加できる。
28
+ * @template T - 処理対象アイテムの型(WeakKey 制約)
29
+ */
6
30
  export declare class Dealer<T extends WeakKey> {
7
31
  #private;
8
32
  constructor(items: readonly T[], options?: DealerOptions<T>);
33
+ /**
34
+ * デバッグログのリスナーを設定する。
35
+ * @param listener - デバッグメッセージを受け取るコールバック
36
+ */
9
37
  debug(listener: (log: string) => void): void;
38
+ /**
39
+ * 全アイテムの処理完了(またはキャンセル完了)時に呼ばれるリスナーを設定する。
40
+ * @param listener - 完了時に呼ばれるコールバック
41
+ */
10
42
  finish(listener: () => void): void;
43
+ /**
44
+ * 並列処理を開始する。
45
+ * {@link setup} を先に呼び出す必要がある。
46
+ */
11
47
  play(): void;
48
+ /**
49
+ * 進捗更新のリスナーを設定する。
50
+ * アイテムが完了するたびに呼び出される。
51
+ * @param listener - 進捗情報を受け取るコールバック
52
+ */
12
53
  progress(listener: (progress: number, done: number, total: number, limit: number) => void): void;
54
+ /**
55
+ * 実行中にアイテムをキューに追加する。
56
+ * 追加されたアイテムには {@link setup} で設定した初期化関数が自動適用される。
57
+ * 処理完了後または signal が abort 済みの場合、呼び出しは無視される。
58
+ * @param items - 追加するアイテム
59
+ */
13
60
  push(...items: T[]): Promise<void>;
61
+ /**
62
+ * 各アイテムの初期化関数を設定する。
63
+ * {@link play} を呼ぶ前に必ず呼び出すこと。
64
+ * @param initializer - 各アイテムを初期化し、実行関数を返すコールバック
65
+ */
14
66
  setup(initializer: ProcessInitializer<T>): Promise<void>;
15
67
  }
package/dist/dealer.js CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * アイテムを並列処理するワーカープールマネージャー。
3
+ *
4
+ * 典型的な使用順序: {@link setup} → {@link finish} / {@link progress} → {@link play}。
5
+ * 処理中に {@link push} で動的にアイテムを追加できる。
6
+ * @template T - 処理対象アイテムの型(WeakKey 制約)
7
+ */
1
8
  export class Dealer {
2
9
  #debug = () => { };
3
10
  #done = new WeakSet();
@@ -11,27 +18,52 @@ export class Dealer {
11
18
  #onPush;
12
19
  #pendingInitCount = 0;
13
20
  #progress = () => { };
21
+ #signal;
14
22
  #starts = new WeakMap();
15
23
  #workers = new Set();
16
24
  constructor(items, options) {
17
25
  this.#items = [...items];
18
26
  this.#limit = options?.limit ?? 10;
19
27
  this.#onPush = options?.onPush;
28
+ this.#signal = options?.signal;
20
29
  }
30
+ /**
31
+ * デバッグログのリスナーを設定する。
32
+ * @param listener - デバッグメッセージを受け取るコールバック
33
+ */
21
34
  debug(listener) {
22
35
  this.#debug = listener;
23
36
  }
37
+ /**
38
+ * 全アイテムの処理完了(またはキャンセル完了)時に呼ばれるリスナーを設定する。
39
+ * @param listener - 完了時に呼ばれるコールバック
40
+ */
24
41
  finish(listener) {
25
42
  this.#finish = listener;
26
43
  }
44
+ /**
45
+ * 並列処理を開始する。
46
+ * {@link setup} を先に呼び出す必要がある。
47
+ */
27
48
  play() {
28
49
  this.#deal();
29
50
  }
51
+ /**
52
+ * 進捗更新のリスナーを設定する。
53
+ * アイテムが完了するたびに呼び出される。
54
+ * @param listener - 進捗情報を受け取るコールバック
55
+ */
30
56
  progress(listener) {
31
57
  this.#progress = listener;
32
58
  }
59
+ /**
60
+ * 実行中にアイテムをキューに追加する。
61
+ * 追加されたアイテムには {@link setup} で設定した初期化関数が自動適用される。
62
+ * 処理完了後または signal が abort 済みの場合、呼び出しは無視される。
63
+ * @param items - 追加するアイテム
64
+ */
33
65
  async push(...items) {
34
- if (this.#finished) {
66
+ if (this.#finished || this.#signal?.aborted) {
35
67
  return;
36
68
  }
37
69
  for (const item of items) {
@@ -42,6 +74,11 @@ export class Dealer {
42
74
  await this.#initializeAndDispatch(item);
43
75
  }
44
76
  }
77
+ /**
78
+ * 各アイテムの初期化関数を設定する。
79
+ * {@link play} を呼ぶ前に必ず呼び出すこと。
80
+ * @param initializer - 各アイテムを初期化し、実行関数を返すコールバック
81
+ */
45
82
  async setup(initializer) {
46
83
  this.#initializer = initializer;
47
84
  for (const item of this.#items) {
@@ -52,6 +89,9 @@ export class Dealer {
52
89
  this.#progress(total === 0 ? 0 : this.#doneCount / total, this.#doneCount, total, this.#limit);
53
90
  }
54
91
  #deal() {
92
+ if (this.#finished) {
93
+ return;
94
+ }
55
95
  const total = this.#items.length;
56
96
  this.#debug(`Done: ${this.#doneCount}/${total} (Limit: ${this.#limit})`);
57
97
  this.#progress(total === 0 ? 0 : this.#doneCount / total, this.#doneCount, total, this.#limit);
@@ -60,6 +100,13 @@ export class Dealer {
60
100
  this.#finish();
61
101
  return;
62
102
  }
103
+ if (this.#signal?.aborted) {
104
+ if (this.#workers.size === 0) {
105
+ this.#finished = true;
106
+ this.#finish();
107
+ }
108
+ return;
109
+ }
63
110
  while (this.#workers.size < this.#limit) {
64
111
  const worker = this.#draw();
65
112
  if (!worker) {
@@ -95,9 +142,13 @@ export class Dealer {
95
142
  throw new Error('setup() must be called before push()');
96
143
  }
97
144
  const start = await this.#initializer(item, this.#nextIndex++);
145
+ this.#pendingInitCount--;
146
+ if (this.#signal?.aborted) {
147
+ this.#deal();
148
+ return;
149
+ }
98
150
  this.#starts.set(item, async () => await start());
99
151
  this.#items.push(item);
100
- this.#pendingInitCount--;
101
152
  this.#deal();
102
153
  }
103
154
  }
package/dist/lanes.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { Animations, FPS } from './types.js';
2
2
  type Log = readonly [id: number, message: string];
3
3
  type SortFunc = (a: Log, b: Log) => number;
4
+ /**
5
+ * {@link Lanes} のコンストラクタオプション。
6
+ */
4
7
  export type LanesOptions = {
5
8
  readonly animations?: Animations;
6
9
  readonly fps?: FPS;
@@ -8,16 +11,46 @@ export type LanesOptions = {
8
11
  readonly sort?: SortFunc;
9
12
  readonly verbose?: boolean;
10
13
  };
14
+ /**
15
+ * 複数のログラインを管理し、順序付きでターミナルに表示するクラス。
16
+ * verbose モードでは上書き表示ではなく追記出力を行う。
17
+ */
11
18
  export declare class Lanes {
12
19
  #private;
13
20
  constructor(options?: LanesOptions);
21
+ /**
22
+ * すべてのログをクリアする。verbose モードでは何もしない。
23
+ * @param options - クリアオプション
24
+ * @param options.header
25
+ */
14
26
  clear(options?: {
15
27
  header?: boolean;
16
28
  }): void;
29
+ /**
30
+ * ディスプレイを閉じ、リソースを解放する。
31
+ */
17
32
  close(): void;
33
+ /**
34
+ * 指定した ID のログを削除する。verbose モードでは何もしない。
35
+ * @param id - 削除するログの ID
36
+ */
18
37
  delete(id: number): void;
38
+ /**
39
+ * ヘッダーテキストを設定する。
40
+ * @param text - ヘッダーとして表示する文字列
41
+ */
19
42
  header(text: string): void;
43
+ /**
44
+ * 指定した ID のログを更新する。
45
+ * verbose モードではヘッダーとログを連結して即時出力する。
46
+ * @param id - 更新するログの ID
47
+ * @param log - ログメッセージ
48
+ */
20
49
  update(id: number, log: string): void;
50
+ /**
51
+ * 現在のログをソートしてターミナルに表示する。
52
+ * verbose モードでは何もしない。
53
+ */
21
54
  write(): void;
22
55
  }
23
56
  export {};
package/dist/lanes.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { Display } from './display.js';
2
2
  const RESET = '\u001B[0m';
3
+ /**
4
+ * 複数のログラインを管理し、順序付きでターミナルに表示するクラス。
5
+ * verbose モードでは上書き表示ではなく追記出力を行う。
6
+ */
3
7
  export class Lanes {
4
8
  #display;
5
9
  #header;
@@ -17,6 +21,11 @@ export class Lanes {
17
21
  this.#sort = options?.sort ?? this.#sort;
18
22
  this.#verbose = options?.verbose ?? false;
19
23
  }
24
+ /**
25
+ * すべてのログをクリアする。verbose モードでは何もしない。
26
+ * @param options - クリアオプション
27
+ * @param options.header
28
+ */
20
29
  clear(options) {
21
30
  if (this.#verbose) {
22
31
  return;
@@ -27,9 +36,16 @@ export class Lanes {
27
36
  }
28
37
  this.write();
29
38
  }
39
+ /**
40
+ * ディスプレイを閉じ、リソースを解放する。
41
+ */
30
42
  close() {
31
43
  this.#display.close();
32
44
  }
45
+ /**
46
+ * 指定した ID のログを削除する。verbose モードでは何もしない。
47
+ * @param id - 削除するログの ID
48
+ */
33
49
  delete(id) {
34
50
  if (this.#verbose) {
35
51
  return;
@@ -37,6 +53,10 @@ export class Lanes {
37
53
  this.#logs.delete(id);
38
54
  this.write();
39
55
  }
56
+ /**
57
+ * ヘッダーテキストを設定する。
58
+ * @param text - ヘッダーとして表示する文字列
59
+ */
40
60
  header(text) {
41
61
  this.#header = text;
42
62
  if (this.#verbose) {
@@ -44,6 +64,12 @@ export class Lanes {
44
64
  }
45
65
  this.write();
46
66
  }
67
+ /**
68
+ * 指定した ID のログを更新する。
69
+ * verbose モードではヘッダーとログを連結して即時出力する。
70
+ * @param id - 更新するログの ID
71
+ * @param log - ログメッセージ
72
+ */
47
73
  update(id, log) {
48
74
  if (this.#verbose) {
49
75
  this.#display.write(`${RESET}${this.#header}${RESET} ${log}`);
@@ -52,6 +78,10 @@ export class Lanes {
52
78
  this.#logs.set(id, log);
53
79
  this.write();
54
80
  }
81
+ /**
82
+ * 現在のログをソートしてターミナルに表示する。
83
+ * verbose モードでは何もしない。
84
+ */
55
85
  write() {
56
86
  if (this.#verbose) {
57
87
  return;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,20 @@
1
+ /**
2
+ * アニメーション定義のマップ。
3
+ * キーはアニメーション名(`%name%` 形式でログ中に埋め込み可能)、
4
+ * 値は先頭が FPS、残りがスプライトフレームのタプル。
5
+ */
1
6
  export type Animations = Record<string, [fps: number, ...sprites: string[]]>;
7
+ /**
8
+ * サポートされるフレームレート。
9
+ */
2
10
  export type FPS = 12 | 24 | 30 | 60;
11
+ /**
12
+ * 各アイテムを初期化し、実行関数を返すコールバック。
13
+ * @template T - 処理対象アイテムの型
14
+ * @param process - 初期化対象のアイテム
15
+ * @param index - アイテムのインデックス(0始まり)
16
+ * @returns 処理を開始する関数を返す Promise
17
+ */
3
18
  export interface ProcessInitializer<T> {
4
19
  (process: T, index: number): Promise<() => Promise<void> | void>;
5
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-zero/dealer",
3
- "version": "1.6.4",
3
+ "version": "1.7.0",
4
4
  "description": "A tool that provides an API and CLI for parallel processing of collections and sequential logging to standard output",
5
5
  "author": "D-ZERO",
6
6
  "license": "MIT",
@@ -26,5 +26,5 @@
26
26
  "@d-zero/shared": "0.20.1",
27
27
  "ansi-colors": "4.1.3"
28
28
  },
29
- "gitHead": "a6b5eb0a0a327c003053f7c25be4c075ed319c76"
29
+ "gitHead": "866364294ddacc3095f65a9f8b488d40c4ccb904"
30
30
  }