[{"data":1,"prerenderedAt":1324},["ShallowReactive",2],{"navigation":3,"docs-path-index":340,"-blog-announcing-1-5":446,"-blog-announcing-1-5-surround":1318},[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":448,"author":449,"body":455,"category":1307,"date":1308,"description":1309,"extension":1310,"image":1311,"meta":1312,"minRead":923,"navigation":68,"path":1313,"seo":1314,"shortImage":1311,"stem":1316,"__hash__":1317},"blog\u002Fblog\u002F5.announcing-1.5.md","Announcing Vercube 1.5",{"name":450,"avatar":451,"to":454,"twitter":450},"OskarLebuda",{"src":452,"alt":453},"https:\u002F\u002Fgithub.com\u002FOskarLebuda.png","Oskar Lebuda","https:\u002F\u002Fgithub.com\u002FOskarLebuda",{"type":456,"value":457,"toc":1299},"minimark",[458,466,474,482,487,511,661,679,686,690,696,792,927,934,941,945,981,999,1006,1010,1055,1068,1081,1085,1088,1147,1156,1160,1167,1199,1202,1278,1281,1295],[459,460,461,465],"p",{},[462,463,464],"strong",{},"Vercube 1.5 is out."," The framework gets a typed client, typed errors, health probes, a project generator with modules and a security pass. The site was redesigned for the release too: homepage, docs, blog and changelog. Drag the divider to compare the old homepage with the new one.",[467,468],"before-after",{":after-label":469,":before-label":470,"after":471,"alt":472,"before":473},"1.5","1.4","\u002Fimages\u002Fblog\u002Fhome-1-5.webp","The vercube.dev home page","\u002Fimages\u002Fblog\u002Fhome-1-4.webp",[459,475,476,477,481],{},"Search is in the header, the homepage opens on ",[478,479,480],"code",{},"create-vercube"," running instead of a code sample, and the diagrams in the docs were redrawn to match. The content itself is unchanged.",[483,484,486],"h2",{"id":485},"a-client-typed-by-your-controllers","A client typed by your controllers",[459,488,489,498,499,502,503,506,507,510],{},[490,491,495],"a",{"href":492,"rel":493},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@vercube\u002Fclient",[494],"nofollow",[478,496,497],{},"@vercube\u002Fclient"," is a ",[478,500,501],{},"fetch"," wrapper that takes its types from your controllers, so there is no separate API description to keep in sync. The response is what the handler returns, the body is what ",[478,504,505],{},"@Body()"," accepts, and the path parameters are the ",[478,508,509],{},":segments"," of the route.",[512,513,518],"pre",{"className":514,"code":515,"language":516,"meta":517,"style":517},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const api = createClient\u003CVercubeRoutes>({ baseURL: 'http:\u002F\u002Flocalhost:3000' });\n\nconst user = await api.get('\u002Fusers\u002F:id', { params: { id: '42' } });\n\u002F\u002F    ^? { id: string; name: string; createdAt: string }\n","ts","",[478,519,520,583,589,654],{"__ignoreMap":517},[521,522,525,529,533,537,541,544,548,551,554,557,561,564,567,571,574,577,580],"span",{"class":523,"line":524},"line",1,[521,526,528],{"class":527},"spNyl","const",[521,530,532],{"class":531},"sTEyZ"," api ",[521,534,536],{"class":535},"sMK4o","=",[521,538,540],{"class":539},"s2Zo4"," createClient",[521,542,543],{"class":535},"\u003C",[521,545,547],{"class":546},"sBMFI","VercubeRoutes",[521,549,550],{"class":535},">",[521,552,553],{"class":531},"(",[521,555,556],{"class":535},"{",[521,558,560],{"class":559},"swJcz"," baseURL",[521,562,563],{"class":535},":",[521,565,566],{"class":535}," '",[521,568,570],{"class":569},"sfazB","http:\u002F\u002Flocalhost:3000",[521,572,573],{"class":535},"'",[521,575,576],{"class":535}," }",[521,578,579],{"class":531},")",[521,581,582],{"class":535},";\n",[521,584,586],{"class":523,"line":585},2,[521,587,588],{"emptyLinePlaceholder":68},"\n",[521,590,592,594,597,599,603,606,609,612,614,616,619,621,624,627,630,632,634,637,639,641,644,646,648,650,652],{"class":523,"line":591},3,[521,593,528],{"class":527},[521,595,596],{"class":531}," user ",[521,598,536],{"class":535},[521,600,602],{"class":601},"s7zQu"," await",[521,604,605],{"class":531}," api",[521,607,608],{"class":535},".",[521,610,611],{"class":539},"get",[521,613,553],{"class":531},[521,615,573],{"class":535},[521,617,618],{"class":569},"\u002Fusers\u002F:id",[521,620,573],{"class":535},[521,622,623],{"class":535},",",[521,625,626],{"class":535}," {",[521,628,629],{"class":559}," params",[521,631,563],{"class":535},[521,633,626],{"class":535},[521,635,636],{"class":559}," id",[521,638,563],{"class":535},[521,640,566],{"class":535},[521,642,643],{"class":569},"42",[521,645,573],{"class":535},[521,647,576],{"class":535},[521,649,576],{"class":535},[521,651,579],{"class":531},[521,653,582],{"class":535},[521,655,657],{"class":523,"line":656},4,[521,658,660],{"class":659},"sHwdD","\u002F\u002F    ^? { id: string; name: string; createdAt: string }\n",[459,662,663,664,667,668,671,672,675,676,608],{},"Rename a field in a handler and every call that no longer fits stops compiling. The route map it relies on is generated from the running app by ",[478,665,666],{},"vercube dev",", ",[478,669,670],{},"vercube build"," and the Vite plugin, and holds no types of its own, so it is small enough to commit and review. About 1.5 kB gzipped, and it also powers ",[478,673,674],{},"app.client"," in ",[478,677,678],{},"@vercube\u002Ftesting",[459,680,681,682,608],{},"Read more: ",[462,683,684],{},[490,685,178],{"href":183},[483,687,689],{"id":688},"typed-errors","Typed errors",[459,691,692,693,563],{},"Declare what a route can answer with, and the caller gets it as a union instead of ",[478,694,695],{},"catch (e: unknown)",[512,697,699],{"className":514,"code":698,"language":516,"meta":517,"style":517},"@Put('\u002F:id\u002Femail')\n@Throws(NotFoundError, ConflictError)\npublic changeEmail(@Param('id') id: string, @Body() body: { email: string }) {}\n",[478,700,701,721,736],{"__ignoreMap":517},[521,702,703,706,709,711,713,716,718],{"class":523,"line":524},[521,704,705],{"class":535},"@",[521,707,708],{"class":539},"Put",[521,710,553],{"class":531},[521,712,573],{"class":535},[521,714,715],{"class":569},"\u002F:id\u002Femail",[521,717,573],{"class":535},[521,719,720],{"class":531},")\n",[521,722,723,725,728,731,733],{"class":523,"line":585},[521,724,705],{"class":535},[521,726,727],{"class":539},"Throws",[521,729,730],{"class":531},"(NotFoundError",[521,732,623],{"class":535},[521,734,735],{"class":531}," ConflictError)\n",[521,737,738,741,744,747,750,752,754,757,759,762,764,767,770,773,775,778,780,783,786,789],{"class":523,"line":591},[521,739,740],{"class":531},"public ",[521,742,743],{"class":539},"changeEmail",[521,745,746],{"class":531},"(@",[521,748,749],{"class":539},"Param",[521,751,553],{"class":531},[521,753,573],{"class":535},[521,755,756],{"class":569},"id",[521,758,573],{"class":535},[521,760,761],{"class":531},") id: string",[521,763,623],{"class":535},[521,765,766],{"class":531}," @",[521,768,769],{"class":539},"Body",[521,771,772],{"class":531},"() body: ",[521,774,556],{"class":535},[521,776,777],{"class":559}," email",[521,779,563],{"class":535},[521,781,782],{"class":531}," string ",[521,784,785],{"class":535},"}",[521,787,788],{"class":531},") ",[521,790,791],{"class":535},"{}\n",[512,793,795],{"className":514,"code":794,"language":516,"meta":517,"style":517},"const res = await api.safe.put('\u002Fusers\u002F:id\u002Femail', { params: { id }, body });\n\nif (res.error?.status === 409) {\n  pickAnother(res.error.body.existingId); \u002F\u002F the ConflictError's own field\n}\n",[478,796,797,854,858,889,921],{"__ignoreMap":517},[521,798,799,801,804,806,808,810,812,815,817,820,822,824,827,829,831,833,835,837,839,842,845,848,850,852],{"class":523,"line":524},[521,800,528],{"class":527},[521,802,803],{"class":531}," res ",[521,805,536],{"class":535},[521,807,602],{"class":601},[521,809,605],{"class":531},[521,811,608],{"class":535},[521,813,814],{"class":531},"safe",[521,816,608],{"class":535},[521,818,819],{"class":539},"put",[521,821,553],{"class":531},[521,823,573],{"class":535},[521,825,826],{"class":569},"\u002Fusers\u002F:id\u002Femail",[521,828,573],{"class":535},[521,830,623],{"class":535},[521,832,626],{"class":535},[521,834,629],{"class":559},[521,836,563],{"class":535},[521,838,626],{"class":535},[521,840,841],{"class":531}," id ",[521,843,844],{"class":535},"},",[521,846,847],{"class":531}," body ",[521,849,785],{"class":535},[521,851,579],{"class":531},[521,853,582],{"class":535},[521,855,856],{"class":523,"line":585},[521,857,588],{"emptyLinePlaceholder":68},[521,859,860,863,866,868,871,874,877,880,884,886],{"class":523,"line":591},[521,861,862],{"class":601},"if",[521,864,865],{"class":531}," (res",[521,867,608],{"class":535},[521,869,870],{"class":531},"error",[521,872,873],{"class":535},"?.",[521,875,876],{"class":531},"status ",[521,878,879],{"class":535},"===",[521,881,883],{"class":882},"sbssI"," 409",[521,885,788],{"class":531},[521,887,888],{"class":535},"{\n",[521,890,891,894,896,899,901,903,905,908,910,913,915,918],{"class":523,"line":656},[521,892,893],{"class":539},"  pickAnother",[521,895,553],{"class":559},[521,897,898],{"class":531},"res",[521,900,608],{"class":535},[521,902,870],{"class":531},[521,904,608],{"class":535},[521,906,907],{"class":531},"body",[521,909,608],{"class":535},[521,911,912],{"class":531},"existingId",[521,914,579],{"class":559},[521,916,917],{"class":535},";",[521,919,920],{"class":659}," \u002F\u002F the ConflictError's own field\n",[521,922,924],{"class":523,"line":923},5,[521,925,926],{"class":535},"}\n",[459,928,929,930,933],{},"The same declarations land in the OpenAPI document that ",[478,931,932],{},"@vercube\u002Fschema"," generates.",[459,935,681,936,608],{},[462,937,938],{},[490,939,689],{"href":940},"\u002Fdocs\u002Fmodules\u002Fclient\u002Frequests#typed-errors",[483,942,944],{"id":943},"health-checks-and-rolling-deploys","Health checks and rolling deploys",[512,946,949],{"className":514,"code":947,"filename":948,"language":516,"meta":517,"style":517},"export default defineConfig({ health: true });\n","vercube.config.ts",[478,950,951],{"__ignoreMap":517},[521,952,953,956,959,962,964,966,969,971,975,977,979],{"class":523,"line":524},[521,954,955],{"class":601},"export",[521,957,958],{"class":601}," default",[521,960,961],{"class":539}," defineConfig",[521,963,553],{"class":531},[521,965,556],{"class":535},[521,967,968],{"class":559}," health",[521,970,563],{"class":535},[521,972,974],{"class":973},"sfNiH"," true",[521,976,576],{"class":535},[521,978,579],{"class":531},[521,980,582],{"class":535},[459,982,983,984,667,987,990,991,994,995,998],{},"That gives you ",[478,985,986],{},"\u002F_health\u002Flive",[478,988,989],{},"\u002Fready"," and ",[478,992,993],{},"\u002Fstartup",", readiness checks registered by the queue, the scheduler and S3 storage, and a shutdown that answers ",[478,996,997],{},"503"," first and keeps serving for a few seconds, so a Kubernetes rolling update drops no requests.",[459,1000,681,1001,608],{},[462,1002,1003],{},[490,1004,1005],{"href":62},"Health checks",[483,1007,1009],{"id":1008},"a-project-generator-with-modules","A project generator with modules",[512,1011,1015],{"className":1012,"code":1013,"language":1014,"meta":517,"style":517},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm create vercube my-app --template fullstack-vue --modules schema,auth,queue,testing\npnpm vercube add cache,ws\n","bash",[478,1016,1017,1043],{"__ignoreMap":517},[521,1018,1019,1022,1025,1028,1031,1034,1037,1040],{"class":523,"line":524},[521,1020,1021],{"class":546},"pnpm",[521,1023,1024],{"class":569}," create",[521,1026,1027],{"class":569}," vercube",[521,1029,1030],{"class":569}," my-app",[521,1032,1033],{"class":569}," --template",[521,1035,1036],{"class":569}," fullstack-vue",[521,1038,1039],{"class":569}," --modules",[521,1041,1042],{"class":569}," schema,auth,queue,testing\n",[521,1044,1045,1047,1049,1052],{"class":523,"line":585},[521,1046,1021],{"class":546},[521,1048,1027],{"class":569},[521,1050,1051],{"class":569}," add",[521,1053,1054],{"class":569}," cache,ws\n",[459,1056,1057,1059,1060,1063,1064,1067],{},[478,1058,480],{}," now assembles a project from modules, each one a recipe that edits your config and container, adds a sample route and its test, and brings the Redis or RabbitMQ it needs. ",[478,1061,1062],{},"vercube add"," applies the same recipes to a project that already exists, and running one twice changes nothing. The new ",[478,1065,1066],{},"fullstack-vue"," template is an API and a Vue app in one workspace, wired together through the typed client.",[459,1069,681,1070,1075,1076,608],{},[462,1071,1072],{},[490,1073,1074],{"href":27},"Project generator"," · ",[462,1077,1078],{},[490,1079,1080],{"href":338},"Writing a recipe",[483,1082,1084],{"id":1083},"security-pass","Security pass",[459,1086,1087],{},"After 1.4 I audited the parts of the framework that take input from the network: error responses, the schema endpoint, WebSocket upgrades, static files, telemetry and auth. The changes you are most likely to notice:",[1089,1090,1091,1099,1112,1123,1126,1129],"ul",{},[1092,1093,1094,1095,1098],"li",{},"Unexpected errors no longer echo their message or ",[478,1096,1097],{},"cause"," outside development.",[1092,1100,1101,1104,1105,1108,1109,608],{},[478,1102,1103],{},"\u002F_schema"," is not served in production by default. Turn it on with ",[478,1106,1107],{},"enabled: true",", behind a ",[478,1110,1111],{},"token",[1092,1113,1114,1115,1118,1119,1122],{},"WebSocket upgrades check ",[478,1116,1117],{},"Origin",", and ",[478,1120,1121],{},"allowedOrigins"," lets a frontend on another origin in.",[1092,1124,1125],{},"Static files ignore dotfiles and refuse symlinks that leave the directory.",[1092,1127,1128],{},"Telemetry redacts credential-named headers and body keys.",[1092,1130,1131,1132,1135,1136,1138,1139,1142,1143,1146],{},"Auth fails closed on an invalid ",[478,1133,1134],{},"validate()"," result, and ",[478,1137,1134],{}," can return ",[478,1140,1141],{},"{ user }"," so ",[478,1144,1145],{},"@User()"," does not load it a second time.",[459,1148,1149,1150,1155],{},"The rest, around thirty fixes in all, is in the ",[490,1151,1154],{"href":1152,"rel":1153},"https:\u002F\u002Fvercube.dev\u002Fchangelog",[494],"changelog",". Devtools also got the new look, and its state-changing endpoints now refuse cross-site requests.",[483,1157,1159],{"id":1158},"upgrade","Upgrade",[459,1161,1162,1163,1166],{},"Every ",[478,1164,1165],{},"@vercube\u002F*"," package is released together and has to be on the same version:",[512,1168,1170],{"className":1012,"code":1169,"language":1014,"meta":517,"style":517},"pnpm up \"@vercube\u002F*\" --latest\npnpm add @vercube\u002Fclient@1.5.0\n",[478,1171,1172,1190],{"__ignoreMap":517},[521,1173,1174,1176,1179,1182,1184,1187],{"class":523,"line":524},[521,1175,1021],{"class":546},[521,1177,1178],{"class":569}," up",[521,1180,1181],{"class":535}," \"",[521,1183,1165],{"class":569},[521,1185,1186],{"class":535},"\"",[521,1188,1189],{"class":569}," --latest\n",[521,1191,1192,1194,1196],{"class":523,"line":585},[521,1193,1021],{"class":546},[521,1195,1051],{"class":569},[521,1197,1198],{"class":569}," @vercube\u002Fclient@1.5.0\n",[459,1200,1201],{},"Most applications upgrade without touching anything. A few behaviours changed on purpose:",[1089,1203,1204,1221,1237,1243,1257],{},[1092,1205,1206,1214,1215,1217,1218,1220],{},[462,1207,1208,1210,1211,608],{},[478,1209,1103],{}," is not served when ",[478,1212,1213],{},"production: true"," Set ",[478,1216,1107],{}," and a ",[478,1219,1111],{}," to keep it.",[1092,1222,1223,1233,1234,608],{},[462,1224,1225,1226,1229,1230],{},"A handler that returns ",[478,1227,1228],{},"undefined"," answers ",[478,1231,1232],{},"204",", not ",[478,1235,1236],{},"200",[1092,1238,1239,1242],{},[462,1240,1241],{},"Unexpected errors no longer echo their message"," to the caller outside development. Look in your logs or in devtools.",[1092,1244,1245,1251,1252,1254,1255,608],{},[462,1246,1247,1248,608],{},"WebSocket upgrades from another origin get ",[478,1249,1250],{},"403"," Requests without an ",[478,1253,1117],{}," header are not affected. List a frontend on another origin in ",[478,1256,1121],{},[1092,1258,1259,1265,1266,1269,1270,1273,1274,1277],{},[462,1260,1261,1264],{},[478,1262,1263],{},"health"," changes two defaults when on:"," ",[478,1267,1268],{},"shutdown.drainDelay"," becomes 5000 and ",[478,1271,1272],{},"shutdown.handleSignals"," becomes ",[478,1275,1276],{},"true",". Setting either explicitly wins.",[1279,1280],"hr",{},[459,1282,1283,1284,1289,1290,608],{},"I would like to hear how the typed client works in a real monorepo, and which modules the generator should support next. Issues and discussions are on ",[490,1285,1288],{"href":1286,"rel":1287},"https:\u002F\u002Fgithub.com\u002Fvercube\u002Fvercube",[494],"GitHub",", and I am on ",[490,1291,1294],{"href":1292,"rel":1293},"https:\u002F\u002Fx.com\u002FOskarLebuda",[494],"X",[1296,1297,1298],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 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 .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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":517,"searchDepth":524,"depth":585,"links":1300},[1301,1302,1303,1304,1305,1306],{"id":485,"depth":585,"text":486},{"id":688,"depth":585,"text":689},{"id":943,"depth":585,"text":944},{"id":1008,"depth":585,"text":1009},{"id":1083,"depth":585,"text":1084},{"id":1158,"depth":585,"text":1159},"Release","2026-11-11","Vercube 1.5 adds a typed API client generated from your controllers, typed errors, health probes, a project generator with modules and a new site.","md",null,{},"\u002Fblog\u002Fannouncing-1.5",{"title":1315,"description":1309},"Vercube 1.5: typed client and typed errors","blog\u002F5.announcing-1.5","7oDVwSHPnOuZCpx3hjTtyaMn2tu9skbthH_3U77DRLQ",[1319,1311],{"title":1320,"path":1321,"stem":1322,"description":1323,"children":-1},"Announcing Vercube 1.4","\u002Fblog\u002Fannouncing-1.4","blog\u002F4.announcing-1.4","Scheduled jobs that run once across any number of replicas, a testing package that boots your real app without a port, graceful shutdown, and more than thirty fixes to how a request is handled.",1794396667894]