[{"data":1,"prerenderedAt":574},["ShallowReactive",2],{"changelog":3,"github-stars":572},{"id":4,"title":5,"body":6,"description":565,"extension":566,"meta":567,"navigation":110,"path":568,"seo":569,"stem":570,"__hash__":571},"changelog\u002FCHANGELOG.md","Changelog",{"type":7,"value":8,"toc":548},"minimark",[9,13,31,39,47,54,58,168,172,200,206,209,366,369,401,408,415,418,421,544],[10,11,5],"h1",{"id":12},"changelog",[14,15,16,17,24,25,30],"p",{},"All notable changes to this project are documented here. The format follows\n",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fkeepachangelog.com\u002Fen\u002F1.1.0\u002F",[22],"nofollow","Keep a Changelog",", and the project follows\n",[18,26,29],{"href":27,"rel":28},"https:\u002F\u002Fsemver.org\u002Fspec\u002Fv2.0.0.html",[22],"Semantic Versioning",".",[14,32,33,34,38],{},"Until 1.0.0, a minor bump may carry a breaking change. Each one is listed under\n",[35,36,37],"strong",{},"Changed"," with the migration.",[40,41,43],"h2",{"id":42},"unreleased",[44,45,46],"span",{},"Unreleased",[40,48,50,53],{"id":49},"_020-2026-08-24",[44,51,52],{},"0.2.0"," - 2026-08-24",[55,56,37],"h3",{"id":57},"changed",[59,60,61],"ul",{},[62,63,64,67,68,72,73,76,77,149,152,153,156,157,159,160,163,164,167],"li",{},[35,65,66],{},"The documentation page is no longer part of the framework."," The small\nself-contained page that was embedded in the module is gone, and\n",[69,70,71],"code",{},"AppOptions.DocsUI"," names the UI to serve instead. It is nil by default, so\nan application publishes its OpenAPI document at ",[69,74,75],{},"\u002Fopenapi.json"," and serves\nno page at all unless it asks for one:",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-go shiki shiki-themes github-dark-default","import \"muzak.dev\u002Fopenapi\u002Fui\"\n\napp := muzak.New(muzak.AppOptions{DocsUI: ui.Files()})\n","go","",[69,85,86,105,112],{"__ignoreMap":83},[44,87,90,94,98,102],{"class":88,"line":89},"line",1,[44,91,93],{"class":92},"suJrU","import",[44,95,97],{"class":96},"s9uIt"," \"",[44,99,101],{"class":100},"sQhOw","muzak.dev\u002Fopenapi\u002Fui",[44,103,104],{"class":96},"\"\n",[44,106,108],{"class":88,"line":107},2,[44,109,111],{"emptyLinePlaceholder":110},true,"\n",[44,113,115,119,122,125,129,132,135,137,140,143,146],{"class":88,"line":114},3,[44,116,118],{"class":117},"sZEs4","app ",[44,120,121],{"class":92},":=",[44,123,124],{"class":117}," muzak.",[44,126,128],{"class":127},"sc3cj","New",[44,130,131],{"class":117},"(",[44,133,134],{"class":100},"muzak",[44,136,30],{"class":117},[44,138,139],{"class":100},"AppOptions",[44,141,142],{"class":117},"{DocsUI: ui.",[44,144,145],{"class":127},"Files",[44,147,148],{"class":117},"()})\n",[150,151],"br",{},"A UI is a module of its own so that a service which does not want one does\nnot carry it: Go downloads and links a module only when something imports it,\nso leaving ",[69,154,155],{},"DocsUI"," unset costs a binary nothing rather than embedding a page\nit will never serve. Nothing is fetched at run time either way.",[150,158],{},"Migration: add the import and the option to keep a page at ",[69,161,162],{},"\u002Fdocs","; change\nnothing to keep only the document. ",[69,165,166],{},"DocsPath"," is no longer reserved when no\nUI is configured, so a route of your own may use it.",[55,169,171],{"id":170},"added","Added",[59,173,174,179],{},[62,175,176,178],{},[69,177,101],{}," serves the OpenAPI dashboard: the reference grouped by\ntag, every schema as an outline, request snippets in thirteen languages, and\na console that sends a request from the page and reports the status, the\ntiming, the headers and the body. It is built from the same document the\nframework generates, so tags, summaries, descriptions, deprecations,\nparameters with their validation constraints, request bodies and the response\nmodel declared for every status code all reach it.",[62,180,181,183,184,187,188,191,192,195,196,199],{},[69,182,71],{}," takes any ",[69,185,186],{},"fs.FS"," meeting a small contract - an\n",[69,189,190],{},"index.html"," whose absolute URLs are written under ",[69,193,194],{},"\u002F__muzak_docs__\u002F"," and\nwhich reads its document from ",[69,197,198],{},"\u002F__muzak_spec__"," - so a service can serve a\ndashboard of its own instead.",[40,201,203,53],{"id":202},"_011-2026-08-24",[44,204,205],{},"0.1.1",[55,207,171],{"id":208},"added-1",[59,210,211,241,255,258,272,275,309,340,347,356],{},[62,212,213,216,217,220,221,224,225,228,229,232,233,236,237,240],{},[69,214,215],{},"WithResponseModel[T](code, description)"," documents a status code and the\nmodel its body carries, so one operation can describe a different schema per\nstatus code rather than the error envelope everywhere: a ",[69,218,219],{},"400"," and a ",[69,222,223],{},"500","\ncarrying the service's own error type, a ",[69,226,227],{},"409"," carrying a conflict report, a\n",[69,230,231],{},"304"," carrying nothing. The type argument is written exactly as a handler's\n",[69,234,235],{},"Out"," type is, and is described once in the components section. Like\n",[69,238,239],{},"WithResponseDoc"," it is a router option as well as a route option, and the\nlast declaration of a status code wins, so a route replaces what it\ninherited.",[62,242,243,244,246,247,250,251,254],{},"An empty description passed to ",[69,245,239],{}," or ",[69,248,249],{},"WithResponseModel"," now\nfalls back to the status code's standard reason phrase, so\n",[69,252,253],{},"WithResponseDoc(404, \"\")"," is documented as \"Not Found\" rather than as a\nresponse with no description at all.",[62,256,257],{},"A documented response status outside 100-599 is a build error naming the\nroute and the code, rather than a response key in the document that no client\ncould ever receive.",[62,259,260,261,263,264,267,268,271],{},"A request console in the documentation page at ",[69,262,162],{},". Every operation can\nbe sent from the page itself, with the parameters, the JSON body and the\nmultipart form filled in beside the schema they come from; the response is\nshown with its status, timing, size, headers and body, an event stream is\nread as it arrives, and the same request can be copied as a ",[69,265,266],{},"curl"," command.\nAn ",[35,269,270],{},"Authorize"," panel adds a bearer token, an API key header or basic\ncredentials to what the console sends. They are held in the tab and never\nstored.",[62,273,274],{},"The rest of the page grew with it: operations grouped by tag with a\ndescription per group, schemas as expandable outlines carrying the\nconstraints the application enforces, generated examples, a filter over every\noperation, deep links to an operation or a group, and a light, dark or\nsystem theme.",[62,276,277,278,281,282,285,286,289,290,293,294,285,297,300,301,304,305,308],{},"A constructor for each HTTP outcome worth a name: ",[69,279,280],{},"muzak.NotFound(message)",",\n",[69,283,284],{},"muzak.Forbidden(message)",", ",[69,287,288],{},"muzak.Conflict(message)"," and seventeen more,\ncovering 400 through 504. Each returns an ",[69,291,292],{},"*HTTPError"," carrying that status\nand its classifier, so ",[69,295,296],{},"Wrap",[69,298,299],{},"WithCode"," and ",[69,302,303],{},"WithDetails"," chain onto every\none of them, and an empty message uses the standard sentence for the status.\n",[69,306,307],{},"NewHTTPError(status, message)"," still covers anything without a name of its\nown.",[62,310,311,312,281,315,285,318,285,321,285,324,281,327,285,330,285,333,336,337,30],{},"Machine-readable codes for the statuses that had none: ",[69,313,314],{},"payment_required",[69,316,317],{},"not_acceptable",[69,319,320],{},"request_timeout",[69,322,323],{},"gone",[69,325,326],{},"precondition_failed",[69,328,329],{},"not_implemented",[69,331,332],{},"bad_gateway",[69,334,335],{},"service_unavailable"," and\n",[69,338,339],{},"gateway_timeout",[62,341,342,343,346],{},"The address the documentation ended up at is reported when the server starts\nlistening, as a URL that can be opened from the terminal:\n",[69,344,345],{},"Documentation at http:\u002F\u002Flocalhost:8080\u002Fdocs",". A wildcard bind is reported as\nlocalhost, since that is where a browser can reach it.",[62,348,349,300,352,355],{},[69,350,351],{},"AppOptions.DocsPath",[69,353,354],{},"AppOptions.OpenAPIPath"," are now validated while\nthe application is built. A path that is not absolute, one that is the same\nas the other, or one that an application route already answers is a build\nerror naming the option to change, rather than a page nobody can reach or a\nroute silently shadowed by the documentation.",[62,357,358,361,362,365],{},[69,359,360],{},"OpenAPIOptions.Tags"," describes the groups operations are sorted into and\ndecides the order the documentation presents them in. Routes join a group\nwith ",[69,363,364],{},"WithTags"," as before; a described tag no route carries is left out, and\na tag nothing describes follows the described ones.",[55,367,37],{"id":368},"changed-1",[59,370,371,384,387],{},[62,372,373,376,377,246,380,383],{},[69,374,375],{},"CodeForStatus"," returns a specific classifier for the nine statuses listed\nabove instead of the generic ",[69,378,379],{},"client_error",[69,381,382],{},"internal_error",". A client\nswitching on the code sees the more precise value; one switching on the\nstatus is unaffected.",[62,385,386],{},"The documentation page and the OpenAPI document are compressed once when the\napplication is built and served with an entity tag per representation, so a\nclient that accepts gzip transfers a fraction of the bytes and a reload\ntransfers none.",[62,388,389,390,393,394,397,398,30],{},"The page's content security policy now names the page's own script and\nstylesheet by hash instead of by a nonce issued per response. The page is a\nconstant again, which is what lets it be cached, revalidated and compressed\nahead of time; ",[69,391,392],{},"Cache-Control"," is ",[69,395,396],{},"no-cache"," rather than ",[69,399,400],{},"no-store",[40,402,404,407],{"id":403},"_010-2026-08-23",[44,405,406],{},"0.1.0"," - 2026-08-23",[14,409,410,411,414],{},"First public release, published as ",[69,412,413],{},"muzak.dev\u002Fframework"," on the Go module proxy.",[14,416,417],{},"This is a pre-1.0 version. The surface is covered by tests and used by the\nexample application, but it is not frozen: expect it to move before 1.0.0.",[55,419,171],{"id":420},"added-2",[59,422,423,426,433,456,467,482,490,493,503,523,533,541],{},[62,424,425],{},"Routing on a segment-wise radix trie, with static and parameter segments and\nrouters that nest under a prefix.",[62,427,428,429,432],{},"Handlers of the form ",[69,430,431],{},"func(ctx *muzak.Context, in In) (Out, error)",", where the\ninput type is the request and the return type is the response. Neither type\nargument is written at the call site.",[62,434,435,436,285,439,285,442,285,445,285,448,451,452,455],{},"Request binding by struct tag from ",[69,437,438],{},"path",[69,440,441],{},"query",[69,443,444],{},"header",[69,446,447],{},"cookie",[69,449,450],{},"form","\nand ",[69,453,454],{},"file",", or from the JSON body when no tag is present, with the binding\nplan compiled once per route.",[62,457,458,459,462,463,466],{},"Validation declared against the field address rather than its name, so\n",[69,460,461],{},"v.String(&in.Email)"," survives a rename and ",[69,464,465],{},"v.Number(&in.Email)"," does not\ncompile.",[62,468,469,470,473,474,477,478,481],{},"Dependencies: guards through ",[69,471,472],{},"WithDependencies"," and typed providers read back\nwith ",[69,475,476],{},"From[T](ctx)",", plus ",[69,479,480],{},"WithSingleton"," for values built once.",[62,483,484,485,487,488,30],{},"OpenAPI 3.1 generated from the same declarations the code runs on, served at\n",[69,486,75],{}," with a self-contained UI at ",[69,489,162],{},[62,491,492],{},"RFC 6455 WebSockets and typed server-sent events, implemented in the module\nrather than delegated to a dependency.",[62,494,495,496,499,500,30],{},"Rate limiting, configuration loading through ",[69,497,498],{},"MustLoadConfig",", and an\nin-process test client under ",[69,501,502],{},"testclient",[62,504,505,506,509,510,513,514,517,518,30],{},"API versioning. ",[69,507,508],{},"AppOptions.Versioning"," turns it on, ",[69,511,512],{},"WithVersion"," declares\nwhat a route or router answers, and the version is read from the path, a\nheader, the ",[69,515,516],{},"Accept"," header or a function of your own. See\n",[18,519,522],{"href":520,"rel":521},"https:\u002F\u002Fmuzak.dev\u002Fdocs\u002Ffundamentals\u002Fversioning",[22],"Versioning",[62,524,525,526,451,529,532],{},"Per-address bounds on long-lived connections: ",[69,527,528],{},"WSOptions.MaxConnectionsPerIP",[69,530,531],{},"SSEOptions.MaxStreamsPerIP",", so one client cannot hold every slot the\nprocess has.",[62,534,535,536,30],{},"Conservative defaults throughout: non-zero listener timeouts, a one mebibyte\nbody cap, rejection of unknown JSON members, CORS closed until a policy is\nwritten, cross-origin WebSocket handshakes refused, no forwarding header\nbelieved until a proxy is named, and a panic reported as a generic 500 with\nthe stack kept in the log. The full list is in\n",[18,537,540],{"href":538,"rel":539},"https:\u002F\u002Fmuzak.dev\u002Fdocs\u002Fsecurity\u002Fsafe-defaults",[22],"Safe Defaults",[62,542,543],{},"Dual licence, MIT or Apache-2.0 at your option.",[545,546,547],"style",{},"html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}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);}",{"title":83,"searchDepth":107,"depth":107,"links":549},[550,551,556,561],{"id":42,"depth":107,"text":46},{"id":49,"depth":107,"text":552,"children":553},"0.2.0 - 2026-08-24",[554,555],{"id":57,"depth":114,"text":37},{"id":170,"depth":114,"text":171},{"id":202,"depth":107,"text":557,"children":558},"0.1.1 - 2026-08-24",[559,560],{"id":208,"depth":114,"text":171},{"id":368,"depth":114,"text":37},{"id":403,"depth":107,"text":562,"children":563},"0.1.0 - 2026-08-23",[564],{"id":420,"depth":114,"text":171},"All notable changes to this project are documented here. The format follows\nKeep a Changelog, and the project follows\nSemantic Versioning.","md",{},"\u002Fchangelog",{"title":5,"description":565},"CHANGELOG","TQ5gY7lfNWY8VQHH7Hkg0aTbXbEdlcoqXP8-1g4o_KQ",{"stars":573},0,1787573769928]