@d-zero/dealer 1.5.3 → 1.6.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
@@ -10,16 +10,14 @@ npm install @d-zero/dealer
10
10
 
11
11
  ## API
12
12
 
13
+ ### 基本的な使い方
14
+
13
15
  ```ts
14
16
  import { deal } from '@d-zero/dealer';
15
17
 
16
- await deal(items, {
17
- limit: 30,
18
- header: (progress, done, total, limit) =>
19
- progress === 1
20
- ? 'HeaderMessage: Done!'
21
- : `HeaderMessage: %earth% %dots% %block% %propeller%`,
22
- setup: (item, update, index) => {
18
+ await deal(
19
+ items,
20
+ (item, update, index, setLineHeader, push) => {
23
21
  item.setup();
24
22
  item.addListeners((state) => {
25
23
  update(`item(${index}): ${state}`);
@@ -30,5 +28,268 @@ await deal(items, {
30
28
  item.cleanup();
31
29
  };
32
30
  },
31
+ {
32
+ limit: 30,
33
+ header: (progress, done, total, limit) =>
34
+ progress === 1
35
+ ? 'HeaderMessage: Done!'
36
+ : `HeaderMessage: %earth% %dots% %block% %propeller%`,
37
+ },
38
+ );
39
+ ```
40
+
41
+ ### deal関数
42
+
43
+ コレクションを並列処理し、ログを順次出力します。
44
+
45
+ #### シグネチャ
46
+
47
+ ```ts
48
+ async function deal<T extends WeakKey>(
49
+ items: readonly T[],
50
+ setup: (
51
+ process: T,
52
+ update: (log: string) => void,
53
+ index: number,
54
+ setLineHeader: (lineHeader: string) => void,
55
+ push: (...items: T[]) => Promise<void>,
56
+ ) => Promise<() => void | Promise<void>> | (() => void | Promise<void>),
57
+ options?: DealOptions,
58
+ ): Promise<void>;
59
+ ```
60
+
61
+ #### パラメータ
62
+
63
+ - `items`: 処理対象のアイテムのコレクション
64
+ - `setup`: 各アイテムを初期化し、開始関数を返す関数
65
+ - `process`: 現在処理中のアイテム
66
+ - `update`: ログを更新する関数
67
+ - `index`: アイテムのインデックス
68
+ - `setLineHeader`: ログの各行にプレフィックスを設定する関数
69
+ - `push`: 実行中にアイテムをキューに追加する関数
70
+ - 戻り値: アイテムを開始する関数
71
+ - `options`: 設定オプション
72
+
73
+ #### 実行フロー
74
+
75
+ 1. `dealer.play()` が並列処理を開始
76
+ 2. 各ワーカーについて:
77
+ - `start()` 関数が呼び出される(アイテムが開始)
78
+ - **インターバル遅延が実行される**(options.intervalが指定されている場合)
79
+ - 待機ログが `%countdown()` 形式で出力される
80
+ - これはアイテム開始**後**、最初の出力の**前**に発生
81
+ - 実際の処理が始まる(ユーザーコードからの最初の `update()` 呼び出し)
82
+
83
+ ### DealOptions型
84
+
85
+ ```ts
86
+ type DealOptions = DealerOptions &
87
+ LanesOptions & {
88
+ readonly header?: DealHeader;
89
+ readonly debug?: boolean;
90
+ readonly interval?: number | DelayOptions;
91
+ };
92
+ ```
93
+
94
+ #### プロパティ
95
+
96
+ - `limit?: number`: 同時実行数の制限(デフォルト: 10)
97
+ - `onPush?: (item: T) => boolean`: `push()`時のフィルタ関数。`false`を返すとそのアイテムは拒否される(例: 重複排除)
98
+ - `header?: DealHeader`: 進捗ヘッダーを生成する関数
99
+ - `debug?: boolean`: デバッグログを表示するかどうか
100
+ - `interval?: number | DelayOptions`: 各処理の間隔(ミリ秒またはDelayOptions)
101
+ - `animations?: Animations`: アニメーション定義
102
+ - `fps?: FPS`: フレームレート(12, 24, 30, 60)
103
+ - `indent?: string`: ログのインデント文字列
104
+ - `sort?: (a: [number, string], b: [number, string]) => number`: ログのソート関数
105
+ - `verbose?: boolean`: 詳細ログモード
106
+
107
+ ### DealHeader型
108
+
109
+ 進捗情報をヘッダー文字列に変換する関数型です。
110
+
111
+ ```ts
112
+ type DealHeader = (
113
+ progress: number,
114
+ done: number,
115
+ total: number,
116
+ limit: number,
117
+ ) => string;
118
+ ```
119
+
120
+ #### パラメータ
121
+
122
+ - `progress`: 進捗率(0〜1)
123
+ - `done`: 完了したアイテム数
124
+ - `total`: 総アイテム数
125
+ - `limit`: 同時実行数制限
126
+
127
+ #### 戻り値
128
+
129
+ ヘッダーとして表示する文字列。アニメーション変数(`%earth%`, `%dots%`など)を含めることができます。
130
+
131
+ ### Dealerクラス
132
+
133
+ 並列処理を制御するクラスです。
134
+
135
+ #### コンストラクタ
136
+
137
+ ```ts
138
+ constructor(items: readonly T[], options?: DealerOptions)
139
+ ```
140
+
141
+ - `items`: 処理対象のアイテム
142
+ - `options.limit`: 同時実行数の制限(デフォルト: 10)
143
+ - `options.onPush`: `push()`時のフィルタ関数
144
+
145
+ #### メソッド
146
+
147
+ ##### debug(listener: (log: string) => void)
148
+
149
+ デバッグログのリスナーを設定します。
150
+
151
+ ```ts
152
+ dealer.debug((log) => {
153
+ console.log(`[DEBUG]: ${log}`);
154
+ });
155
+ ```
156
+
157
+ ##### finish(listener: () => void)
158
+
159
+ すべての処理が完了したときのリスナーを設定します。
160
+
161
+ ```ts
162
+ dealer.finish(() => {
163
+ console.log('All done!');
164
+ });
165
+ ```
166
+
167
+ ##### play()
168
+
169
+ 並列処理を開始します。
170
+
171
+ ```ts
172
+ dealer.play();
173
+ ```
174
+
175
+ ##### progress(listener: (progress: number, done: number, total: number, limit: number) => void)
176
+
177
+ 進捗更新のリスナーを設定します。
178
+
179
+ ```ts
180
+ dealer.progress((progress, done, total, limit) => {
181
+ console.log(`Progress: ${(progress * 100).toFixed(1)}% (${done}/${total})`);
182
+ });
183
+ ```
184
+
185
+ ##### async push(...items: T[])
186
+
187
+ 実行中にアイテムをキューに追加します。追加されたアイテムには`setup()`で設定した初期化関数が自動適用されます。完了後の呼び出しは無視されます。
188
+
189
+ ```ts
190
+ await dealer.push(newItem1, newItem2);
191
+ ```
192
+
193
+ - `items`: 追加するアイテム。`onPush`が設定されている場合、`false`を返したアイテムはスキップされます。
194
+
195
+ ##### async setup(initializer: ProcessInitializer<T>)
196
+
197
+ 各アイテムの初期化関数を設定します。
198
+
199
+ ```ts
200
+ await dealer.setup(async (item, index) => {
201
+ // 初期化処理
202
+ return async () => {
203
+ // 実行処理
204
+ };
33
205
  });
34
206
  ```
207
+
208
+ - `initializer`: 各アイテムを初期化し、開始関数を返す関数
209
+ - `process`: 現在のアイテム
210
+ - `index`: アイテムのインデックス
211
+ - 戻り値: アイテムを開始する非同期関数
212
+
213
+ ### Lanesクラス
214
+
215
+ 複数のログラインを管理し、順序付きで表示するクラスです。
216
+
217
+ #### コンストラクタ
218
+
219
+ ```ts
220
+ constructor(options?: LanesOptions)
221
+ ```
222
+
223
+ - `options.animations`: アニメーション定義
224
+ - `options.fps`: フレームレート(12, 24, 30, 60)
225
+ - `options.indent`: ログのインデント文字列
226
+ - `options.sort`: ログのソート関数
227
+ - `options.verbose`: 詳細ログモード(true の場合、ログをクリアせずに追加表示)
228
+
229
+ #### メソッド
230
+
231
+ ##### clear(options?: { header?: boolean })
232
+
233
+ すべてのログをクリアします。
234
+
235
+ ```ts
236
+ lanes.clear(); // ログのみクリア
237
+ lanes.clear({ header: true }); // ヘッダーもクリア
238
+ ```
239
+
240
+ - `options.header`: ヘッダーもクリアするかどうか(デフォルト: false)
241
+
242
+ 注: verboseモードでは何もしません。
243
+
244
+ ##### close()
245
+
246
+ ディスプレイを閉じます。
247
+
248
+ ```ts
249
+ lanes.close();
250
+ ```
251
+
252
+ ##### delete(id: number)
253
+
254
+ 指定したIDのログを削除します。
255
+
256
+ ```ts
257
+ lanes.delete(42);
258
+ ```
259
+
260
+ - `id`: 削除するログのID
261
+
262
+ 注: verboseモードでは何もしません。
263
+
264
+ ##### header(text: string)
265
+
266
+ ヘッダーテキストを設定します。
267
+
268
+ ```ts
269
+ lanes.header('Processing items...');
270
+ ```
271
+
272
+ - `text`: ヘッダーとして表示するテキスト
273
+
274
+ ##### update(id: number, log: string)
275
+
276
+ 指定したIDのログを更新します。
277
+
278
+ ```ts
279
+ lanes.update(42, 'Item 42: Processing...');
280
+ ```
281
+
282
+ - `id`: 更新するログのID
283
+ - `log`: ログメッセージ
284
+
285
+ 注: verboseモードでは、ヘッダーとログを連結して即座に出力します。
286
+
287
+ ##### write()
288
+
289
+ 現在のログをすべて表示します。
290
+
291
+ ```ts
292
+ lanes.write();
293
+ ```
294
+
295
+ 注: verboseモードでは何もしません。通常モードでは、ソート後のログをヘッダーと共に出力します。
package/dist/deal.d.ts CHANGED
@@ -41,4 +41,4 @@ export type DealHeader = (progress: number, done: number, total: number, limit:
41
41
  * @param options - Configuration options including interval delay
42
42
  * @returns Promise that resolves when all items are processed
43
43
  */
44
- export declare function deal<T extends WeakKey>(items: readonly T[], setup: (process: T, update: (log: string) => void, index: number, setLineHeader: (lineHeader: string) => void) => Promise<() => void | Promise<void>> | (() => void | Promise<void>), options?: DealOptions): Promise<void>;
44
+ 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): Promise<void>;
package/dist/deal.js CHANGED
@@ -50,7 +50,8 @@ export async function deal(items, setup, options) {
50
50
  lineHeader = header;
51
51
  };
52
52
  const update = (log) => lanes.update(index, lineHeader + log);
53
- const start = await setup(process, update, index, setLineHeader);
53
+ const push = (...newItems) => dealer.push(...newItems);
54
+ const start = await setup(process, update, index, setLineHeader, push);
54
55
  return async () => {
55
56
  await delay(options?.interval ?? 0, (determinedInterval) => {
56
57
  update(`Waiting interval: %countdown(${determinedInterval},${index}_interval)%ms`);
package/dist/dealer.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ProcessInitializer } from './types.js';
2
2
  export interface DealerOptions {
3
3
  limit?: number;
4
+ onPush?: (item: never) => boolean;
4
5
  }
5
6
  export declare class Dealer<T extends WeakKey> {
6
7
  #private;
@@ -9,5 +10,6 @@ export declare class Dealer<T extends WeakKey> {
9
10
  finish(listener: () => void): void;
10
11
  play(): void;
11
12
  progress(listener: (progress: number, done: number, total: number, limit: number) => void): void;
13
+ push(...items: T[]): Promise<void>;
12
14
  setup(initializer: ProcessInitializer<T>): Promise<void>;
13
15
  }
package/dist/dealer.js CHANGED
@@ -3,14 +3,20 @@ export class Dealer {
3
3
  #done = new WeakSet();
4
4
  #doneCount = 0;
5
5
  #finish = () => { };
6
+ #finished = false;
7
+ #initializer = null;
6
8
  #items;
7
9
  #limit;
10
+ #nextIndex = 0;
11
+ #onPush;
12
+ #pendingInitCount = 0;
8
13
  #progress = () => { };
9
14
  #starts = new WeakMap();
10
15
  #workers = new Set();
11
16
  constructor(items, options) {
12
- this.#items = items;
17
+ this.#items = [...items];
13
18
  this.#limit = options?.limit ?? 10;
19
+ this.#onPush = options?.onPush;
14
20
  }
15
21
  debug(listener) {
16
22
  this.#debug = listener;
@@ -24,18 +30,35 @@ export class Dealer {
24
30
  progress(listener) {
25
31
  this.#progress = listener;
26
32
  }
33
+ async push(...items) {
34
+ if (this.#finished) {
35
+ return;
36
+ }
37
+ for (const item of items) {
38
+ if (this.#onPush && !this.#onPush(item)) {
39
+ continue;
40
+ }
41
+ this.#pendingInitCount++;
42
+ await this.#initializeAndDispatch(item);
43
+ }
44
+ }
27
45
  async setup(initializer) {
28
- for (const [index, item] of this.#items.entries()) {
29
- const start = await initializer(item, index);
46
+ this.#initializer = initializer;
47
+ for (const item of this.#items) {
48
+ const start = await initializer(item, this.#nextIndex++);
30
49
  this.#starts.set(item, async () => await start());
31
50
  }
32
- this.#progress(this.#doneCount / this.#items.length, this.#doneCount, this.#items.length, this.#limit);
51
+ const total = this.#items.length;
52
+ this.#progress(total === 0 ? 0 : this.#doneCount / total, this.#doneCount, total, this.#limit);
33
53
  }
34
54
  #deal() {
35
- this.#debug(`Done: ${this.#doneCount}/${this.#items.length} (Limit: ${this.#limit})`);
36
- this.#progress(this.#doneCount / this.#items.length, this.#doneCount, this.#items.length, this.#limit);
37
- if (this.#doneCount === this.#items.length) {
55
+ const total = this.#items.length;
56
+ this.#debug(`Done: ${this.#doneCount}/${total} (Limit: ${this.#limit})`);
57
+ this.#progress(total === 0 ? 0 : this.#doneCount / total, this.#doneCount, total, this.#limit);
58
+ if (total > 0 && this.#doneCount === total && this.#pendingInitCount === 0) {
59
+ this.#finished = true;
38
60
  this.#finish();
61
+ return;
39
62
  }
40
63
  while (this.#workers.size < this.#limit) {
41
64
  const worker = this.#draw();
@@ -67,4 +90,14 @@ export class Dealer {
67
90
  }
68
91
  return null;
69
92
  }
93
+ async #initializeAndDispatch(item) {
94
+ if (!this.#initializer) {
95
+ throw new Error('setup() must be called before push()');
96
+ }
97
+ const start = await this.#initializer(item, this.#nextIndex++);
98
+ this.#starts.set(item, async () => await start());
99
+ this.#items.push(item);
100
+ this.#pendingInitCount--;
101
+ this.#deal();
102
+ }
70
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-zero/dealer",
3
- "version": "1.5.3",
3
+ "version": "1.6.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",
@@ -23,8 +23,8 @@
23
23
  "clean": "tsc --build --clean"
24
24
  },
25
25
  "dependencies": {
26
- "@d-zero/shared": "0.17.1",
26
+ "@d-zero/shared": "0.18.0",
27
27
  "ansi-colors": "4.1.3"
28
28
  },
29
- "gitHead": "c976f890ac4225e20df8fc83118f31231bd6323c"
29
+ "gitHead": "a6d7b36c485bbc0782375c6e1ad0d0606f423e97"
30
30
  }