[{"data":1,"prerenderedAt":486},["ShallowReactive",2],{"docs:\u002Fen\u002Fguide\u002Foverview":3},{"page":4,"untranslated":485,"translated":190},{"id":5,"title":6,"body":7,"description":476,"extension":477,"meta":478,"navigation":190,"path":479,"publishedAt":480,"seo":481,"stem":482,"updatedAt":483,"__hash__":484},"docs_en\u002Fen\u002Fguide\u002Foverview.md","Overview",{"type":8,"value":9,"toc":465},"minimark",[10,14,28,33,36,41,47,58,62,68,82,86,92,104,108,111,131,135,138,391,415,419,439,443,455,461],[11,12,6],"h1",{"id":13},"概要",[15,16,17,18,21,22,24,25,27],"p",{},"Tsumugi is a job management system designed for Cloudflare Workers.",[19,20],"br",{},"\nYou define job processing as a performer and submit jobs with enqueue.",[19,23],{},"\nTsumugi handles job delivery, execution, and state recording.",[19,26],{},"\nIt also provides a management dashboard and a REST API.",[29,30,32],"h2",{"id":31},"何を解決するの","What problems does it solve?",[15,34,35],{},"Using Cloudflare Queues directly as a job queue causes several problems.",[37,38,40],"h3",{"id":39},"ジョブの状態を管理しづらく成功したジョブが残らない","Job state is hard to manage, and successful jobs are not retained",[15,42,43,44,46],{},"Queues does not provide an API for querying the state of a message you have submitted.",[19,45],{},"\nSuccessful messages are not retained either, so there is no way to review past executions later.",[15,48,49,50,52,53,57],{},"Tsumugi's dashboard and REST API provide listing, search, and aggregation for both running jobs and finished jobs.",[19,51],{},"\nWhen you configure the ",[54,55,56],"code",{},"TSUMUGI_METRICS"," binding, the failure rate and execution time are recorded as metrics.",[37,59,61],{"id":60},"排他制御と重複排除を実装しづらい","Exclusive control and deduplication are hard to implement",[15,63,64,65,67],{},"Queues does not manage dependencies or relationships between messages.",[19,66],{},"\nAs a result, you must implement on your own the prevention of concurrent execution for the same customer and the elimination of duplicate submissions.",[15,69,70,71,74,75,77,78,81],{},"In Tsumugi, jobs that specify a ",[54,72,73],{},"concurrencyKey"," are executed one at a time, in order, among jobs with the same key.",[19,76],{},"\nWhen you specify a ",[54,79,80],{},"uniqueKey",", a job submitted with the same key within a certain period is treated as a duplicate, and the existing job ID is returned without creating a new job.",[37,83,85],{"id":84},"ジョブの種類が増えるほど分岐が増える","The more job types you have, the more branching you need",[15,87,88,89,91],{},"With Queues, you can set only one consumer for each queue.",[19,90],{},"\nAs a result, as job types increase, you must either add more queues or deal with increasingly complex branching inside the consumer.",[15,93,94,95,97,98,103],{},"Tsumugi distinguishes job types by using the export name of each performer as the binding name, so you do not need to write branching in a consumer.",[19,96],{},"\nThe relationship between binding names and payload types is described in ",[99,100,102],"a",{"href":101},"\u002Fen\u002Fguide\u002Fperformer#binding%E5%90%8D","binding名",".",[37,105,107],{"id":106},"まれにメッセージが失われる","Messages are occasionally lost",[15,109,110],{},"Queues does not provide a way to detect messages that were lost without being processed.",[15,112,113,114,116,117,120,121,116,123,126,127,130],{},"When a job that has started execution does not report a result, Tsumugi determines that the job is unresponsive.",[19,115],{},"\nWith ",[54,118,119],{},"at-least-once"," semantics, an unresponsive job is re-enqueued.",[19,122],{},[54,124,125],{},"at-most-once"," semantics, an unresponsive job transitions to the ",[54,128,129],{},"STALLED"," state and is not executed until it is retried manually.",[29,132,134],{"id":133},"利用者が記述するコード","The code you write",[15,136,137],{},"You write code in only two places: the job processing definition and the submission.",[139,140,145],"pre",{"className":141,"code":142,"language":143,"meta":144,"style":144},"language-ts shiki shiki-themes github-light github-dark","import { defineTsumugi } from 'tsumugi';\nimport { Performer } from 'tsumugi\u002Fperformer';\n\n\u002F\u002F 1. Define the job processing as a performer and export it\nexport class SendMail extends Performer\u003C{ to: string }, void, {}, Env> {\n  async perform(payload: { to: string }): Promise\u003Cvoid> {\n    await this.env.MAILER.send(payload.to);\n  }\n}\n\nconst tsumugi = defineTsumugi({ performers: { SendMail } });\n\n\u002F\u002F 2. Submit the job. The binding name is the exported name\nconst id = await tsumugi.enqueue(env, { binding: 'SendMail', payload: { to: 'a@example.com' } });\n","ts","",[54,146,147,170,185,192,199,247,288,311,317,323,328,346,351,357],{"__ignoreMap":144},[148,149,152,156,160,163,167],"span",{"class":150,"line":151},"line",1,[148,153,155],{"class":154},"szBVR","import",[148,157,159],{"class":158},"sVt8B"," { defineTsumugi } ",[148,161,162],{"class":154},"from",[148,164,166],{"class":165},"sZZnC"," 'tsumugi'",[148,168,169],{"class":158},";\n",[148,171,173,175,178,180,183],{"class":150,"line":172},2,[148,174,155],{"class":154},[148,176,177],{"class":158}," { Performer } ",[148,179,162],{"class":154},[148,181,182],{"class":165}," 'tsumugi\u002Fperformer'",[148,184,169],{"class":158},[148,186,188],{"class":150,"line":187},3,[148,189,191],{"emptyLinePlaceholder":190},true,"\n",[148,193,195],{"class":150,"line":194},4,[148,196,198],{"class":197},"sJ8bj","\u002F\u002F 1. Define the job processing as a performer and export it\n",[148,200,202,205,208,212,215,218,221,225,228,232,235,238,241,244],{"class":150,"line":201},5,[148,203,204],{"class":154},"export",[148,206,207],{"class":154}," class",[148,209,211],{"class":210},"sScJk"," SendMail",[148,213,214],{"class":154}," extends",[148,216,217],{"class":210}," Performer",[148,219,220],{"class":158},"\u003C{ ",[148,222,224],{"class":223},"s4XuR","to",[148,226,227],{"class":154},":",[148,229,231],{"class":230},"sj4cs"," string",[148,233,234],{"class":158}," }, ",[148,236,237],{"class":230},"void",[148,239,240],{"class":158},", {}, ",[148,242,243],{"class":210},"Env",[148,245,246],{"class":158},"> {\n",[148,248,250,253,256,259,262,264,267,269,271,273,276,278,281,284,286],{"class":150,"line":249},6,[148,251,252],{"class":154},"  async",[148,254,255],{"class":210}," perform",[148,257,258],{"class":158},"(",[148,260,261],{"class":223},"payload",[148,263,227],{"class":154},[148,265,266],{"class":158}," { ",[148,268,224],{"class":223},[148,270,227],{"class":154},[148,272,231],{"class":230},[148,274,275],{"class":158}," })",[148,277,227],{"class":154},[148,279,280],{"class":210}," Promise",[148,282,283],{"class":158},"\u003C",[148,285,237],{"class":230},[148,287,246],{"class":158},[148,289,291,294,297,300,303,305,308],{"class":150,"line":290},7,[148,292,293],{"class":154},"    await",[148,295,296],{"class":230}," this",[148,298,299],{"class":158},".env.",[148,301,302],{"class":230},"MAILER",[148,304,103],{"class":158},[148,306,307],{"class":210},"send",[148,309,310],{"class":158},"(payload.to);\n",[148,312,314],{"class":150,"line":313},8,[148,315,316],{"class":158},"  }\n",[148,318,320],{"class":150,"line":319},9,[148,321,322],{"class":158},"}\n",[148,324,326],{"class":150,"line":325},10,[148,327,191],{"emptyLinePlaceholder":190},[148,329,331,334,337,340,343],{"class":150,"line":330},11,[148,332,333],{"class":154},"const",[148,335,336],{"class":230}," tsumugi",[148,338,339],{"class":154}," =",[148,341,342],{"class":210}," defineTsumugi",[148,344,345],{"class":158},"({ performers: { SendMail } });\n",[148,347,349],{"class":150,"line":348},12,[148,350,191],{"emptyLinePlaceholder":190},[148,352,354],{"class":150,"line":353},13,[148,355,356],{"class":197},"\u002F\u002F 2. Submit the job. The binding name is the exported name\n",[148,358,360,362,365,367,370,373,376,379,382,385,388],{"class":150,"line":359},14,[148,361,333],{"class":154},[148,363,364],{"class":230}," id",[148,366,339],{"class":154},[148,368,369],{"class":154}," await",[148,371,372],{"class":158}," tsumugi.",[148,374,375],{"class":210},"enqueue",[148,377,378],{"class":158},"(env, { binding: ",[148,380,381],{"class":165},"'SendMail'",[148,383,384],{"class":158},", payload: { to: ",[148,386,387],{"class":165},"'a@example.com'",[148,389,390],{"class":158}," } });\n",[15,392,393,394,396,397,401,402,401,406,410,411,103],{},"You do not need to write the queue configuration, consumer branching, or state recording.",[19,395],{},"\nEach feature is described in ",[99,398,400],{"href":399},"\u002Fen\u002Fguide\u002Fenqueue","Enqueueing jobs",", ",[99,403,405],{"href":404},"\u002Fen\u002Fguide\u002Fexecution","Controlling execution",[99,407,409],{"href":408},"\u002Fen\u002Fguide\u002Fschedule","Scheduled execution",", and ",[99,412,414],{"href":413},"\u002Fen\u002Fguide\u002Fdashboard","Dashboard and authentication",[29,416,418],{"id":417},"前提条件","Prerequisites",[15,420,421,422,425,426,428,429,103,432,434,435,103],{},"You need the Workers Paid plan and a ",[54,423,424],{},"compatibility_date"," of 2025-11-17 or later.",[19,427],{},"\nCreate the required resources and configuration with ",[54,430,431],{},"npx tsumugi init",[19,433],{},"\nFor the steps, see ",[99,436,438],{"href":437},"\u002Fen\u002Fguide\u002Fgetting-started","Getting Started",[29,440,442],{"id":441},"着想","Inspiration",[15,444,445,446,103,452,454],{},"The design of a job queue built entirely on Cloudflare services, bundled with a dashboard, was inspired by ",[99,447,451],{"href":448,"rel":449},"https:\u002F\u002Fkiribi.pages.dev\u002F",[450],"nofollow","Kiribi",[19,453],{},"\nTsumugi also shares Kiribi's policy of minimizing the setup steps and keeping the internal architecture hidden from users.",[15,456,457,458,460],{},"Tsumugi provides execution volume limits, per-job execution guarantees, retry control, multi-stage execution with Flow, and scheduled execution.",[19,459],{},"\nBecause of these features, the resources and configuration you must prepare are greater than with Kiribi.",[462,463,464],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .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":144,"searchDepth":187,"depth":187,"links":466},[467,473,474,475],{"id":31,"depth":172,"text":32,"children":468},[469,470,471,472],{"id":39,"depth":187,"text":40},{"id":60,"depth":187,"text":61},{"id":84,"depth":187,"text":85},{"id":106,"depth":187,"text":107},{"id":133,"depth":172,"text":134},{"id":417,"depth":172,"text":418},{"id":441,"depth":172,"text":442},"The problems Tsumugi, a job management system for Cloudflare Workers, solves, the code you write, and the prerequisites","md",{},"\u002Fen\u002Fguide\u002Foverview","2026-07-22",{"title":6,"description":476},"en\u002Fguide\u002Foverview","2026-10-25","5Igz7spo9ki5GJCiOWRBt41y-8K0Kflrrh4R0nyYpb8",false,1794027568546]