[{"data":1,"prerenderedAt":2095},["ShallowReactive",2],{"navigation":3,"docs-path-index":340,"-docs-modules-scheduler-clustering":446,"-docs-modules-scheduler-clustering-surround":2090},[4],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":64},"Docs","i-lucide-book-marked","\u002Fdocs","docs",[10,30,65,293,318],{"title":11,"path":12,"stem":13,"children":14,"icon":29},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started\u002F1.index",[15,17,21,25],{"title":16,"path":12,"stem":13},"Introduction",{"title":18,"path":19,"stem":20},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F1.getting-started\u002F2.installation",{"title":22,"path":23,"stem":24},"Examples","\u002Fdocs\u002Fgetting-started\u002Fexamples","docs\u002F1.getting-started\u002F3.examples",{"title":26,"path":27,"stem":28},"Project Generator","\u002Fdocs\u002Fgetting-started\u002Fproject-generator","docs\u002F1.getting-started\u002F4.project-generator","i-lucide-rocket",{"title":31,"icon":32,"path":33,"stem":34,"children":35,"page":64},"Core features","i-lucide-square-function","\u002Fdocs\u002Fcore-features","docs\u002F2.core-features",[36,40,44,48,52,56,60],{"title":37,"path":38,"stem":39},"Configuration","\u002Fdocs\u002Fcore-features\u002Fconfiguration","docs\u002F2.core-features\u002F0.configuration",{"title":41,"path":42,"stem":43},"Dependency Injection","\u002Fdocs\u002Fcore-features\u002Fcontainer","docs\u002F2.core-features\u002F1.container",{"title":45,"path":46,"stem":47},"Controllers","\u002Fdocs\u002Fcore-features\u002Fcontrollers","docs\u002F2.core-features\u002F2.controllers",{"title":49,"path":50,"stem":51},"Middlewares","\u002Fdocs\u002Fcore-features\u002Fmiddlewares","docs\u002F2.core-features\u002F3.middlewares",{"title":53,"path":54,"stem":55},"Validation","\u002Fdocs\u002Fcore-features\u002Fvalidation","docs\u002F2.core-features\u002F4.validation",{"title":57,"path":58,"stem":59},"Introspection","\u002Fdocs\u002Fcore-features\u002Fintrospection","docs\u002F2.core-features\u002F5.introspection",{"title":61,"path":62,"stem":63},"Health Checks","\u002Fdocs\u002Fcore-features\u002Fhealth","docs\u002F2.core-features\u002F6.health",false,{"title":66,"icon":67,"defaultOpen":68,"path":69,"stem":70,"children":71,"page":64},"Modules","i-lucide-boxes",true,"\u002Fdocs\u002Fmodules","docs\u002F3.modules",[72,89,100,116,135,154,177,191,206,214,229,240,255,274,282],{"title":73,"icon":64,"defaultOpen":64,"path":74,"stem":75,"children":76,"page":64},"Auth","\u002Fdocs\u002Fmodules\u002Fauth","docs\u002F3.modules\u002F1.auth",[77,81,85],{"title":78,"path":79,"stem":80},"Overview","\u002Fdocs\u002Fmodules\u002Fauth\u002Foverview","docs\u002F3.modules\u002F1.auth\u002F0.overview",{"title":82,"path":83,"stem":84},"Decorators","\u002Fdocs\u002Fmodules\u002Fauth\u002Fdecorators","docs\u002F3.modules\u002F1.auth\u002F1.decorators",{"title":86,"path":87,"stem":88},"API Reference","\u002Fdocs\u002Fmodules\u002Fauth\u002Fapi","docs\u002F3.modules\u002F1.auth\u002F2.api",{"title":90,"icon":64,"defaultOpen":64,"path":91,"stem":92,"children":93,"page":64},"Devtools","\u002Fdocs\u002Fmodules\u002Fdevtools","docs\u002F3.modules\u002F10.devtools",[94,97],{"title":78,"path":95,"stem":96},"\u002Fdocs\u002Fmodules\u002Fdevtools\u002Foverview","docs\u002F3.modules\u002F10.devtools\u002F0.overview",{"title":37,"path":98,"stem":99},"\u002Fdocs\u002Fmodules\u002Fdevtools\u002Fconfiguration","docs\u002F3.modules\u002F10.devtools\u002F1.configuration",{"title":101,"icon":64,"defaultOpen":64,"path":102,"stem":103,"children":104,"page":64},"Telemetry","\u002Fdocs\u002Fmodules\u002Ftelemetry","docs\u002F3.modules\u002F11.telemetry",[105,108,112],{"title":78,"path":106,"stem":107},"\u002Fdocs\u002Fmodules\u002Ftelemetry\u002Foverview","docs\u002F3.modules\u002F11.telemetry\u002F0.overview",{"title":109,"path":110,"stem":111},"API","\u002Fdocs\u002Fmodules\u002Ftelemetry\u002Fapi","docs\u002F3.modules\u002F11.telemetry\u002F1.api",{"title":113,"path":114,"stem":115},"Instrumentation","\u002Fdocs\u002Fmodules\u002Ftelemetry\u002Finstrumentation","docs\u002F3.modules\u002F11.telemetry\u002F2.instrumentation",{"title":117,"icon":64,"defaultOpen":64,"path":118,"stem":119,"children":120,"page":64},"Queue","\u002Fdocs\u002Fmodules\u002Fqueue","docs\u002F3.modules\u002F12.queue",[121,124,128,132],{"title":78,"path":122,"stem":123},"\u002Fdocs\u002Fmodules\u002Fqueue\u002Foverview","docs\u002F3.modules\u002F12.queue\u002F0.overview",{"title":125,"path":126,"stem":127},"Consumers","\u002Fdocs\u002Fmodules\u002Fqueue\u002Fconsumers","docs\u002F3.modules\u002F12.queue\u002F1.consumers",{"title":129,"path":130,"stem":131},"Strategies","\u002Fdocs\u002Fmodules\u002Fqueue\u002Fstrategies","docs\u002F3.modules\u002F12.queue\u002F2.strategies",{"title":109,"path":133,"stem":134},"\u002Fdocs\u002Fmodules\u002Fqueue\u002Fapi","docs\u002F3.modules\u002F12.queue\u002F3.api",{"title":136,"icon":64,"defaultOpen":64,"path":137,"stem":138,"children":139,"page":64},"Scheduler","\u002Fdocs\u002Fmodules\u002Fscheduler","docs\u002F3.modules\u002F13.scheduler",[140,143,147,151],{"title":78,"path":141,"stem":142},"\u002Fdocs\u002Fmodules\u002Fscheduler\u002Foverview","docs\u002F3.modules\u002F13.scheduler\u002F0.overview",{"title":144,"path":145,"stem":146},"Jobs","\u002Fdocs\u002Fmodules\u002Fscheduler\u002Fjobs","docs\u002F3.modules\u002F13.scheduler\u002F1.jobs",{"title":148,"path":149,"stem":150},"Clustering","\u002Fdocs\u002Fmodules\u002Fscheduler\u002Fclustering","docs\u002F3.modules\u002F13.scheduler\u002F2.clustering",{"title":109,"path":152,"stem":153},"\u002Fdocs\u002Fmodules\u002Fscheduler\u002Fapi","docs\u002F3.modules\u002F13.scheduler\u002F3.api",{"title":155,"icon":64,"defaultOpen":64,"path":156,"stem":157,"children":158,"page":64},"Testing","\u002Fdocs\u002Fmodules\u002Ftesting","docs\u002F3.modules\u002F14.testing",[159,162,166,170,174],{"title":78,"path":160,"stem":161},"\u002Fdocs\u002Fmodules\u002Ftesting\u002Foverview","docs\u002F3.modules\u002F14.testing\u002F0.overview",{"title":163,"path":164,"stem":165},"Requests","\u002Fdocs\u002Fmodules\u002Ftesting\u002Frequests","docs\u002F3.modules\u002F14.testing\u002F1.requests",{"title":167,"path":168,"stem":169},"Dependencies","\u002Fdocs\u002Fmodules\u002Ftesting\u002Fdependencies","docs\u002F3.modules\u002F14.testing\u002F2.dependencies",{"title":171,"path":172,"stem":173},"Extensions","\u002Fdocs\u002Fmodules\u002Ftesting\u002Fextensions","docs\u002F3.modules\u002F14.testing\u002F3.extensions",{"title":109,"path":175,"stem":176},"\u002Fdocs\u002Fmodules\u002Ftesting\u002Fapi","docs\u002F3.modules\u002F14.testing\u002F4.api",{"title":178,"icon":64,"defaultOpen":64,"path":179,"stem":180,"children":181,"page":64},"Client","\u002Fdocs\u002Fmodules\u002Fclient","docs\u002F3.modules\u002F15.client",[182,185,188],{"title":78,"path":183,"stem":184},"\u002Fdocs\u002Fmodules\u002Fclient\u002Foverview","docs\u002F3.modules\u002F15.client\u002F0.overview",{"title":163,"path":186,"stem":187},"\u002Fdocs\u002Fmodules\u002Fclient\u002Frequests","docs\u002F3.modules\u002F15.client\u002F1.requests",{"title":109,"path":189,"stem":190},"\u002Fdocs\u002Fmodules\u002Fclient\u002Fapi","docs\u002F3.modules\u002F15.client\u002F2.api",{"title":192,"icon":64,"defaultOpen":64,"path":193,"stem":194,"children":195,"page":64},"Logger","\u002Fdocs\u002Fmodules\u002Flogger","docs\u002F3.modules\u002F2.logger",[196,199,203],{"title":78,"path":197,"stem":198},"\u002Fdocs\u002Fmodules\u002Flogger\u002Foverview","docs\u002F3.modules\u002F2.logger\u002F0.overview",{"title":200,"path":201,"stem":202},"Drains & Adapters","\u002Fdocs\u002Fmodules\u002Flogger\u002Fdrivers","docs\u002F3.modules\u002F2.logger\u002F1.drivers",{"title":109,"path":204,"stem":205},"\u002Fdocs\u002Fmodules\u002Flogger\u002Fapi","docs\u002F3.modules\u002F2.logger\u002F2.api",{"title":207,"icon":64,"defaultOpen":64,"path":208,"stem":209,"children":210,"page":64},"Serverless","\u002Fdocs\u002Fmodules\u002Fserverless","docs\u002F3.modules\u002F3.serverless",[211],{"title":78,"path":212,"stem":213},"\u002Fdocs\u002Fmodules\u002Fserverless\u002Foverview","docs\u002F3.modules\u002F3.serverless\u002F0.overview",{"title":215,"icon":64,"defaultOpen":64,"path":216,"stem":217,"children":218,"page":64},"Storage","\u002Fdocs\u002Fmodules\u002Fstorage","docs\u002F3.modules\u002F4.storage",[219,222,226],{"title":78,"path":220,"stem":221},"\u002Fdocs\u002Fmodules\u002Fstorage\u002Foverview","docs\u002F3.modules\u002F4.storage\u002F0.overview",{"title":223,"path":224,"stem":225},"Drivers","\u002Fdocs\u002Fmodules\u002Fstorage\u002Fdrivers","docs\u002F3.modules\u002F4.storage\u002F1.drivers",{"title":109,"path":227,"stem":228},"\u002Fdocs\u002Fmodules\u002Fstorage\u002Fapi","docs\u002F3.modules\u002F4.storage\u002F2.api",{"title":230,"icon":64,"defaultOpen":64,"path":231,"stem":232,"children":233,"page":64},"Web Sockets","\u002Fdocs\u002Fmodules\u002Fweb-sockets","docs\u002F3.modules\u002F5.web-sockets",[234,237],{"title":78,"path":235,"stem":236},"\u002Fdocs\u002Fmodules\u002Fweb-sockets\u002Foverview","docs\u002F3.modules\u002F5.web-sockets\u002F0.overview",{"title":109,"path":238,"stem":239},"\u002Fdocs\u002Fmodules\u002Fweb-sockets\u002Fapi","docs\u002F3.modules\u002F5.web-sockets\u002F1.api",{"title":241,"icon":64,"defaultOpen":64,"path":242,"stem":243,"children":244,"page":64},"Schema","\u002Fdocs\u002Fmodules\u002Fschema","docs\u002F3.modules\u002F6.schema",[245,248,251],{"title":78,"path":246,"stem":247},"\u002Fdocs\u002Fmodules\u002Fschema\u002Foverview","docs\u002F3.modules\u002F6.schema\u002F0.overview",{"title":82,"path":249,"stem":250},"\u002Fdocs\u002Fmodules\u002Fschema\u002Fdecorators","docs\u002F3.modules\u002F6.schema\u002F1.decorators",{"title":252,"path":253,"stem":254},"Scalar","\u002Fdocs\u002Fmodules\u002Fschema\u002Fscalar","docs\u002F3.modules\u002F6.schema\u002F2.scalar",{"title":256,"icon":64,"defaultOpen":64,"path":257,"stem":258,"children":259,"page":64},"Nitro","\u002Fdocs\u002Fmodules\u002Fnitro","docs\u002F3.modules\u002F7.nitro",[260,263,266,270],{"title":78,"path":261,"stem":262},"\u002Fdocs\u002Fmodules\u002Fnitro\u002Foverview","docs\u002F3.modules\u002F7.nitro\u002F0.overview",{"title":215,"path":264,"stem":265},"\u002Fdocs\u002Fmodules\u002Fnitro\u002Fstorage","docs\u002F3.modules\u002F7.nitro\u002F1.storage",{"title":267,"path":268,"stem":269},"File-Based Routes","\u002Fdocs\u002Fmodules\u002Fnitro\u002Ffile-based-routes","docs\u002F3.modules\u002F7.nitro\u002F2.file-based-routes",{"title":271,"path":272,"stem":273},"Middleware","\u002Fdocs\u002Fmodules\u002Fnitro\u002Fmiddleware","docs\u002F3.modules\u002F7.nitro\u002F3.middleware",{"title":275,"icon":64,"defaultOpen":64,"path":276,"stem":277,"children":278,"page":64},"Vite","\u002Fdocs\u002Fmodules\u002Fvite","docs\u002F3.modules\u002F8.vite",[279],{"title":78,"path":280,"stem":281},"\u002Fdocs\u002Fmodules\u002Fvite\u002Foverview","docs\u002F3.modules\u002F8.vite\u002F0.overview",{"title":283,"icon":64,"defaultOpen":64,"path":284,"stem":285,"children":286,"page":64},"Cache","\u002Fdocs\u002Fmodules\u002Fcache","docs\u002F3.modules\u002F9.cache",[287,290],{"title":78,"path":288,"stem":289},"\u002Fdocs\u002Fmodules\u002Fcache\u002Foverview","docs\u002F3.modules\u002F9.cache\u002F0.overview",{"title":109,"path":291,"stem":292},"\u002Fdocs\u002Fmodules\u002Fcache\u002Fapi","docs\u002F3.modules\u002F9.cache\u002F1.api",{"title":294,"icon":295,"path":296,"stem":297,"children":298,"page":64},"Deployment","i-lucide-cloud-upload","\u002Fdocs\u002Fdeployment","docs\u002F4.deployment",[299,302,306,310,314],{"title":78,"path":300,"stem":301},"\u002Fdocs\u002Fdeployment\u002Foverview","docs\u002F4.deployment\u002F0.overview",{"title":303,"path":304,"stem":305},"Vercel","\u002Fdocs\u002Fdeployment\u002Fvercel","docs\u002F4.deployment\u002F1.vercel",{"title":307,"path":308,"stem":309},"AWS Lambda","\u002Fdocs\u002Fdeployment\u002Faws-lambda","docs\u002F4.deployment\u002F2.aws-lambda",{"title":311,"path":312,"stem":313},"Azure Functions","\u002Fdocs\u002Fdeployment\u002Fazure-functions","docs\u002F4.deployment\u002F3.azure-functions",{"title":315,"path":316,"stem":317},"Node.js Server","\u002Fdocs\u002Fdeployment\u002Fnode-server","docs\u002F4.deployment\u002F4.node-server",{"title":319,"icon":320,"path":321,"stem":322,"children":323,"page":64},"Advanced","i-lucide-brain","\u002Fdocs\u002Fadvanced","docs\u002F99.advanced",[324,328,332,336],{"title":325,"path":326,"stem":327},"Custom Decorator","\u002Fdocs\u002Fadvanced\u002Fcustom-decorator","docs\u002F99.advanced\u002F1.custom-decorator",{"title":329,"path":330,"stem":331},"Plugins","\u002Fdocs\u002Fadvanced\u002Fcustom-plugin","docs\u002F99.advanced\u002F2.custom-plugin",{"title":333,"path":334,"stem":335},"Custom CLI Command","\u002Fdocs\u002Fadvanced\u002Fcustom-cli-command","docs\u002F99.advanced\u002F3.custom-cli-command",{"title":337,"path":338,"stem":339},"Writing a Recipe","\u002Fdocs\u002Fadvanced\u002Fwriting-a-recipe","docs\u002F99.advanced\u002F4.writing-a-recipe",[341,343,345,346,347,348,349,351,352,353,354,355,356,357,358,360,362,363,364,365,367,368,369,371,372,373,374,376,377,378,379,380,382,383,384,385,386,388,389,390,391,392,393,395,396,397,398,400,401,402,403,405,406,408,409,410,411,413,414,415,417,418,419,420,422,423,424,425,426,428,429,431,432,433,435,436,437,438,439,440,442,443,444,445],{"path":342},"\u002Fdocs\u002F.navigation",{"path":344},"\u002Fdocs\u002Fgetting-started\u002F.navigation",{"path":12},{"path":19},{"path":23},{"path":27},{"path":350},"\u002Fdocs\u002Fcore-features\u002F.navigation",{"path":38},{"path":42},{"path":46},{"path":50},{"path":54},{"path":58},{"path":62},{"path":359},"\u002Fdocs\u002Fmodules\u002F.navigation",{"path":361},"\u002Fdocs\u002Fmodules\u002Fauth\u002F.navigation",{"path":79},{"path":83},{"path":87},{"path":366},"\u002Fdocs\u002Fmodules\u002Fdevtools\u002F.navigation",{"path":95},{"path":98},{"path":370},"\u002Fdocs\u002Fmodules\u002Ftelemetry\u002F.navigation",{"path":106},{"path":110},{"path":114},{"path":375},"\u002Fdocs\u002Fmodules\u002Fqueue\u002F.navigation",{"path":122},{"path":126},{"path":130},{"path":133},{"path":381},"\u002Fdocs\u002Fmodules\u002Fscheduler\u002F.navigation",{"path":141},{"path":145},{"path":149},{"path":152},{"path":387},"\u002Fdocs\u002Fmodules\u002Ftesting\u002F.navigation",{"path":160},{"path":164},{"path":168},{"path":172},{"path":175},{"path":394},"\u002Fdocs\u002Fmodules\u002Fclient\u002F.navigation",{"path":183},{"path":186},{"path":189},{"path":399},"\u002Fdocs\u002Fmodules\u002Flogger\u002F.navigation",{"path":197},{"path":201},{"path":204},{"path":404},"\u002Fdocs\u002Fmodules\u002Fserverless\u002F.navigation",{"path":212},{"path":407},"\u002Fdocs\u002Fmodules\u002Fstorage\u002F.navigation",{"path":220},{"path":224},{"path":227},{"path":412},"\u002Fdocs\u002Fmodules\u002Fweb-sockets\u002F.navigation",{"path":235},{"path":238},{"path":416},"\u002Fdocs\u002Fmodules\u002Fschema\u002F.navigation",{"path":246},{"path":249},{"path":253},{"path":421},"\u002Fdocs\u002Fmodules\u002Fnitro\u002F.navigation",{"path":261},{"path":264},{"path":268},{"path":272},{"path":427},"\u002Fdocs\u002Fmodules\u002Fvite\u002F.navigation",{"path":280},{"path":430},"\u002Fdocs\u002Fmodules\u002Fcache\u002F.navigation",{"path":288},{"path":291},{"path":434},"\u002Fdocs\u002Fdeployment\u002F.navigation",{"path":300},{"path":304},{"path":308},{"path":312},{"path":316},{"path":441},"\u002Fdocs\u002Fadvanced\u002F.navigation",{"path":326},{"path":330},{"path":334},{"path":338},{"id":447,"title":148,"body":448,"description":2084,"extension":2085,"links":2086,"meta":2087,"navigation":68,"path":149,"seo":2088,"stem":150,"__hash__":2089},"docs\u002Fdocs\u002F3.modules\u002F13.scheduler\u002F2.clustering.md",{"type":449,"value":450,"toc":2065},"minimark",[451,456,460,501,504,509,539,543,610,613,620,730,741,744,755,831,837,841,854,858,865,891,894,898,901,1057,1064,1071,1075,1089,1099,1189,1203,1207,1221,1225,1234,1390,1416,1604,1608,1627,1697,1725,1793,1797,1807,1970,1981,1985,1992,2053,2061],[452,453,455],"h2",{"id":454},"how-an-occurrence-is-claimed","How an occurrence is claimed",[457,458,459],"p",{},"Every replica arms a timer for the same occurrence. When it fires, a cluster job goes through two claims in the store before it runs:",[461,462,463,480],"ol",{},[464,465,466,470,471,475,476,479],"li",{},[467,468,469],"strong",{},"The occurrence claim",", ",[472,473,474],"code",{},"occ:\u003Cjob>:\u003Cepoch ms>",". The first replica to create it runs the occurrence, the others record ",[472,477,478],{},"skipped-claimed",". It is never released: a replica whose clock is a few seconds behind, waking up after the winner already finished, still finds it and stays out.",[464,481,482,470,485,488,489,492,493,496,497,500],{},[467,483,484],{},"The overlap lock",[472,486,487],{},"active:\u003Cjob>",", only with ",[472,490,491],{},"overlap: 'skip'",". It is held for the whole run and extended by a heartbeat every third of ",[472,494,495],{},"lockTtl",". While it is held, the next occurrence is ",[472,498,499],{},"skipped-overlap"," on every replica.",[457,502,503],{},"Instance jobs skip both. Their overlap is checked locally.",[505,506,508],"h3",{"id":507},"what-the-guarantee-is","What the guarantee is",[457,510,511,512,515,516,519,520,523,524,527,528,531,532,535,536,538],{},"A cluster job runs ",[467,513,514],{},"at most once per occurrence"," in normal operation, and ",[467,517,518],{},"at least once"," as long as one replica is up when it is due (with ",[472,521,522],{},"misfire: 'run-once'"," covering restarts). The edge is a lost lock: when the heartbeat cannot extend the overlap lock, the run's ",[472,525,526],{},"ctx.signal"," is aborted with reason ",[472,529,530],{},"lock-lost",", because another replica may take over. Write handlers so that running twice does no harm, using ",[472,533,534],{},"ctx.occurrenceId"," as the idempotency key, and check ",[472,537,526],{}," between steps.",[452,540,542],{"id":541},"stores","Stores",[544,545,546,562],"table",{},[547,548,549],"thead",{},[550,551,552,556,559],"tr",{},[553,554,555],"th",{},"Store",[553,557,558],{},"Import",[553,560,561],{},"Distributed",[563,564,565,581,595],"tbody",{},[550,566,567,573,578],{},[568,569,570],"td",{},[472,571,572],{},"RedisStore",[568,574,575],{},[472,576,577],{},"@vercube\u002Fscheduler\u002Fstores\u002FRedisStore",[568,579,580],{},"yes",[550,582,583,588,593],{},[568,584,585],{},[472,586,587],{},"PostgresStore",[568,589,590],{},[472,591,592],{},"@vercube\u002Fscheduler\u002Fstores\u002FPostgresStore",[568,594,580],{},[550,596,597,602,607],{},[568,598,599],{},[472,600,601],{},"MemoryStore",[568,603,604],{},[472,605,606],{},"@vercube\u002Fscheduler\u002Fstores\u002FMemoryStore",[568,608,609],{},"no, one process only",[505,611,572],{"id":612},"redisstore",[457,614,615,616,619],{},"A claim is a key created with ",[472,617,618],{},"SET NX PX",". Extending and releasing go through scripts that check the owner first, so a replica can never touch a claim it lost. Every operation uses a single key, which keeps Redis Cluster working without hash tags.",[621,622,627],"pre",{"className":623,"code":624,"language":625,"meta":626,"style":626},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","store: defineSchedulerStore({\n  store: RedisStore,\n  initOptions: { url: process.env.REDIS_URL, prefix: 'myapp:scheduler' },\n}),\n","ts","",[472,628,629,653,668,719],{"__ignoreMap":626},[630,631,634,638,642,646,650],"span",{"class":632,"line":633},"line",1,[630,635,637],{"class":636},"sBMFI","store",[630,639,641],{"class":640},"sMK4o",":",[630,643,645],{"class":644},"s2Zo4"," defineSchedulerStore",[630,647,649],{"class":648},"sTEyZ","(",[630,651,652],{"class":640},"{\n",[630,654,656,660,662,665],{"class":632,"line":655},2,[630,657,659],{"class":658},"swJcz","  store",[630,661,641],{"class":640},[630,663,664],{"class":648}," RedisStore",[630,666,667],{"class":640},",\n",[630,669,671,674,676,679,682,684,687,690,693,695,698,701,704,706,709,713,716],{"class":632,"line":670},3,[630,672,673],{"class":658},"  initOptions",[630,675,641],{"class":640},[630,677,678],{"class":640}," {",[630,680,681],{"class":658}," url",[630,683,641],{"class":640},[630,685,686],{"class":648}," process",[630,688,689],{"class":640},".",[630,691,692],{"class":648},"env",[630,694,689],{"class":640},[630,696,697],{"class":648},"REDIS_URL",[630,699,700],{"class":640},",",[630,702,703],{"class":658}," prefix",[630,705,641],{"class":640},[630,707,708],{"class":640}," '",[630,710,712],{"class":711},"sfazB","myapp:scheduler",[630,714,715],{"class":640},"'",[630,717,718],{"class":640}," },\n",[630,720,722,725,728],{"class":632,"line":721},4,[630,723,724],{"class":640},"}",[630,726,727],{"class":648},")",[630,729,667],{"class":640},[457,731,732,733,736,737,740],{},"Pass ",[472,734,735],{},"client"," instead of ",[472,738,739],{},"url"," to share a connection you already have, for example the one BullMQ uses. The store never closes a client it was given.",[505,742,587],{"id":743},"postgresstore",[457,745,746,747,750,751,754],{},"Claims and state live in two tables, created on first start unless ",[472,748,749],{},"createTables: false",". Expiry is always computed with the database's ",[472,752,753],{},"now()",", so pods whose clocks drift apart still agree on when a claim expires. Claims that expired more than an hour ago are deleted every ten minutes.",[621,756,758],{"className":623,"code":757,"language":625,"meta":626,"style":626},"store: defineSchedulerStore({\n  store: PostgresStore,\n  initOptions: { connectionString: process.env.DATABASE_URL, schema: 'ops' },\n}),\n",[472,759,760,772,783,823],{"__ignoreMap":626},[630,761,762,764,766,768,770],{"class":632,"line":633},[630,763,637],{"class":636},[630,765,641],{"class":640},[630,767,645],{"class":644},[630,769,649],{"class":648},[630,771,652],{"class":640},[630,773,774,776,778,781],{"class":632,"line":655},[630,775,659],{"class":658},[630,777,641],{"class":640},[630,779,780],{"class":648}," PostgresStore",[630,782,667],{"class":640},[630,784,785,787,789,791,794,796,798,800,802,804,807,809,812,814,816,819,821],{"class":632,"line":670},[630,786,673],{"class":658},[630,788,641],{"class":640},[630,790,678],{"class":640},[630,792,793],{"class":658}," connectionString",[630,795,641],{"class":640},[630,797,686],{"class":648},[630,799,689],{"class":640},[630,801,692],{"class":648},[630,803,689],{"class":640},[630,805,806],{"class":648},"DATABASE_URL",[630,808,700],{"class":640},[630,810,811],{"class":658}," schema",[630,813,641],{"class":640},[630,815,708],{"class":640},[630,817,818],{"class":711},"ops",[630,820,715],{"class":640},[630,822,718],{"class":640},[630,824,825,827,829],{"class":632,"line":721},[630,826,724],{"class":640},[630,828,727],{"class":648},[630,830,667],{"class":640},[457,832,833,834,836],{},"Advisory locks were left out on purpose: they belong to a session, which breaks behind a connection pool. The table definitions are in the ",[472,835,587],{}," source for teams whose migrations own the schema.",[505,838,840],{"id":839},"memorystore-in-production","MemoryStore in production",[457,842,843,845,846,849,850,853],{},[472,844,601],{}," only coordinates the process it lives in. In development the scheduler logs one warning and carries on. With ",[472,847,848],{},"NODE_ENV=production",", cluster jobs on it refuse to start, since every replica would run them. Set ",[472,851,852],{},"allowLocalCluster: true"," only for a deployment that really is a single process.",[452,855,857],{"id":856},"misfires","Misfires",[457,859,860,861,864],{},"An occurrence is missed when no replica was up at the time, or when a timer fires later than ",[472,862,863],{},"misfireGrace"," because the event loop was blocked, the laptop slept or the pod was throttled.",[866,867,868,878],"ul",{},[464,869,870,873,874,877],{},[472,871,872],{},"misfire: 'skip'"," records it as ",[472,875,876],{},"missed"," and waits for the next one.",[464,879,880,882,883,886,887,890],{},[472,881,522],{}," runs the ",[467,884,885],{},"latest"," missed occurrence, with ",[472,888,889],{},"ctx.trigger === 'misfire'",". On start, each cluster job compares the store's record of its last run with its schedule and catches up on anything missed in the last 24 hours, through the normal claim, so twenty pods starting together run it once.",[457,892,893],{},"A job the store has no record of is never caught up on, so a new daily job does not run the moment it is first deployed.",[452,895,897],{"id":896},"kubernetes-and-multi-server-setups","Kubernetes and multi-server setups",[457,899,900],{},"Run as many replicas as you like with the same image and the same store. Nothing needs to know about the others.",[621,902,906],{"className":903,"code":904,"language":905,"meta":626,"style":626},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: api\nspec:\n  replicas: 4\n  template:\n    spec:\n      terminationGracePeriodSeconds: 30\n      containers:\n        - name: api\n          image: registry.example.com\u002Fapi:1.4.0\n          env:\n            - name: REDIS_URL\n              value: redis:\u002F\u002Fredis:6379\n","yaml",[472,907,908,918,928,936,946,954,966,974,982,993,1001,1014,1025,1033,1046],{"__ignoreMap":626},[630,909,910,913,915],{"class":632,"line":633},[630,911,912],{"class":658},"apiVersion",[630,914,641],{"class":640},[630,916,917],{"class":711}," apps\u002Fv1\n",[630,919,920,923,925],{"class":632,"line":655},[630,921,922],{"class":658},"kind",[630,924,641],{"class":640},[630,926,927],{"class":711}," Deployment\n",[630,929,930,933],{"class":632,"line":670},[630,931,932],{"class":658},"metadata",[630,934,935],{"class":640},":\n",[630,937,938,941,943],{"class":632,"line":721},[630,939,940],{"class":658},"  name",[630,942,641],{"class":640},[630,944,945],{"class":711}," api\n",[630,947,949,952],{"class":632,"line":948},5,[630,950,951],{"class":658},"spec",[630,953,935],{"class":640},[630,955,957,960,962],{"class":632,"line":956},6,[630,958,959],{"class":658},"  replicas",[630,961,641],{"class":640},[630,963,965],{"class":964},"sbssI"," 4\n",[630,967,969,972],{"class":632,"line":968},7,[630,970,971],{"class":658},"  template",[630,973,935],{"class":640},[630,975,977,980],{"class":632,"line":976},8,[630,978,979],{"class":658},"    spec",[630,981,935],{"class":640},[630,983,985,988,990],{"class":632,"line":984},9,[630,986,987],{"class":658},"      terminationGracePeriodSeconds",[630,989,641],{"class":640},[630,991,992],{"class":964}," 30\n",[630,994,996,999],{"class":632,"line":995},10,[630,997,998],{"class":658},"      containers",[630,1000,935],{"class":640},[630,1002,1004,1007,1010,1012],{"class":632,"line":1003},11,[630,1005,1006],{"class":640},"        -",[630,1008,1009],{"class":658}," name",[630,1011,641],{"class":640},[630,1013,945],{"class":711},[630,1015,1017,1020,1022],{"class":632,"line":1016},12,[630,1018,1019],{"class":658},"          image",[630,1021,641],{"class":640},[630,1023,1024],{"class":711}," registry.example.com\u002Fapi:1.4.0\n",[630,1026,1028,1031],{"class":632,"line":1027},13,[630,1029,1030],{"class":658},"          env",[630,1032,935],{"class":640},[630,1034,1036,1039,1041,1043],{"class":632,"line":1035},14,[630,1037,1038],{"class":640},"            -",[630,1040,1009],{"class":658},[630,1042,641],{"class":640},[630,1044,1045],{"class":711}," REDIS_URL\n",[630,1047,1049,1052,1054],{"class":632,"line":1048},15,[630,1050,1051],{"class":658},"              value",[630,1053,641],{"class":640},[630,1055,1056],{"class":711}," redis:\u002F\u002Fredis:6379\n",[457,1058,1059,1060,1063],{},"The replica id defaults to ",[472,1061,1062],{},"HOSTNAME",", which Kubernetes sets to the pod name, followed by the process id and a random suffix. It shows up in the store, the devtools and every run event, so you can tell which pod ran what.",[457,1065,1066,1067,1070],{},"To keep scheduled work off the pods that serve traffic, set ",[472,1068,1069],{},"autoStart: false"," on the web deployment and run a second deployment of the same image with it on.",[452,1072,1074],{"id":1073},"graceful-shutdown","Graceful shutdown",[457,1076,1077,1080,1081,1084,1085,1088],{},[472,1078,1079],{},"stop()"," stops arming timers, waits for runs in flight, and once ",[472,1082,1083],{},"shutdownTimeout"," is reached aborts them with reason ",[472,1086,1087],{},"shutdown",". Overlap locks are released, occurrence claims stay so no other replica runs those occurrences again.",[457,1090,1091,1094,1095,1098],{},[472,1092,1093],{},"SchedulerPlugin"," closes the scheduler with the application, so turning on signals in the core config is enough: on ",[472,1096,1097],{},"SIGTERM"," the HTTP server stops first, then the scheduler drains, then the process exits.",[621,1100,1103],{"className":623,"code":1101,"filename":1102,"language":625,"meta":626,"style":626},"export default defineConfig({\n  shutdown: { handleSignals: true },\n  plugins: [[SchedulerPlugin, { store, shutdownTimeout: '25s' }]],\n});\n","vercube.config.ts",[472,1104,1105,1121,1141,1180],{"__ignoreMap":626},[630,1106,1107,1111,1114,1117,1119],{"class":632,"line":633},[630,1108,1110],{"class":1109},"s7zQu","export",[630,1112,1113],{"class":1109}," default",[630,1115,1116],{"class":644}," defineConfig",[630,1118,649],{"class":648},[630,1120,652],{"class":640},[630,1122,1123,1126,1128,1130,1133,1135,1139],{"class":632,"line":655},[630,1124,1125],{"class":658},"  shutdown",[630,1127,641],{"class":640},[630,1129,678],{"class":640},[630,1131,1132],{"class":658}," handleSignals",[630,1134,641],{"class":640},[630,1136,1138],{"class":1137},"sfNiH"," true",[630,1140,718],{"class":640},[630,1142,1143,1146,1148,1151,1153,1155,1158,1160,1163,1165,1167,1170,1172,1175,1178],{"class":632,"line":670},[630,1144,1145],{"class":658},"  plugins",[630,1147,641],{"class":640},[630,1149,1150],{"class":648}," [[SchedulerPlugin",[630,1152,700],{"class":640},[630,1154,678],{"class":640},[630,1156,1157],{"class":648}," store",[630,1159,700],{"class":640},[630,1161,1162],{"class":658}," shutdownTimeout",[630,1164,641],{"class":640},[630,1166,708],{"class":640},[630,1168,1169],{"class":711},"25s",[630,1171,715],{"class":640},[630,1173,1174],{"class":640}," }",[630,1176,1177],{"class":648},"]]",[630,1179,667],{"class":640},[630,1181,1182,1184,1186],{"class":632,"line":721},[630,1183,724],{"class":640},[630,1185,727],{"class":648},[630,1187,1188],{"class":640},";\n",[457,1190,1191,1192,1195,1196,1198,1199,1202],{},"The scheduler can also stop on its own signals with ",[472,1193,1194],{},"handleSignals: true"," in its options. It never exits the process then, other parts of the app may still be closing. Either way, keep ",[472,1197,1083],{}," below the pod's ",[472,1200,1201],{},"terminationGracePeriodSeconds"," (30 seconds by default), or Kubernetes kills the process before the runs were aborted cleanly.",[452,1204,1206],{"id":1205},"external-triggers","External triggers",[457,1208,1209,1210,1213,1214,1217,1218,1220],{},"On platforms without a long-lived process, or when you prefer Kubernetes ",[472,1211,1212],{},"CronJob","s, an external scheduler can fire the jobs instead. Set ",[472,1215,1216],{},"external: true"," on jobs that should never run from an in-process timer. Both triggers below claim the occurrence the job's schedule is at, so a ",[472,1219,1212],{}," that fires twice for 03:00 still runs the job once.",[505,1222,1224],{"id":1223},"from-the-cli","From the CLI",[457,1226,1227,1228,1230,1231,1233],{},"With ",[472,1229,1093],{}," listed in ",[472,1232,1102],{},", the CLI gets two commands. They import the built app without starting its server or its timers.",[621,1235,1239],{"className":1236,"code":1237,"language":1238,"meta":626,"style":626},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","$ vercube scheduler list\nNAME               SCHEDULE   TIMEZONE       SCOPE    TARGET  NEXT RUNS\nbilling.close-day  0 3 * * *  Europe\u002FWarsaw  cluster  inline  2026-01-02T02:00:00.000Z\n\n$ vercube scheduler run billing.close-day\n{\n  \"name\": \"billing.close-day\",\n  \"occurrenceId\": \"billing.close-day@2026-01-01T02:00:00.000Z\",\n  \"outcome\": \"completed\",\n  ...\n}\n","bash",[472,1240,1241,1255,1278,1308,1313,1327,1331,1348,1364,1380,1385],{"__ignoreMap":626},[630,1242,1243,1246,1249,1252],{"class":632,"line":633},[630,1244,1245],{"class":636},"$",[630,1247,1248],{"class":711}," vercube",[630,1250,1251],{"class":711}," scheduler",[630,1253,1254],{"class":711}," list\n",[630,1256,1257,1260,1263,1266,1269,1272,1275],{"class":632,"line":655},[630,1258,1259],{"class":636},"NAME",[630,1261,1262],{"class":711},"               SCHEDULE",[630,1264,1265],{"class":711},"   TIMEZONE",[630,1267,1268],{"class":711},"       SCOPE",[630,1270,1271],{"class":711},"    TARGET",[630,1273,1274],{"class":711},"  NEXT",[630,1276,1277],{"class":711}," RUNS\n",[630,1279,1280,1283,1286,1289,1292,1294,1296,1299,1302,1305],{"class":632,"line":670},[630,1281,1282],{"class":636},"billing.close-day",[630,1284,1285],{"class":964},"  0",[630,1287,1288],{"class":964}," 3",[630,1290,1291],{"class":648}," *",[630,1293,1291],{"class":648},[630,1295,1291],{"class":648},[630,1297,1298],{"class":711},"  Europe\u002FWarsaw",[630,1300,1301],{"class":711},"  cluster",[630,1303,1304],{"class":711},"  inline",[630,1306,1307],{"class":711},"  2026-01-02T02:00:00.000Z\n",[630,1309,1310],{"class":632,"line":721},[630,1311,1312],{"emptyLinePlaceholder":68},"\n",[630,1314,1315,1317,1319,1321,1324],{"class":632,"line":948},[630,1316,1245],{"class":636},[630,1318,1248],{"class":711},[630,1320,1251],{"class":711},[630,1322,1323],{"class":711}," run",[630,1325,1326],{"class":711}," billing.close-day\n",[630,1328,1329],{"class":632,"line":956},[630,1330,652],{"class":640},[630,1332,1333,1336,1338,1341,1343,1346],{"class":632,"line":968},[630,1334,1335],{"class":636},"  \"name\"",[630,1337,641],{"class":644},[630,1339,1340],{"class":640}," \"",[630,1342,1282],{"class":711},[630,1344,1345],{"class":640},"\"",[630,1347,667],{"class":711},[630,1349,1350,1353,1355,1357,1360,1362],{"class":632,"line":976},[630,1351,1352],{"class":636},"  \"occurrenceId\"",[630,1354,641],{"class":644},[630,1356,1340],{"class":640},[630,1358,1359],{"class":711},"billing.close-day@2026-01-01T02:00:00.000Z",[630,1361,1345],{"class":640},[630,1363,667],{"class":711},[630,1365,1366,1369,1371,1373,1376,1378],{"class":632,"line":984},[630,1367,1368],{"class":636},"  \"outcome\"",[630,1370,641],{"class":644},[630,1372,1340],{"class":640},[630,1374,1375],{"class":711},"completed",[630,1377,1345],{"class":640},[630,1379,667],{"class":711},[630,1381,1382],{"class":632,"line":995},[630,1383,1384],{"class":644},"  ...\n",[630,1386,1387],{"class":632,"line":1003},[630,1388,1389],{"class":640},"}\n",[457,1391,1392,1395,1396,1399,1400,1403,1404,1407,1408,1411,1412,1415],{},[472,1393,1394],{},"run"," exits with ",[472,1397,1398],{},"0"," when the job completed, was enqueued, or was skipped because another replica has it, and with ",[472,1401,1402],{},"1"," when it failed. ",[472,1405,1406],{},"--force"," skips the occurrence claim, ",[472,1409,1410],{},"--entry"," points at another built entry, ",[472,1413,1414],{},"--build"," builds the app first.",[621,1417,1419],{"className":903,"code":1418,"language":905,"meta":626,"style":626},"apiVersion: batch\u002Fv1\nkind: CronJob\nmetadata:\n  name: billing-close-day\nspec:\n  schedule: '0 3 * * *'\n  timeZone: Europe\u002FWarsaw\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          restartPolicy: Never\n          containers:\n            - name: run\n              image: registry.example.com\u002Fapi:1.4.0\n              command: ['npx', 'vercube', 'scheduler', 'run', 'billing.close-day']\n",[472,1420,1421,1430,1439,1445,1454,1460,1475,1485,1492,1498,1505,1512,1522,1529,1540,1549],{"__ignoreMap":626},[630,1422,1423,1425,1427],{"class":632,"line":633},[630,1424,912],{"class":658},[630,1426,641],{"class":640},[630,1428,1429],{"class":711}," batch\u002Fv1\n",[630,1431,1432,1434,1436],{"class":632,"line":655},[630,1433,922],{"class":658},[630,1435,641],{"class":640},[630,1437,1438],{"class":711}," CronJob\n",[630,1440,1441,1443],{"class":632,"line":670},[630,1442,932],{"class":658},[630,1444,935],{"class":640},[630,1446,1447,1449,1451],{"class":632,"line":721},[630,1448,940],{"class":658},[630,1450,641],{"class":640},[630,1452,1453],{"class":711}," billing-close-day\n",[630,1455,1456,1458],{"class":632,"line":948},[630,1457,951],{"class":658},[630,1459,935],{"class":640},[630,1461,1462,1465,1467,1469,1472],{"class":632,"line":956},[630,1463,1464],{"class":658},"  schedule",[630,1466,641],{"class":640},[630,1468,708],{"class":640},[630,1470,1471],{"class":711},"0 3 * * *",[630,1473,1474],{"class":640},"'\n",[630,1476,1477,1480,1482],{"class":632,"line":968},[630,1478,1479],{"class":658},"  timeZone",[630,1481,641],{"class":640},[630,1483,1484],{"class":711}," Europe\u002FWarsaw\n",[630,1486,1487,1490],{"class":632,"line":976},[630,1488,1489],{"class":658},"  jobTemplate",[630,1491,935],{"class":640},[630,1493,1494,1496],{"class":632,"line":984},[630,1495,979],{"class":658},[630,1497,935],{"class":640},[630,1499,1500,1503],{"class":632,"line":995},[630,1501,1502],{"class":658},"      template",[630,1504,935],{"class":640},[630,1506,1507,1510],{"class":632,"line":1003},[630,1508,1509],{"class":658},"        spec",[630,1511,935],{"class":640},[630,1513,1514,1517,1519],{"class":632,"line":1016},[630,1515,1516],{"class":658},"          restartPolicy",[630,1518,641],{"class":640},[630,1520,1521],{"class":711}," Never\n",[630,1523,1524,1527],{"class":632,"line":1027},[630,1525,1526],{"class":658},"          containers",[630,1528,935],{"class":640},[630,1530,1531,1533,1535,1537],{"class":632,"line":1035},[630,1532,1038],{"class":640},[630,1534,1009],{"class":658},[630,1536,641],{"class":640},[630,1538,1539],{"class":711}," run\n",[630,1541,1542,1545,1547],{"class":632,"line":1048},[630,1543,1544],{"class":658},"              image",[630,1546,641],{"class":640},[630,1548,1024],{"class":711},[630,1550,1552,1555,1557,1560,1562,1565,1567,1569,1571,1574,1576,1578,1580,1583,1585,1587,1589,1591,1593,1595,1597,1599,1601],{"class":632,"line":1551},16,[630,1553,1554],{"class":658},"              command",[630,1556,641],{"class":640},[630,1558,1559],{"class":640}," [",[630,1561,715],{"class":640},[630,1563,1564],{"class":711},"npx",[630,1566,715],{"class":640},[630,1568,700],{"class":640},[630,1570,708],{"class":640},[630,1572,1573],{"class":711},"vercube",[630,1575,715],{"class":640},[630,1577,700],{"class":640},[630,1579,708],{"class":640},[630,1581,1582],{"class":711},"scheduler",[630,1584,715],{"class":640},[630,1586,700],{"class":640},[630,1588,708],{"class":640},[630,1590,1394],{"class":711},[630,1592,715],{"class":640},[630,1594,700],{"class":640},[630,1596,708],{"class":640},[630,1598,1282],{"class":711},[630,1600,715],{"class":640},[630,1602,1603],{"class":640},"]\n",[505,1605,1607],{"id":1606},"over-http","Over HTTP",[457,1609,1610,1611,1614,1615,1618,1619,1622,1623,1626],{},"The ",[472,1612,1613],{},"http"," option mounts ",[472,1616,1617],{},"GET"," and ",[472,1620,1621],{},"POST"," ",[472,1624,1625],{},"\u003Cpath>\u002Frun\u002F\u003Cjob>",". It refuses to start without a secret, and every request has to send it as a bearer token.",[621,1628,1630],{"className":623,"code":1629,"language":625,"meta":626,"style":626},"[\n  SchedulerPlugin,\n  { store, autoStart: false, http: { secret: process.env.CRON_SECRET } },\n];\n",[472,1631,1632,1637,1644,1690],{"__ignoreMap":626},[630,1633,1634],{"class":632,"line":633},[630,1635,1636],{"class":648},"[\n",[630,1638,1639,1642],{"class":632,"line":655},[630,1640,1641],{"class":648},"  SchedulerPlugin",[630,1643,667],{"class":640},[630,1645,1646,1649,1651,1653,1656,1658,1661,1663,1666,1668,1670,1673,1675,1677,1679,1681,1683,1686,1688],{"class":632,"line":670},[630,1647,1648],{"class":640},"  {",[630,1650,1157],{"class":648},[630,1652,700],{"class":640},[630,1654,1655],{"class":658}," autoStart",[630,1657,641],{"class":640},[630,1659,1660],{"class":1137}," false",[630,1662,700],{"class":640},[630,1664,1665],{"class":658}," http",[630,1667,641],{"class":640},[630,1669,678],{"class":640},[630,1671,1672],{"class":658}," secret",[630,1674,641],{"class":640},[630,1676,686],{"class":648},[630,1678,689],{"class":640},[630,1680,692],{"class":648},[630,1682,689],{"class":640},[630,1684,1685],{"class":648},"CRON_SECRET ",[630,1687,724],{"class":640},[630,1689,718],{"class":640},[630,1691,1692,1695],{"class":632,"line":721},[630,1693,1694],{"class":648},"]",[630,1696,1188],{"class":640},[457,1698,1699,1700,1703,1704,470,1707,1710,1711,1714,1715,1718,1719,1703,1721,1724],{},"The response is sent once the job finished, because a serverless function stops as soon as it has answered: ",[472,1701,1702],{},"200"," with ",[472,1705,1706],{},"{ outcome, occurrenceId, durationMs }",[472,1708,1709],{},"401"," for a wrong secret, ",[472,1712,1713],{},"404"," for an unknown job, ",[472,1716,1717],{},"500"," when it failed. Vercel Cron sends ",[472,1720,1617],{},[472,1722,1723],{},"Authorization: Bearer $CRON_SECRET",", so this is all it needs:",[621,1726,1731],{"className":1727,"code":1728,"filename":1729,"language":1730,"meta":626,"style":626},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"crons\": [{ \"path\": \"\u002F_scheduler\u002Frun\u002Fbilling.close-day\", \"schedule\": \"0 3 * * *\" }]\n}\n","vercel.json","json",[472,1732,1733,1737,1789],{"__ignoreMap":626},[630,1734,1735],{"class":632,"line":633},[630,1736,652],{"class":640},[630,1738,1739,1742,1746,1748,1750,1753,1755,1758,1760,1762,1764,1767,1769,1771,1773,1776,1778,1780,1782,1784,1786],{"class":632,"line":655},[630,1740,1741],{"class":640},"  \"",[630,1743,1745],{"class":1744},"spNyl","crons",[630,1747,1345],{"class":640},[630,1749,641],{"class":640},[630,1751,1752],{"class":640}," [{",[630,1754,1340],{"class":640},[630,1756,1757],{"class":636},"path",[630,1759,1345],{"class":640},[630,1761,641],{"class":640},[630,1763,1340],{"class":640},[630,1765,1766],{"class":711},"\u002F_scheduler\u002Frun\u002Fbilling.close-day",[630,1768,1345],{"class":640},[630,1770,700],{"class":640},[630,1772,1340],{"class":640},[630,1774,1775],{"class":636},"schedule",[630,1777,1345],{"class":640},[630,1779,641],{"class":640},[630,1781,1340],{"class":640},[630,1783,1471],{"class":711},[630,1785,1345],{"class":640},[630,1787,1788],{"class":640}," }]\n",[630,1790,1791],{"class":632,"line":670},[630,1792,1389],{"class":640},[452,1794,1796],{"id":1795},"queue-targets","Queue targets",[457,1798,1799,1800,1806],{},"For heavy work, or work that needs retries with backoff, hand the occurrence to ",[1801,1802,1803],"a",{"href":122},[472,1804,1805],{},"@vercube\u002Fqueue",". The replica that claims the occurrence only enqueues it, and whichever worker consumes the queue runs the method.",[621,1808,1810],{"className":623,"code":1809,"language":625,"meta":626,"style":626},"@Cron('0 * * * *', {\n  name: 'reports.build',\n  target: {\n    queue: 'reports',\n    attempts: 5,\n    backoff: { type: 'exponential', delay: 1000 },\n  },\n})\npublic async build(ctx: SchedulerTypes.JobContext): Promise\u003Cvoid> {\n  \u002F\u002F runs on a worker, ctx.trigger is 'queue' and ctx.attempt counts the queue's\n  \u002F\u002F attempts\n}\n",[472,1811,1812,1834,1849,1858,1874,1886,1919,1924,1931,1955,1961,1966],{"__ignoreMap":626},[630,1813,1814,1817,1820,1822,1824,1827,1829,1831],{"class":632,"line":633},[630,1815,1816],{"class":640},"@",[630,1818,1819],{"class":644},"Cron",[630,1821,649],{"class":648},[630,1823,715],{"class":640},[630,1825,1826],{"class":711},"0 * * * *",[630,1828,715],{"class":640},[630,1830,700],{"class":640},[630,1832,1833],{"class":640}," {\n",[630,1835,1836,1838,1840,1842,1845,1847],{"class":632,"line":655},[630,1837,940],{"class":658},[630,1839,641],{"class":640},[630,1841,708],{"class":640},[630,1843,1844],{"class":711},"reports.build",[630,1846,715],{"class":640},[630,1848,667],{"class":640},[630,1850,1851,1854,1856],{"class":632,"line":670},[630,1852,1853],{"class":658},"  target",[630,1855,641],{"class":640},[630,1857,1833],{"class":640},[630,1859,1860,1863,1865,1867,1870,1872],{"class":632,"line":721},[630,1861,1862],{"class":658},"    queue",[630,1864,641],{"class":640},[630,1866,708],{"class":640},[630,1868,1869],{"class":711},"reports",[630,1871,715],{"class":640},[630,1873,667],{"class":640},[630,1875,1876,1879,1881,1884],{"class":632,"line":948},[630,1877,1878],{"class":658},"    attempts",[630,1880,641],{"class":640},[630,1882,1883],{"class":964}," 5",[630,1885,667],{"class":640},[630,1887,1888,1891,1893,1895,1898,1900,1902,1905,1907,1909,1912,1914,1917],{"class":632,"line":956},[630,1889,1890],{"class":658},"    backoff",[630,1892,641],{"class":640},[630,1894,678],{"class":640},[630,1896,1897],{"class":658}," type",[630,1899,641],{"class":640},[630,1901,708],{"class":640},[630,1903,1904],{"class":711},"exponential",[630,1906,715],{"class":640},[630,1908,700],{"class":640},[630,1910,1911],{"class":658}," delay",[630,1913,641],{"class":640},[630,1915,1916],{"class":964}," 1000",[630,1918,718],{"class":640},[630,1920,1921],{"class":632,"line":968},[630,1922,1923],{"class":640},"  },\n",[630,1925,1926,1928],{"class":632,"line":976},[630,1927,724],{"class":640},[630,1929,1930],{"class":648},")\n",[630,1932,1933,1936,1939,1942,1944,1947,1950,1953],{"class":632,"line":984},[630,1934,1935],{"class":648},"public async ",[630,1937,1938],{"class":644},"build",[630,1940,1941],{"class":648},"(ctx: SchedulerTypes",[630,1943,689],{"class":640},[630,1945,1946],{"class":648},"JobContext): ",[630,1948,1949],{"class":636},"Promise",[630,1951,1952],{"class":640},"\u003Cvoid>",[630,1954,1833],{"class":640},[630,1956,1957],{"class":632,"line":995},[630,1958,1960],{"class":1959},"sHwdD","  \u002F\u002F runs on a worker, ctx.trigger is 'queue' and ctx.attempt counts the queue's\n",[630,1962,1963],{"class":632,"line":1003},[630,1964,1965],{"class":1959},"  \u002F\u002F attempts\n",[630,1967,1968],{"class":632,"line":1016},[630,1969,1389],{"class":640},[457,1971,1972,1973,1976,1977,1980],{},"The occurrence is still claimed before it is enqueued: only some transports deduplicate on the job id. Overlap is enforced on the worker, with the same lock as an inline run. A run that fails is rethrown to the queue, so its attempts and backoff apply, and the job's own ",[472,1974,1975],{},"retries"," are ignored. The scheduler and ",[472,1978,1979],{},"QueuePlugin"," both have to be registered, and a queue target needs cluster scope.",[452,1982,1984],{"id":1983},"observability","Observability",[457,1986,1987,1988,1991],{},"Every run is a ",[472,1989,1990],{},"scheduler.run \u003Cjob>"," span carrying the job, scope, trigger, occurrence, replica, attempt and outcome. A queue target's enqueue runs inside that span, so the worker's run lands in the same trace. Three metrics come with it:",[544,1993,1994,2007],{},[547,1995,1996],{},[550,1997,1998,2001,2004],{},[553,1999,2000],{},"Metric",[553,2002,2003],{},"Attributes",[553,2005,2006],{},"What it shows",[563,2008,2009,2028,2040],{},[550,2010,2011,2016,2019],{},[568,2012,2013],{},[472,2014,2015],{},"vercube.scheduler.runs",[568,2017,2018],{},"job, outcome",[568,2020,2021,2022,2024,2025,2027],{},"Occurrences by outcome, skips included. One ",[472,2023,1375],{}," and N-1 ",[472,2026,478],{}," per occurrence is what proves exactly one replica ran it.",[550,2029,2030,2035,2037],{},[568,2031,2032],{},[472,2033,2034],{},"vercube.scheduler.duration",[568,2036,2018],{},[568,2038,2039],{},"How long runs took, in milliseconds.",[550,2041,2042,2047,2050],{},[568,2043,2044],{},[472,2045,2046],{},"vercube.scheduler.lag",[568,2048,2049],{},"job",[568,2051,2052],{},"How late runs started after their occurrence, in milliseconds.",[457,2054,1227,2055,2060],{},[1801,2056,2057],{"href":95},[472,2058,2059],{},"@vercube\u002Fdevtools"," enabled, the Scheduler panel lists every job with its next run, its last run anywhere in the cluster and the replica that ran it, and runs a job on demand.",[2062,2063,2064],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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}",{"title":626,"searchDepth":633,"depth":655,"links":2066},[2067,2070,2075,2076,2077,2078,2082,2083],{"id":454,"depth":655,"text":455,"children":2068},[2069],{"id":507,"depth":670,"text":508},{"id":541,"depth":655,"text":542,"children":2071},[2072,2073,2074],{"id":612,"depth":670,"text":572},{"id":743,"depth":670,"text":587},{"id":839,"depth":670,"text":840},{"id":856,"depth":655,"text":857},{"id":896,"depth":655,"text":897},{"id":1073,"depth":655,"text":1074},{"id":1205,"depth":655,"text":1206,"children":2079},[2080,2081],{"id":1223,"depth":670,"text":1224},{"id":1606,"depth":670,"text":1607},{"id":1795,"depth":655,"text":1796},{"id":1983,"depth":655,"text":1984},"How the scheduler runs each occurrence once across replicas, the stores, Kubernetes, graceful shutdown, external triggers and queue targets","md",null,{},{"title":148,"description":2084},"rBl9wgyrXb7zofzEx7xJfsWUNM2MgTh_hEmCk3Iuh6s",[2091,2093],{"title":144,"path":145,"stem":146,"description":2092,"children":-1},"Declaring scheduled jobs with @Cron and @Every, their options, the job context, time zones and daylight saving time",{"title":109,"path":152,"stem":153,"description":2094,"children":-1},"Reference for SchedulerPlugin, SchedulerManager, the stores, the CLI, testing helpers and errors",1794396677117]