[{"data":1,"prerenderedAt":763},["ShallowReactive",2],{"navigation_docs":3,"-guides-releasing":172,"-guides-releasing-surround":758},[4,26,67,98,124,140,161],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting Started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Core","i-lucide-box","\u002Fcore","2.core",[32,37,42,47,52,57,62],{"title":33,"path":34,"stem":35,"icon":36},"Routing","\u002Fcore\u002Frouting","2.core\u002F1.routing","i-lucide-route",{"title":38,"path":39,"stem":40,"icon":41},"Handlers","\u002Fcore\u002Fhandlers","2.core\u002F2.handlers","i-lucide-braces",{"title":43,"path":44,"stem":45,"icon":46},"Request Binding","\u002Fcore\u002Frequest-binding","2.core\u002F3.request-binding","i-lucide-tags",{"title":48,"path":49,"stem":50,"icon":51},"Validation","\u002Fcore\u002Fvalidation","2.core\u002F4.validation","i-lucide-check-check",{"title":53,"path":54,"stem":55,"icon":56},"Context","\u002Fcore\u002Fcontext","2.core\u002F5.context","i-lucide-file-input",{"title":58,"path":59,"stem":60,"icon":61},"Errors","\u002Fcore\u002Ferrors","2.core\u002F6.errors","i-lucide-triangle-alert",{"title":63,"path":64,"stem":65,"icon":66},"Middleware","\u002Fcore\u002Fmiddleware","2.core\u002F7.middleware","i-lucide-layers",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":25},"Auth","i-lucide-shield-check","\u002Fauth","3.auth",[73,78,83,88,93],{"title":74,"path":75,"stem":76,"icon":77},"Overview","\u002Fauth\u002Foverview","3.auth\u002F1.overview","i-lucide-shield",{"title":79,"path":80,"stem":81,"icon":82},"Auth Rules","\u002Fauth\u002Fauth-rules","3.auth\u002F2.auth-rules","i-lucide-lock",{"title":84,"path":85,"stem":86,"icon":87},"JWT Providers","\u002Fauth\u002Fjwt-providers","3.auth\u002F3.jwt-providers","i-lucide-key",{"title":89,"path":90,"stem":91,"icon":92},"Roles & Permissions","\u002Fauth\u002Froles-and-permissions","3.auth\u002F4.roles-and-permissions","i-lucide-users",{"title":94,"path":95,"stem":96,"icon":97},"Audit Logging","\u002Fauth\u002Faudit-logging","3.auth\u002F5.audit-logging","i-lucide-scroll-text",{"title":99,"icon":100,"path":101,"stem":102,"children":103,"page":25},"GoTrue","i-lucide-key-round","\u002Fgotrue","4.gotrue",[104,109,114,119],{"title":105,"path":106,"stem":107,"icon":108},"Proxy","\u002Fgotrue\u002Fproxy","4.gotrue\u002F1.proxy","i-lucide-shuffle",{"title":110,"path":111,"stem":112,"icon":113},"Endpoints","\u002Fgotrue\u002Fendpoints","4.gotrue\u002F2.endpoints","i-lucide-list",{"title":115,"path":116,"stem":117,"icon":118},"Client IP & Rate Limits","\u002Fgotrue\u002Fclient-ip-and-rate-limits","4.gotrue\u002F3.client-ip-and-rate-limits","i-lucide-network",{"title":120,"path":121,"stem":122,"icon":123},"Deployment","\u002Fgotrue\u002Fdeployment","4.gotrue\u002F4.deployment","i-lucide-server",{"title":125,"icon":126,"path":127,"stem":128,"children":129,"page":25},"API Docs","i-lucide-file-text","\u002Fapi-docs","5.api-docs",[130,135],{"title":131,"path":132,"stem":133,"icon":134},"OpenAPI","\u002Fapi-docs\u002Fopenapi","5.api-docs\u002F1.openapi","i-lucide-file-json",{"title":136,"path":137,"stem":138,"icon":139},"Scalar UI","\u002Fapi-docs\u002Fscalar","5.api-docs\u002F2.scalar","i-lucide-book-open-text",{"title":141,"icon":142,"path":143,"stem":144,"children":145,"page":25},"Guides","i-lucide-compass","\u002Fguides","6.guides",[146,151,156],{"title":147,"path":148,"stem":149,"icon":150},"Testing","\u002Fguides\u002Ftesting","6.guides\u002F1.testing","i-lucide-flask-conical",{"title":152,"path":153,"stem":154,"icon":155},"Performance","\u002Fguides\u002Fperformance","6.guides\u002F2.performance","i-lucide-gauge",{"title":157,"path":158,"stem":159,"icon":160},"Releasing","\u002Fguides\u002Freleasing","6.guides\u002F3.releasing","i-lucide-tag",{"title":162,"icon":163,"path":164,"stem":165,"children":166,"page":25},"Reference","i-lucide-code","\u002Freference","7.reference",[167],{"title":168,"path":169,"stem":170,"icon":171},"API Reference","\u002Freference\u002Fapi-reference","7.reference\u002F1.api-reference","i-lucide-list-tree",{"id":173,"title":157,"body":174,"description":751,"extension":752,"links":753,"meta":754,"navigation":755,"path":158,"seo":756,"stem":159,"__hash__":757},"docs\u002F6.guides\u002F3.releasing.md",{"type":175,"value":176,"toc":736},"minimark",[177,181,186,203,214,299,309,326,329,340,485,492,496,503,528,541,552,556,571,623,637,663,687,691,694,722,725,732],[178,179,180],"p",{},"ezz is distributed as a private Go module. There is no registry to publish to and no build step — a release is a Git tag, and consuming services resolve it by tag name. That makes releasing cheap, and it also means an untagged change is invisible to everyone downstream.",[182,183,185],"h2",{"id":184},"write-commits-the-release-notes-can-read","Write commits the release notes can read",[178,187,188,189,196,197,202],{},"Release notes are generated by ",[190,191,195],"a",{"href":192,"rel":193},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fchangelogithub",[194],"nofollow","changelogithub",", which reads ",[190,198,201],{"href":199,"rel":200},"https:\u002F\u002Fwww.conventionalcommits.org\u002F",[194],"Conventional Commits",". The format is the subject line:",[204,205,210],"pre",{"className":206,"code":208,"language":209},[207],"language-text","type(scope): description\n","text",[211,212,208],"code",{"__ignoreMap":213},"",[215,216,217,230],"table",{},[218,219,220],"thead",{},[221,222,223,227],"tr",{},[224,225,226],"th",{},"Prefix",[224,228,229],{},"Section in the notes",[231,232,233,244,254,264,284],"tbody",{},[221,234,235,241],{},[236,237,238],"td",{},[211,239,240],{},"fix:",[236,242,243],{},"🐞 Bug Fixes",[221,245,246,251],{},[236,247,248],{},[211,249,250],{},"feat:",[236,252,253],{},"🚀 Features",[221,255,256,261],{},[236,257,258],{},[211,259,260],{},"perf:",[236,262,263],{},"🏎 Performance",[221,265,266,281],{},[236,267,268,271,272,271,275,271,278],{},[211,269,270],{},"refactor:",", ",[211,273,274],{},"chore:",[211,276,277],{},"docs:",[211,279,280],{},"test:",[236,282,283],{},"Grouped under their own headings",[221,285,286,296],{},[236,287,288,289,292,293],{},"Any type with ",[211,290,291],{},"!",", e.g. ",[211,294,295],{},"feat!:",[236,297,298],{},"🚨 Breaking Changes, listed first",[178,300,301,302,271,305,308],{},"The scope is optional and reads well as the package: ",[211,303,304],{},"feat(auth): add passkey provider",[211,306,307],{},"fix(binding): reject empty content-type",".",[310,311,312,313,317,318,322,323,325],"caution",{},"A commit that is not in this format is ",[314,315,316],"strong",{},"silently dropped"," from the release notes — not listed under a fallback heading, just absent. A release whose notes say ",[319,320,321],"em",{},"No significant changes"," usually means the commits were not formatted, not that nothing shipped. Mark breaking changes with ",[211,324,291],{},"; nothing else infers them, and the major-version decision below depends on spotting them.",[182,327,157],{"id":328},"releasing",[178,330,331,332,335,336,339],{},"There is nothing to run. Push to ",[211,333,334],{},"main"," and ",[211,337,338],{},".github\u002Fworkflows\u002Frelease.yml"," does the rest:",[341,342,344,349,372,378,382,396,409,413,426,430,433,440,444,449],"steps",{"level":343},"3",[345,346,348],"h3",{"id":347},"it-only-starts-for-go-changes","It only starts for Go changes",[178,350,351,352,271,355,358,359,362,363,335,365,367,368,371],{},"The workflow is filtered to ",[211,353,354],{},"**.go",[211,356,357],{},"go.mod",", and ",[211,360,361],{},"go.sum",". A push that touches only documentation, the docs site, the Makefile, or the workflow itself does not start a run at all — nothing appears in the Actions tab, because only these paths can change what a consuming service downloads. ",[211,364,357],{},[211,366,361],{}," are in the list because a dependency bump is consumer-visible without touching a single ",[211,369,370],{},".go"," file.",[178,373,374,375,377],{},"Commits from a filtered-out push are not lost. The version is computed from every commit since the last tag, not from the commits in one push, so a ",[211,376,250],{}," that only edited markdown is still counted by the next run that does touch Go — and correctly makes it a minor bump.",[345,379,381],{"id":380},"it-picks-the-version","It picks the version",[178,383,384,385,387,388,271,390,392,393,395],{},"The commits since the last tag decide the bump. A ",[211,386,250],{}," makes it a minor, a ",[211,389,240],{},[211,391,260],{},", or ",[211,394,270],{}," makes it a patch, and the highest one present wins.",[178,397,398,399,271,401,271,403,271,405,408],{},"If the push contains none of those — only ",[211,400,277],{},[211,402,274],{},[211,404,280],{},[211,406,407],{},"ci:",", or unformatted subjects — nothing is released and the run stops. Comment tweaks do not burn version numbers.",[345,410,412],{"id":411},"it-verifies-before-tagging","It verifies before tagging",[178,414,415,418,419,358,422,425],{},[211,416,417],{},"go mod tidy"," leaves no diff, ",[211,420,421],{},"go vet",[211,423,424],{},"go test -race .\u002F..."," — which covers both examples, since neither is a separate module. Only then is the tag created and pushed.",[345,427,429],{"id":428},"it-proves-the-tag-is-consumable","It proves the tag is consumable",[178,431,432],{},"A throwaway module outside the repository resolves the new tag and imports every documented path. This is the only check that exercises the real consumer path — proxy bypass, Git authentication, tag resolution, and module zip contents. Building inside the ezz repository proves none of it, because a local checkout satisfies every import regardless of what the tag contains.",[178,434,435,436,439],{},"If this fails, ",[314,437,438],{},"the tag is deleted again"," and no release is published. Consumers fetch direct from GitHub with no proxy cache, so removing the ref genuinely retracts the version.",[345,441,443],{"id":442},"it-writes-the-notes","It writes the notes",[178,445,446,448],{},[211,447,195],{}," groups the commits and creates the GitHub release. Preview what it will say before you push:",[204,450,455],{"className":451,"code":452,"filename":453,"language":454,"meta":213,"style":213},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpx changelogithub@0.13 --dry --from v1.1.0 --to HEAD\n","Terminal","bash",[211,456,457],{"__ignoreMap":213},[458,459,462,466,470,473,476,479,482],"span",{"class":460,"line":461},"line",1,[458,463,465],{"class":464},"sBMFI","pnpx",[458,467,469],{"class":468},"sfazB"," changelogithub@0.13",[458,471,472],{"class":468}," --dry",[458,474,475],{"class":468}," --from",[458,477,478],{"class":468}," v1.1.0",[458,480,481],{"class":468}," --to",[458,483,484],{"class":468}," HEAD\n",[486,487,488,489,491],"warning",{},"If you ever verify a tag by hand, do not run ",[211,490,417],{}," in the throwaway module. When an import is not satisfied by the tag under test, tidy does not fail — it quietly upgrades to a newer tag that does satisfy it, and you get a passing smoke test for a broken release. The workflow asserts the resolved version never moved off the tag, which is the only reason its check means anything.",[182,493,495],{"id":494},"forcing-a-specific-version","Forcing a specific version",[178,497,498,499,502],{},"Automatic bumps never go major, for a reason covered below. To release an exact version — a ",[211,500,501],{},"v2.0.0"," after changing the module path, or a rebuild of a version the planner would skip — trigger the workflow by hand:",[204,504,506],{"className":451,"code":505,"filename":453,"language":454,"meta":213,"style":213},"gh workflow run release.yml -f version=v2.0.0\n",[211,507,508],{"__ignoreMap":213},[458,509,510,513,516,519,522,525],{"class":460,"line":461},[458,511,512],{"class":464},"gh",[458,514,515],{"class":468}," workflow",[458,517,518],{"class":468}," run",[458,520,521],{"class":468}," release.yml",[458,523,524],{"class":468}," -f",[458,526,527],{"class":468}," version=v2.0.0\n",[178,529,530,531,533,534,536,537,540],{},"The version you pass is used verbatim. Every verification gate still runs, so a ",[211,532,501],{}," whose ",[211,535,357],{}," still says ",[211,538,539],{},"module github.com\u002Fsulv-io\u002Fezz"," fails at the consume step and the tag is rolled back.",[542,543,544,545,547,548,551],"note",{},"The path filter applies only to pushes. A manual run always proceeds, which is also the way to release a change that never touched a ",[211,546,370],{}," file — a corrected ",[211,549,550],{},"go:generate"," directive in a comment, say, or a fixture the tests depend on.",[182,553,555],{"id":554},"when-to-bump-major","When to bump major",[178,557,558,559,562,563,566,567,570],{},"Go enforces semantic versioning through the import path, and it does so only at the major version. Within v1, ",[211,560,561],{},"go get -u"," will happily move a service from ",[211,564,565],{},"v1.1.0"," to ",[211,568,569],{},"v1.9.0",", so anything that breaks a caller has to be either avoided or promoted to v2.",[215,572,573,583],{},[218,574,575],{},[221,576,577,580],{},[224,578,579],{},"Change",[224,581,582],{},"Version",[231,584,585,593,601,609,616],{},[221,586,587,590],{},[236,588,589],{},"New route helper, new middleware, new optional field",[236,591,592],{},"Minor",[221,594,595,598],{},[236,596,597],{},"Bug fix with no signature change",[236,599,600],{},"Patch",[221,602,603,606],{},[236,604,605],{},"Renamed or removed exported identifier",[236,607,608],{},"Major",[221,610,611,614],{},[236,612,613],{},"Changed function signature or interface method set",[236,615,608],{},[221,617,618,621],{},[236,619,620],{},"Package moved to a different import path",[236,622,608],{},[486,624,625,626,629,630,632,633,636],{},"A major version is not just a different tag. Go treats ",[211,627,628],{},"v2"," and above as a distinct module: the path in ",[211,631,357],{}," becomes ",[211,634,635],{},"github.com\u002Fsulv-io\u002Fezz\u002Fv2",", every internal import has to be updated to match, and every consuming service rewrites its imports too. The suffix is permanent. Given that ezz has a small and known set of consumers, absorbing a breaking change as a minor bump and updating those services directly is usually the cheaper trade — but it is a deliberate choice, not an accident.",[178,638,639,640,642,643,646,647,649,650,652,653,655,656,659,660,662],{},"This is why the workflow never bumps major on its own. A ",[211,641,295],{}," commit is released as a ",[314,644,645],{},"minor",", with a note on the run summary saying so. Tagging ",[211,648,501],{}," while ",[211,651,357],{}," still reads ",[211,654,539],{}," produces a tag Go flatly refuses to resolve — ",[319,657,658],{},"major version must be compatible: should be v0 or v1, not v2"," — so an automatic major bump would publish a version nobody can install. Going major means editing ",[211,661,357],{}," and every import first, then triggering the release by hand.",[178,664,665,666,669,670,672,673,675,676,679,680,683,684,308],{},"That trade is what produced the current numbering. The rewrite that moved the framework from ",[211,667,668],{},"core\u002F"," to the module root was breaking by any reading of semver, and it shipped as ",[211,671,565],{}," rather than ",[211,674,501],{}," because the alternative was a permanent ",[211,677,678],{},"\u002Fv2"," in every import path to protect consumers that did not exist. The cost is that ",[211,681,682],{},"v1.0.0"," is still resolvable and still broken — see ",[190,685,16],{"href":686},"\u002Fgetting-started\u002Finstallation#pick-a-version",[182,688,690],{"id":689},"what-ships-to-consumers","What ships to consumers",[178,692,693],{},"The module zip is built from the repository tree, not from a manifest, so anything committed at the tagged commit is downloaded by every consuming service. Two things keep that small:",[695,696,697,713],"ul",{},[698,699,700,703,704,707,708,710,711,308],"li",{},[314,701,702],{},"Test-only dependencies are pruned."," ",[211,705,706],{},"miniredis"," is a direct requirement in ",[211,709,357],{},", but Go's module graph pruning keeps test-only dependencies of a dependency out of the consumer's graph, so it never appears in a consuming service's ",[211,712,357],{},[698,714,715,703,718,721],{},[314,716,717],{},"Test files are included but not built.",[211,719,720],{},"_test.go"," files ship in the zip and are ignored unless someone runs tests against the module directly.",[178,723,724],{},"Everything else in the tree — documentation, fixtures, the docs site — is downloaded. It is currently around 157 KB, which is not worth optimizing, but it is worth not committing binaries or vendored dependencies into.",[542,726,727,728,731],{},"Compiled example binaries are covered by ",[211,729,730],{},".gitignore",". If you add a new example that produces one, add its output path there too rather than relying on the name being caught.",[733,734,735],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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);}",{"title":213,"searchDepth":737,"depth":737,"links":738},2,[739,740,748,749,750],{"id":184,"depth":737,"text":185},{"id":328,"depth":737,"text":157,"children":741},[742,744,745,746,747],{"id":347,"depth":743,"text":348},3,{"id":380,"depth":743,"text":381},{"id":411,"depth":743,"text":412},{"id":428,"depth":743,"text":429},{"id":442,"depth":743,"text":443},{"id":494,"depth":737,"text":495},{"id":554,"depth":737,"text":555},{"id":689,"depth":737,"text":690},"Cut a version of ezz that consuming services can actually resolve, and know when a change forces a major version.","md",null,{},{"icon":160},{"title":157,"description":751},"TFw5OGQp6c_2wYx6QEEiuVrjfrHdbSAX5nxFttwkYkc",[759,761],{"title":152,"path":153,"stem":154,"description":760,"icon":155,"children":-1},"What ezz precomputes and pools, the rules that keeps, and how to benchmark your own service.",{"title":168,"path":169,"stem":170,"description":762,"icon":171,"children":-1},"Every exported type and function, package by package.",1784970048994]