soseki 0.0.1

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 (88) hide show
  1. package/.config/_is-debug-mode.ts +9 -0
  2. package/.config/dependency-cruiser.cjs +408 -0
  3. package/.config/env.d.ts +8 -0
  4. package/.config/mise.toml +28 -0
  5. package/.config/navigation-api.d.ts +18 -0
  6. package/.config/tsconfig.build.json +28 -0
  7. package/.config/tsconfig.config.json +21 -0
  8. package/.config/tsconfig.test.json +26 -0
  9. package/.config/tsconfig.web.json +26 -0
  10. package/.config/vitest.client.ts +30 -0
  11. package/package.json +45 -0
  12. package/src/components/action-id.tsx +35 -0
  13. package/src/components/browser-router.tsx +31 -0
  14. package/src/components/hidden-input.tsx +39 -0
  15. package/src/components/outlet.tsx +17 -0
  16. package/src/components/router.tsx +234 -0
  17. package/src/contexts/route-context.ts +21 -0
  18. package/src/contexts/router-context.ts +55 -0
  19. package/src/core/_action-id-registry.ts +11 -0
  20. package/src/core/_capture-stack-trace.ts +12 -0
  21. package/src/core/_compare-route-paths.ts +90 -0
  22. package/src/core/_create-html-form-element-form-form-data.ts +32 -0
  23. package/src/core/_encode-pathname.ts +17 -0
  24. package/src/core/_is-error.ts +16 -0
  25. package/src/core/_is-promise-like.ts +14 -0
  26. package/src/core/_match-route-path.ts +39 -0
  27. package/src/core/_process-routes.ts +56 -0
  28. package/src/core/_singleton.ts +45 -0
  29. package/src/core/_unreachable.ts +22 -0
  30. package/src/core/_use-singleton.ts +24 -0
  31. package/src/core/_valibot.ts +147 -0
  32. package/src/core/_weak-id-registry.ts +125 -0
  33. package/src/core/constants.ts +4 -0
  34. package/src/core/data-map.types.ts +28 -0
  35. package/src/core/data-store.types.ts +25 -0
  36. package/src/core/deferred-promise.ts +408 -0
  37. package/src/core/errors.ts +680 -0
  38. package/src/core/expect-history-entry.ts +95 -0
  39. package/src/core/history-entry-id-schema.ts +27 -0
  40. package/src/core/history-entry-url-schema.ts +35 -0
  41. package/src/core/init-loaders.ts +79 -0
  42. package/src/core/match-routes.ts +91 -0
  43. package/src/core/readonly-form-data.types.ts +63 -0
  44. package/src/core/readonly-url.types.ts +156 -0
  45. package/src/core/redirect-response.ts +36 -0
  46. package/src/core/route-request.ts +92 -0
  47. package/src/core/route.types.ts +351 -0
  48. package/src/core/start-actions.ts +274 -0
  49. package/src/core/start-loaders.ts +254 -0
  50. package/src/core.ts +43 -0
  51. package/src/engines/engine.types.ts +216 -0
  52. package/src/engines/navigation-api-engine.ts +406 -0
  53. package/src/hooks/_use-route-context.ts +19 -0
  54. package/src/hooks/_use-router-context.ts +25 -0
  55. package/src/hooks/use-action-data.ts +37 -0
  56. package/src/hooks/use-loader-data.ts +28 -0
  57. package/src/hooks/use-navigate.ts +64 -0
  58. package/src/hooks/use-params.ts +11 -0
  59. package/src/hooks/use-pathname.ts +10 -0
  60. package/src/hooks/use-submit.ts +111 -0
  61. package/src/soseki.ts +75 -0
  62. package/src/utils/get-action-id.ts +12 -0
  63. package/src/utils/href.ts +17 -0
  64. package/src/utils/redirect.ts +13 -0
  65. package/src/utils/route-index.ts +70 -0
  66. package/src/utils/route-route.ts +111 -0
  67. package/src/utils/set-action-id.ts +14 -0
  68. package/tests/core/_capture-stack-trace.test.ts +46 -0
  69. package/tests/core/_compare-route-paths.test.ts +134 -0
  70. package/tests/core/_encode-pathname.test.ts +77 -0
  71. package/tests/core/_is-error.test.ts +108 -0
  72. package/tests/core/_is-promise-like.test.ts +100 -0
  73. package/tests/core/_match-route-path.test.ts +74 -0
  74. package/tests/core/_process-routes.test.ts +146 -0
  75. package/tests/core/_singleton.test.ts +102 -0
  76. package/tests/core/_unreachable.test.ts +38 -0
  77. package/tests/core/_use-singleton.test.ts +47 -0
  78. package/tests/core/_weak-id-registry.test.ts +137 -0
  79. package/tests/core/deferred-promise.test.ts +218 -0
  80. package/tests/core/expect-history-entry.test.ts +112 -0
  81. package/tests/core/init-loaders.test.ts +172 -0
  82. package/tests/core/match-routes.test.ts +178 -0
  83. package/tests/core/redirect-response.test.ts +36 -0
  84. package/tests/core/route-request.test.ts +93 -0
  85. package/tests/core/start-actions.test.ts +319 -0
  86. package/tests/core/start-loaders.test.ts +276 -0
  87. package/tests/engines/navigation-api-engine.test.ts +162 -0
  88. package/tsconfig.json +7 -0
@@ -0,0 +1,351 @@
1
+ import type * as React from "react";
2
+ import type { RouteParams } from "regexparam";
3
+ import type { ReadonlyURL } from "./readonly-url.types.js";
4
+ import type RouteRequest from "./route-request.js";
5
+
6
+ /**
7
+ * パスパラメーターの内部的な型定義です。
8
+ *
9
+ * @template TParams `regexparam` から抽出されたパラメーターの型です。
10
+ */
11
+ // dprint-ignore
12
+ type PathParams_<TParams extends { [_ in string]?: string }> =
13
+ [keyof TParams] extends [never]
14
+ ? { readonly [_ in string]?: string }
15
+ : Readonly<TParams>
16
+
17
+ /**
18
+ * 指定されたパス文字列からパラメーターを抽出するための型定義です。
19
+ *
20
+ * @template TPath 解析対象となるパス文字列の型です。
21
+ */
22
+ export type PathParams<TPath extends string = string> = PathParams_<RouteParams<TPath>>;
23
+
24
+ /**
25
+ * アクションを実行すべきかどうかを判定する関数(`shouldAction`)に渡される引数の型定義です。
26
+ *
27
+ * @template TPath パス文字列の型です。
28
+ */
29
+ export type ShouldActionArgs<TPath extends string = string> = {
30
+ /**
31
+ * URL から抽出されたパスパラメーターのオブジェクトです。
32
+ */
33
+ params: PathParams<TPath>;
34
+
35
+ /**
36
+ * ルーティングに関するリクエスト情報です。
37
+ */
38
+ request: RouteRequest;
39
+
40
+ /**
41
+ * デフォルトのルールに基づいた判定結果です。
42
+ */
43
+ defaultShouldAction: boolean;
44
+ };
45
+
46
+ /**
47
+ * 特定の条件でアクションを実行するかどうかを制御するインターフェースです。
48
+ *
49
+ * @template TPath パス文字列の型です。
50
+ */
51
+ export interface IShouldAction<TPath extends string = string> {
52
+ /**
53
+ * アクションの実行可否を判定します。
54
+ *
55
+ * @param args 判定に必要なパラメーターとリクエスト情報です。
56
+ * @returns アクションを実行する場合は `true`、そうでない場合は `false` を返します。
57
+ */
58
+ (args: ShouldActionArgs<TPath>): boolean;
59
+ }
60
+
61
+ /**
62
+ * アクション関数(`action`)に渡される引数の型定義です。
63
+ *
64
+ * @template TPath パス文字列の型です。
65
+ */
66
+ export type ActionArgs<TPath extends string = string> = {
67
+ /**
68
+ * URL から抽出されたパスパラメーターのオブジェクトです。
69
+ */
70
+ params: PathParams<TPath>;
71
+
72
+ /**
73
+ * ルーティングに関するリクエスト情報です。
74
+ */
75
+ request: RouteRequest;
76
+ };
77
+
78
+ /**
79
+ * データの更新処理などを行うアクション関数のインターフェースです。
80
+ *
81
+ * @template TPath パス文字列の型です。
82
+ * @template TData アクションが返すデータの型です。
83
+ */
84
+ export interface IAction<TPath extends string = string, TData = unknown> {
85
+ /**
86
+ * 指定された引数に基づいてアクションを実行します。
87
+ *
88
+ * @param args アクションの実行に必要なパラメーターとリクエスト情報です。
89
+ * @returns 処理結果として任意のデータを返します。
90
+ */
91
+ (args: ActionArgs<TPath>): TData;
92
+ }
93
+
94
+ /**
95
+ * データの再読み込み(リロード)を行うべきかどうかを判定する関数に渡される引数の型定義です。
96
+ *
97
+ * GET リクエスト時と POST リクエスト時で異なるプロパティーを持ちます。
98
+ *
99
+ * @template TPath 現在のパス文字列の型です。
100
+ */
101
+ export type ShouldReloadArgs<TPath extends string = string> = {
102
+ /**
103
+ * 最読み込みをトリガーしたリクエストメソッドです。
104
+ */
105
+ triggerMethod: "GET";
106
+
107
+ /**
108
+ * 現在の URL オブジェクトです。
109
+ */
110
+ currentUrl: ReadonlyURL;
111
+
112
+ /**
113
+ * 現在のパスパラメーターです。
114
+ */
115
+ currentParams: PathParams<TPath>;
116
+
117
+ /**
118
+ * 遷移前の URL オブジェクトです。
119
+ */
120
+ prevUrl: ReadonlyURL;
121
+
122
+ /**
123
+ * 遷移前のパスパラメーターです。
124
+ */
125
+ prevParams: PathParams;
126
+
127
+ /**
128
+ * 送信されたフォームデータです。GET 時は常にありません。
129
+ */
130
+ formData?: undefined;
131
+
132
+ /**
133
+ * 直前に行われたアクションの結果です。GET 時は常にありません。
134
+ */
135
+ actionResult?: undefined;
136
+
137
+ /**
138
+ * デフォルトのルールに基づいた判定結果です。
139
+ */
140
+ defaultShouldReload: boolean;
141
+ } | {
142
+ /**
143
+ * 最読み込みをトリガーしたリクエストメソッドです。
144
+ */
145
+ triggerMethod: "POST";
146
+
147
+ /**
148
+ * 現在の URL オブジェクトです。
149
+ */
150
+ currentUrl: ReadonlyURL;
151
+
152
+ /**
153
+ * 現在のパスパラメーターです。
154
+ */
155
+ currentParams: PathParams<TPath>;
156
+
157
+ /**
158
+ * 遷移前の URL オブジェクトです。
159
+ */
160
+ prevUrl: ReadonlyURL;
161
+
162
+ /**
163
+ * 遷移前のパスパラメーターです。
164
+ */
165
+ prevParams: PathParams;
166
+
167
+ /**
168
+ * 送信されたフォームデータです。
169
+ */
170
+ formData: FormData;
171
+
172
+ /**
173
+ * 直前に行われたアクションの結果です。
174
+ */
175
+ actionResult?: unknown;
176
+
177
+ /**
178
+ * デフォルトのルールに基づいた判定結果です。
179
+ */
180
+ defaultShouldReload: boolean;
181
+ };
182
+
183
+ /**
184
+ * データの再読み込みが必要かどうかを判定するインターフェースです。
185
+ *
186
+ * @template TPath パス文字列の型です。
187
+ */
188
+ export interface IShouldReload<TPath extends string = string> {
189
+ /**
190
+ * 再読み込みの要否を判定します。
191
+ *
192
+ * @param args 遷移先後やアクションの結果を含む判定用データです。
193
+ * @returns 再読み込みを行う場合は `true`、そうでない場合は `false` を返します。
194
+ */
195
+ (args: ShouldReloadArgs<TPath>): boolean;
196
+ }
197
+
198
+ /**
199
+ * ローダー関数(`loader`)に渡される引数の型定義です。
200
+ *
201
+ * @template TPath パス文字列の型です。
202
+ */
203
+ export type LoaderArgs<TPath extends string = string> = {
204
+ /**
205
+ * URL から抽出されたパスパラメーターのオブジェクトです。
206
+ */
207
+ params: PathParams<TPath>;
208
+
209
+ /**
210
+ * ルーティングに関するリクエスト情報です。
211
+ */
212
+ request: RouteRequest;
213
+ };
214
+
215
+ /**
216
+ * データを取得するためのローダー関数のインターフェースです。
217
+ *
218
+ * @template TPath パス文字列の型です。
219
+ * @template TData ローダーが取得するデータの型です。
220
+ */
221
+ export interface ILoader<TPath extends string = string, TData = unknown> {
222
+ /**
223
+ * 指定された引数に基づいてデータを読み込みます。
224
+ *
225
+ * @param args データ取得に必要なパラメーターとリクエスト情報です。
226
+ * @returns 取得したデータを返します。
227
+ */
228
+ (args: LoaderArgs<TPath>): TData;
229
+ }
230
+
231
+ /**
232
+ * ルートに紐づくデータ処理用の関数群をまとめたオブジェクトの型定義です。
233
+ *
234
+ * @template TPath パス文字列の型です。
235
+ */
236
+ export type DataFunctionObject<TPath extends string = string> = {
237
+ /**
238
+ * アクションを実行すべきか判定するオプションの関数です。
239
+ */
240
+ readonly shouldAction?: IShouldAction<TPath> | undefined;
241
+
242
+ /**
243
+ * データの更新などを行うオプションのアクション関数です。
244
+ */
245
+ readonly action?: IAction<TPath> | undefined;
246
+
247
+ /**
248
+ * データを再読み込みすべきか判定するオプションの関数です。
249
+ */
250
+ readonly shouldReload?: IShouldReload<TPath> | undefined;
251
+
252
+ /**
253
+ * データを取得するオプションのローダー関数です。
254
+ */
255
+ readonly loader?: ILoader<TPath> | undefined;
256
+ };
257
+
258
+ /**
259
+ * ルーティングの定義を表す型定義です。
260
+ *
261
+ * @template TPath パス文字列の型です。
262
+ */
263
+ export type RouteDefinition<TPath extends string = string> = {
264
+ /**
265
+ * ルートのパス(パスパターン)です。
266
+ */
267
+ readonly path: TPath;
268
+
269
+ /**
270
+ * このルートで使用するデータ操作関数の配列です。
271
+ */
272
+ readonly dataFunctions?: readonly DataFunctionObject<TPath>[] | undefined;
273
+
274
+ /**
275
+ * このルートで描画される React コンポーネントです。
276
+ */
277
+ readonly component?: React.ComponentType<{}> | undefined;
278
+
279
+ /**
280
+ * 子ルートの定義リストです。
281
+ */
282
+ readonly children?: readonly RouteDefinition[] | undefined;
283
+ };
284
+
285
+ /**
286
+ * ルート情報を表す型定義です。
287
+ */
288
+ export type Route = {
289
+ /**
290
+ * 正規化されたルートのパス(パスパターン)です。
291
+ */
292
+ readonly path: string;
293
+
294
+ /**
295
+ * このルートがインデックスルート(子ルートを持たない末端のルート)かどうかを示します。
296
+ */
297
+ readonly index: boolean;
298
+
299
+ /**
300
+ * パスマッチングに使用される正規表現オブジェクトです。
301
+ */
302
+ readonly pathPattern: RegExp;
303
+
304
+ /**
305
+ * パスから抽出されたパラメーター名の配列です。
306
+ */
307
+ readonly paramKeys: readonly string[];
308
+
309
+ /**
310
+ * このルートに紐づくデータ操作関数群の配列です。
311
+ *
312
+ * デフォルトの判定関数などが補完された状態で保持されます。
313
+ */
314
+ readonly dataFuncs: readonly {
315
+ /**
316
+ * アクションの実行可否を判定します。
317
+ *
318
+ * @param args 判定に必要なパラメーターとリクエスト情報です。
319
+ * @returns アクションを実行する場合は `true`、そうでない場合は `false` を返すはずです。
320
+ */
321
+ readonly shouldAction: (args: ShouldActionArgs) => unknown;
322
+
323
+ /**
324
+ * データの更新などを行うオプションのアクション関数です。
325
+ */
326
+ readonly action: IAction | undefined;
327
+
328
+ /**
329
+ * 再読み込みの要否を判定します。
330
+ *
331
+ * @param args 遷移先後やアクションの結果を含む判定用データです。
332
+ * @returns 再読み込みを行う場合は `true`、そうでない場合は `false` を返すはずです。
333
+ */
334
+ readonly shouldReload: (args: ShouldReloadArgs) => unknown;
335
+
336
+ /**
337
+ * データを取得するオプションのローダー関数です。
338
+ */
339
+ readonly loader: ILoader | undefined;
340
+ }[];
341
+
342
+ /**
343
+ * このルートで描画される React コンポーネントです。
344
+ */
345
+ readonly component: React.ComponentType<{}> | undefined;
346
+
347
+ /**
348
+ * 前処理済みの子ルートの配列です。
349
+ */
350
+ readonly children: readonly Route[];
351
+ };
@@ -0,0 +1,274 @@
1
+ import actionIdRegistry from "./_action-id-registry.js";
2
+ import unreachable from "./_unreachable.js";
3
+ import { ACTION_ID_FORM_DATA_NAME } from "./constants.js";
4
+ import type { IDataMap } from "./data-map.types.js";
5
+ import type { IDataStore } from "./data-store.types.js";
6
+ import DeferredPromise from "./deferred-promise.js";
7
+ import { ActionConditionError, ActionExecutionError, MultipleRedirectError } from "./errors.js";
8
+ import type { HistoryEntry } from "./expect-history-entry.js";
9
+ import type { MatchedRoute } from "./match-routes.js";
10
+ import type { ReadonlyFormData } from "./readonly-form-data.types.js";
11
+ import RedirectResponse from "./redirect-response.js";
12
+ import RouteRequest from "./route-request.js";
13
+ import type { IAction } from "./route.types.js";
14
+
15
+ /**
16
+ * アクションの開始に使用するパラメーターの型定義です。
17
+ */
18
+ export type StartActionsParams = {
19
+ /**
20
+ * マッチしたルート情報のリストです。パラメーターとデータ関数を含みます。
21
+ */
22
+ readonly routes: readonly Pick<MatchedRoute, "params" | "urlPath" | "dataFuncs">[];
23
+
24
+ /**
25
+ * 履歴エントリーの情報です。 ID と URL を含みます。
26
+ */
27
+ readonly entry: Pick<HistoryEntry, "id" | "url">;
28
+
29
+ /**
30
+ * フォームから送信された読み取り専用のデータです。
31
+ */
32
+ readonly formData: ReadonlyFormData;
33
+
34
+ /**
35
+ * アクションの結果を保持するためのデータストアです。
36
+ */
37
+ readonly dataStore: IDataStore<IAction>;
38
+
39
+ /**
40
+ * 非同期処理を中断するためのシグナルです。
41
+ */
42
+ readonly signal: AbortSignal;
43
+ };
44
+
45
+ /**
46
+ * アクションの実行結果を表す型定義です。
47
+ */
48
+ export type ActionsResult = {
49
+ /**
50
+ * リダイレクトが必要な場合のパス名です。リダイレクトが発生しない場合は undefined となります。
51
+ */
52
+ redirect: string | undefined;
53
+
54
+ /**
55
+ * 各アクションと実行結果を紐付けたマップです。
56
+ */
57
+ resultMap: ReadonlyMap<IAction, unknown>;
58
+ };
59
+
60
+ /**
61
+ * ルートに定義されたアクションの実行を開始します。
62
+ *
63
+ * 条件に合致するアクションが存在する場合、それらを待機するための非同期関数を返します。
64
+ *
65
+ * @param params アクションの開始に必要なパラメーターオブジェクトです。
66
+ * @returns アクションの完了を待機するための関数です。実行すべきアクションがない場合は undefined を返します。
67
+ */
68
+ export default function startActions(params: StartActionsParams): undefined | {
69
+ /**
70
+ * アクションの完了を待機し、結果を集約して返す非同期関数です。
71
+ *
72
+ * @returns アクションの結果(リダイレクト先や実行結果のマップ)を返します。
73
+ */
74
+ (): Promise<ActionsResult>;
75
+ } {
76
+ const {
77
+ entry,
78
+ routes,
79
+ signal,
80
+ formData,
81
+ dataStore,
82
+ } = params;
83
+ const request = new RouteRequest("POST", entry.url, signal, formData);
84
+ const redirects: string[] = [];
85
+ const dataIncMap: IDataMap<IAction> = new Map();
86
+ let encountered = false;
87
+ // ルートを走査し、実行すべきアクションを特定します。
88
+ for (const route of routes) {
89
+ for (const { action, shouldAction } of route.dataFuncs) {
90
+ if (!action) {
91
+ continue;
92
+ }
93
+
94
+ // アクションを持つルートが見つかったため、フラグを立てます。
95
+ encountered = true;
96
+
97
+ // アクションを実行すべきかどうかを判定します。
98
+ const should = DeferredPromise.try(function executeShouldAction() {
99
+ return shouldAction({
100
+ params: route.params,
101
+ request,
102
+ defaultShouldAction: formData.has(ACTION_ID_FORM_DATA_NAME)
103
+ ? formData.get(ACTION_ID_FORM_DATA_NAME) === actionIdRegistry.get(action)
104
+ : true,
105
+ });
106
+ });
107
+
108
+ let data: DeferredPromise<unknown>;
109
+ switch (should.status) {
110
+ case "pending": {
111
+ // shouldAction は同期的に真偽値を返す必要があるので、pending 状態(つまり非同期)であってはいけません。
112
+ const error = new ActionConditionError(request.url.href, shouldAction, should);
113
+ // エラーハンドリングは、このアクションデータを参照するコンポーネントに任せます。
114
+ data = DeferredPromise.reject(error);
115
+ break;
116
+ }
117
+
118
+ case "rejected":
119
+ // エラーハンドリングは、このアクションデータを参照するコンポーネントに任せます。
120
+ data = should;
121
+ break;
122
+
123
+ case "fulfilled": {
124
+ const { value } = should;
125
+ switch (value) {
126
+ case true: {
127
+ // 条件を満たす場合のみ、アクションの実行をスケジュールします。
128
+ const clientData = DeferredPromise.try(function executeAction() {
129
+ return action({
130
+ params: route.params,
131
+ request,
132
+ });
133
+ });
134
+ switch (clientData.status) {
135
+ case "pending": {
136
+ // 特定の返り値を別の値に入れ替えるために中継 DeferredPromise をアクションデータとします。
137
+ const proxy = DeferredPromise.withResolvers();
138
+ data = proxy.promise;
139
+ (async () => {
140
+ try {
141
+ const value = await clientData;
142
+ switch (true) {
143
+ case value instanceof RedirectResponse:
144
+ // リダイレクトを指示する返り値を記録し、コンポーネントが参照するデータを undefined に置き換えます。
145
+ redirects.push(value.pathname);
146
+ proxy.resolve(undefined);
147
+ break;
148
+
149
+ default:
150
+ proxy.resolve(value);
151
+ }
152
+ } catch (ex) {
153
+ proxy.reject(ex);
154
+ }
155
+ })();
156
+ break;
157
+ }
158
+
159
+ case "rejected":
160
+ // エラーハンドリングは、このアクションデータを参照するコンポーネントに任せます。
161
+ data = clientData;
162
+ break;
163
+
164
+ case "fulfilled": {
165
+ const { value } = clientData;
166
+ switch (true) {
167
+ case value instanceof RedirectResponse:
168
+ // リダイレクトを指示する返り値を記録し、コンポーネントが参照するデータを undefined に置き換えます。
169
+ redirects.push(value.pathname);
170
+ data = DeferredPromise.resolve(undefined);
171
+ break;
172
+
173
+ default:
174
+ data = clientData;
175
+ }
176
+
177
+ break;
178
+ }
179
+
180
+ default:
181
+ unreachable(clientData);
182
+ }
183
+
184
+ break;
185
+ }
186
+
187
+ case false:
188
+ // 条件を満たさない場合はアクションの実行をスキップします。
189
+ continue;
190
+
191
+ default: {
192
+ // shouldAction が真偽値を返さなかった場合、エラーとします。
193
+ const error = new ActionConditionError(request.url.href, shouldAction, value);
194
+ // エラーハンドリングは、このアクションデータを参照するコンポーネントに任せます。
195
+ data = DeferredPromise.reject(error);
196
+ }
197
+ }
198
+
199
+ break;
200
+ }
201
+
202
+ default:
203
+ unreachable(should);
204
+ }
205
+
206
+ dataIncMap.set(action, data);
207
+ }
208
+
209
+ // アクションを持つルートが見つかった時点で走査を終了します。
210
+ if (encountered) {
211
+ break;
212
+ }
213
+ }
214
+
215
+ if (!encountered) {
216
+ // 実行されたアクションが無い場合は、これ以上することは何もありません。
217
+ return;
218
+ }
219
+
220
+ if (dataIncMap.size <= 0) {
221
+ return async () => ({
222
+ redirect: undefined,
223
+ resultMap: new Map(),
224
+ });
225
+ }
226
+
227
+ // 特定されたアクションのマップをデータストアに格納します。
228
+ const dataMap = dataStore.get(entry.id);
229
+ if (dataMap) {
230
+ for (const [action, data] of dataIncMap) {
231
+ dataMap.set(action, data);
232
+ }
233
+ } else {
234
+ dataStore.set(entry.id, dataIncMap);
235
+ }
236
+
237
+ return async function waitForActionsComplete() {
238
+ const resultMap: IDataMap<IAction, unknown> = new Map();
239
+ const promises: Promise<void>[] = [];
240
+ const rejected: { action: IAction; reason: unknown }[] = [];
241
+ for (const [action, data] of dataIncMap) {
242
+ const promise = (async () => {
243
+ try {
244
+ const value = await data;
245
+ resultMap.set(action, value);
246
+ } catch (ex) {
247
+ rejected.push({
248
+ action,
249
+ reason: ex,
250
+ });
251
+ }
252
+ })();
253
+ promises.push(promise);
254
+ }
255
+
256
+ // すべてのアクションの完了を待ちます。
257
+ await Promise.all(promises);
258
+
259
+ // エラーが発生したアクションがある場合は例外を投げます。
260
+ if (rejected.length > 0) {
261
+ throw new ActionExecutionError(request.url.href, rejected);
262
+ }
263
+
264
+ // 複数のアクションからリダイレクトが返された場合は不正な状態としてエラーを投げます。
265
+ if (redirects.length > 1) {
266
+ throw new MultipleRedirectError(request.url.href, redirects);
267
+ }
268
+
269
+ return {
270
+ redirect: redirects[0],
271
+ resultMap,
272
+ };
273
+ };
274
+ }