dl-once 0.0.1 → 0.0.2

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -28
  3. package/dist/_bytes-builder.d.ts +34 -0
  4. package/dist/_bytes-builder.d.ts.map +1 -0
  5. package/dist/_bytes-builder.js +48 -0
  6. package/dist/cache-storage.d.ts +74 -0
  7. package/dist/cache-storage.d.ts.map +1 -0
  8. package/dist/cache-storage.js +1 -0
  9. package/dist/dl-once.d.ts +242 -0
  10. package/dist/dl-once.d.ts.map +1 -0
  11. package/dist/dl-once.js +245 -0
  12. package/dist/hash-verification-hook.d.ts +46 -0
  13. package/dist/hash-verification-hook.d.ts.map +1 -0
  14. package/dist/hash-verification-hook.js +97 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/indexeddb-cache-storage.d.ts +51 -0
  19. package/dist/indexeddb-cache-storage.d.ts.map +1 -0
  20. package/dist/indexeddb-cache-storage.js +573 -0
  21. package/dist/md5-verification-hook.d.ts +15 -0
  22. package/dist/md5-verification-hook.d.ts.map +1 -0
  23. package/dist/md5-verification-hook.js +17 -0
  24. package/dist/memory-cache-storage.d.ts +41 -0
  25. package/dist/memory-cache-storage.d.ts.map +1 -0
  26. package/dist/memory-cache-storage.js +290 -0
  27. package/dist/node-fs-cache-storage.d.ts +63 -0
  28. package/dist/node-fs-cache-storage.d.ts.map +1 -0
  29. package/dist/node-fs-cache-storage.js +565 -0
  30. package/dist/sha256-verification-hook.d.ts +15 -0
  31. package/dist/sha256-verification-hook.d.ts.map +1 -0
  32. package/dist/sha256-verification-hook.js +17 -0
  33. package/package.json +46 -7
  34. package/src/_bytes-builder.ts +66 -0
  35. package/src/cache-storage.ts +86 -0
  36. package/src/dl-once.ts +554 -0
  37. package/src/hash-verification-hook.ts +98 -0
  38. package/src/index.ts +28 -0
  39. package/src/indexeddb-cache-storage.ts +535 -0
  40. package/src/md5-verification-hook.ts +18 -0
  41. package/src/memory-cache-storage.ts +361 -0
  42. package/src/node-fs-cache-storage.ts +511 -0
  43. package/src/sha256-verification-hook.ts +18 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `Uint8Array` のチャンクを効率的に積み上げ、最終的に一つのバイナリーデータとして結合するためのビルダー形式のクラスです。
3
+ */
4
+ export default class BytesBuilder {
5
+ /**
6
+ * 書き込まれたバイナリーデータのチャンクを保持する配列です。
7
+ * @private
8
+ */
9
+ private chunks: Uint8Array<ArrayBuffer>[];
10
+
11
+ /**
12
+ * 現在までに書き込まれた全データの合計バイト長です。
13
+ * @private
14
+ */
15
+ private length: number;
16
+
17
+ /**
18
+ * `BytesBuilder` クラスの新しいインスタンスを初期化します。
19
+ */
20
+ public constructor() {
21
+ // 内部状態を初期化します。
22
+ this.chunks = [];
23
+ this.length = 0;
24
+ }
25
+
26
+ /**
27
+ * 新しいバイナリーデータをチャンクとして追加します。
28
+ *
29
+ * @param data 追加する `Uint8Array` 形式のデータです。
30
+ */
31
+ public write(data: Uint8Array<ArrayBuffer>): void {
32
+ // チャンク配列にデータを追加し、全体の長さを更新します。
33
+ this.chunks.push(data);
34
+ this.length += data.length;
35
+ }
36
+
37
+ /**
38
+ * 現在保持しているすべてのチャンクを結合して、一つの `Uint8Array` として返します。
39
+ *
40
+ * このメソッドを呼び出すと、内部のチャンクと長さの情報はリセットされます。
41
+ *
42
+ * @returns 結合されたすべてのデータを含む `Uint8Array` です。
43
+ */
44
+ public bytes(): Uint8Array<ArrayBuffer> {
45
+ // 全体の長さに合わせた新しいバッファーを確保します。
46
+ const bytes = new Uint8Array(this.length);
47
+
48
+ // 書き込み位置を管理するためのオフセット変数です。
49
+ let offset = 0;
50
+
51
+ // 保持している各チャンクを、作成したバッファーの適切な位置にコピーしていきます。
52
+ for (const data of this.chunks) {
53
+ // 指定したオフセットからデータをセットします。
54
+ bytes.set(data, offset);
55
+ // 次のチャンクのためにオフセットを更新します。
56
+ offset += data.length;
57
+ }
58
+
59
+ // 次回の書き込みに備えて、内部の状態(チャンク配列と合計長)をリセットします。
60
+ this.chunks = [];
61
+ this.length = 0;
62
+
63
+ // 結合済みのバイナリーデータを返します。
64
+ return bytes;
65
+ }
66
+ }
@@ -0,0 +1,86 @@
1
+ import type { ICacheHandle, MaybePromise } from "./dl-once.js";
2
+
3
+ /**
4
+ * ストレージを開始する際に渡せるオプションの型定義です。
5
+ */
6
+ export type OpenOptions = {
7
+ /**
8
+ * 中断を監視するためのシグナルです。
9
+ */
10
+ readonly signal?: AbortSignal | undefined;
11
+ };
12
+
13
+ /**
14
+ * ストレージを終了する際に渡せるオプションの型定義です。
15
+ */
16
+ export type CloseOptions = {
17
+ /**
18
+ * 中断を監視するためのシグナルです。
19
+ */
20
+ readonly signal?: AbortSignal | undefined;
21
+ };
22
+
23
+ /**
24
+ * ストレージをクリアする際に渡せるオプションの型定義です。
25
+ */
26
+ export type ClearOptions = {
27
+ /**
28
+ * キャッシュを一意に識別する文字列です。
29
+ */
30
+ readonly cacheKey?: string | undefined;
31
+
32
+ /**
33
+ * 中断を監視するためのシグナルです。
34
+ */
35
+ readonly signal?: AbortSignal | undefined;
36
+ };
37
+
38
+ /**
39
+ * データキャッシュを管理するストレージのインターフェースです。
40
+ */
41
+ export interface ICacheStorage {
42
+ /**
43
+ * ストレージが現在開いているかどうかを返します。
44
+ */
45
+ readonly isOpen: boolean;
46
+
47
+ /**
48
+ * ストレージを開き、利用可能な状態にします。
49
+ *
50
+ * @param options オプションです。
51
+ */
52
+ open(options?: OpenOptions | undefined): MaybePromise<void>;
53
+
54
+ /**
55
+ * ストレージを閉じ、すべてのキャッシュデータを破棄します。
56
+ *
57
+ * @param options オプションです。
58
+ */
59
+ close(options?: CloseOptions | undefined): MaybePromise<void>;
60
+
61
+ /**
62
+ * キャッシュをクリアします。引数が指定された場合はそのキーのみ、指定されない場合は全てのキャッシュを削除します。
63
+ *
64
+ * @param cacheKey キャッシュを一意に識別する文字列です。
65
+ * @param options オプションです。
66
+ */
67
+ clear(
68
+ cacheKey?: string | undefined,
69
+ options?: Omit<ClearOptions, "cacheKey"> | undefined,
70
+ ): MaybePromise<void>;
71
+
72
+ /**
73
+ * キャッシュをクリアします。引数が指定された場合はそのキーのみ、指定されない場合は全てのキャッシュを削除します。
74
+ *
75
+ * @param options オプションです。
76
+ */
77
+ clear(options?: ClearOptions | undefined): MaybePromise<void>;
78
+
79
+ /**
80
+ * 指定したキーに対応するキャッシュハンドルを取得、または新規作成します。
81
+ *
82
+ * @param key キャッシュを一意に識別する文字列です。
83
+ * @returns ICacheHandle を実装したオブジェクトを返します。
84
+ */
85
+ createCacheHandle(key: string): ICacheHandle;
86
+ }
package/src/dl-once.ts ADDED
@@ -0,0 +1,554 @@
1
+ import { getLogger } from "@logtape/logtape";
2
+ import BytesBuilder from "./_bytes-builder.js";
3
+
4
+ /**
5
+ * ロガーのインスタンスです。
6
+ */
7
+ const log = getLogger("dl-once");
8
+
9
+ /**
10
+ * 同期的な値、または Promise ライクな値を表す型です。
11
+ *
12
+ * @template T 解決される値の型です。
13
+ */
14
+ export type MaybePromise<T> = T | PromiseLike<T>;
15
+
16
+ /**
17
+ * ダウンロード開始前に実行されるハンドラーに渡される引数の型定義です。
18
+ */
19
+ export type BeforeDownloadHandlerArgs = {
20
+ /**
21
+ * 実行されるリクエストオブジェクトです。
22
+ */
23
+ request: Request;
24
+
25
+ /**
26
+ * 中断を監視するためのシグナルです。
27
+ */
28
+ signal: AbortSignal;
29
+ };
30
+
31
+ /**
32
+ * データのチャンクが読み込まれた際に実行されるハンドラーに渡される引数の型定義です。
33
+ */
34
+ export type ChunkReadHandlerArgs = {
35
+ /**
36
+ * 読み込まれたバイナリーデータです。
37
+ */
38
+ chunkData: Uint8Array<ArrayBuffer>;
39
+
40
+ /**
41
+ * サーバーからのレスポンスオブジェクトです。
42
+ */
43
+ response: Response;
44
+
45
+ /**
46
+ * 中断を監視するためのシグナルです。
47
+ */
48
+ signal: AbortSignal;
49
+ };
50
+
51
+ /**
52
+ * 処理が正常に終了した際に実行されるハンドラーに渡される引数の型定義です。
53
+ */
54
+ export type CloseHandlerArgs = {
55
+ /**
56
+ * 中断を監視するためのシグナルです。
57
+ */
58
+ signal: AbortSignal;
59
+ };
60
+
61
+ /**
62
+ * エラーが発生した際に実行されるハンドラーに渡される引数の型定義です。
63
+ */
64
+ export type ErrorHandlerArgs = {
65
+ /**
66
+ * エラーの原因となったオブジェクトです。
67
+ */
68
+ reason: unknown;
69
+
70
+ /**
71
+ * 中断を監視するためのシグナルです。
72
+ */
73
+ signal: AbortSignal;
74
+ };
75
+
76
+ /**
77
+ * ダウンロードの各フェーズで実行されるフック関数のインターフェースです。
78
+ */
79
+ export interface IHook {
80
+ /**
81
+ * ダウンロード開始前に呼び出されます。
82
+ */
83
+ onBeforeDownload?(args: BeforeDownloadHandlerArgs): MaybePromise<void>;
84
+
85
+ /**
86
+ * チャンクデータが読み込まれるたびに呼び出されます。
87
+ */
88
+ onChunkRead?(args: ChunkReadHandlerArgs): MaybePromise<void>;
89
+
90
+ /**
91
+ * 正常終了時に呼び出されます。
92
+ */
93
+ onClose?(args: CloseHandlerArgs): MaybePromise<void>;
94
+
95
+ /**
96
+ * エラー発生時に呼び出されます。
97
+ */
98
+ onError?(args: ErrorHandlerArgs): MaybePromise<void>;
99
+ }
100
+
101
+ /**
102
+ * キャッシュリーダーを取得する際に渡される引数の型定義です。
103
+ */
104
+ export type GetReaderArgs = {
105
+ /**
106
+ * 中断を監視するためのシグナルです。
107
+ */
108
+ signal: AbortSignal;
109
+ };
110
+
111
+ /**
112
+ * キャッシュからデータを読み出すためのリーダー型です。
113
+ *
114
+ * 同期または非同期の反復子として定義されます。
115
+ */
116
+ export type IReader =
117
+ | IterableIterator<Uint8Array<ArrayBuffer>>
118
+ | AsyncIterableIterator<Uint8Array<ArrayBuffer>>;
119
+
120
+ /**
121
+ * キャッシュライターを取得する際に渡される引数の型定義です。
122
+ */
123
+ export type GetWriterArgs = {
124
+ /**
125
+ * サーバーからのレスポンスオブジェクトです。
126
+ */
127
+ response: Response;
128
+
129
+ /**
130
+ * 実行されたリクエストオブジェクトです。
131
+ */
132
+ request: Request;
133
+
134
+ /**
135
+ * 中断を監視するためのシグナルです。
136
+ */
137
+ signal: AbortSignal;
138
+ };
139
+
140
+ /**
141
+ * キャッシュへデータを書き込むためのインターフェースです。
142
+ */
143
+ export interface IWriter {
144
+ /**
145
+ * チャンクデータを書き込みます。
146
+ */
147
+ write(chunkData: Uint8Array<ArrayBuffer>): MaybePromise<void>;
148
+
149
+ /**
150
+ * 書き込みを完了し、リソースを閉じます。
151
+ */
152
+ close(): MaybePromise<void>;
153
+
154
+ /**
155
+ * 書き込みを中断します。
156
+ */
157
+ abort(reason: unknown): MaybePromise<void>;
158
+ }
159
+
160
+ /**
161
+ * キャッシュの読み書きを管理するハンドルのインターフェースです。
162
+ */
163
+ export interface ICacheHandle {
164
+ /**
165
+ * 指定された条件でキャッシュリーダーを取得します。存在しない場合は null を返します。
166
+ */
167
+ getReader(args: GetReaderArgs): MaybePromise<IReader | null>;
168
+
169
+ /**
170
+ * 書き込み用のライターを取得します。
171
+ */
172
+ getWriter(args: GetWriterArgs): MaybePromise<IWriter>;
173
+ }
174
+
175
+ /**
176
+ * フェッチャーに渡される初期化オプションの型定義です。
177
+ */
178
+ export type FetcherRequestInit = {
179
+ /**
180
+ * 中断を監視するためのシグナルです。
181
+ */
182
+ readonly signal: AbortSignal;
183
+ };
184
+
185
+ /**
186
+ * カスタムの HTTP リクエスト実行関数の型定義です。
187
+ */
188
+ export interface IFetcher {
189
+ /**
190
+ * HTTP リクエストを実行し、レスポンスを返します。
191
+ *
192
+ * @param request リクエストオブジェクトです。
193
+ * @param init 初期化オプションです。
194
+ * @returns レスポンスまたはそれを解決する Promise です。
195
+ */
196
+ (request: Request, init: FetcherRequestInit | undefined): MaybePromise<Response>;
197
+ }
198
+
199
+ /**
200
+ * 単一のリクエストターゲットを表すプリミティブな型です。
201
+ */
202
+ export type PrimitiveTarget = string | URL | Request;
203
+
204
+ /**
205
+ * 動的にリクエストターゲットを生成するファクトリー関数の型定義です。
206
+ */
207
+ export interface ITargetFactory {
208
+ (): MaybePromise<PrimitiveTarget>;
209
+ }
210
+
211
+ /**
212
+ * 解決可能なターゲットの型定義です。
213
+ */
214
+ export type Target = PrimitiveTarget | ITargetFactory;
215
+
216
+ /**
217
+ * JavaScript における Falsy な値の型定義です。
218
+ */
219
+ export type Falsy = 0 | "" | false | null | undefined;
220
+
221
+ /**
222
+ * 個別のダウンロード設定を定義する型です。
223
+ */
224
+ export type Config = {
225
+ /**
226
+ * ダウンロード対象のターゲットです。
227
+ */
228
+ readonly target: Target;
229
+
230
+ /**
231
+ * 使用するキャッシュハンドルです。
232
+ */
233
+ readonly cacheHandle?: ICacheHandle | undefined;
234
+
235
+ /**
236
+ * キャッシュを無視して強制的にダウンロードするかどうかのフラグです。
237
+ */
238
+ readonly forceDownload?: boolean | undefined;
239
+
240
+ /**
241
+ * 適用されるフックのリストです。
242
+ */
243
+ readonly hooks?: readonly (IHook | Falsy)[] | undefined;
244
+
245
+ /**
246
+ * 使用するカスタムフェッチャーです。
247
+ */
248
+ readonly fetcher?: IFetcher | undefined;
249
+ };
250
+
251
+ /**
252
+ * 全体的なダウンロード動作を制御するオプションの型定義です。
253
+ */
254
+ export type Options = {
255
+ /**
256
+ * デフォルトで使用するキャッシュハンドルです。
257
+ */
258
+ readonly cacheHandle?: ICacheHandle | undefined;
259
+
260
+ /**
261
+ * デフォルトでキャッシュを無視するかどうかのフラグです。
262
+ */
263
+ readonly forceDownload?: boolean | undefined;
264
+
265
+ /**
266
+ * グローバルに適用されるフックのリストです。
267
+ */
268
+ readonly hooks?: readonly (IHook | Falsy)[] | undefined;
269
+
270
+ /**
271
+ * デフォルトで使用するフェッチャーです。
272
+ */
273
+ readonly fetcher?: IFetcher | undefined;
274
+
275
+ /**
276
+ * 処理全体の中断を管理するシグナルです。
277
+ */
278
+ readonly signal?: AbortSignal | undefined;
279
+ };
280
+
281
+ /**
282
+ * ターゲット情報を解析して Request オブジェクトに変換します。
283
+ *
284
+ * @param target 解析対象のターゲットです。
285
+ * @returns 解析された Request オブジェクトを返す Promise です。
286
+ */
287
+ async function parseTarget(target: Target): Promise<Request> {
288
+ return typeof target === "function"
289
+ ? await parseTarget(await target())
290
+ : target instanceof Request
291
+ ? target
292
+ : new Request(target);
293
+ }
294
+
295
+ /**
296
+ * 指定されたターゲットからデータを一度だけダウンロードし、バイナリーデータとして返します。
297
+ *
298
+ * キャッシュが利用可能な場合はキャッシュから取得を試み、失敗した場合はネットワーク経由で取得します。
299
+ *
300
+ * @param target ダウンロード対象(単体、設定オブジェクト、またはその配列)です。
301
+ * @param options ダウンロードの動作を制御するオプションです。
302
+ * @returns 取得されたバイナリーデータ(Uint8Array)を解決する Promise です。
303
+ */
304
+ export default async function downloadOnce(
305
+ target: Target | Config | readonly (Target | Config)[],
306
+ options: Options | undefined = {},
307
+ ): Promise<Uint8Array<ArrayBuffer>> {
308
+ // ターゲットを配列に正規化します。
309
+ const targets = Array.isArray(target)
310
+ ? target
311
+ : [target];
312
+ if (targets.length === 0) {
313
+ throw new Error("No targets provided");
314
+ }
315
+
316
+ // オプションから各設定を抽出します。
317
+ const {
318
+ hooks: rawGlobalHooks = [],
319
+ signal,
320
+ fetcher: defaultFetcher = fetch,
321
+ cacheHandle: defaultCacheHandle,
322
+ forceDownload: defaultForceDownload = false,
323
+ } = options;
324
+
325
+ // Falsy な値を除去したグローバルフックのリストを作成します。
326
+ const globalHooks = rawGlobalHooks.filter(hook => !!hook);
327
+
328
+ // コールバック(フック等)で使用するタイムアウト付きのシグナルを設定します(デフォルトは 60 分)。
329
+ const callbackSignal = signal || AbortSignal.timeout(60 * 60e3);
330
+
331
+ const errors: unknown[] = [];
332
+
333
+ // 複数のターゲットを順番に試行します。
334
+ for (const item of targets) {
335
+ // 各ループの開始時に中断を確認します。
336
+ signal?.throwIfAborted();
337
+
338
+ // ターゲットが Config オブジェクトか、プリミティブな Target かを判定して正規化します。
339
+ const {
340
+ hooks: targetHooks = [],
341
+ target: actualTarget,
342
+ fetcher = defaultFetcher,
343
+ cacheHandle = defaultCacheHandle,
344
+ forceDownload = defaultForceDownload,
345
+ } = typeof item === "string"
346
+ || item instanceof URL
347
+ || item instanceof Request
348
+ || typeof item === "function"
349
+ ? { target: item }
350
+ : item;
351
+
352
+ let writer: IWriter | null = null;
353
+ const hooks: IHook[] = [];
354
+ const closedHooks = new Set<number>();
355
+
356
+ try {
357
+ // キャッシュからの読み込みを試行します。
358
+ if (cacheHandle && !forceDownload) {
359
+ try {
360
+ const reader = await cacheHandle.getReader({
361
+ signal: callbackSignal,
362
+ });
363
+ if (reader) {
364
+ const chunks = new BytesBuilder();
365
+ for await (const data of reader) {
366
+ signal?.throwIfAborted();
367
+ chunks.write(data);
368
+ }
369
+
370
+ return chunks.bytes();
371
+ }
372
+ } catch (ex) {
373
+ // キャッシュ読み込み失敗時はログを出力し、ネットワークフェッチへフォールバックします。
374
+ log.warn`Cache read failed, falling back to network fetch: ${ex}`;
375
+ }
376
+ }
377
+
378
+ // ネットワークからのダウンロード準備を開始します。
379
+ const request = await parseTarget(actualTarget);
380
+
381
+ // 前処理フックを実行します(個別設定のフックを優先)。
382
+ for (const hook of [...targetHooks.filter((hook: IHook | Falsy) => !!hook), ...globalHooks]) {
383
+ await hook.onBeforeDownload?.({
384
+ signal: callbackSignal,
385
+ request,
386
+ });
387
+ hooks.push(hook);
388
+ }
389
+
390
+ // リクエスト初期化設定を作成します。
391
+ const requestInit = signal instanceof AbortSignal
392
+ ? { signal }
393
+ : undefined;
394
+
395
+ // HTTP リクエストを実行します。
396
+ const response = await fetcher(request, requestInit);
397
+ if (!response.ok) {
398
+ throw new Error(`HTTP Error: [${response.status}] ${response.statusText}`);
399
+ }
400
+ if (!response.body) {
401
+ throw new Error("Response body is null");
402
+ }
403
+
404
+ // キャッシュへの書き込みが可能な場合はライターを初期化します。
405
+ if (cacheHandle) {
406
+ try {
407
+ writer = await cacheHandle.getWriter({
408
+ signal: callbackSignal,
409
+ request,
410
+ response,
411
+ });
412
+ } catch (ex) {
413
+ log.warn`Failed to create cache writer: ${ex}`;
414
+ }
415
+ }
416
+
417
+ // ストリームからデータを読み取ります。
418
+ const reader = response.body.getReader();
419
+ const chunks = new BytesBuilder();
420
+ try {
421
+ while (true) {
422
+ signal?.throwIfAborted();
423
+
424
+ const {
425
+ done,
426
+ value: chunkData,
427
+ } = await reader.read();
428
+
429
+ if (chunkData) {
430
+ // データを受信するたびに各フックの onChunkRead を呼び出します。
431
+ for (let i = 0; i < hooks.length; i++) {
432
+ if (closedHooks.has(i)) {
433
+ continue;
434
+ }
435
+
436
+ const hook = hooks[i]!;
437
+ try {
438
+ await hook.onChunkRead?.({
439
+ signal: callbackSignal,
440
+ response,
441
+ chunkData,
442
+ });
443
+ } catch (ex) {
444
+ // フック内でのエラー発生時は onError を呼び出し、そのフックをクローズ扱い(以降無視)にします。
445
+ try {
446
+ closedHooks.add(i);
447
+ await hook.onError?.({
448
+ reason: ex,
449
+ signal: callbackSignal,
450
+ });
451
+ } catch (ex) {
452
+ log.warn`Failed to call error handler: ${ex}`;
453
+ }
454
+ }
455
+ }
456
+
457
+ // 読み込んだデータをビルダーとキャッシュライターに書き込みます。
458
+ chunks.write(chunkData);
459
+ await writer?.write(chunkData);
460
+ }
461
+
462
+ if (done) {
463
+ break;
464
+ }
465
+ }
466
+ } catch (ex) {
467
+ // リーダーの読み取り中にエラーが発生した場合はストリームをキャンセルします。
468
+ try {
469
+ await reader.cancel(ex);
470
+ } catch (ex) {
471
+ log.warn`Failed to cancel reader: ${ex}`;
472
+ }
473
+
474
+ throw ex;
475
+ } finally {
476
+ // ロックを確実に解放します。
477
+ try {
478
+ reader.releaseLock();
479
+ } catch (ex) {
480
+ log.warn`Failed to release reader lock: ${ex}`;
481
+ }
482
+ }
483
+
484
+ // 正常終了時のクリーンアップ処理です。
485
+ for (let i = 0; i < hooks.length; i++) {
486
+ if (closedHooks.has(i)) {
487
+ continue;
488
+ }
489
+
490
+ const hook = hooks[i]!;
491
+ try {
492
+ closedHooks.add(i);
493
+ await hook.onClose?.({
494
+ signal: callbackSignal,
495
+ });
496
+ } catch (ex) {
497
+ try {
498
+ await hook.onError?.({
499
+ reason: ex,
500
+ signal: callbackSignal,
501
+ });
502
+ } catch (ex) {
503
+ log.warn`Failed to call error handler: ${ex}`;
504
+ }
505
+
506
+ throw ex;
507
+ }
508
+ }
509
+
510
+ // キャッシュライターを閉じます。
511
+ await writer?.close();
512
+ writer = null;
513
+
514
+ // すべてのデータを結合したバイナリーを返します。
515
+ return chunks.bytes();
516
+ } catch (ex) {
517
+ // 試行が失敗した場合はエラーを蓄積し、次のターゲット(存在すれば)に移行します。
518
+ errors.push(ex);
519
+
520
+ // キャッシュ書き込みを中止します。
521
+ try {
522
+ await writer?.abort(ex);
523
+ writer = null;
524
+ } catch (ex) {
525
+ log.warn`Failed to abort cache writer: ${ex}`;
526
+ }
527
+
528
+ // まだ生存しているフックに対してエラー通知を行います。
529
+ for (let i = 0; i < hooks.length; i++) {
530
+ if (closedHooks.has(i)) {
531
+ continue;
532
+ }
533
+
534
+ const hook = hooks[i]!;
535
+ try {
536
+ closedHooks.add(i);
537
+ await hook.onError?.({
538
+ reason: ex,
539
+ signal: callbackSignal,
540
+ });
541
+ } catch (ex) {
542
+ log.warn`Failed to call error handler: ${ex}`;
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ // すべてのターゲットで失敗した場合は AggregateError を投げます。
549
+ if (errors.length === 1) {
550
+ throw errors[0];
551
+ } else {
552
+ throw new AggregateError(errors);
553
+ }
554
+ }