atl-fetch 1.0.0 → 1.2.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.
@@ -135,6 +135,236 @@ export const convertAdfToPlainText = (adf) => {
135
135
  }
136
136
  return '';
137
137
  };
138
+ // ============================================================
139
+ // ADF → Markdown 変換(Confluence と同じ TurndownService を使用)
140
+ // ============================================================
141
+ /**
142
+ * HTML 特殊文字をエスケープする
143
+ *
144
+ * @param text エスケープ対象の文字列
145
+ * @returns エスケープ済み文字列
146
+ */
147
+ const escapeHtml = (text) => {
148
+ return text
149
+ .replace(/&/g, '&')
150
+ .replace(/</g, '&lt;')
151
+ .replace(/>/g, '&gt;')
152
+ .replace(/"/g, '&quot;')
153
+ .replace(/'/g, '&#39;');
154
+ };
155
+ /**
156
+ * ADF マークを HTML タグで囲む
157
+ *
158
+ * @param text 対象のテキスト
159
+ * @param marks 適用するマーク配列
160
+ * @returns マークを適用した HTML
161
+ */
162
+ const applyMarksToHtml = (text, marks) => {
163
+ let result = text;
164
+ for (const mark of marks) {
165
+ switch (mark.type) {
166
+ case 'strong':
167
+ result = `<strong>${result}</strong>`;
168
+ break;
169
+ case 'em':
170
+ result = `<em>${result}</em>`;
171
+ break;
172
+ case 'code':
173
+ result = `<code>${result}</code>`;
174
+ break;
175
+ case 'strike':
176
+ result = `<s>${result}</s>`;
177
+ break;
178
+ case 'underline':
179
+ result = `<u>${result}</u>`;
180
+ break;
181
+ case 'link': {
182
+ const href = mark.attrs?.['href'];
183
+ if (typeof href === 'string') {
184
+ result = `<a href="${escapeHtml(href)}">${result}</a>`;
185
+ }
186
+ break;
187
+ }
188
+ case 'textColor': {
189
+ const color = mark.attrs?.['color'];
190
+ if (typeof color === 'string') {
191
+ result = `<span style="color: ${escapeHtml(color)}">${result}</span>`;
192
+ }
193
+ break;
194
+ }
195
+ case 'subsup': {
196
+ const subType = mark.attrs?.['type'];
197
+ if (subType === 'sub') {
198
+ result = `<sub>${result}</sub>`;
199
+ }
200
+ else if (subType === 'sup') {
201
+ result = `<sup>${result}</sup>`;
202
+ }
203
+ break;
204
+ }
205
+ // 未知のマークタイプは無視
206
+ default:
207
+ break;
208
+ }
209
+ }
210
+ return result;
211
+ };
212
+ /**
213
+ * ADF ノードを HTML に変換する
214
+ *
215
+ * @param node ADF ノード
216
+ * @param attachmentPaths 添付ファイル ID → ローカルパスのマッピング
217
+ * @returns HTML 文字列
218
+ */
219
+ const convertAdfNodeToHtml = (node, attachmentPaths) => {
220
+ // テキストノードの場合
221
+ if (node.type === 'text' && node.text !== undefined) {
222
+ const escapedText = escapeHtml(node.text);
223
+ if (node.marks !== undefined && node.marks.length > 0) {
224
+ return applyMarksToHtml(escapedText, node.marks);
225
+ }
226
+ return escapedText;
227
+ }
228
+ // hardBreak の場合
229
+ if (node.type === 'hardBreak') {
230
+ return '<br>';
231
+ }
232
+ // rule(水平線)の場合
233
+ if (node.type === 'rule') {
234
+ return '<hr>';
235
+ }
236
+ // メンションの場合
237
+ if (node.type === 'mention' && node.attrs !== undefined) {
238
+ const text = node.attrs['text'];
239
+ if (typeof text === 'string') {
240
+ return escapeHtml(text);
241
+ }
242
+ return '@ユーザー';
243
+ }
244
+ // 絵文字の場合
245
+ if (node.type === 'emoji' && node.attrs !== undefined) {
246
+ const text = node.attrs['text'];
247
+ const shortName = node.attrs['shortName'];
248
+ if (typeof text === 'string') {
249
+ return text;
250
+ }
251
+ if (typeof shortName === 'string') {
252
+ return shortName;
253
+ }
254
+ return '';
255
+ }
256
+ // media ノードの場合(添付ファイル)
257
+ if (node.type === 'media' && node.attrs !== undefined) {
258
+ const mediaId = node.attrs['id'];
259
+ const mediaType = node.attrs['type'];
260
+ if (typeof mediaId === 'string' && attachmentPaths?.[mediaId] !== undefined) {
261
+ const localPath = attachmentPaths[mediaId];
262
+ const alt = typeof node.attrs['alt'] === 'string' ? node.attrs['alt'] : mediaId;
263
+ return `<img src="${escapeHtml(localPath)}" alt="${escapeHtml(alt)}">`;
264
+ }
265
+ // 外部リンクの場合
266
+ if (mediaType === 'external' || mediaType === 'link') {
267
+ const url = node.attrs['url'];
268
+ if (typeof url === 'string') {
269
+ return `<img src="${escapeHtml(url)}" alt="">`;
270
+ }
271
+ }
272
+ // マッピングがない場合はプレースホルダー
273
+ return '[添付ファイル]';
274
+ }
275
+ // mediaSingle(メディアコンテナ)の場合
276
+ if (node.type === 'mediaSingle' && node.content !== undefined) {
277
+ return node.content.map((child) => convertAdfNodeToHtml(child, attachmentPaths)).join('');
278
+ }
279
+ // mediaGroup の場合
280
+ if (node.type === 'mediaGroup' && node.content !== undefined) {
281
+ return node.content.map((child) => convertAdfNodeToHtml(child, attachmentPaths)).join('');
282
+ }
283
+ // inlineCard(インラインリンク)の場合
284
+ if (node.type === 'inlineCard' && node.attrs !== undefined) {
285
+ const url = node.attrs['url'];
286
+ if (typeof url === 'string') {
287
+ return `<a href="${escapeHtml(url)}">${escapeHtml(url)}</a>`;
288
+ }
289
+ return '';
290
+ }
291
+ // blockCard(ブロックリンク)の場合
292
+ if (node.type === 'blockCard' && node.attrs !== undefined) {
293
+ const url = node.attrs['url'];
294
+ if (typeof url === 'string') {
295
+ return `<p><a href="${escapeHtml(url)}">${escapeHtml(url)}</a></p>`;
296
+ }
297
+ return '';
298
+ }
299
+ // 子ノードがある場合
300
+ if (node.content !== undefined && Array.isArray(node.content)) {
301
+ const childrenHtml = node.content.map((child) => convertAdfNodeToHtml(child, attachmentPaths)).join('');
302
+ switch (node.type) {
303
+ case 'doc':
304
+ return childrenHtml;
305
+ case 'paragraph':
306
+ return `<p>${childrenHtml}</p>`;
307
+ case 'heading': {
308
+ const level = typeof node.attrs?.['level'] === 'number' ? node.attrs['level'] : 1;
309
+ const safeLevel = Math.max(1, Math.min(6, level));
310
+ return `<h${safeLevel}>${childrenHtml}</h${safeLevel}>`;
311
+ }
312
+ case 'bulletList':
313
+ return `<ul>${childrenHtml}</ul>`;
314
+ case 'orderedList':
315
+ return `<ol>${childrenHtml}</ol>`;
316
+ case 'listItem':
317
+ return `<li>${childrenHtml}</li>`;
318
+ case 'blockquote':
319
+ return `<blockquote>${childrenHtml}</blockquote>`;
320
+ case 'codeBlock': {
321
+ const language = typeof node.attrs?.['language'] === 'string' ? node.attrs['language'] : '';
322
+ const langClass = language ? ` class="language-${escapeHtml(language)}"` : '';
323
+ // コードブロック内のテキストは子ノードから取得
324
+ const codeText = node.content
325
+ .map((child) => (child.type === 'text' && child.text !== undefined ? child.text : ''))
326
+ .join('');
327
+ return `<pre><code${langClass}>${escapeHtml(codeText)}</code></pre>`;
328
+ }
329
+ case 'table':
330
+ return `<table>${childrenHtml}</table>`;
331
+ case 'tableRow':
332
+ return `<tr>${childrenHtml}</tr>`;
333
+ case 'tableHeader':
334
+ return `<th>${childrenHtml}</th>`;
335
+ case 'tableCell':
336
+ return `<td>${childrenHtml}</td>`;
337
+ case 'panel': {
338
+ // panel タイプを GitHub Alerts 形式に変換
339
+ const panelType = typeof node.attrs?.['panelType'] === 'string' ? node.attrs['panelType'] : 'info';
340
+ const alertTypeMap = {
341
+ error: 'WARNING',
342
+ info: 'NOTE',
343
+ note: 'NOTE',
344
+ success: 'TIP',
345
+ warning: 'WARNING',
346
+ };
347
+ const alertType = alertTypeMap[panelType] || 'NOTE';
348
+ return `<blockquote data-github-alert="${alertType}">${childrenHtml}</blockquote>`;
349
+ }
350
+ // 未知のノードタイプは子ノードの内容を返す
351
+ default:
352
+ return childrenHtml;
353
+ }
354
+ }
355
+ // 子ノードもテキストもない場合は空文字列
356
+ return '';
357
+ };
358
+ /**
359
+ * ADF ドキュメントを HTML に変換する
360
+ *
361
+ * @param content ADF ドキュメントのコンテンツ配列
362
+ * @param attachmentPaths 添付ファイル ID → ローカルパスのマッピング
363
+ * @returns HTML 文字列
364
+ */
365
+ const convertAdfContentToHtml = (content, attachmentPaths) => {
366
+ return content.map((node) => convertAdfNodeToHtml(node, attachmentPaths)).join('');
367
+ };
138
368
  /**
139
369
  * HTML エンティティをデコードする
140
370
  *
@@ -348,20 +578,12 @@ const preprocessHtmlForMarkdown = (html, attachmentPaths) => {
348
578
  return result;
349
579
  };
350
580
  /**
351
- * Confluence Storage Format(XHTML)を Markdown に変換する
581
+ * TurndownService インスタンスを作成する(共通設定)
582
+ * Jira ADF と Confluence Storage Format の両方で使用する
352
583
  *
353
- * @param storageFormat Storage Format 文字列(HTML/XHTML)
354
- * @param attachmentPaths 添付ファイル名 → ローカルパスのマッピング
355
- * @returns Markdown 文字列
584
+ * @returns 設定済みの TurndownService インスタンス
356
585
  */
357
- export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) => {
358
- // null または undefined の場合は空文字列を返す
359
- if (storageFormat === null || storageFormat === undefined || storageFormat === '') {
360
- return '';
361
- }
362
- // 前処理
363
- const preprocessedHtml = preprocessHtmlForMarkdown(storageFormat, attachmentPaths);
364
- // Turndown インスタンス作成
586
+ const createTurndownService = () => {
365
587
  const turndownService = new TurndownService({
366
588
  bulletListMarker: '-',
367
589
  codeBlockStyle: 'fenced',
@@ -371,9 +593,7 @@ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) =
371
593
  });
372
594
  // GFM プラグイン(テーブル、取り消し線など)を使用
373
595
  turndownService.use(gfm);
374
- // --------------------------------------------------
375
596
  // カスタムルール: キャプション付き画像(<figure>)
376
- // --------------------------------------------------
377
597
  turndownService.addRule('figureWithCaption', {
378
598
  filter: (node) => {
379
599
  return node.nodeName === 'FIGURE';
@@ -397,9 +617,7 @@ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) =
397
617
  return '';
398
618
  },
399
619
  });
400
- // --------------------------------------------------
401
620
  // カスタムルール: GitHub Alerts(<blockquote data-github-alert="...">)
402
- // --------------------------------------------------
403
621
  turndownService.addRule('githubAlerts', {
404
622
  filter: (node) => {
405
623
  if (node.nodeName !== 'BLOCKQUOTE')
@@ -417,9 +635,7 @@ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) =
417
635
  return `\n> [!${alertType}]\n${quotedContent}\n`;
418
636
  },
419
637
  });
420
- // --------------------------------------------------
421
638
  // カスタムルール: 色変更テキスト(HTML のまま出力)
422
- // --------------------------------------------------
423
639
  turndownService.addRule('coloredText', {
424
640
  filter: (node) => {
425
641
  if (node.nodeName !== 'SPAN')
@@ -432,9 +648,7 @@ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) =
432
648
  return node.outerHTML;
433
649
  },
434
650
  });
435
- // --------------------------------------------------
436
651
  // カスタムルール: セル結合/セル内改行のあるテーブル(HTML のまま出力)
437
- // --------------------------------------------------
438
652
  turndownService.addRule('complexTable', {
439
653
  filter: (node) => {
440
654
  if (node.nodeName !== 'TABLE')
@@ -447,6 +661,65 @@ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) =
447
661
  return node.outerHTML;
448
662
  },
449
663
  });
664
+ return turndownService;
665
+ };
666
+ /**
667
+ * ADF(Atlassian Document Format)を Markdown に変換する
668
+ *
669
+ * @param adf ADF ドキュメント(オブジェクトまたは JSON 文字列)
670
+ * @param attachmentPaths 添付ファイル ID → ローカルパスのマッピング
671
+ * @returns Markdown 文字列
672
+ */
673
+ export const convertAdfToMarkdown = (adf, attachmentPaths) => {
674
+ // null または undefined の場合は空文字列を返す
675
+ if (adf === null || adf === undefined) {
676
+ return '';
677
+ }
678
+ // 文字列の場合は JSON としてパースを試みる
679
+ if (typeof adf === 'string') {
680
+ // 空文字列の場合
681
+ if (adf === '') {
682
+ return '';
683
+ }
684
+ try {
685
+ const parsed = JSON.parse(adf);
686
+ if (isAdfDocument(parsed)) {
687
+ const html = convertAdfContentToHtml(parsed.content, attachmentPaths);
688
+ const turndownService = createTurndownService();
689
+ return turndownService.turndown(html).trim();
690
+ }
691
+ }
692
+ catch {
693
+ // JSON パースに失敗した場合は元の文字列を返す
694
+ return adf;
695
+ }
696
+ // パースできたが ADF 形式でない場合は元の文字列を返す
697
+ return adf;
698
+ }
699
+ // オブジェクトの場合は ADF ドキュメントとして処理
700
+ if (isAdfDocument(adf)) {
701
+ const html = convertAdfContentToHtml(adf.content, attachmentPaths);
702
+ const turndownService = createTurndownService();
703
+ return turndownService.turndown(html).trim();
704
+ }
705
+ return '';
706
+ };
707
+ /**
708
+ * Confluence Storage Format(XHTML)を Markdown に変換する
709
+ *
710
+ * @param storageFormat Storage Format 文字列(HTML/XHTML)
711
+ * @param attachmentPaths 添付ファイル名 → ローカルパスのマッピング
712
+ * @returns Markdown 文字列
713
+ */
714
+ export const convertStorageFormatToMarkdown = (storageFormat, attachmentPaths) => {
715
+ // null または undefined の場合は空文字列を返す
716
+ if (storageFormat === null || storageFormat === undefined || storageFormat === '') {
717
+ return '';
718
+ }
719
+ // 前処理
720
+ const preprocessedHtml = preprocessHtmlForMarkdown(storageFormat, attachmentPaths);
721
+ // 共通の TurndownService を使用
722
+ const turndownService = createTurndownService();
450
723
  // Markdown に変換
451
724
  const markdown = turndownService.turndown(preprocessedHtml);
452
725
  // 末尾の空白を除去
@@ -6,8 +6,10 @@ export interface JiraComment {
6
6
  readonly id: string;
7
7
  /** 作成者の表示名 */
8
8
  readonly author: string;
9
- /** コメント本文 */
9
+ /** コメント本文(プレーンテキスト) */
10
10
  readonly body: string;
11
+ /** コメント本文(ADF 形式、Markdown 変換用) */
12
+ readonly bodyAdf: unknown;
11
13
  /** 作成日時(ISO 8601 形式) */
12
14
  readonly created: string;
13
15
  /** 更新日時(ISO 8601 形式) */
@@ -62,6 +64,8 @@ export interface JiraIssue {
62
64
  readonly summary: string;
63
65
  /** Issue 説明(null の場合は説明なし) */
64
66
  readonly description: string | null;
67
+ /** Issue 説明(ADF 形式、Markdown 変換用) */
68
+ readonly descriptionAdf: unknown;
65
69
  /** コメント一覧 */
66
70
  readonly comments: readonly JiraComment[];
67
71
  /** 変更履歴一覧 */
@@ -93,9 +93,9 @@ export interface JiraSaveData {
93
93
  readonly key: string;
94
94
  /** Issue タイトル(要約) */
95
95
  readonly summary: string;
96
- /** Issue 説明(null の場合は説明なし) */
97
- readonly description: string | null;
98
- /** 説明のプレーンテキスト(null の場合は説明なし) */
96
+ /** Issue 説明(ADF 形式、null の場合は説明なし) */
97
+ readonly description: unknown;
98
+ /** 説明のプレーンテキスト(null の場合は説明なし、後方互換性のため維持) */
99
99
  readonly descriptionPlainText: string | null;
100
100
  /** コメント一覧 */
101
101
  readonly comments: readonly JiraComment[];
package/package.json CHANGED
@@ -12,6 +12,8 @@
12
12
  "got": "14.6.6",
13
13
  "jsdom": "27.4.0",
14
14
  "neverthrow": "8.2.0",
15
+ "ora": "^9.0.0",
16
+ "picocolors": "^1.1.1",
15
17
  "turndown": "7.2.2",
16
18
  "turndown-plugin-gfm": "1.0.2",
17
19
  "yaml": "2.8.2",
@@ -80,7 +82,7 @@
80
82
  },
81
83
  "type": "module",
82
84
  "types": "./dist/index.d.ts",
83
- "version": "1.0.0",
85
+ "version": "1.2.0",
84
86
  "scripts": {
85
87
  "build": "tsc",
86
88
  "dev": "tsc --watch",
@@ -1,104 +0,0 @@
1
- /**
2
- * Result 型 - 成功または失敗を表現する discriminated union
3
- *
4
- * @example
5
- * ```typescript
6
- * function divide(a: number, b: number): Result<number, DivisionError> {
7
- * if (b === 0) {
8
- * return err({ kind: 'DIVISION_BY_ZERO', message: 'Cannot divide by zero' });
9
- * }
10
- * return ok(a / b);
11
- * }
12
- * ```
13
- */
14
- /**
15
- * 成功を表す型
16
- */
17
- export interface Ok<T> {
18
- readonly success: true;
19
- readonly value: T;
20
- }
21
- /**
22
- * 失敗を表す型
23
- */
24
- export interface Err<E> {
25
- readonly success: false;
26
- readonly error: E;
27
- }
28
- /**
29
- * Result 型 - 成功(Ok)または失敗(Err)のどちらかを表す
30
- */
31
- export type Result<T, E> = Ok<T> | Err<E>;
32
- /**
33
- * 成功 Result を作成する
34
- *
35
- * @param value - 成功値
36
- * @returns 成功 Result
37
- */
38
- export declare function ok<T>(value: T): Ok<T>;
39
- /**
40
- * 失敗 Result を作成する
41
- *
42
- * @param error - エラー値
43
- * @returns 失敗 Result
44
- */
45
- export declare function err<E>(error: E): Err<E>;
46
- /**
47
- * Result が成功かどうかを判定する型ガード
48
- *
49
- * @param result - 判定対象の Result
50
- * @returns 成功の場合 true
51
- */
52
- export declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
53
- /**
54
- * Result が失敗かどうかを判定する型ガード
55
- *
56
- * @param result - 判定対象の Result
57
- * @returns 失敗の場合 true
58
- */
59
- export declare function isErr<T, E>(result: Result<T, E>): result is Err<E>;
60
- /**
61
- * 成功 Result から値を取り出す
62
- * 失敗 Result の場合はエラーをスローする
63
- *
64
- * @param result - 対象の Result
65
- * @returns 成功値
66
- * @throws 失敗 Result の場合
67
- */
68
- export declare function unwrap<T, E>(result: Result<T, E>): T;
69
- /**
70
- * 失敗 Result からエラー値を取り出す
71
- * 成功 Result の場合はエラーをスローする
72
- *
73
- * @param result - 対象の Result
74
- * @returns エラー値
75
- * @throws 成功 Result の場合
76
- */
77
- export declare function unwrapErr<T, E>(result: Result<T, E>): E;
78
- /**
79
- * 成功 Result から値を取り出す
80
- * 失敗 Result の場合はデフォルト値を返す
81
- *
82
- * @param result - 対象の Result
83
- * @param defaultValue - 失敗時のデフォルト値
84
- * @returns 成功値またはデフォルト値
85
- */
86
- export declare function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T;
87
- /**
88
- * 成功 Result の値を変換する
89
- * 失敗 Result の場合はそのまま返す
90
- *
91
- * @param result - 対象の Result
92
- * @param fn - 変換関数
93
- * @returns 変換された Result
94
- */
95
- export declare function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
96
- /**
97
- * 失敗 Result のエラー値を変換する
98
- * 成功 Result の場合はそのまま返す
99
- *
100
- * @param result - 対象の Result
101
- * @param fn - 変換関数
102
- * @returns 変換された Result
103
- */
104
- export declare function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
@@ -1,119 +0,0 @@
1
- /**
2
- * Result 型 - 成功または失敗を表現する discriminated union
3
- *
4
- * @example
5
- * ```typescript
6
- * function divide(a: number, b: number): Result<number, DivisionError> {
7
- * if (b === 0) {
8
- * return err({ kind: 'DIVISION_BY_ZERO', message: 'Cannot divide by zero' });
9
- * }
10
- * return ok(a / b);
11
- * }
12
- * ```
13
- */
14
- /**
15
- * 成功 Result を作成する
16
- *
17
- * @param value - 成功値
18
- * @returns 成功 Result
19
- */
20
- export function ok(value) {
21
- return { success: true, value };
22
- }
23
- /**
24
- * 失敗 Result を作成する
25
- *
26
- * @param error - エラー値
27
- * @returns 失敗 Result
28
- */
29
- export function err(error) {
30
- return { error, success: false };
31
- }
32
- /**
33
- * Result が成功かどうかを判定する型ガード
34
- *
35
- * @param result - 判定対象の Result
36
- * @returns 成功の場合 true
37
- */
38
- export function isOk(result) {
39
- return result.success;
40
- }
41
- /**
42
- * Result が失敗かどうかを判定する型ガード
43
- *
44
- * @param result - 判定対象の Result
45
- * @returns 失敗の場合 true
46
- */
47
- export function isErr(result) {
48
- return !result.success;
49
- }
50
- /**
51
- * 成功 Result から値を取り出す
52
- * 失敗 Result の場合はエラーをスローする
53
- *
54
- * @param result - 対象の Result
55
- * @returns 成功値
56
- * @throws 失敗 Result の場合
57
- */
58
- export function unwrap(result) {
59
- if (isOk(result)) {
60
- return result.value;
61
- }
62
- throw new Error(`Attempted to unwrap an Err value: ${String(result.error)}`);
63
- }
64
- /**
65
- * 失敗 Result からエラー値を取り出す
66
- * 成功 Result の場合はエラーをスローする
67
- *
68
- * @param result - 対象の Result
69
- * @returns エラー値
70
- * @throws 成功 Result の場合
71
- */
72
- export function unwrapErr(result) {
73
- if (isErr(result)) {
74
- return result.error;
75
- }
76
- throw new Error('Attempted to unwrapErr an Ok value');
77
- }
78
- /**
79
- * 成功 Result から値を取り出す
80
- * 失敗 Result の場合はデフォルト値を返す
81
- *
82
- * @param result - 対象の Result
83
- * @param defaultValue - 失敗時のデフォルト値
84
- * @returns 成功値またはデフォルト値
85
- */
86
- export function unwrapOr(result, defaultValue) {
87
- if (isOk(result)) {
88
- return result.value;
89
- }
90
- return defaultValue;
91
- }
92
- /**
93
- * 成功 Result の値を変換する
94
- * 失敗 Result の場合はそのまま返す
95
- *
96
- * @param result - 対象の Result
97
- * @param fn - 変換関数
98
- * @returns 変換された Result
99
- */
100
- export function mapResult(result, fn) {
101
- if (isOk(result)) {
102
- return ok(fn(result.value));
103
- }
104
- return result;
105
- }
106
- /**
107
- * 失敗 Result のエラー値を変換する
108
- * 成功 Result の場合はそのまま返す
109
- *
110
- * @param result - 対象の Result
111
- * @param fn - 変換関数
112
- * @returns 変換された Result
113
- */
114
- export function mapErr(result, fn) {
115
- if (isErr(result)) {
116
- return err(fn(result.error));
117
- }
118
- return result;
119
- }