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