[{"data":1,"prerenderedAt":1496},["ShallowReactive",2],{"navigation_docs_en":3,"-en-async-queue":342,"-en-async-queue-surround":1491},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":173,"body":344,"description":1484,"extension":1485,"links":1486,"meta":1487,"navigation":1488,"path":174,"seo":1489,"stem":175,"__hash__":1490},"docs_en\u002Fen\u002F5.async\u002F1.queue.md",{"type":345,"value":346,"toc":1474},"minimark",[347,377,406,418,437,441,447,497,569,572,595,609,613,735,755,764,797,916,925,929,932,1042,1061,1118,1132,1136,1143,1272,1290,1295,1325,1329,1332,1409,1424,1428,1470],[348,349,354],"pre",{"className":350,"code":351,"language":352,"meta":353,"style":353},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fgpsystem\u002Fqueue\"\n","go","",[355,356,357],"code",{"__ignoreMap":353},[358,359,362,366,370,374],"span",{"class":360,"line":361},"line",1,[358,363,365],{"class":364},"s7zQu","import",[358,367,369],{"class":368},"sMK4o"," \"",[358,371,373],{"class":372},"sBMFI","github.com\u002Fgp-system\u002Fgpsystem\u002Fqueue",[358,375,376],{"class":368},"\"\n",[378,379,380,383,384,388,389,388,392,388,395,398,399,405],"p",{},[355,381,382],{},"queue"," is the kit's Redis\u002Fasynq foundation: everything that follows in this category (",[385,386,387],"a",{"href":178},"events",", ",[385,390,391],{"href":183},"outbox",[385,393,394],{"href":188},"scheduling",[385,396,397],{"href":193},"worker",") builds on this package. It combines connection config, task enqueueing, and per-task options (queue name, delay, retries) in one place; the server side is ",[385,400,404],{"href":401,"rel":402},"https:\u002F\u002Fgithub.com\u002Fhibiken\u002Fasynq",[403],"nofollow","asynq",", which provides the worker processing and monitoring.",[378,407,408,409,413,414,417],{},"Day to day you rarely call it directly: events reach the queue through a ",[385,410,412],{"href":411},"\u002Fen\u002Fasync\u002Fevents#dispatch","dispatcher",", jobs through the ",[385,415,416],{"href":188},"scheduler",". But the concepts here (options, task types, the envelope) reappear in every higher layer, so this page is the right place to start.",[419,420,421,422,426,427,388,430,388,433,436],"note",{},"The kit ",[423,424,425],"strong",{},"centralizes, it does not abstract",": asynq's types (",[355,428,429],{},"*asynq.Task",[355,431,432],{},"*asynq.TaskInfo",[355,434,435],{},"asynq.Option",") are deliberately visible in the public API. When the event\u002Fscheduler layer doesn't fit, application code is free to use asynq directly through this same client. Since nothing is hidden, asynq's ecosystem tooling (e.g. the Asynqmon web UI, the closest thing to a Horizon dashboard) works immediately against the same Redis.",[438,439,52],"h2",{"id":440},"configuration",[378,442,443,446],{},[355,444,445],{},"queue.Config"," is the single Redis connection description in the kit: the client, the worker, the scheduler and the outbox relay all receive it. Compose it under a prefix:",[348,448,450],{"className":350,"code":449,"language":352,"meta":353,"style":353},"type Config struct {\n    Redis queue.Config `envPrefix:\"REDIS_\"`\n}\n",[355,451,452,466,491],{"__ignoreMap":353},[358,453,454,457,460,463],{"class":360,"line":361},[358,455,456],{"class":368},"type",[358,458,459],{"class":372}," Config",[358,461,462],{"class":368}," struct",[358,464,465],{"class":368}," {\n",[358,467,469,473,475,478,481,484,488],{"class":360,"line":468},2,[358,470,472],{"class":471},"sTEyZ","    Redis ",[358,474,382],{"class":372},[358,476,477],{"class":368},".",[358,479,480],{"class":372},"Config",[358,482,483],{"class":368}," `",[358,485,487],{"class":486},"sfazB","envPrefix:\"REDIS_\"",[358,489,490],{"class":368},"`\n",[358,492,494],{"class":360,"line":493},3,[358,495,496],{"class":368},"}\n",[498,499,500,516],"table",{},[501,502,503],"thead",{},[504,505,506,510,513],"tr",{},[507,508,509],"th",{},"Variable",[507,511,512],{},"Default",[507,514,515],{},"Meaning",[517,518,519,538,554],"tbody",{},[504,520,521,527,532],{},[522,523,524],"td",{},[355,525,526],{},"REDIS_ADDR",[522,528,529],{},[355,530,531],{},"localhost:6379",[522,533,534,535],{},"Redis ",[355,536,537],{},"host:port",[504,539,540,545,551],{},[522,541,542],{},[355,543,544],{},"REDIS_PASSWORD",[522,546,547],{},[548,549,550],"em",{},"(empty)",[522,552,553],{},"password; empty means no auth",[504,555,556,561,566],{},[522,557,558],{},[355,559,560],{},"REDIS_DB",[522,562,563],{},[355,564,565],{},"0",[522,567,568],{},"logical Redis database",[378,570,571],{},"The config opens connections in two directions, and both libraries are configured from the same single source:",[573,574,575,586],"ul",{},[576,577,578,581,582,585],"li",{},[355,579,580],{},"cfg.RedisConnOpt()"," → ",[355,583,584],{},"asynq.RedisClientOpt"," (asynq client, server, scheduler),",[576,587,588,581,591,594],{},[355,589,590],{},"cfg.RedisOptions()",[355,592,593],{},"*redis.Options"," (direct go-redis calls, e.g. the scheduler's lease).",[378,596,597,598,604,605,608],{},"The ",[385,599,601,602],{"href":600},"\u002Fen\u002Fasync\u002Fworker#configuration","worker's ",[355,603,480],{}," already embeds it under the ",[355,606,607],{},"REDIS_"," prefix. In a generated project there is nothing extra to wire.",[438,610,612],{"id":611},"client","Client",[348,614,616],{"className":350,"code":615,"language":352,"meta":353,"style":353},"client, err := queue.NewClient(ctx, cfg.Redis)   \u002F\u002F or queue.MustNewClient in main()\ndefer client.Close()\n\ninfo, err := client.Enqueue(ctx, task, queue.OnQueue(\"mail\"))\n",[355,617,618,663,679,685],{"__ignoreMap":353},[358,619,620,622,625,628,631,634,636,640,643,646,648,651,653,656,659],{"class":360,"line":361},[358,621,611],{"class":471},[358,623,624],{"class":368},",",[358,626,627],{"class":471}," err ",[358,629,630],{"class":368},":=",[358,632,633],{"class":471}," queue",[358,635,477],{"class":368},[358,637,639],{"class":638},"s2Zo4","NewClient",[358,641,642],{"class":368},"(",[358,644,645],{"class":471},"ctx",[358,647,624],{"class":368},[358,649,650],{"class":471}," cfg",[358,652,477],{"class":368},[358,654,655],{"class":471},"Redis",[358,657,658],{"class":368},")",[358,660,662],{"class":661},"sHwdD","   \u002F\u002F or queue.MustNewClient in main()\n",[358,664,665,668,671,673,676],{"class":360,"line":468},[358,666,667],{"class":364},"defer",[358,669,670],{"class":471}," client",[358,672,477],{"class":368},[358,674,675],{"class":638},"Close",[358,677,678],{"class":368},"()\n",[358,680,681],{"class":360,"line":493},[358,682,684],{"emptyLinePlaceholder":683},true,"\n",[358,686,688,691,693,695,697,699,701,704,706,708,710,713,715,717,719,722,724,727,730,732],{"class":360,"line":687},4,[358,689,690],{"class":471},"info",[358,692,624],{"class":368},[358,694,627],{"class":471},[358,696,630],{"class":368},[358,698,670],{"class":471},[358,700,477],{"class":368},[358,702,703],{"class":638},"Enqueue",[358,705,642],{"class":368},[358,707,645],{"class":471},[358,709,624],{"class":368},[358,711,712],{"class":471}," task",[358,714,624],{"class":368},[358,716,633],{"class":471},[358,718,477],{"class":368},[358,720,721],{"class":638},"OnQueue",[358,723,642],{"class":368},[358,725,726],{"class":368},"\"",[358,728,729],{"class":486},"mail",[358,731,726],{"class":368},[358,733,734],{"class":368},"))\n",[378,736,737,739,740,743,744,747,748,751,752,477],{},[355,738,639],{}," opens the Redis connection and ",[423,741,742],{},"verifies it with a PING at construction",", the same convention as ",[355,745,746],{},"pg.NewPool",": a wrong address fails at startup, not at the first enqueue. ",[355,749,750],{},"MustNewClient"," is the panicking variant for ",[355,753,754],{},"main()",[378,756,757,760,761,763],{},[355,758,759],{},"Enqueue(ctx, *asynq.Task, opts...)"," submits the task to Redis and returns an ",[355,762,432],{},". It adds two things on top of raw asynq:",[573,765,766,781],{},[576,767,768,771,772,775,776,780],{},[423,769,770],{},"A producer span",": every enqueue opens an OpenTelemetry span (",[355,773,774],{},"enqueue \u003Ctask-type>","), so handing work to the queue shows up in the trace (see ",[385,777,779],{"href":778},"#the-envelope","trace propagation"," below).",[576,782,783,788,789,792,793,796],{},[423,784,785],{},[355,786,787],{},"queue.ErrDuplicate",": when a ",[355,790,791],{},"TaskID"," or ",[355,794,795],{},"Unique"," constraint means the task is already there, you get a single sentinel error instead of asynq's two distinct ones. Callers that rely on idempotent delivery (the outbox relay, the event fan-out) treat it as success:",[348,798,800],{"className":350,"code":799,"language":352,"meta":353,"style":353},"if _, err := client.Enqueue(ctx, task, queue.TaskID(id)); err != nil {\n    if errors.Is(err, queue.ErrDuplicate) {\n        return nil \u002F\u002F already enqueued, exactly what we wanted\n    }\n    return err\n}\n",[355,801,802,856,887,897,902,911],{"__ignoreMap":353},[358,803,804,807,810,812,814,816,818,820,822,824,826,828,830,832,834,836,838,840,843,846,848,851,854],{"class":360,"line":361},[358,805,806],{"class":364},"if",[358,808,809],{"class":471}," _",[358,811,624],{"class":368},[358,813,627],{"class":471},[358,815,630],{"class":368},[358,817,670],{"class":471},[358,819,477],{"class":368},[358,821,703],{"class":638},[358,823,642],{"class":368},[358,825,645],{"class":471},[358,827,624],{"class":368},[358,829,712],{"class":471},[358,831,624],{"class":368},[358,833,633],{"class":471},[358,835,477],{"class":368},[358,837,791],{"class":638},[358,839,642],{"class":368},[358,841,842],{"class":471},"id",[358,844,845],{"class":368},"));",[358,847,627],{"class":471},[358,849,850],{"class":368},"!=",[358,852,853],{"class":368}," nil",[358,855,465],{"class":368},[358,857,858,861,864,866,869,871,874,876,878,880,883,885],{"class":360,"line":468},[358,859,860],{"class":364},"    if",[358,862,863],{"class":471}," errors",[358,865,477],{"class":368},[358,867,868],{"class":638},"Is",[358,870,642],{"class":368},[358,872,873],{"class":471},"err",[358,875,624],{"class":368},[358,877,633],{"class":471},[358,879,477],{"class":368},[358,881,882],{"class":471},"ErrDuplicate",[358,884,658],{"class":368},[358,886,465],{"class":368},[358,888,889,892,894],{"class":360,"line":493},[358,890,891],{"class":364},"        return",[358,893,853],{"class":368},[358,895,896],{"class":661}," \u002F\u002F already enqueued, exactly what we wanted\n",[358,898,899],{"class":360,"line":687},[358,900,901],{"class":368},"    }\n",[358,903,905,908],{"class":360,"line":904},5,[358,906,907],{"class":364},"    return",[358,909,910],{"class":471}," err\n",[358,912,914],{"class":360,"line":913},6,[358,915,496],{"class":368},[378,917,918,921,922,924],{},[355,919,920],{},"Close()"," closes the shared Redis connection (the asynq client is built from it and doesn't own it). The ",[385,923,397],{"href":193}," does this for you as part of its shutdown order.",[438,926,928],{"id":927},"enqueue-options","Enqueue options",[378,930,931],{},"The kit re-exports the useful subset of asynq's options so common cases don't require importing asynq:",[498,933,934,944],{},[501,935,936],{},[504,937,938,941],{},[507,939,940],{},"Option",[507,942,943],{},"Effect",[517,945,946,963,973,983,993,1006,1016,1029],{},[504,947,948,953],{},[522,949,950],{},[355,951,952],{},"OnQueue(name)",[522,954,955,956,959,960],{},"routes to a named queue (matched against the worker's ",[355,957,958],{},"WORKER_QUEUES"," weights); unset means ",[355,961,962],{},"default",[504,964,965,970],{},[522,966,967],{},[355,968,969],{},"MaxRetry(n)",[522,971,972],{},"how many retries before a failing task is archived",[504,974,975,980],{},[522,976,977],{},[355,978,979],{},"Timeout(d)",[522,981,982],{},"bounds a single execution attempt",[504,984,985,990],{},[522,986,987],{},[355,988,989],{},"Deadline(t)",[522,991,992],{},"absolute deadline across all attempts",[504,994,995,1000],{},[522,996,997],{},[355,998,999],{},"ProcessIn(d)",[522,1001,1002,1003],{},"delays processing by ",[355,1004,1005],{},"d",[504,1007,1008,1013],{},[522,1009,1010],{},[355,1011,1012],{},"Unique(ttl)",[522,1014,1015],{},"suppresses an identical task (same type + payload) while a previous one is pending",[504,1017,1018,1023],{},[522,1019,1020],{},[355,1021,1022],{},"Retention(d)",[522,1024,1025,1026,1028],{},"keeps the completed task in Redis for ",[355,1027,1005],{},", for inspection",[504,1030,1031,1036],{},[522,1032,1033],{},[355,1034,1035],{},"TaskID(id)",[522,1037,1038,1039,1041],{},"explicit task ID; a conflict surfaces as ",[355,1040,882],{}," (the basis of idempotent delivery)",[378,1043,1044,1045,1047,1048,1054,1055,1057,1058,1060],{},"The options look the same everywhere a task is born: at ",[355,1046,703],{},", at the ",[385,1049,1051],{"href":1050},"\u002Fen\u002Fasync\u002Fevents#listeners",[355,1052,1053],{},"events.Listen"," listener registration, and on ",[385,1056,416],{"href":188}," entries. The shop's ",[355,1059,729],{}," queue, for example, appears in the listener registration:",[348,1062,1064],{"className":350,"code":1063,"language":352,"meta":353,"style":353},"events.Listen(reg, \"sendOrderConfirmation\", sendOrderConfirmation(deps), queue.OnQueue(\"mail\"))\n",[355,1065,1066],{"__ignoreMap":353},[358,1067,1068,1070,1072,1075,1077,1080,1082,1084,1087,1089,1091,1094,1096,1099,1102,1104,1106,1108,1110,1112,1114,1116],{"class":360,"line":361},[358,1069,387],{"class":471},[358,1071,477],{"class":368},[358,1073,1074],{"class":638},"Listen",[358,1076,642],{"class":368},[358,1078,1079],{"class":471},"reg",[358,1081,624],{"class":368},[358,1083,369],{"class":368},[358,1085,1086],{"class":486},"sendOrderConfirmation",[358,1088,726],{"class":368},[358,1090,624],{"class":368},[358,1092,1093],{"class":638}," sendOrderConfirmation",[358,1095,642],{"class":368},[358,1097,1098],{"class":471},"deps",[358,1100,1101],{"class":368},"),",[358,1103,633],{"class":471},[358,1105,477],{"class":368},[358,1107,721],{"class":638},[358,1109,642],{"class":368},[358,1111,726],{"class":368},[358,1113,729],{"class":486},[358,1115,726],{"class":368},[358,1117,734],{"class":368},[378,1119,1120,1121,1124,1125,1128,1129,1131],{},"When you need the full asynq option set, ",[355,1122,1123],{},"queue.AsynqOptions(opts...)"," converts kit options into raw ",[355,1126,1127],{},"[]asynq.Option",", and asynq's own API remains open alongside ",[355,1130,703],{},": an escape hatch, not a forbidden zone.",[438,1133,1135],{"id":1134},"the-envelope","The Envelope",[378,1137,1138,1139,1142],{},"Every event task payload is the same wire format, ",[355,1140,1141],{},"queue.Envelope",":",[348,1144,1146],{"className":350,"code":1145,"language":352,"meta":353,"style":353},"type Envelope struct {\n    ID         string            `json:\"id\"`         \u002F\u002F dispatch id, doubles as the asynq TaskID\n    Name       string            `json:\"name\"`       \u002F\u002F the event name (EventName())\n    Payload    json.RawMessage   `json:\"payload\"`    \u002F\u002F the JSON-encoded event value\n    Metadata   map[string]string `json:\"metadata,omitempty\"` \u002F\u002F W3C trace context\n    OccurredAt time.Time         `json:\"occurred_at\"`\n}\n",[355,1147,1148,1159,1180,1197,1221,1246,1267],{"__ignoreMap":353},[358,1149,1150,1152,1155,1157],{"class":360,"line":361},[358,1151,456],{"class":368},[358,1153,1154],{"class":372}," Envelope",[358,1156,462],{"class":368},[358,1158,465],{"class":368},[358,1160,1161,1164,1168,1171,1174,1177],{"class":360,"line":468},[358,1162,1163],{"class":471},"    ID         ",[358,1165,1167],{"class":1166},"spNyl","string",[358,1169,1170],{"class":368},"            `",[358,1172,1173],{"class":486},"json:\"id\"",[358,1175,1176],{"class":368},"`",[358,1178,1179],{"class":661},"         \u002F\u002F dispatch id, doubles as the asynq TaskID\n",[358,1181,1182,1185,1187,1189,1192,1194],{"class":360,"line":493},[358,1183,1184],{"class":471},"    Name       ",[358,1186,1167],{"class":1166},[358,1188,1170],{"class":368},[358,1190,1191],{"class":486},"json:\"name\"",[358,1193,1176],{"class":368},[358,1195,1196],{"class":661},"       \u002F\u002F the event name (EventName())\n",[358,1198,1199,1202,1205,1207,1210,1213,1216,1218],{"class":360,"line":687},[358,1200,1201],{"class":471},"    Payload    ",[358,1203,1204],{"class":372},"json",[358,1206,477],{"class":368},[358,1208,1209],{"class":372},"RawMessage",[358,1211,1212],{"class":368},"   `",[358,1214,1215],{"class":486},"json:\"payload\"",[358,1217,1176],{"class":368},[358,1219,1220],{"class":661},"    \u002F\u002F the JSON-encoded event value\n",[358,1222,1223,1226,1229,1231,1234,1236,1238,1241,1243],{"class":360,"line":904},[358,1224,1225],{"class":471},"    Metadata   ",[358,1227,1228],{"class":368},"map[",[358,1230,1167],{"class":1166},[358,1232,1233],{"class":368},"]",[358,1235,1167],{"class":1166},[358,1237,483],{"class":368},[358,1239,1240],{"class":486},"json:\"metadata,omitempty\"",[358,1242,1176],{"class":368},[358,1244,1245],{"class":661}," \u002F\u002F W3C trace context\n",[358,1247,1248,1251,1254,1256,1259,1262,1265],{"class":360,"line":913},[358,1249,1250],{"class":471},"    OccurredAt ",[358,1252,1253],{"class":372},"time",[358,1255,477],{"class":368},[358,1257,1258],{"class":372},"Time",[358,1260,1261],{"class":368},"         `",[358,1263,1264],{"class":486},"json:\"occurred_at\"",[358,1266,490],{"class":368},[358,1268,1270],{"class":360,"line":1269},7,[358,1271,496],{"class":368},[378,1273,1274,1275,1277,1278,1281,1282,1285,1286,1289],{},"The envelope travels unchanged from the dispatcher (or the ",[385,1276,391],{"href":183},") through the fan-out task to every listener task: the listener sees the same ",[355,1279,1280],{},"ID",", payload and trace context the producer set. ",[355,1283,1284],{},"env.Task()"," renders it as the fan-out task; ",[355,1287,1288],{},"queue.DecodeEnvelope(task)"," unpacks it on the other side.",[1291,1292,1294],"h3",{"id":1293},"trace-propagation-across-the-queue","Trace propagation across the queue",[378,1296,1297,1300,1301,1304,1305,1308,1309,1312,1313,1316,1317,1320,1321,1324],{},[355,1298,1299],{},"env.InjectTrace(ctx)"," writes the context's trace context into ",[355,1302,1303],{},"Metadata"," in W3C form (",[355,1306,1307],{},"traceparent","\u002F",[355,1310,1311],{},"tracestate","); on the worker side, ",[355,1314,1315],{},"env.ExtractTrace(ctx)"," continues the same trace. That makes the HTTP request → outbox → relay → worker → listener path ",[423,1318,1319],{},"a single trace",", in the shop from placing the order to the confirmation e-mail. The dispatchers and the worker middleware do this automatically; see the ",[385,1322,1323],{"href":233},"OpenTelemetry page"," for details.",[438,1326,1328],{"id":1327},"task-types","Task types",[378,1330,1331],{},"The kit uses four task-type prefixes, and the helpers are the single source of the wire names:",[498,1333,1334,1347],{},[501,1335,1336],{},[504,1337,1338,1341,1344],{},[507,1339,1340],{},"Helper",[507,1342,1343],{},"Task type",[507,1345,1346],{},"Created by",[517,1348,1349,1364,1379,1394],{},[504,1350,1351,1356,1361],{},[522,1352,1353],{},[355,1354,1355],{},"EventTaskType(name)",[522,1357,1358],{},[355,1359,1360],{},"event:shop.orderPlaced",[522,1362,1363],{},"dispatch \u002F outbox relay (the starting point of the fan-out)",[504,1365,1366,1371,1376],{},[522,1367,1368],{},[355,1369,1370],{},"ListenerTaskType(event, listener)",[522,1372,1373],{},[355,1374,1375],{},"listener:shop.orderPlaced:sendOrderConfirmation",[522,1377,1378],{},"the worker's fan-out handler, one per listener",[504,1380,1381,1386,1391],{},[522,1382,1383],{},[355,1384,1385],{},"ScheduleTaskType(name)",[522,1387,1388],{},[355,1389,1390],{},"schedule:shop.someEvent",[522,1392,1393],{},"the scheduler, when firing a scheduled event",[504,1395,1396,1401,1406],{},[522,1397,1398],{},[355,1399,1400],{},"JobTaskType(name)",[522,1402,1403],{},[355,1404,1405],{},"job:shop.nightlySalesReport",[522,1407,1408],{},"the scheduler, when firing a scheduled job",[378,1410,1411,1412,1415,1416,1419,1420,1423],{},"The worker routes tasks to its four built-in handlers by these prefixes; the ",[355,1413,1414],{},"Is*TaskType"," \u002F ",[355,1417,1418],{},"JobName"," helpers give the same classification to anyone working with raw tasks (say, in a handler registered via ",[355,1421,1422],{},"WithMux","). These are also the names you see in Asynqmon and in the worker's logs: you can tell at a glance which task is which listener of which event.",[438,1425,1427],{"id":1426},"when-to-reach-for-it-directly","When to reach for it directly",[573,1429,1430,1450,1462],{},[576,1431,1432,1435,1436,1439,1440,1443,1444,477],{},[423,1433,1434],{},"Your own, non-event task",": build an ",[355,1437,1438],{},"asynq.NewTask(...)",", submit it with ",[355,1441,1442],{},"client.Enqueue",", and register a handler for it in the worker ",[385,1445,1447,1448],{"href":1446},"\u002Fen\u002Fasync\u002Fworker#options","with ",[355,1449,1422],{},[576,1451,1452,1455,1456,1458,1459,1461],{},[423,1453,1454],{},"An idempotent \"act exactly once\" enqueue",": ",[355,1457,791],{}," + ",[355,1460,882],{}," handling, as above.",[576,1463,1464,1467,1468,477],{},[423,1465,1466],{},"Everything else"," (event dispatch, scheduling) belongs to the higher layers: start at ",[385,1469,387],{"href":178},[1471,1472,1473],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":353,"searchDepth":468,"depth":468,"links":1475},[1476,1477,1478,1479,1482,1483],{"id":440,"depth":468,"text":52},{"id":611,"depth":468,"text":612},{"id":927,"depth":468,"text":928},{"id":1134,"depth":468,"text":1135,"children":1480},[1481],{"id":1293,"depth":493,"text":1294},{"id":1327,"depth":468,"text":1328},{"id":1426,"depth":468,"text":1427},"The asynq + Redis foundation: client, enqueue options, task types and the event envelope the whole async category builds on.","md",null,{},{"icon":131},{"title":173,"description":1484},"t9A_kSnSFVeaxwKrrGHEu_3_HeL5txNNqD8NMnvFNJc",[1492,1494],{"title":162,"path":163,"stem":164,"description":1493,"icon":165,"children":-1},"Idempotent baseline data loading after migrations, built on first-or-create, with optional guards and multi-tenant support.",{"title":177,"path":178,"stem":179,"description":1495,"icon":180,"children":-1},"Events and listeners over asynq, every listener is an independent task with its own retry budget.",1784668701128]