[{"data":1,"prerenderedAt":2680},["ShallowReactive",2],{"docs:\u002Fguide\u002Fperformer":3},{"page":4,"untranslated":2679,"translated":2679},{"id":5,"title":6,"body":7,"description":2670,"extension":2671,"meta":2672,"navigation":69,"path":2673,"publishedAt":2674,"seo":2675,"stem":2676,"updatedAt":2677,"__hash__":2678},"docs_ja\u002Fguide\u002Fperformer.md","Performer",{"type":8,"value":9,"toc":2649},"minimark",[10,14,22,26,321,335,338,351,375,396,464,467,497,516,546,549,559,672,684,688,705,760,768,770,789,959,999,1002,1015,1060,1085,1088,1124,1127,1133,1307,1322,1325,1347,1517,1534,1733,1786,1789,1800,1839,1855,1909,1926,1930,1933,2149,2152,2209,2225,2291,2295,2325,2351,2364,2367,2389,2510,2539,2542,2554,2587,2593,2630,2633,2645],[11,12,6],"h1",{"id":13},"performer",[15,16,17,18,21],"p",{},"ジョブの処理は、",[19,20,6],"code",{},"を継承したクラスとして定義します。",[23,24,25],"h2",{"id":25},"基本形",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-ts shiki shiki-themes github-light github-dark","import { Performer, type JobContext } from 'tsumugi\u002Fperformer';\n\nexport class SendMail extends Performer\u003C{ to: string; subject: string }, void, {}, Env> {\n  async perform(payload: { to: string; subject: string }, ctx: JobContext): Promise\u003Cvoid> {\n    const remaining = ctx.deadlineAt - Date.now();\n    await fetch('https:\u002F\u002Fapi.example.com\u002Fmail', {\n      method: 'POST',\n      body: JSON.stringify(payload),\n      \u002F\u002F 期限超過時は即中断\n      signal: remaining > 0 ? AbortSignal.timeout(remaining) : AbortSignal.abort(),\n    });\n  }\n}\n","ts","",[19,34,35,64,71,129,188,215,232,244,262,269,303,309,315],{"__ignoreMap":32},[36,37,40,44,48,51,54,57,61],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"szBVR","import",[36,45,47],{"class":46},"sVt8B"," { Performer, ",[36,49,50],{"class":42},"type",[36,52,53],{"class":46}," JobContext } ",[36,55,56],{"class":42},"from",[36,58,60],{"class":59},"sZZnC"," 'tsumugi\u002Fperformer'",[36,62,63],{"class":46},";\n",[36,65,67],{"class":38,"line":66},2,[36,68,70],{"emptyLinePlaceholder":69},true,"\n",[36,72,74,77,80,84,87,90,93,97,100,104,107,110,112,114,117,120,123,126],{"class":38,"line":73},3,[36,75,76],{"class":42},"export",[36,78,79],{"class":42}," class",[36,81,83],{"class":82},"sScJk"," SendMail",[36,85,86],{"class":42}," extends",[36,88,89],{"class":82}," Performer",[36,91,92],{"class":46},"\u003C{ ",[36,94,96],{"class":95},"s4XuR","to",[36,98,99],{"class":42},":",[36,101,103],{"class":102},"sj4cs"," string",[36,105,106],{"class":46},"; ",[36,108,109],{"class":95},"subject",[36,111,99],{"class":42},[36,113,103],{"class":102},[36,115,116],{"class":46}," }, ",[36,118,119],{"class":102},"void",[36,121,122],{"class":46},", {}, ",[36,124,125],{"class":82},"Env",[36,127,128],{"class":46},"> {\n",[36,130,132,135,138,141,144,146,149,151,153,155,157,159,161,163,165,168,170,173,176,178,181,184,186],{"class":38,"line":131},4,[36,133,134],{"class":42},"  async",[36,136,137],{"class":82}," perform",[36,139,140],{"class":46},"(",[36,142,143],{"class":95},"payload",[36,145,99],{"class":42},[36,147,148],{"class":46}," { ",[36,150,96],{"class":95},[36,152,99],{"class":42},[36,154,103],{"class":102},[36,156,106],{"class":46},[36,158,109],{"class":95},[36,160,99],{"class":42},[36,162,103],{"class":102},[36,164,116],{"class":46},[36,166,167],{"class":95},"ctx",[36,169,99],{"class":42},[36,171,172],{"class":82}," JobContext",[36,174,175],{"class":46},")",[36,177,99],{"class":42},[36,179,180],{"class":82}," Promise",[36,182,183],{"class":46},"\u003C",[36,185,119],{"class":102},[36,187,128],{"class":46},[36,189,191,194,197,200,203,206,209,212],{"class":38,"line":190},5,[36,192,193],{"class":42},"    const",[36,195,196],{"class":102}," remaining",[36,198,199],{"class":42}," =",[36,201,202],{"class":46}," ctx.deadlineAt ",[36,204,205],{"class":42},"-",[36,207,208],{"class":46}," Date.",[36,210,211],{"class":82},"now",[36,213,214],{"class":46},"();\n",[36,216,218,221,224,226,229],{"class":38,"line":217},6,[36,219,220],{"class":42},"    await",[36,222,223],{"class":82}," fetch",[36,225,140],{"class":46},[36,227,228],{"class":59},"'https:\u002F\u002Fapi.example.com\u002Fmail'",[36,230,231],{"class":46},", {\n",[36,233,235,238,241],{"class":38,"line":234},7,[36,236,237],{"class":46},"      method: ",[36,239,240],{"class":59},"'POST'",[36,242,243],{"class":46},",\n",[36,245,247,250,253,256,259],{"class":38,"line":246},8,[36,248,249],{"class":46},"      body: ",[36,251,252],{"class":102},"JSON",[36,254,255],{"class":46},".",[36,257,258],{"class":82},"stringify",[36,260,261],{"class":46},"(payload),\n",[36,263,265],{"class":38,"line":264},9,[36,266,268],{"class":267},"sJ8bj","      \u002F\u002F 期限超過時は即中断\n",[36,270,272,275,278,281,284,287,290,293,295,297,300],{"class":38,"line":271},10,[36,273,274],{"class":46},"      signal: remaining ",[36,276,277],{"class":42},">",[36,279,280],{"class":102}," 0",[36,282,283],{"class":42}," ?",[36,285,286],{"class":46}," AbortSignal.",[36,288,289],{"class":82},"timeout",[36,291,292],{"class":46},"(remaining) ",[36,294,99],{"class":42},[36,296,286],{"class":46},[36,298,299],{"class":82},"abort",[36,301,302],{"class":46},"(),\n",[36,304,306],{"class":38,"line":305},11,[36,307,308],{"class":46},"    });\n",[36,310,312],{"class":38,"line":311},12,[36,313,314],{"class":46},"  }\n",[36,316,318],{"class":38,"line":317},13,[36,319,320],{"class":46},"}\n",[15,322,323,324,326,327,330,331,334],{},"型引数は、順にpayload, 戻り値, 必須キーの宣言, ",[19,325,125],{},"です。",[328,329],"br",{},"\nperformer内では、",[19,332,333],{},"this.env","でWorkerのbindingを参照します。",[23,336,337],{"id":337},"binding名",[15,339,340,341,343,346,347,350],{},"binding名は、Workerのエントリからexportした名前です。",[328,342],{},[19,344,345],{},"export class SendMail","の場合、binding名は",[19,348,349],{},"SendMail","となり、別途登録する必要はありません。",[27,352,354],{"className":29,"code":353,"language":31,"meta":32,"style":32},"\u002F\u002F src\u002Findex.ts\nexport { SendMail } from '.\u002Fperformers\u002Fsend-mail.js';\n",[19,355,356,361],{"__ignoreMap":32},[36,357,358],{"class":38,"line":39},[36,359,360],{"class":267},"\u002F\u002F src\u002Findex.ts\n",[36,362,363,365,368,370,373],{"class":38,"line":66},[36,364,76],{"class":42},[36,366,367],{"class":46}," { SendMail } ",[36,369,56],{"class":42},[36,371,372],{"class":59}," '.\u002Fperformers\u002Fsend-mail.js'",[36,374,63],{"class":46},[15,376,377,380,381,384,385,387,388,390,391,393,395],{},[19,378,379],{},"defineTsumugi","の",[19,382,383],{},"performers","には、performerをまとめたモジュールをそのまま渡します。",[328,386],{},"\nbindingごとのpayloadと必須キーの型は、この",[19,389,383],{},"から導出されます。",[328,392],{},[19,394,383],{},"のキーは、Workerのエントリからexportした名前と一致させてください。",[27,397,399],{"className":29,"code":398,"language":31,"meta":32,"style":32},"import * as performers from '.\u002Fperformers\u002Findex.js';\n\nexport * from '.\u002Fperformers\u002Findex.js';\n\nconst tsumugi = defineTsumugi({ performers, \u002F* ... *\u002F });\n",[19,400,401,421,425,438,442],{"__ignoreMap":32},[36,402,403,405,408,411,414,416,419],{"class":38,"line":39},[36,404,43],{"class":42},[36,406,407],{"class":102}," *",[36,409,410],{"class":42}," as",[36,412,413],{"class":46}," performers ",[36,415,56],{"class":42},[36,417,418],{"class":59}," '.\u002Fperformers\u002Findex.js'",[36,420,63],{"class":46},[36,422,423],{"class":38,"line":66},[36,424,70],{"emptyLinePlaceholder":69},[36,426,427,429,431,434,436],{"class":38,"line":73},[36,428,76],{"class":42},[36,430,407],{"class":102},[36,432,433],{"class":42}," from",[36,435,418],{"class":59},[36,437,63],{"class":46},[36,439,440],{"class":38,"line":131},[36,441,70],{"emptyLinePlaceholder":69},[36,443,444,447,450,452,455,458,461],{"class":38,"line":190},[36,445,446],{"class":42},"const",[36,448,449],{"class":102}," tsumugi",[36,451,199],{"class":42},[36,453,454],{"class":82}," defineTsumugi",[36,456,457],{"class":46},"({ performers, ",[36,459,460],{"class":267},"\u002F* ... *\u002F",[36,462,463],{"class":46}," });\n",[15,465,466],{},"binding名を別名にする場合は、performerをまとめてexportするファイルで別名を付けてください。",[27,468,470],{"className":29,"code":469,"language":31,"meta":32,"style":32},"\u002F\u002F src\u002Fperformers\u002Findex.ts\nexport { SendMail as MAIL } from '.\u002Fsend-mail.js';\n",[19,471,472,477],{"__ignoreMap":32},[36,473,474],{"class":38,"line":39},[36,475,476],{"class":267},"\u002F\u002F src\u002Fperformers\u002Findex.ts\n",[36,478,479,481,484,487,490,492,495],{"class":38,"line":66},[36,480,76],{"class":42},[36,482,483],{"class":46}," { SendMail ",[36,485,486],{"class":42},"as",[36,488,489],{"class":46}," MAIL } ",[36,491,56],{"class":42},[36,493,494],{"class":59}," '.\u002Fsend-mail.js'",[36,496,63],{"class":46},[15,498,499,500,503,504,506,507,509,510,512,513,515],{},"Workerのエントリで別名を付けると、実行時のbinding名は",[19,501,502],{},"MAIL","になります。",[328,505],{},"\nこのとき、",[19,508,383],{},"のキーは",[19,511,349],{},"のままなので、両者が一致しません。",[328,514],{},"\n別名はWorkerのエントリでは付けないでください。",[27,517,519],{"className":29,"code":518,"language":31,"meta":32,"style":32},"\u002F\u002F src\u002Findex.ts\n\u002F\u002F 実行時のexportのみ変更, 型の上のbinding名は不一致\nexport { SendMail as MAIL } from '.\u002Fperformers\u002Fsend-mail.js';\n",[19,520,521,525,530],{"__ignoreMap":32},[36,522,523],{"class":38,"line":39},[36,524,360],{"class":267},[36,526,527],{"class":38,"line":66},[36,528,529],{"class":267},"\u002F\u002F 実行時のexportのみ変更, 型の上のbinding名は不一致\n",[36,531,532,534,536,538,540,542,544],{"class":38,"line":73},[36,533,76],{"class":42},[36,535,483],{"class":46},[36,537,486],{"class":42},[36,539,489],{"class":46},[36,541,56],{"class":42},[36,543,372],{"class":59},[36,545,63],{"class":46},[23,547,548],{"id":548},"実行コンテキスト",[15,550,551,554,555,558],{},[19,552,553],{},"perform","の第2引数には、",[19,556,557],{},"JobContext","が渡されます。",[560,561,562,575],"table",{},[563,564,565],"thead",{},[566,567,568,572],"tr",{},[569,570,571],"th",{},"フィールド",[569,573,574],{},"内容",[576,577,578,589,599,609,619,629,639,652,662],"tbody",{},[566,579,580,586],{},[581,582,583],"td",{},[19,584,585],{},"jobId",[581,587,588],{},"ジョブID",[566,590,591,596],{},[581,592,593],{},[19,594,595],{},"attempt",[581,597,598],{},"試行回数。初回は1",[566,600,601,606],{},[581,602,603],{},[19,604,605],{},"idempotencyKey",[581,607,608],{},"ジョブごとに一定の値。再実行でも同じ値",[566,610,611,616],{},[581,612,613],{},[19,614,615],{},"deadlineAt",[581,617,618],{},"タイムアウトの時刻。epochミリ秒",[566,620,621,626],{},[581,622,623],{},[19,624,625],{},"heartbeat",[581,627,628],{},"実行中であることを報告する関数",[566,630,631,636],{},[581,632,633],{},[19,634,635],{},"log",[581,637,638],{},"処理途中のメッセージを保存する非同期関数",[566,640,641,646],{},[581,642,643],{},[19,644,645],{},"traceparent",[581,647,648,649],{},"投入時に指定したトレース情報。未指定の場合は",[19,650,651],{},"null",[566,653,654,659],{},[581,655,656],{},[19,657,658],{},"spawn",[581,660,661],{},"実行中のFlowのノードに子ノードを追加する関数",[566,663,664,669],{},[581,665,666],{},[19,667,668],{},"fail",[581,670,671],{},"失敗した試行の再試行可否を指定する関数(使い方は「再試行の指定」)",[15,673,674,677,678,680,681,683],{},[19,675,676],{},"at-least-once","では、同じジョブが複数回実行されることを前提にしてください。",[328,679],{},"\n外部への副作用は、",[19,682,605],{},"を利用して冪等性を担保します。",[685,686,687],"h3",{"id":687},"タイムアウトによる中断",[15,689,690,691,693,694,697,698,700,701,704],{},"処理をタイムアウトで中断する場合は、",[19,692,615],{},"から",[19,695,696],{},"AbortSignal","を作成し、処理に渡します。",[328,699],{},"\n期限を過ぎている場合は、",[19,702,703],{},"AbortSignal.abort()","を使用してください。",[27,706,708],{"className":29,"code":707,"language":31,"meta":32,"style":32},"const remaining = ctx.deadlineAt - Date.now();\nconst signal = remaining > 0 ? AbortSignal.timeout(remaining) : AbortSignal.abort();\n",[19,709,710,728],{"__ignoreMap":32},[36,711,712,714,716,718,720,722,724,726],{"class":38,"line":39},[36,713,446],{"class":42},[36,715,196],{"class":102},[36,717,199],{"class":42},[36,719,202],{"class":46},[36,721,205],{"class":42},[36,723,208],{"class":46},[36,725,211],{"class":82},[36,727,214],{"class":46},[36,729,730,732,735,737,740,742,744,746,748,750,752,754,756,758],{"class":38,"line":66},[36,731,446],{"class":42},[36,733,734],{"class":102}," signal",[36,736,199],{"class":42},[36,738,739],{"class":46}," remaining ",[36,741,277],{"class":42},[36,743,280],{"class":102},[36,745,283],{"class":42},[36,747,286],{"class":46},[36,749,289],{"class":82},[36,751,292],{"class":46},[36,753,99],{"class":42},[36,755,286],{"class":46},[36,757,299],{"class":82},[36,759,214],{"class":46},[15,761,762,763,765,767],{},"Tsumugiは、期限を過ぎたperformerの実行を自動的に停止しません。",[328,764],{},[19,766,615],{},"を用いて中断を実装しないperformerは、期限経過後も実行を継続することがあります。",[685,769,625],{"id":625},[15,771,772,773,776,777,779,782,783,785,786,788],{},"所要時間が入力によって大きく変わる処理では、",[19,774,775],{},"timeoutMs","を最長の所要時間に合わせて設定する必要があります。",[328,778],{},[19,780,781],{},"ctx.heartbeat()","を呼び出すと、タイムアウトの起点が最後の報告された時刻に更新されます。",[328,784],{},"\nそのため、",[19,787,775],{},"は報告の間隔を基準にした値で設定してください。",[27,790,792],{"className":29,"code":791,"language":31,"meta":32,"style":32},"class Import extends Performer\u003C{ rows: string[] }, void, {}, Env> {\n  async perform(payload: { rows: string[] }, ctx: JobContext): Promise\u003Cvoid> {\n    for (const [index, row] of payload.rows.entries()) {\n      await store(row);\n      await ctx.heartbeat((index + 1) \u002F payload.rows.length);\n    }\n  }\n}\n",[19,793,794,826,866,903,914,946,951,955],{"__ignoreMap":32},[36,795,796,799,802,804,806,808,811,813,815,818,820,822,824],{"class":38,"line":39},[36,797,798],{"class":42},"class",[36,800,801],{"class":82}," Import",[36,803,86],{"class":42},[36,805,89],{"class":82},[36,807,92],{"class":46},[36,809,810],{"class":95},"rows",[36,812,99],{"class":42},[36,814,103],{"class":102},[36,816,817],{"class":46},"[] }, ",[36,819,119],{"class":102},[36,821,122],{"class":46},[36,823,125],{"class":82},[36,825,128],{"class":46},[36,827,828,830,832,834,836,838,840,842,844,846,848,850,852,854,856,858,860,862,864],{"class":38,"line":66},[36,829,134],{"class":42},[36,831,137],{"class":82},[36,833,140],{"class":46},[36,835,143],{"class":95},[36,837,99],{"class":42},[36,839,148],{"class":46},[36,841,810],{"class":95},[36,843,99],{"class":42},[36,845,103],{"class":102},[36,847,817],{"class":46},[36,849,167],{"class":95},[36,851,99],{"class":42},[36,853,172],{"class":82},[36,855,175],{"class":46},[36,857,99],{"class":42},[36,859,180],{"class":82},[36,861,183],{"class":46},[36,863,119],{"class":102},[36,865,128],{"class":46},[36,867,868,871,874,876,879,882,885,888,891,894,897,900],{"class":38,"line":73},[36,869,870],{"class":42},"    for",[36,872,873],{"class":46}," (",[36,875,446],{"class":42},[36,877,878],{"class":46}," [",[36,880,881],{"class":102},"index",[36,883,884],{"class":46},", ",[36,886,887],{"class":102},"row",[36,889,890],{"class":46},"] ",[36,892,893],{"class":42},"of",[36,895,896],{"class":46}," payload.rows.",[36,898,899],{"class":82},"entries",[36,901,902],{"class":46},"()) {\n",[36,904,905,908,911],{"class":38,"line":131},[36,906,907],{"class":42},"      await",[36,909,910],{"class":82}," store",[36,912,913],{"class":46},"(row);\n",[36,915,916,918,921,923,926,929,932,935,938,940,943],{"class":38,"line":190},[36,917,907],{"class":42},[36,919,920],{"class":46}," ctx.",[36,922,625],{"class":82},[36,924,925],{"class":46},"((index ",[36,927,928],{"class":42},"+",[36,930,931],{"class":102}," 1",[36,933,934],{"class":46},") ",[36,936,937],{"class":42},"\u002F",[36,939,896],{"class":46},[36,941,942],{"class":102},"length",[36,944,945],{"class":46},");\n",[36,947,948],{"class":38,"line":217},[36,949,950],{"class":46},"    }\n",[36,952,953],{"class":38,"line":234},[36,954,314],{"class":46},[36,956,957],{"class":38,"line":246},[36,958,320],{"class":46},[15,960,961,962,964,965,967,968,970,971,973,974,976,977,980,981,983,984,987,988,326,990,992,993,998],{},"引数の進捗は、0以上1以下の数値です。",[328,963],{},"\n範囲外の値は丸められ、数値以外の値は進捗なしの報告として扱われます。",[328,966],{},"\n報告した進捗は、ダッシュボードのジョブの詳細に表示されます。",[328,969],{},"\n前回の報告から5秒以内の呼び出しでは、報告が送信されません。",[328,972],{},"\n報告の失敗は、例外として通知されません。",[328,975],{},"\n戻り値は",[19,978,979],{},"{ cancelRequested: boolean }","で、取り消しが要求されているかどうかを示す値です。",[328,982],{},"\n一度",[19,985,986],{},"true","になった後は、以降の呼び出しの戻り値も",[19,989,986],{},[328,991],{},"\n取り消しの動作の詳細は、",[994,995,997],"a",{"href":996},"\u002Fguide\u002Fexecution#%E5%AE%9F%E8%A1%8C%E4%B8%AD%E3%81%AE%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97","実行中の取り消し","の節にあります。",[685,1000,1001],{"id":1001},"ログ",[15,1003,1004,1007,1008,1010,1011,1014],{},[19,1005,1006],{},"ctx.log(message)","は、処理途中のメッセージを保存します。",[328,1009],{},"\n呼び出しには",[19,1012,1013],{},"await","が必要です。",[27,1016,1018],{"className":29,"code":1017,"language":31,"meta":32,"style":32},"await ctx.log('Import started');\nawait importRows(payload.rows);\nawait ctx.log('Import completed');\n",[19,1019,1020,1035,1045],{"__ignoreMap":32},[36,1021,1022,1024,1026,1028,1030,1033],{"class":38,"line":39},[36,1023,1013],{"class":42},[36,1025,920],{"class":46},[36,1027,635],{"class":82},[36,1029,140],{"class":46},[36,1031,1032],{"class":59},"'Import started'",[36,1034,945],{"class":46},[36,1036,1037,1039,1042],{"class":38,"line":66},[36,1038,1013],{"class":42},[36,1040,1041],{"class":82}," importRows",[36,1043,1044],{"class":46},"(payload.rows);\n",[36,1046,1047,1049,1051,1053,1055,1058],{"class":38,"line":73},[36,1048,1013],{"class":42},[36,1050,920],{"class":46},[36,1052,635],{"class":82},[36,1054,140],{"class":46},[36,1056,1057],{"class":59},"'Import completed'",[36,1059,945],{"class":46},[15,1061,1062,1063,1065,1066,1068,1069,1071,1072,1074,1075,1077,1078,1080,1081,1084],{},"ダッシュボードは、保存したログをジョブの詳細に試行回数、記録時刻、本文とともに表示します。",[328,1064],{},"\nperformerがその後に異常終了した場合も、保存したログは削除されません。",[328,1067],{},"\n保存されるログは、1ジョブあたり最新の20件です。",[328,1070],{},"\n本文は、JavaScriptの文字列長で2,000文字までです。",[328,1073],{},"\n超過した部分は切り捨てられます。",[328,1076],{},"\n前回の保存から1秒以内の呼び出しでは、ログは保存されません。",[328,1079],{},"\n保存に失敗した場合は、",[19,1082,1083],{},"console.error","へ出力され、ジョブの処理は続行します。",[685,1086,1087],{"id":1087},"トレース情報",[15,1089,1090,1093,1094,1096,1097,326,1099,1101,1102,1104,1105,1107,1108,1110,1111,1113,1114,1118,1119,1123],{},[19,1091,1092],{},"ctx.traceparent","は、投入時に指定した",[19,1095,645],{},"の値で、未指定の場合は",[19,1098,651],{},[328,1100],{},"\nリトライ時も、投入時と同一の値です。",[328,1103],{},"\nトレースのSDKを使用する場合は、",[19,1106,1092],{},"を親のコンテキストとして、spanを作成する必要があります。",[328,1109],{},"\n投入時に指定する形式と",[19,1112,658],{},"の使用方法は、それぞれ",[994,1115,1117],{"href":1116},"\u002Fguide\u002Fenqueue#%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%E6%83%85%E5%A0%B1","ジョブの投入","と",[994,1120,1122],{"href":1121},"\u002Fguide\u002Fflow#perform%E5%86%85%E3%81%8B%E3%82%89%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E8%BF%BD%E5%8A%A0","Flow","を参照してください。",[23,1125,1126],{"id":1126},"失敗とリトライ",[15,1128,1129,1130,1132],{},"ジョブが失敗した場合、例外が送出されます。",[328,1131],{},"\n試行回数が上限に達していないときは、リトライが実行されます。",[27,1134,1136],{"className":29,"code":1135,"language":31,"meta":32,"style":32},"class ChargeCard extends Performer\u003C{ customerId: string; amountJpy: number }, void, { concurrencyKey: true }, Env> {\n  async perform(payload: { customerId: string; amountJpy: number }): Promise\u003Cvoid> {\n    const res = await this.env.PAYMENT.charge(payload);\n    if (!res.ok) throw new Error(`payment failed: ${res.status}`);\n  }\n}\n",[19,1137,1138,1189,1230,1259,1299,1303],{"__ignoreMap":32},[36,1139,1140,1142,1145,1147,1149,1151,1154,1156,1158,1160,1163,1165,1168,1170,1172,1175,1178,1180,1183,1185,1187],{"class":38,"line":39},[36,1141,798],{"class":42},[36,1143,1144],{"class":82}," ChargeCard",[36,1146,86],{"class":42},[36,1148,89],{"class":82},[36,1150,92],{"class":46},[36,1152,1153],{"class":95},"customerId",[36,1155,99],{"class":42},[36,1157,103],{"class":102},[36,1159,106],{"class":46},[36,1161,1162],{"class":95},"amountJpy",[36,1164,99],{"class":42},[36,1166,1167],{"class":102}," number",[36,1169,116],{"class":46},[36,1171,119],{"class":102},[36,1173,1174],{"class":46},", { ",[36,1176,1177],{"class":95},"concurrencyKey",[36,1179,99],{"class":42},[36,1181,1182],{"class":102}," true",[36,1184,116],{"class":46},[36,1186,125],{"class":82},[36,1188,128],{"class":46},[36,1190,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217,1220,1222,1224,1226,1228],{"class":38,"line":66},[36,1192,134],{"class":42},[36,1194,137],{"class":82},[36,1196,140],{"class":46},[36,1198,143],{"class":95},[36,1200,99],{"class":42},[36,1202,148],{"class":46},[36,1204,1153],{"class":95},[36,1206,99],{"class":42},[36,1208,103],{"class":102},[36,1210,106],{"class":46},[36,1212,1162],{"class":95},[36,1214,99],{"class":42},[36,1216,1167],{"class":102},[36,1218,1219],{"class":46}," })",[36,1221,99],{"class":42},[36,1223,180],{"class":82},[36,1225,183],{"class":46},[36,1227,119],{"class":102},[36,1229,128],{"class":46},[36,1231,1232,1234,1237,1239,1242,1245,1248,1251,1253,1256],{"class":38,"line":73},[36,1233,193],{"class":42},[36,1235,1236],{"class":102}," res",[36,1238,199],{"class":42},[36,1240,1241],{"class":42}," await",[36,1243,1244],{"class":102}," this",[36,1246,1247],{"class":46},".env.",[36,1249,1250],{"class":102},"PAYMENT",[36,1252,255],{"class":46},[36,1254,1255],{"class":82},"charge",[36,1257,1258],{"class":46},"(payload);\n",[36,1260,1261,1264,1266,1269,1272,1275,1278,1281,1283,1286,1289,1291,1294,1297],{"class":38,"line":131},[36,1262,1263],{"class":42},"    if",[36,1265,873],{"class":46},[36,1267,1268],{"class":42},"!",[36,1270,1271],{"class":46},"res.ok) ",[36,1273,1274],{"class":42},"throw",[36,1276,1277],{"class":42}," new",[36,1279,1280],{"class":82}," Error",[36,1282,140],{"class":46},[36,1284,1285],{"class":59},"`payment failed: ${",[36,1287,1288],{"class":46},"res",[36,1290,255],{"class":59},[36,1292,1293],{"class":46},"status",[36,1295,1296],{"class":59},"}`",[36,1298,945],{"class":46},[36,1300,1301],{"class":38,"line":190},[36,1302,314],{"class":46},[36,1304,1305],{"class":38,"line":217},[36,1306,320],{"class":46},[15,1308,1309,1310,1312,1313,1315,1316,1318,1319,1123],{},"例外のメッセージは試行履歴に保存され、ダッシュボードはジョブの詳細に試行履歴を表示します。",[328,1311],{},"\n保存の上限は、メッセージが2,000文字、試行履歴が1ジョブあたり20件です。",[328,1314],{},"\n戻り値が8,192文字を超える場合や、直列化できない場合、戻り値は保存されません。",[328,1317],{},"\nFlowのノードでの扱いは、",[994,1320,1122],{"href":1321},"\u002Fguide\u002Fflow#%E5%88%B6%E7%B4%84",[685,1323,1324],{"id":1324},"再試行の指定",[15,1326,1327,1330,1331,1333,1334,1336,1339,1340,1343,1344,1346],{},[19,1328,1329],{},"ctx.fail()","を呼び出した後に例外を送出すると、その試行の後続リトライに",[19,1332,1329],{},"で指定した内容が適用されます。",[328,1335],{},[19,1337,1338],{},"{ retry: false }","を指定した場合、試行回数が上限に達していなくてもジョブはリトライされず、",[19,1341,1342],{},"FAILED","へ遷移します。",[328,1345],{},"\nこの指定は、再試行しても結果が変わらない失敗に対して使用してください。",[27,1348,1350],{"className":29,"code":1349,"language":31,"meta":32,"style":32},"class SyncUser extends Performer\u003C{ userId: string }, void, {}, Env> {\n  async perform(payload: { userId: string }, ctx: JobContext): Promise\u003Cvoid> {\n    const res = await fetch(`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${payload.userId}`);\n    if (res.status === 404) {\n      await ctx.fail({ retry: false });\n      throw new Error(`user not found: ${payload.userId}`);\n    }\n  }\n}\n",[19,1351,1352,1382,1422,1449,1465,1481,1505,1509,1513],{"__ignoreMap":32},[36,1353,1354,1356,1359,1361,1363,1365,1368,1370,1372,1374,1376,1378,1380],{"class":38,"line":39},[36,1355,798],{"class":42},[36,1357,1358],{"class":82}," SyncUser",[36,1360,86],{"class":42},[36,1362,89],{"class":82},[36,1364,92],{"class":46},[36,1366,1367],{"class":95},"userId",[36,1369,99],{"class":42},[36,1371,103],{"class":102},[36,1373,116],{"class":46},[36,1375,119],{"class":102},[36,1377,122],{"class":46},[36,1379,125],{"class":82},[36,1381,128],{"class":46},[36,1383,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420],{"class":38,"line":66},[36,1385,134],{"class":42},[36,1387,137],{"class":82},[36,1389,140],{"class":46},[36,1391,143],{"class":95},[36,1393,99],{"class":42},[36,1395,148],{"class":46},[36,1397,1367],{"class":95},[36,1399,99],{"class":42},[36,1401,103],{"class":102},[36,1403,116],{"class":46},[36,1405,167],{"class":95},[36,1407,99],{"class":42},[36,1409,172],{"class":82},[36,1411,175],{"class":46},[36,1413,99],{"class":42},[36,1415,180],{"class":82},[36,1417,183],{"class":46},[36,1419,119],{"class":102},[36,1421,128],{"class":46},[36,1423,1424,1426,1428,1430,1432,1434,1436,1439,1441,1443,1445,1447],{"class":38,"line":73},[36,1425,193],{"class":42},[36,1427,1236],{"class":102},[36,1429,199],{"class":42},[36,1431,1241],{"class":42},[36,1433,223],{"class":82},[36,1435,140],{"class":46},[36,1437,1438],{"class":59},"`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${",[36,1440,143],{"class":46},[36,1442,255],{"class":59},[36,1444,1367],{"class":46},[36,1446,1296],{"class":59},[36,1448,945],{"class":46},[36,1450,1451,1453,1456,1459,1462],{"class":38,"line":131},[36,1452,1263],{"class":42},[36,1454,1455],{"class":46}," (res.status ",[36,1457,1458],{"class":42},"===",[36,1460,1461],{"class":102}," 404",[36,1463,1464],{"class":46},") {\n",[36,1466,1467,1469,1471,1473,1476,1479],{"class":38,"line":190},[36,1468,907],{"class":42},[36,1470,920],{"class":46},[36,1472,668],{"class":82},[36,1474,1475],{"class":46},"({ retry: ",[36,1477,1478],{"class":102},"false",[36,1480,463],{"class":46},[36,1482,1483,1486,1488,1490,1492,1495,1497,1499,1501,1503],{"class":38,"line":217},[36,1484,1485],{"class":42},"      throw",[36,1487,1277],{"class":42},[36,1489,1280],{"class":82},[36,1491,140],{"class":46},[36,1493,1494],{"class":59},"`user not found: ${",[36,1496,143],{"class":46},[36,1498,255],{"class":59},[36,1500,1367],{"class":46},[36,1502,1296],{"class":59},[36,1504,945],{"class":46},[36,1506,1507],{"class":38,"line":234},[36,1508,950],{"class":46},[36,1510,1511],{"class":38,"line":246},[36,1512,314],{"class":46},[36,1514,1515],{"class":38,"line":264},[36,1516,320],{"class":46},[15,1518,1519,1522,1523,1526,1527,1529,1530,1533],{},[19,1520,1521],{},"{ retryAt }","にepochミリ秒を指定すると、その試行に限り、",[19,1524,1525],{},"backoff","で算出した間隔ではなく、指定した時刻に再試行されます。",[328,1528],{},"\n次の例は、",[19,1531,1532],{},"Retry-After","ヘッダの値(秒数またはHTTP-date)を時刻に変換します。",[27,1535,1537],{"className":29,"code":1536,"language":31,"meta":32,"style":32},"if (res.status === 429) {\n  const header = res.headers.get('Retry-After') ?? '';\n  const date = Date.parse(header);\n  \u002F\u002F 解釈不能時は60秒後\n  const retryAt = \u002F^\\d+$\u002F.test(header)\n    ? Date.now() + Number(header) * 1_000\n    : Number.isNaN(date)\n      ? Date.now() + 60_000\n      : date;\n  await ctx.fail({ retryAt });\n  throw new Error('rate limited');\n}\n",[19,1538,1539,1553,1584,1601,1606,1637,1663,1677,1693,1701,1713,1729],{"__ignoreMap":32},[36,1540,1541,1544,1546,1548,1551],{"class":38,"line":39},[36,1542,1543],{"class":42},"if",[36,1545,1455],{"class":46},[36,1547,1458],{"class":42},[36,1549,1550],{"class":102}," 429",[36,1552,1464],{"class":46},[36,1554,1555,1558,1561,1563,1566,1569,1571,1574,1576,1579,1582],{"class":38,"line":66},[36,1556,1557],{"class":42},"  const",[36,1559,1560],{"class":102}," header",[36,1562,199],{"class":42},[36,1564,1565],{"class":46}," res.headers.",[36,1567,1568],{"class":82},"get",[36,1570,140],{"class":46},[36,1572,1573],{"class":59},"'Retry-After'",[36,1575,934],{"class":46},[36,1577,1578],{"class":42},"??",[36,1580,1581],{"class":59}," ''",[36,1583,63],{"class":46},[36,1585,1586,1588,1591,1593,1595,1598],{"class":38,"line":73},[36,1587,1557],{"class":42},[36,1589,1590],{"class":102}," date",[36,1592,199],{"class":42},[36,1594,208],{"class":46},[36,1596,1597],{"class":82},"parse",[36,1599,1600],{"class":46},"(header);\n",[36,1602,1603],{"class":38,"line":131},[36,1604,1605],{"class":267},"  \u002F\u002F 解釈不能時は60秒後\n",[36,1607,1608,1610,1613,1615,1618,1621,1624,1627,1629,1631,1634],{"class":38,"line":190},[36,1609,1557],{"class":42},[36,1611,1612],{"class":102}," retryAt",[36,1614,199],{"class":42},[36,1616,1617],{"class":59}," \u002F",[36,1619,1620],{"class":42},"^",[36,1622,1623],{"class":102},"\\d",[36,1625,1626],{"class":42},"+$",[36,1628,937],{"class":59},[36,1630,255],{"class":46},[36,1632,1633],{"class":82},"test",[36,1635,1636],{"class":46},"(header)\n",[36,1638,1639,1642,1644,1646,1649,1651,1654,1657,1660],{"class":38,"line":217},[36,1640,1641],{"class":42},"    ?",[36,1643,208],{"class":46},[36,1645,211],{"class":82},[36,1647,1648],{"class":46},"() ",[36,1650,928],{"class":42},[36,1652,1653],{"class":82}," Number",[36,1655,1656],{"class":46},"(header) ",[36,1658,1659],{"class":42},"*",[36,1661,1662],{"class":102}," 1_000\n",[36,1664,1665,1668,1671,1674],{"class":38,"line":234},[36,1666,1667],{"class":42},"    :",[36,1669,1670],{"class":46}," Number.",[36,1672,1673],{"class":82},"isNaN",[36,1675,1676],{"class":46},"(date)\n",[36,1678,1679,1682,1684,1686,1688,1690],{"class":38,"line":246},[36,1680,1681],{"class":42},"      ?",[36,1683,208],{"class":46},[36,1685,211],{"class":82},[36,1687,1648],{"class":46},[36,1689,928],{"class":42},[36,1691,1692],{"class":102}," 60_000\n",[36,1694,1695,1698],{"class":38,"line":264},[36,1696,1697],{"class":42},"      :",[36,1699,1700],{"class":46}," date;\n",[36,1702,1703,1706,1708,1710],{"class":38,"line":271},[36,1704,1705],{"class":42},"  await",[36,1707,920],{"class":46},[36,1709,668],{"class":82},[36,1711,1712],{"class":46},"({ retryAt });\n",[36,1714,1715,1718,1720,1722,1724,1727],{"class":38,"line":305},[36,1716,1717],{"class":42},"  throw",[36,1719,1277],{"class":42},[36,1721,1280],{"class":82},[36,1723,140],{"class":46},[36,1725,1726],{"class":59},"'rate limited'",[36,1728,945],{"class":46},[36,1730,1731],{"class":38,"line":311},[36,1732,320],{"class":46},[15,1734,1735,1738,1739,1741,1744,1745,1343,1747,1749,1750,1753,1754,1343,1757,1759,1760,1762,1764,1765,1767,1768,1770,1771,1773,1774,1776,1777,1779,1780,1782,1783,1785],{},[19,1736,1737],{},"retryAt","を指定しても、その試行は試行回数に含まれます。",[328,1740],{},[19,1742,1743],{},"maxAttempts","に達している場合、ジョブは",[19,1746,1342],{},[328,1748],{},"\n指定した時刻が期限(",[19,1751,1752],{},"expiresAt",")より後の場合、ジョブは",[19,1755,1756],{},"CANCELLED",[328,1758],{},"\n過去の時刻を指定した場合は、直ちに再試行の対象となります。",[328,1761],{},[19,1763,1329],{},"の指定は、throwで終了した試行にのみ適用されます。",[328,1766],{},"\nthrowせずに",[19,1769,553],{},"が終了した場合、",[19,1772,1329],{},"を呼んでいてもジョブは完了します。",[328,1775],{},"\n1回の試行で",[19,1778,1329],{},"を複数回呼び出した場合、最後の指定が有効です。",[328,1781],{},"\n不正な形式の値を",[19,1784,1329],{},"に渡すと、その呼び出しで例外が発生します。",[23,1787,1788],{"id":1788},"キーの必須化",[15,1790,1791,1792,1795,1796,1799],{},"第3型引数に",[19,1793,1794],{},"{ concurrencyKey: true }","または",[19,1797,1798],{},"{ uniqueKey: true }","を指定したperformerでは、ジョブの投入時にキーの指定が必須となります。",[27,1801,1803],{"className":29,"code":1802,"language":31,"meta":32,"style":32},"class ChargeCard extends Performer\u003CPayload, void, { concurrencyKey: true }, Env> {}\n",[19,1804,1805],{"__ignoreMap":32},[36,1806,1807,1809,1811,1813,1815,1817,1820,1822,1824,1826,1828,1830,1832,1834,1836],{"class":38,"line":39},[36,1808,798],{"class":42},[36,1810,1144],{"class":82},[36,1812,86],{"class":42},[36,1814,89],{"class":82},[36,1816,183],{"class":46},[36,1818,1819],{"class":82},"Payload",[36,1821,884],{"class":46},[36,1823,119],{"class":102},[36,1825,1174],{"class":46},[36,1827,1177],{"class":95},[36,1829,99],{"class":42},[36,1831,1182],{"class":102},[36,1833,116],{"class":46},[36,1835,125],{"class":82},[36,1837,1838],{"class":46},"> {}\n",[15,1840,1841,1842,1844,1845,1847,1850,1851,1854],{},"キーは、ジョブの投入時に文字列として渡します。",[328,1843],{},"\nTsumugiは、performer側でキーを導出する関数を定義する仕組みを提供していません。",[328,1846],{},[19,1848,1849],{},"tsumugi.enqueue","および",[19,1852,1853],{},"tsumugi.jobs(env)","では、キーを指定しない場合、コンパイル時にエラーが発生します。",[27,1856,1858],{"className":29,"code":1857,"language":31,"meta":32,"style":32},"await tsumugi.enqueue(env, { binding: 'CHARGE', payload, concurrencyKey: 'customer:c1' });\nawait tsumugi.jobs(env).enqueue('CHARGE', payload, { concurrencyKey: 'customer:c1' });\n",[19,1859,1860,1884],{"__ignoreMap":32},[36,1861,1862,1864,1867,1870,1873,1876,1879,1882],{"class":38,"line":39},[36,1863,1013],{"class":42},[36,1865,1866],{"class":46}," tsumugi.",[36,1868,1869],{"class":82},"enqueue",[36,1871,1872],{"class":46},"(env, { binding: ",[36,1874,1875],{"class":59},"'CHARGE'",[36,1877,1878],{"class":46},", payload, concurrencyKey: ",[36,1880,1881],{"class":59},"'customer:c1'",[36,1883,463],{"class":46},[36,1885,1886,1888,1890,1893,1896,1898,1900,1902,1905,1907],{"class":38,"line":66},[36,1887,1013],{"class":42},[36,1889,1866],{"class":46},[36,1891,1892],{"class":82},"jobs",[36,1894,1895],{"class":46},"(env).",[36,1897,1869],{"class":82},[36,1899,140],{"class":46},[36,1901,1875],{"class":59},[36,1903,1904],{"class":46},", payload, { concurrencyKey: ",[36,1906,1881],{"class":59},[36,1908,463],{"class":46},[1910,1911,1913],"callout",{"type":1912},"warning",[15,1914,1915,1918,1919,1921,1922,1123],{},[19,1916,1917],{},"createClient()","には、キーの必須化が適用されません。",[328,1920],{},"\n詳細は",[994,1923,1925],{"href":1924},"\u002Fguide\u002Fenqueue#%E6%8A%95%E5%85%A5%E6%96%B9%E6%B3%95","投入方法",[23,1927,1929],{"id":1928},"別workerへの配置","別Workerへの配置",[15,1931,1932],{},"performerは、Service Bindingで接続した別のWorkerに配置した場合でも、Tsumugiから実行されます。",[27,1934,1936],{"className":29,"code":1935,"language":31,"meta":32,"style":32},"\u002F\u002F performerを配置するWorker\nimport { Performer, type JobContext } from 'tsumugi\u002Fperformer';\n\nexport class SendMail extends Performer\u003C{ to: string; subject: string }, void, {}, Env> {\n  async perform(payload: { to: string; subject: string }, ctx: JobContext): Promise\u003Cvoid> {\n    \u002F\u002F ...\n  }\n}\n\n\u002F\u002F WorkerEntrypointの名前付きexportに加えてdefaultも必要\nexport default {\n  async fetch(): Promise\u003CResponse> {\n    return new Response('performer only', { status: 404 });\n  },\n} satisfies ExportedHandler\u003CEnv>;\n",[19,1937,1938,1943,1959,1963,2001,2049,2054,2058,2062,2066,2071,2081,2101,2124,2130],{"__ignoreMap":32},[36,1939,1940],{"class":38,"line":39},[36,1941,1942],{"class":267},"\u002F\u002F performerを配置するWorker\n",[36,1944,1945,1947,1949,1951,1953,1955,1957],{"class":38,"line":66},[36,1946,43],{"class":42},[36,1948,47],{"class":46},[36,1950,50],{"class":42},[36,1952,53],{"class":46},[36,1954,56],{"class":42},[36,1956,60],{"class":59},[36,1958,63],{"class":46},[36,1960,1961],{"class":38,"line":73},[36,1962,70],{"emptyLinePlaceholder":69},[36,1964,1965,1967,1969,1971,1973,1975,1977,1979,1981,1983,1985,1987,1989,1991,1993,1995,1997,1999],{"class":38,"line":131},[36,1966,76],{"class":42},[36,1968,79],{"class":42},[36,1970,83],{"class":82},[36,1972,86],{"class":42},[36,1974,89],{"class":82},[36,1976,92],{"class":46},[36,1978,96],{"class":95},[36,1980,99],{"class":42},[36,1982,103],{"class":102},[36,1984,106],{"class":46},[36,1986,109],{"class":95},[36,1988,99],{"class":42},[36,1990,103],{"class":102},[36,1992,116],{"class":46},[36,1994,119],{"class":102},[36,1996,122],{"class":46},[36,1998,125],{"class":82},[36,2000,128],{"class":46},[36,2002,2003,2005,2007,2009,2011,2013,2015,2017,2019,2021,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045,2047],{"class":38,"line":190},[36,2004,134],{"class":42},[36,2006,137],{"class":82},[36,2008,140],{"class":46},[36,2010,143],{"class":95},[36,2012,99],{"class":42},[36,2014,148],{"class":46},[36,2016,96],{"class":95},[36,2018,99],{"class":42},[36,2020,103],{"class":102},[36,2022,106],{"class":46},[36,2024,109],{"class":95},[36,2026,99],{"class":42},[36,2028,103],{"class":102},[36,2030,116],{"class":46},[36,2032,167],{"class":95},[36,2034,99],{"class":42},[36,2036,172],{"class":82},[36,2038,175],{"class":46},[36,2040,99],{"class":42},[36,2042,180],{"class":82},[36,2044,183],{"class":46},[36,2046,119],{"class":102},[36,2048,128],{"class":46},[36,2050,2051],{"class":38,"line":217},[36,2052,2053],{"class":267},"    \u002F\u002F ...\n",[36,2055,2056],{"class":38,"line":234},[36,2057,314],{"class":46},[36,2059,2060],{"class":38,"line":246},[36,2061,320],{"class":46},[36,2063,2064],{"class":38,"line":264},[36,2065,70],{"emptyLinePlaceholder":69},[36,2067,2068],{"class":38,"line":271},[36,2069,2070],{"class":267},"\u002F\u002F WorkerEntrypointの名前付きexportに加えてdefaultも必要\n",[36,2072,2073,2075,2078],{"class":38,"line":305},[36,2074,76],{"class":42},[36,2076,2077],{"class":42}," default",[36,2079,2080],{"class":46}," {\n",[36,2082,2083,2085,2087,2090,2092,2094,2096,2099],{"class":38,"line":311},[36,2084,134],{"class":42},[36,2086,223],{"class":82},[36,2088,2089],{"class":46},"()",[36,2091,99],{"class":42},[36,2093,180],{"class":82},[36,2095,183],{"class":46},[36,2097,2098],{"class":82},"Response",[36,2100,128],{"class":46},[36,2102,2103,2106,2108,2111,2113,2116,2119,2122],{"class":38,"line":317},[36,2104,2105],{"class":42},"    return",[36,2107,1277],{"class":42},[36,2109,2110],{"class":82}," Response",[36,2112,140],{"class":46},[36,2114,2115],{"class":59},"'performer only'",[36,2117,2118],{"class":46},", { status: ",[36,2120,2121],{"class":102},"404",[36,2123,463],{"class":46},[36,2125,2127],{"class":38,"line":2126},14,[36,2128,2129],{"class":46},"  },\n",[36,2131,2133,2136,2139,2142,2144,2146],{"class":38,"line":2132},15,[36,2134,2135],{"class":46},"} ",[36,2137,2138],{"class":42},"satisfies",[36,2140,2141],{"class":82}," ExportedHandler",[36,2143,183],{"class":46},[36,2145,125],{"class":82},[36,2147,2148],{"class":46},">;\n",[15,2150,2151],{},"呼び出し側のwrangler.jsoncには、binding名とentrypointを対応させるService Bindingを設定する必要があります。",[27,2153,2157],{"className":2154,"code":2155,"language":2156,"meta":32,"style":32},"language-jsonc shiki shiki-themes github-light github-dark","\"services\": [\n  { \"binding\": \"MAIL\", \"service\": \"my-mailer\", \"entrypoint\": \"SendMail\" },\n],\n","jsonc",[19,2158,2159,2167,2204],{"__ignoreMap":32},[36,2160,2161,2164],{"class":38,"line":39},[36,2162,2163],{"class":59},"\"services\"",[36,2165,2166],{"class":46},": [\n",[36,2168,2169,2172,2175,2178,2181,2183,2186,2188,2191,2193,2196,2198,2201],{"class":38,"line":66},[36,2170,2171],{"class":46},"  { ",[36,2173,2174],{"class":102},"\"binding\"",[36,2176,2177],{"class":46},": ",[36,2179,2180],{"class":59},"\"MAIL\"",[36,2182,884],{"class":46},[36,2184,2185],{"class":102},"\"service\"",[36,2187,2177],{"class":46},[36,2189,2190],{"class":59},"\"my-mailer\"",[36,2192,884],{"class":46},[36,2194,2195],{"class":102},"\"entrypoint\"",[36,2197,2177],{"class":46},[36,2199,2200],{"class":59},"\"SendMail\"",[36,2202,2203],{"class":46}," },\n",[36,2205,2206],{"class":38,"line":73},[36,2207,2208],{"class":46},"],\n",[15,2210,2211,2212,2214,2215,2218,2219,2221,2222,2224],{},"呼び出し側の",[19,2213,383],{},"には、クラスの代わりに",[19,2216,2217],{},"remote()","を指定してください。",[328,2220],{},"\n同じWorkerのperformerと別Workerのperformerは、1つの",[19,2223,383],{},"に混在させる構成にも対応しています。",[27,2226,2228],{"className":29,"code":2227,"language":31,"meta":32,"style":32},"import { remote } from 'tsumugi';\nimport type { SendMail } from 'my-mailer';\n\nconst performers = { ...local, MAIL: remote\u003CSendMail>() };\n",[19,2229,2230,2244,2260,2264],{"__ignoreMap":32},[36,2231,2232,2234,2237,2239,2242],{"class":38,"line":39},[36,2233,43],{"class":42},[36,2235,2236],{"class":46}," { remote } ",[36,2238,56],{"class":42},[36,2240,2241],{"class":59}," 'tsumugi'",[36,2243,63],{"class":46},[36,2245,2246,2248,2251,2253,2255,2258],{"class":38,"line":66},[36,2247,43],{"class":42},[36,2249,2250],{"class":42}," type",[36,2252,367],{"class":46},[36,2254,56],{"class":42},[36,2256,2257],{"class":59}," 'my-mailer'",[36,2259,63],{"class":46},[36,2261,2262],{"class":38,"line":73},[36,2263,70],{"emptyLinePlaceholder":69},[36,2265,2266,2268,2271,2273,2275,2278,2281,2284,2286,2288],{"class":38,"line":131},[36,2267,446],{"class":42},[36,2269,2270],{"class":102}," performers",[36,2272,199],{"class":42},[36,2274,148],{"class":46},[36,2276,2277],{"class":42},"...",[36,2279,2280],{"class":46},"local, MAIL: ",[36,2282,2283],{"class":82},"remote",[36,2285,183],{"class":46},[36,2287,349],{"class":82},[36,2289,2290],{"class":46},">() };\n",[685,2292,2294],{"id":2293},"別workerに配置した場合の制約","別Workerに配置した場合の制約",[15,2296,2297,2298,1118,2301,2304,2305,1014,2307,2309,2311,2312,2314,2315,2317,2318,2320,2311,2322,2324],{},"別Workerに配置されたperformerでは、",[19,2299,2300],{},"ctx.spawn",[19,2302,2303],{},"ctx.fail","の呼び出しに",[19,2306,1013],{},[328,2308],{},[19,2310,2300],{},"を",[19,2313,1013],{},"せずに",[19,2316,553],{},"が終了すると、子ノードの追加は反映されません。",[328,2319],{},[19,2321,2303],{},[19,2323,1013],{},"せずに例外をthrowした場合も、指定は反映されない可能性があります。",[27,2326,2328],{"className":29,"code":2327,"language":31,"meta":32,"style":32},"await ctx.spawn('child', 'MAIL', payload);\n",[19,2329,2330],{"__ignoreMap":32},[36,2331,2332,2334,2336,2338,2340,2343,2345,2348],{"class":38,"line":39},[36,2333,1013],{"class":42},[36,2335,920],{"class":46},[36,2337,658],{"class":82},[36,2339,140],{"class":46},[36,2341,2342],{"class":59},"'child'",[36,2344,884],{"class":46},[36,2346,2347],{"class":59},"'MAIL'",[36,2349,2350],{"class":46},", payload);\n",[15,2352,2353,2355,2356,2358,2359,2361,2363],{},[19,2354,658],{},"で追加した子ノードは、",[19,2357,553],{},"の完了後に作成されます。",[328,2360],{},[19,2362,658],{},"の呼び出し時点では、子ノードは実行されません。",[23,2365,2366],{"id":2366},"テスト",[15,2368,2369,380,2372,2375,2376,2378,2380,2381,2384,2385,2388],{},[19,2370,2371],{},"tsumugi\u002Ftesting",[19,2373,2374],{},"runPerformer","は、ジョブを投入せずにperformerを直接呼び出します。",[328,2377],{},[19,2379,6],{},"のコンストラクタは、",[19,2382,2383],{},"WorkerEntrypoint","と同じ",[19,2386,2387],{},"(ctx, env)","の2引数を取ります。",[27,2390,2392],{"className":29,"code":2391,"language":31,"meta":32,"style":32},"import { createTestContext, runPerformer } from 'tsumugi\u002Ftesting';\n\ndeclare const ctx: ExecutionContext;\ndeclare const env: Env;\n\nconst result = await runPerformer(new SendWelcome(ctx, env), { userId: 'u_1' });\n\nif (result.ok) console.log(result.value);\nelse console.error(result.error);\n",[19,2393,2394,2408,2412,2430,2446,2450,2480,2484,2496],{"__ignoreMap":32},[36,2395,2396,2398,2401,2403,2406],{"class":38,"line":39},[36,2397,43],{"class":42},[36,2399,2400],{"class":46}," { createTestContext, runPerformer } ",[36,2402,56],{"class":42},[36,2404,2405],{"class":59}," 'tsumugi\u002Ftesting'",[36,2407,63],{"class":46},[36,2409,2410],{"class":38,"line":66},[36,2411,70],{"emptyLinePlaceholder":69},[36,2413,2414,2417,2420,2423,2425,2428],{"class":38,"line":73},[36,2415,2416],{"class":42},"declare",[36,2418,2419],{"class":42}," const",[36,2421,2422],{"class":102}," ctx",[36,2424,99],{"class":42},[36,2426,2427],{"class":82}," ExecutionContext",[36,2429,63],{"class":46},[36,2431,2432,2434,2436,2439,2441,2444],{"class":38,"line":131},[36,2433,2416],{"class":42},[36,2435,2419],{"class":42},[36,2437,2438],{"class":102}," env",[36,2440,99],{"class":42},[36,2442,2443],{"class":82}," Env",[36,2445,63],{"class":46},[36,2447,2448],{"class":38,"line":190},[36,2449,70],{"emptyLinePlaceholder":69},[36,2451,2452,2454,2457,2459,2461,2464,2466,2469,2472,2475,2478],{"class":38,"line":217},[36,2453,446],{"class":42},[36,2455,2456],{"class":102}," result",[36,2458,199],{"class":42},[36,2460,1241],{"class":42},[36,2462,2463],{"class":82}," runPerformer",[36,2465,140],{"class":46},[36,2467,2468],{"class":42},"new",[36,2470,2471],{"class":82}," SendWelcome",[36,2473,2474],{"class":46},"(ctx, env), { userId: ",[36,2476,2477],{"class":59},"'u_1'",[36,2479,463],{"class":46},[36,2481,2482],{"class":38,"line":234},[36,2483,70],{"emptyLinePlaceholder":69},[36,2485,2486,2488,2491,2493],{"class":38,"line":246},[36,2487,1543],{"class":42},[36,2489,2490],{"class":46}," (result.ok) console.",[36,2492,635],{"class":82},[36,2494,2495],{"class":46},"(result.value);\n",[36,2497,2498,2501,2504,2507],{"class":38,"line":264},[36,2499,2500],{"class":42},"else",[36,2502,2503],{"class":46}," console.",[36,2505,2506],{"class":82},"error",[36,2508,2509],{"class":46},"(result.error);\n",[15,2511,2512,2514,2515,2517,2518,2520,2521,2523,2525,2526,2528,2531,2532,2534,2535,2538],{},[19,2513,333],{},"を使用しないperformerでは、",[19,2516,2374],{},"は",[19,2519,553],{},"を持つ通常のオブジェクトも受け付けます。",[328,2522],{},[19,2524,2374],{},"は例外を再送出せず、成功と失敗のどちらも同じ形式の結果を戻り値とします。",[328,2527],{},[19,2529,2530],{},"createTestContext()","の戻り値であるコンテキストは、",[19,2533,1329],{},"に渡された指定を、呼び出し順に",[19,2536,2537],{},"fails","へ記録します。",[685,2540,2541],{"id":2541},"実行コンテキストの差し替え",[15,2543,2544,2547,2548,2550,2551,2553],{},[19,2545,2546],{},"createTestContext","に値を指定すると、",[19,2549,2374],{},"に渡す実行コンテキストの項目を差し替えられます。",[328,2552],{},"\n次の例は、試行回数を3回目に設定します。",[27,2555,2557],{"className":29,"code":2556,"language":31,"meta":32,"style":32},"const ctx = createTestContext({ attempt: 3 });\nawait runPerformer(performer, payload, ctx);\n",[19,2558,2559,2578],{"__ignoreMap":32},[36,2560,2561,2563,2565,2567,2570,2573,2576],{"class":38,"line":39},[36,2562,446],{"class":42},[36,2564,2422],{"class":102},[36,2566,199],{"class":42},[36,2568,2569],{"class":82}," createTestContext",[36,2571,2572],{"class":46},"({ attempt: ",[36,2574,2575],{"class":102},"3",[36,2577,463],{"class":46},[36,2579,2580,2582,2584],{"class":38,"line":66},[36,2581,1013],{"class":42},[36,2583,2463],{"class":82},[36,2585,2586],{"class":46},"(performer, payload, ctx);\n",[15,2588,2589,2590,2592],{},"期限に対するperformerの動作を検証する場合は、",[19,2591,615],{},"に過去の時刻または近い将来の時刻を指定します。",[27,2594,2596],{"className":29,"code":2595,"language":31,"meta":32,"style":32},"const ctx = createTestContext({ deadlineAt: Date.now() + 50 });\nawait runPerformer(performer, payload, ctx);\n",[19,2597,2598,2622],{"__ignoreMap":32},[36,2599,2600,2602,2604,2606,2608,2611,2613,2615,2617,2620],{"class":38,"line":39},[36,2601,446],{"class":42},[36,2603,2422],{"class":102},[36,2605,199],{"class":42},[36,2607,2569],{"class":82},[36,2609,2610],{"class":46},"({ deadlineAt: Date.",[36,2612,211],{"class":82},[36,2614,1648],{"class":46},[36,2616,928],{"class":42},[36,2618,2619],{"class":102}," 50",[36,2621,463],{"class":46},[36,2623,2624,2626,2628],{"class":38,"line":66},[36,2625,1013],{"class":42},[36,2627,2463],{"class":82},[36,2629,2586],{"class":46},[685,2631,2632],{"id":2632},"投入から実行までのテスト",[15,2634,2635,2636,2639,2640,2642,2644],{},"ジョブの投入から実行までの一連の流れを検証する場合は、",[19,2637,2638],{},"@cloudflare\u002Fvitest-pool-workers","を利用してください。",[328,2641],{},[19,2643,2371],{},"は、この範囲のテストを対象としていません。",[2646,2647,2648],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":32,"searchDepth":73,"depth":73,"links":2650},[2651,2652,2653,2659,2662,2663,2666],{"id":25,"depth":66,"text":25},{"id":337,"depth":66,"text":337},{"id":548,"depth":66,"text":548,"children":2654},[2655,2656,2657,2658],{"id":687,"depth":73,"text":687},{"id":625,"depth":73,"text":625},{"id":1001,"depth":73,"text":1001},{"id":1087,"depth":73,"text":1087},{"id":1126,"depth":66,"text":1126,"children":2660},[2661],{"id":1324,"depth":73,"text":1324},{"id":1788,"depth":66,"text":1788},{"id":1928,"depth":66,"text":1929,"children":2664},[2665],{"id":2293,"depth":73,"text":2294},{"id":2366,"depth":66,"text":2366,"children":2667},[2668,2669],{"id":2541,"depth":73,"text":2541},{"id":2632,"depth":73,"text":2632},"ジョブの処理を定義するPerformerの実行コンテキスト, 失敗とリトライ, 別Workerへの配置, テストについて","md",{},"\u002Fguide\u002Fperformer","2026-07-22",{"title":6,"description":2670},"guide\u002Fperformer","2026-10-25","XKxZAc15PMjpnk4V6o8A9wTrVDhO9K7NqqZHF8jtti0",false,1794027571230]