[{"data":1,"prerenderedAt":4789},["ShallowReactive",2],{"navigation_docs_en":3,"-en-concepts-design-patterns":342,"-en-concepts-design-patterns-surround":4784},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":92,"body":344,"description":4777,"extension":4778,"links":4779,"meta":4780,"navigation":4781,"path":93,"seo":4782,"stem":94,"__hash__":4783},"docs_en\u002Fen\u002F2.concepts\u002F6.design-patterns.md",{"type":345,"value":346,"toc":4738},"minimark",[347,356,363,368,373,400,418,686,752,763,794,798,803,1034,1048,1058,1062,1075,1212,1236,1245,1249,1265,1424,1452,1462,1466,1494,1725,1746,1758,1762,1779,1793,1797,1801,1821,1953,1966,1975,1983,1999,2208,2218,2227,2234,2254,2399,2414,2425,2429,2446,2621,2638,2647,2650,2654,2669,2829,2848,2858,2868,2872,2888,3214,3223,3241,3250,3254,3267,3362,3382,3396,3400,3404,3413,3486,3495,3509,3518,3522,3535,3555,3575,3584,3588,3601,3662,3675,3684,3687,3691,3704,3822,3835,3843,3847,3867,4108,4119,4129,4133,4149,4165,4175,4179,4183,4192,4369,4382,4387,4391,4408,4593,4610,4627,4631,4636,4647,4661,4665,4671,4711,4715,4734],[348,349,350,351,355],"p",{},"This page isn't a new layer: it's a ",[352,353,354],"strong",{},"map onto code that already exists",": it gathers, in one place, the design patterns that recur across several of the kit's packages. Each entry carries the same three things: what it is, why the kit chose it (not \"because it's best practice\", but the concrete constraint that brought it in), and where to find it in production. None of these patterns are new or gpsystem-specific: they're standard tools from the Go ecosystem and software engineering at large; the links point to their canonical descriptions.",[348,357,358,359,362],{},"If you haven't yet, read ",[360,361,68],"a",{"href":69}," first: it has the package map and the \"centralize, don't abstract\" principle that this page breaks down at the pattern level.",[364,365,367],"h2",{"id":366},"http-and-api-layer","HTTP and API layer",[369,370,372],"h3",{"id":371},"functional-options","Functional options",[348,374,375,378,379,383,384,387,388,391,392,395,396,399],{},[352,376,377],{},"What:"," a constructor takes ",[380,381,382],"code",{},"...Option",", where ",[380,385,386],{},"Option"," is a ",[380,389,390],{},"func(*internalStruct)",", so ",[380,393,394],{},"New","\u002F",[380,397,398],{},"Run"," keeps a stable signature while accepting any number of optional parameters, extensible in a backward-compatible way.",[348,401,402,405,406,395,408,395,410,413,414,417],{},[352,403,404],{},"Why here:"," the kit's ",[380,407,394],{},[380,409,398],{},[380,411,412],{},"Setup"," functions have 5-10 optional settings (closer, middleware, validator, exporter...), and these are unreadable as positional variadic parameters, while a single ",[380,415,416],{},"Config"," struct would force every caller to see every field even when changing just one.",[419,420,425],"pre",{"className":421,"code":422,"language":423,"meta":424,"style":424},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F server\u002Ffiberx\u002Ffiberx.go\ntype Option func(*options)\n\nfunc WithCloser(name string, fn func(context.Context) error) Option {\n    return func(o *options) { o.closers = append(o.closers, app.Closer{Name: name, Fn: fn}) }\n}\nfunc WithMiddleware(mw ...fiber.Handler) Option {\n    return func(o *options) { o.middleware = append(o.middleware, mw...) }\n}\n","go","",[380,426,427,436,456,463,515,598,604,634,681],{"__ignoreMap":424},[428,429,432],"span",{"class":430,"line":431},"line",1,[428,433,435],{"class":434},"sHwdD","\u002F\u002F server\u002Ffiberx\u002Ffiberx.go\n",[428,437,439,443,447,450,453],{"class":430,"line":438},2,[428,440,442],{"class":441},"sMK4o","type",[428,444,446],{"class":445},"sBMFI"," Option",[428,448,449],{"class":441}," func(*",[428,451,452],{"class":445},"options",[428,454,455],{"class":441},")\n",[428,457,459],{"class":430,"line":458},3,[428,460,462],{"emptyLinePlaceholder":461},true,"\n",[428,464,466,469,473,476,480,484,487,490,493,496,499,502,505,508,510,512],{"class":430,"line":465},4,[428,467,468],{"class":441},"func",[428,470,472],{"class":471},"s2Zo4"," WithCloser",[428,474,475],{"class":441},"(",[428,477,479],{"class":478},"sHdIc","name",[428,481,483],{"class":482},"spNyl"," string",[428,485,486],{"class":441},",",[428,488,489],{"class":478}," fn",[428,491,492],{"class":441}," func(",[428,494,495],{"class":445},"context",[428,497,498],{"class":441},".",[428,500,501],{"class":445},"Context",[428,503,504],{"class":441},")",[428,506,507],{"class":482}," error",[428,509,504],{"class":441},[428,511,446],{"class":445},[428,513,514],{"class":441}," {\n",[428,516,518,522,524,527,530,532,534,537,541,543,546,549,552,554,556,558,561,563,566,568,571,574,577,580,583,585,588,590,592,595],{"class":430,"line":517},5,[428,519,521],{"class":520},"s7zQu","    return",[428,523,492],{"class":441},[428,525,526],{"class":478},"o",[428,528,529],{"class":441}," *",[428,531,452],{"class":445},[428,533,504],{"class":441},[428,535,536],{"class":441}," {",[428,538,540],{"class":539},"sTEyZ"," o",[428,542,498],{"class":441},[428,544,545],{"class":539},"closers ",[428,547,548],{"class":441},"=",[428,550,551],{"class":471}," append",[428,553,475],{"class":441},[428,555,526],{"class":539},[428,557,498],{"class":441},[428,559,560],{"class":539},"closers",[428,562,486],{"class":441},[428,564,565],{"class":445}," app",[428,567,498],{"class":441},[428,569,570],{"class":445},"Closer",[428,572,573],{"class":441},"{",[428,575,576],{"class":539},"Name",[428,578,579],{"class":441},":",[428,581,582],{"class":539}," name",[428,584,486],{"class":441},[428,586,587],{"class":539}," Fn",[428,589,579],{"class":441},[428,591,489],{"class":539},[428,593,594],{"class":441},"})",[428,596,597],{"class":441}," }\n",[428,599,601],{"class":430,"line":600},6,[428,602,603],{"class":441},"}\n",[428,605,607,609,612,614,617,620,623,625,628,630,632],{"class":430,"line":606},7,[428,608,468],{"class":441},[428,610,611],{"class":471}," WithMiddleware",[428,613,475],{"class":441},[428,615,616],{"class":478},"mw",[428,618,619],{"class":441}," ...",[428,621,622],{"class":445},"fiber",[428,624,498],{"class":441},[428,626,627],{"class":445},"Handler",[428,629,504],{"class":441},[428,631,446],{"class":445},[428,633,514],{"class":441},[428,635,637,639,641,643,645,647,649,651,653,655,658,660,662,664,666,668,671,673,676,679],{"class":430,"line":636},8,[428,638,521],{"class":520},[428,640,492],{"class":441},[428,642,526],{"class":478},[428,644,529],{"class":441},[428,646,452],{"class":445},[428,648,504],{"class":441},[428,650,536],{"class":441},[428,652,540],{"class":539},[428,654,498],{"class":441},[428,656,657],{"class":539},"middleware ",[428,659,548],{"class":441},[428,661,551],{"class":471},[428,663,475],{"class":441},[428,665,526],{"class":539},[428,667,498],{"class":441},[428,669,670],{"class":539},"middleware",[428,672,486],{"class":441},[428,674,675],{"class":539}," mw",[428,677,678],{"class":441},"...)",[428,680,597],{"class":441},[428,682,684],{"class":430,"line":683},9,[428,685,603],{"class":441},[348,687,688,691,692,698,699,704,705,708,709,708,712,715,716,704,721,724,725,704,730,708,733,736,737,715,740,704,745,708,748,751],{},[352,689,690],{},"Where:"," ",[360,693,695],{"href":694},"\u002Fen\u002Fhttp\u002Ffiber#options",[380,696,697],{},"fiberx"," and ",[360,700,701],{"href":114},[380,702,703],{},"chix"," (",[380,706,707],{},"WithCloser",", ",[380,710,711],{},"WithMiddleware",[380,713,714],{},"WithConfig","), ",[360,717,718],{"href":233},[380,719,720],{},"otelx",[380,722,723],{},"WithSpanExporter",", nil-tolerant, see below), ",[360,726,727],{"href":124},[380,728,729],{},"validate",[380,731,732],{},"WithTagNameFunc",[380,734,735],{},"WithRegister",", directly on the wrapped ",[380,738,739],{},"*validator.Validate",[360,741,742],{"href":129},[380,743,744],{},"paginate",[380,746,747],{},"WithDefaultPerPage",[380,749,750],{},"WithMaxPerPage",").",[348,753,754,691,757,498],{},[352,755,756],{},"External source:",[360,758,762],{"href":759,"rel":760},"https:\u002F\u002Fdave.cheney.net\u002F2014\u002F10\u002F17\u002Ffunctional-options-for-friendly-apis",[761],"nofollow","Dave Cheney: Functional options for friendly APIs",[764,765,766,767,770,771,774,775,391,778,781,782,785,786,789,790,793],"tip",{},"One variant that also appears in the kit: ",[352,768,769],{},"the option itself absorbs the nil\u002Fempty case",", so the caller can pass it unconditionally. ",[380,772,773],{},"otelx.WithSpanExporter(exp)"," simply skips when ",[380,776,777],{},"exp == nil",[380,779,780],{},"telemetry.Setup"," can always pass ",[380,783,784],{},"sentryHandle.SpanExporter()"," (which is ",[380,787,788],{},"nil"," when Sentry is disabled) without an ",[380,791,792],{},"if"," branch.",[369,795,797],{"id":796},"middleware-chain-with-a-fixed-order","Middleware chain with a fixed order",[348,799,800,802],{},[352,801,377],{}," the framework's middleware sequence (recovery, telemetry, CORS, ...) is built in a fixed, documented order before user middleware or routes run, the order itself is the behavior (e.g. recovery is always outermost, so it catches panics from every middleware below it).",[419,804,806],{"className":421,"code":805,"language":423,"meta":424,"style":424},"\u002F\u002F server\u002Ffiberx\u002Ffiberx.go: newApp\nfiberApp.Use(recoverMiddleware)\nif !o.withoutTelemetry {\n    fiberApp.Use(contribotel.Middleware())\n}\nfiberApp.Use(func(c fiber.Ctx) error {\n    c.SetContext(sentryx.WithRequestHub(c.Context()))\n    return c.Next()\n})\nfiberApp.Use(cors.New(cors.Config{ \u002F* ... *\u002F }))\nfor _, mw := range o.middleware {\n    fiberApp.Use(mw)\n}\n",[380,807,808,813,830,847,869,873,901,932,947,952,986,1014,1029],{"__ignoreMap":424},[428,809,810],{"class":430,"line":431},[428,811,812],{"class":434},"\u002F\u002F server\u002Ffiberx\u002Ffiberx.go: newApp\n",[428,814,815,818,820,823,825,828],{"class":430,"line":438},[428,816,817],{"class":539},"fiberApp",[428,819,498],{"class":441},[428,821,822],{"class":471},"Use",[428,824,475],{"class":441},[428,826,827],{"class":539},"recoverMiddleware",[428,829,455],{"class":441},[428,831,832,834,837,839,841,844],{"class":430,"line":458},[428,833,792],{"class":520},[428,835,836],{"class":441}," !",[428,838,526],{"class":539},[428,840,498],{"class":441},[428,842,843],{"class":539},"withoutTelemetry ",[428,845,846],{"class":441},"{\n",[428,848,849,852,854,856,858,861,863,866],{"class":430,"line":465},[428,850,851],{"class":539},"    fiberApp",[428,853,498],{"class":441},[428,855,822],{"class":471},[428,857,475],{"class":441},[428,859,860],{"class":539},"contribotel",[428,862,498],{"class":441},[428,864,865],{"class":471},"Middleware",[428,867,868],{"class":441},"())\n",[428,870,871],{"class":430,"line":517},[428,872,603],{"class":441},[428,874,875,877,879,881,884,887,890,892,895,897,899],{"class":430,"line":600},[428,876,817],{"class":539},[428,878,498],{"class":441},[428,880,822],{"class":471},[428,882,883],{"class":441},"(func(",[428,885,886],{"class":478},"c",[428,888,889],{"class":445}," fiber",[428,891,498],{"class":441},[428,893,894],{"class":445},"Ctx",[428,896,504],{"class":441},[428,898,507],{"class":482},[428,900,514],{"class":441},[428,902,903,906,908,911,913,916,918,921,923,925,927,929],{"class":430,"line":606},[428,904,905],{"class":539},"    c",[428,907,498],{"class":441},[428,909,910],{"class":471},"SetContext",[428,912,475],{"class":441},[428,914,915],{"class":539},"sentryx",[428,917,498],{"class":441},[428,919,920],{"class":471},"WithRequestHub",[428,922,475],{"class":441},[428,924,886],{"class":539},[428,926,498],{"class":441},[428,928,501],{"class":471},[428,930,931],{"class":441},"()))\n",[428,933,934,936,939,941,944],{"class":430,"line":636},[428,935,521],{"class":520},[428,937,938],{"class":539}," c",[428,940,498],{"class":441},[428,942,943],{"class":471},"Next",[428,945,946],{"class":441},"()\n",[428,948,949],{"class":430,"line":683},[428,950,951],{"class":441},"})\n",[428,953,955,957,959,961,963,966,968,970,972,974,976,978,980,983],{"class":430,"line":954},10,[428,956,817],{"class":539},[428,958,498],{"class":441},[428,960,822],{"class":471},[428,962,475],{"class":441},[428,964,965],{"class":539},"cors",[428,967,498],{"class":441},[428,969,394],{"class":471},[428,971,475],{"class":441},[428,973,965],{"class":445},[428,975,498],{"class":441},[428,977,416],{"class":445},[428,979,573],{"class":441},[428,981,982],{"class":434}," \u002F* ... *\u002F",[428,984,985],{"class":441}," }))\n",[428,987,989,992,995,997,1000,1003,1006,1008,1010,1012],{"class":430,"line":988},11,[428,990,991],{"class":520},"for",[428,993,994],{"class":539}," _",[428,996,486],{"class":441},[428,998,999],{"class":539}," mw ",[428,1001,1002],{"class":441},":=",[428,1004,1005],{"class":520}," range",[428,1007,540],{"class":539},[428,1009,498],{"class":441},[428,1011,657],{"class":539},[428,1013,846],{"class":441},[428,1015,1017,1019,1021,1023,1025,1027],{"class":430,"line":1016},12,[428,1018,851],{"class":539},[428,1020,498],{"class":441},[428,1022,822],{"class":471},[428,1024,475],{"class":441},[428,1026,616],{"class":539},[428,1028,455],{"class":441},[428,1030,1032],{"class":430,"line":1031},13,[428,1033,603],{"class":441},[348,1035,1036,691,1038,698,1043,1047],{},[352,1037,690],{},[360,1039,1041],{"href":1040},"\u002Fen\u002Fhttp\u002Ffiber#what-run-wires-up",[380,1042,697],{},[360,1044,1045],{"href":114},[380,1046,703],{},": recover → otel → sentry-hub → (dev logger) → cors → user middleware.",[348,1049,1050,1052,1053,498],{},[352,1051,756],{}," the pattern itself is framework-agnostic; Fiber's middleware model: ",[360,1054,1057],{"href":1055,"rel":1056},"https:\u002F\u002Fdocs.gofiber.io\u002F",[761],"docs.gofiber.io",[369,1059,1061],{"id":1060},"generic-middleware-adapter","Generic middleware adapter",[348,1063,1064,1066,1067,1070,1071,1074],{},[352,1065,377],{}," a middleware factory written with Go generics, parameterized over a ",[380,1068,1069],{},"~func(...)"," type family, so the same code fits every ",[380,1072,1073],{},"StrictHandlerFunc"," that oapi-codegen generates, without referencing the concrete generated type.",[419,1076,1078],{"className":421,"code":1077,"language":423,"meta":424,"style":424},"\u002F\u002F server\u002Fchix\u002Fstrict.go\nfunc StrictValidator[H ~func(ctx context.Context, w http.ResponseWriter, r *http.Request, request any) (any, error)](\n    v *validate.Validator,\n) func(f H, operationID string) H {\n    \u002F\u002F ...\n}\n",[380,1079,1080,1085,1161,1178,1203,1208],{"__ignoreMap":424},[428,1081,1082],{"class":430,"line":431},[428,1083,1084],{"class":434},"\u002F\u002F server\u002Fchix\u002Fstrict.go\n",[428,1086,1087,1089,1092,1095,1098,1101,1104,1107,1109,1111,1113,1116,1119,1121,1124,1126,1129,1131,1134,1136,1139,1141,1144,1147,1149,1151,1154,1156,1158],{"class":430,"line":438},[428,1088,468],{"class":441},[428,1090,1091],{"class":471}," StrictValidator",[428,1093,1094],{"class":441},"[",[428,1096,1097],{"class":478},"H",[428,1099,1100],{"class":441}," ~func(",[428,1102,1103],{"class":478},"ctx",[428,1105,1106],{"class":445}," context",[428,1108,498],{"class":441},[428,1110,501],{"class":445},[428,1112,486],{"class":441},[428,1114,1115],{"class":478}," w",[428,1117,1118],{"class":445}," http",[428,1120,498],{"class":441},[428,1122,1123],{"class":445},"ResponseWriter",[428,1125,486],{"class":441},[428,1127,1128],{"class":478}," r",[428,1130,529],{"class":441},[428,1132,1133],{"class":445},"http",[428,1135,498],{"class":441},[428,1137,1138],{"class":445},"Request",[428,1140,486],{"class":441},[428,1142,1143],{"class":478}," request",[428,1145,1146],{"class":445}," any",[428,1148,504],{"class":441},[428,1150,704],{"class":441},[428,1152,1153],{"class":445},"any",[428,1155,486],{"class":441},[428,1157,507],{"class":482},[428,1159,1160],{"class":441},")](\n",[428,1162,1163,1166,1168,1170,1172,1175],{"class":430,"line":458},[428,1164,1165],{"class":478},"    v",[428,1167,529],{"class":441},[428,1169,729],{"class":445},[428,1171,498],{"class":441},[428,1173,1174],{"class":445},"Validator",[428,1176,1177],{"class":441},",\n",[428,1179,1180,1182,1184,1187,1190,1192,1195,1197,1199,1201],{"class":430,"line":465},[428,1181,504],{"class":441},[428,1183,492],{"class":441},[428,1185,1186],{"class":478},"f",[428,1188,1189],{"class":445}," H",[428,1191,486],{"class":441},[428,1193,1194],{"class":478}," operationID",[428,1196,483],{"class":482},[428,1198,504],{"class":441},[428,1200,1189],{"class":445},[428,1202,514],{"class":441},[428,1204,1205],{"class":430,"line":517},[428,1206,1207],{"class":434},"    \u002F\u002F ...\n",[428,1209,1210],{"class":430,"line":600},[428,1211,603],{"class":441},[348,1213,1214,691,1216,708,1221,1226,1227,708,1230,1235],{},[352,1215,690],{},[360,1217,1218],{"href":114},[380,1219,1220],{},"chix.StrictValidator[H]",[360,1222,1223],{"href":214},[380,1224,1225],{},"policy.FiberEnforcer[H]"," \u002F ",[380,1228,1229],{},"policy.HTTPEnforcer[H]",[360,1231,1232],{"href":204},[380,1233,1234],{},"auth.TokenIssuer[C jwt.Claims]",", all three apply the same pattern: the concrete (generated or caller-defined) type stays in the generic parameter, and the kit's code is written once.",[348,1237,1238,691,1240,498],{},[352,1239,756],{},[360,1241,1244],{"href":1242,"rel":1243},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Ftutorial\u002Fgenerics",[761],"Go generics: official tutorial",[369,1246,1248],{"id":1247},"context-injected-dependency","Context-injected dependency",[348,1250,1251,1253,1254,1257,1258,395,1261,1264],{},[352,1252,377],{}," a value (validator, identity, transaction) is placed into context under an ",[352,1255,1256],{},"unexported struct key",", with a ",[380,1259,1260],{},"With*",[380,1262,1263],{},"*FromContext"," function pair, so a value passes from middleware to code below without a global variable or signature changes, and the key can't collide from outside the package.",[419,1266,1268],{"className":421,"code":1267,"language":423,"meta":424,"style":424},"\u002F\u002F rbac\u002Fidentity.go\ntype identityKey struct{}\n\nfunc WithIdentity(ctx context.Context, id *Identity) context.Context {\n    return context.WithValue(ctx, identityKey{}, id)\n}\nfunc FromContext(ctx context.Context) *Identity {\n    id, _ := ctx.Value(identityKey{}).(*Identity)\n    return id\n}\n",[380,1269,1270,1275,1285,1289,1326,1352,1356,1381,1413,1420],{"__ignoreMap":424},[428,1271,1272],{"class":430,"line":431},[428,1273,1274],{"class":434},"\u002F\u002F rbac\u002Fidentity.go\n",[428,1276,1277,1279,1282],{"class":430,"line":438},[428,1278,442],{"class":441},[428,1280,1281],{"class":445}," identityKey",[428,1283,1284],{"class":441}," struct{}\n",[428,1286,1287],{"class":430,"line":458},[428,1288,462],{"emptyLinePlaceholder":461},[428,1290,1291,1293,1296,1298,1300,1302,1304,1306,1308,1311,1313,1316,1318,1320,1322,1324],{"class":430,"line":465},[428,1292,468],{"class":441},[428,1294,1295],{"class":471}," WithIdentity",[428,1297,475],{"class":441},[428,1299,1103],{"class":478},[428,1301,1106],{"class":445},[428,1303,498],{"class":441},[428,1305,501],{"class":445},[428,1307,486],{"class":441},[428,1309,1310],{"class":478}," id",[428,1312,529],{"class":441},[428,1314,1315],{"class":445},"Identity",[428,1317,504],{"class":441},[428,1319,1106],{"class":445},[428,1321,498],{"class":441},[428,1323,501],{"class":445},[428,1325,514],{"class":441},[428,1327,1328,1330,1332,1334,1337,1339,1341,1343,1345,1348,1350],{"class":430,"line":517},[428,1329,521],{"class":520},[428,1331,1106],{"class":539},[428,1333,498],{"class":441},[428,1335,1336],{"class":471},"WithValue",[428,1338,475],{"class":441},[428,1340,1103],{"class":539},[428,1342,486],{"class":441},[428,1344,1281],{"class":445},[428,1346,1347],{"class":441},"{},",[428,1349,1310],{"class":539},[428,1351,455],{"class":441},[428,1353,1354],{"class":430,"line":600},[428,1355,603],{"class":441},[428,1357,1358,1360,1363,1365,1367,1369,1371,1373,1375,1377,1379],{"class":430,"line":606},[428,1359,468],{"class":441},[428,1361,1362],{"class":471}," FromContext",[428,1364,475],{"class":441},[428,1366,1103],{"class":478},[428,1368,1106],{"class":445},[428,1370,498],{"class":441},[428,1372,501],{"class":445},[428,1374,504],{"class":441},[428,1376,529],{"class":441},[428,1378,1315],{"class":445},[428,1380,514],{"class":441},[428,1382,1383,1386,1388,1391,1393,1396,1398,1401,1403,1406,1409,1411],{"class":430,"line":636},[428,1384,1385],{"class":539},"    id",[428,1387,486],{"class":441},[428,1389,1390],{"class":539}," _ ",[428,1392,1002],{"class":441},[428,1394,1395],{"class":539}," ctx",[428,1397,498],{"class":441},[428,1399,1400],{"class":471},"Value",[428,1402,475],{"class":441},[428,1404,1405],{"class":445},"identityKey",[428,1407,1408],{"class":441},"{}).(*",[428,1410,1315],{"class":445},[428,1412,455],{"class":441},[428,1414,1415,1417],{"class":430,"line":683},[428,1416,521],{"class":520},[428,1418,1419],{"class":539}," id\n",[428,1421,1422],{"class":430,"line":954},[428,1423,603],{"class":441},[348,1425,1426,691,1428,708,1436,1438,1439,715,1442,698,1445,1448,1449,1451],{},[352,1427,690],{},[360,1429,1430,395,1433],{"href":209},[380,1431,1432],{},"rbac.WithIdentity",[380,1434,1435],{},"FromContext",[380,1437,703],{}," validator injection (",[380,1440,1441],{},"server\u002Fchix\u002Fchix.go",[380,1443,1444],{},"dbx\u002Fpg",[380,1446,1447],{},"dbx\u002Fbunx"," transaction carrying: see ",[360,1450,152],{"href":153}," below.",[348,1453,1454,691,1456,1461],{},[352,1455,756],{},[360,1457,1460],{"href":1458,"rel":1459},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fcontext-and-structs",[761],"Go blog: Context and structs"," (on the correct use of context keys).",[369,1463,1465],{"id":1464},"service-layer","Service layer",[348,1467,1468,691,1470,1473,1474,1477,1478,1481,1482,1226,1486,1489,1490,1493],{},[352,1469,377],{},[352,1471,1472],{},"one"," generated ",[380,1475,1476],{},"core\u002F"," package per module holds the shared business logic (plain structs with methods, rules written once, sentinel errors), and every surface's ",[380,1479,1480],{},"service\u002F"," package is a thin, audience-specific seam over it: it opens the transaction boundary, delegates shared rules to core, dispatches an event inside the transaction. There is no separate \"domain model\" layer in the DDD sense; the kit deliberately picks Fowler's ",[1483,1484,1485],"em",{},"Service Layer",[1483,1487,1488],{},"Transaction Script"," pattern over full hexagonal\u002FDDD layering. ",[380,1491,1492],{},"core"," is not a DDD domain layer: no aggregates, no value objects, no repository-abstraction ceremony, just the rule, in one place.",[419,1495,1497],{"className":421,"code":1496,"language":423,"meta":424,"style":424},"\u002F\u002F internal\u002Fmodules\u002F\u003Cmodule>\u002Fsurfaces\u002F\u003Csurface>\u002Fservice\u002Fservice.go: the thin seam\ntype Service struct{ core *core.Service }\n\nfunc (s *Service) Submit(ctx context.Context, req Request) (Ack, error) {\n    return s.core.Submit(ctx, req) \u002F\u002F the shared rule lives in the module's core\n}\n\n\u002F\u002F internal\u002Fmodules\u002F\u003Cmodule>\u002Fcore\u002F: the rule, written once\nfunc (c *Service) Submit(ctx context.Context, req Request) (Ack, error) {\n    return c.tx.WithinTransaction(ctx, func(ctx context.Context) error {\n        \u002F\u002F business rule + repo call + event dispatch, in one place\n    })\n}\n",[380,1498,1499,1504,1529,1533,1584,1613,1617,1621,1626,1673,1711,1716,1721],{"__ignoreMap":424},[428,1500,1501],{"class":430,"line":431},[428,1502,1503],{"class":434},"\u002F\u002F internal\u002Fmodules\u002F\u003Cmodule>\u002Fsurfaces\u002F\u003Csurface>\u002Fservice\u002Fservice.go: the thin seam\n",[428,1505,1506,1508,1511,1514,1517,1520,1522,1524,1527],{"class":430,"line":438},[428,1507,442],{"class":441},[428,1509,1510],{"class":445}," Service",[428,1512,1513],{"class":441}," struct{",[428,1515,1516],{"class":539}," core ",[428,1518,1519],{"class":441},"*",[428,1521,1492],{"class":445},[428,1523,498],{"class":441},[428,1525,1526],{"class":445},"Service",[428,1528,597],{"class":441},[428,1530,1531],{"class":430,"line":458},[428,1532,462],{"emptyLinePlaceholder":461},[428,1534,1535,1537,1539,1542,1544,1546,1548,1551,1553,1555,1557,1559,1561,1563,1566,1569,1571,1573,1576,1578,1580,1582],{"class":430,"line":465},[428,1536,468],{"class":441},[428,1538,704],{"class":441},[428,1540,1541],{"class":478},"s ",[428,1543,1519],{"class":441},[428,1545,1526],{"class":445},[428,1547,504],{"class":441},[428,1549,1550],{"class":471}," Submit",[428,1552,475],{"class":441},[428,1554,1103],{"class":478},[428,1556,1106],{"class":445},[428,1558,498],{"class":441},[428,1560,501],{"class":445},[428,1562,486],{"class":441},[428,1564,1565],{"class":478}," req",[428,1567,1568],{"class":445}," Request",[428,1570,504],{"class":441},[428,1572,704],{"class":441},[428,1574,1575],{"class":445},"Ack",[428,1577,486],{"class":441},[428,1579,507],{"class":482},[428,1581,504],{"class":441},[428,1583,514],{"class":441},[428,1585,1586,1588,1591,1593,1595,1597,1600,1602,1604,1606,1608,1610],{"class":430,"line":517},[428,1587,521],{"class":520},[428,1589,1590],{"class":539}," s",[428,1592,498],{"class":441},[428,1594,1492],{"class":539},[428,1596,498],{"class":441},[428,1598,1599],{"class":471},"Submit",[428,1601,475],{"class":441},[428,1603,1103],{"class":539},[428,1605,486],{"class":441},[428,1607,1565],{"class":539},[428,1609,504],{"class":441},[428,1611,1612],{"class":434}," \u002F\u002F the shared rule lives in the module's core\n",[428,1614,1615],{"class":430,"line":600},[428,1616,603],{"class":441},[428,1618,1619],{"class":430,"line":606},[428,1620,462],{"emptyLinePlaceholder":461},[428,1622,1623],{"class":430,"line":636},[428,1624,1625],{"class":434},"\u002F\u002F internal\u002Fmodules\u002F\u003Cmodule>\u002Fcore\u002F: the rule, written once\n",[428,1627,1628,1630,1632,1635,1637,1639,1641,1643,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671],{"class":430,"line":683},[428,1629,468],{"class":441},[428,1631,704],{"class":441},[428,1633,1634],{"class":478},"c ",[428,1636,1519],{"class":441},[428,1638,1526],{"class":445},[428,1640,504],{"class":441},[428,1642,1550],{"class":471},[428,1644,475],{"class":441},[428,1646,1103],{"class":478},[428,1648,1106],{"class":445},[428,1650,498],{"class":441},[428,1652,501],{"class":445},[428,1654,486],{"class":441},[428,1656,1565],{"class":478},[428,1658,1568],{"class":445},[428,1660,504],{"class":441},[428,1662,704],{"class":441},[428,1664,1575],{"class":445},[428,1666,486],{"class":441},[428,1668,507],{"class":482},[428,1670,504],{"class":441},[428,1672,514],{"class":441},[428,1674,1675,1677,1679,1681,1684,1686,1689,1691,1693,1695,1697,1699,1701,1703,1705,1707,1709],{"class":430,"line":954},[428,1676,521],{"class":520},[428,1678,938],{"class":539},[428,1680,498],{"class":441},[428,1682,1683],{"class":539},"tx",[428,1685,498],{"class":441},[428,1687,1688],{"class":471},"WithinTransaction",[428,1690,475],{"class":441},[428,1692,1103],{"class":539},[428,1694,486],{"class":441},[428,1696,492],{"class":441},[428,1698,1103],{"class":478},[428,1700,1106],{"class":445},[428,1702,498],{"class":441},[428,1704,501],{"class":445},[428,1706,504],{"class":441},[428,1708,507],{"class":482},[428,1710,514],{"class":441},[428,1712,1713],{"class":430,"line":988},[428,1714,1715],{"class":434},"        \u002F\u002F business rule + repo call + event dispatch, in one place\n",[428,1717,1718],{"class":430,"line":1016},[428,1719,1720],{"class":441},"    })\n",[428,1722,1723],{"class":430,"line":1031},[428,1724,603],{"class":441},[348,1726,1727,1729,1730,1734,1735,1737,1738,1741,1742,1745],{},[352,1728,404],{}," projects built with gpsystem are small-to-mid CRUD-adjacent backends, one handler chain per surface. Full DDD layering would mean an extra package, an extra interface, an extra constructor for every generated surface; without invariant-rich aggregates that's pure ceremony, and it contradicts the kit's ",[360,1731,1733],{"href":1732},"\u002Fen\u002Fconcepts\u002Farchitecture#centralize-dont-abstract","\"centralize, don't abstract\""," principle. What multiple entry points actually need (several surfaces sharing rules) is what the generated ",[380,1736,1492],{}," provides; the per-surface ",[380,1739,1740],{},"service"," plays, in DDD terms, the ",[1483,1743,1744],{},"application service"," role.",[348,1747,1748,1751,1752,1754,1755,1757],{},[352,1749,1750],{},"Growth path:"," when invariants grow, don't introduce a new layer: grow plain structs with methods inside the module's ",[380,1753,1476],{}," package. What concerns only one surface may stay in that surface's ",[380,1756,1740],{},"; the moment a second surface needs it, it moves to core. If a project genuinely needs more than that (real aggregates, several bounded contexts, a model per context), you have outgrown gpsystem: that is the territory of a hand-layered architecture, not of a generator.",[1759,1760,1761],"note",{},"Pre-rename projects and texts called a surface a \"domain\"; it is the same per-audience bundle.",[348,1763,1764,691,1766,1770,1771,1774,1775,1778],{},[352,1765,690],{},[360,1767,1768],{"href":315},[380,1769,314],{}," generates the seam and, alongside a module's first surface, the core; the layer rule (",[380,1772,1773],{},"http → service → core → repository",") is on the ",[360,1776,35],{"href":1777},"\u002Fen\u002Fgetting-started\u002Fproject-structure#layer-rules"," page.",[348,1780,1781,691,1783,708,1788,498],{},[352,1782,756],{},[360,1784,1787],{"href":1785,"rel":1786},"https:\u002F\u002Fmartinfowler.com\u002FeaaCatalog\u002FserviceLayer.html",[761],"martinfowler.com: Service Layer",[360,1789,1792],{"href":1790,"rel":1791},"https:\u002F\u002Fmartinfowler.com\u002FeaaCatalog\u002FtransactionScript.html",[761],"martinfowler.com: Transaction Script",[364,1794,1796],{"id":1795},"error-handling","Error handling",[369,1798,1800],{"id":1799},"rfc-9457-problem-details","RFC 9457 Problem Details",[348,1802,1803,1805,1806,708,1808,708,1811,708,1814,708,1817,1820],{},[352,1804,377],{}," every HTTP error response is a standardized JSON object (",[380,1807,442],{},[380,1809,1810],{},"title",[380,1812,1813],{},"status",[380,1815,1816],{},"detail",[380,1818,1819],{},"instance",") that a client can process uniformly from machine-readable code, instead of every endpoint inventing its own error shape.",[419,1822,1824],{"className":421,"code":1823,"language":423,"meta":424,"style":424},"\u002F\u002F httperr\u002Fproblem.go\ntype Problem struct {\n    Type   string `json:\"type\"`\n    Title  string `json:\"title\"`\n    Status int    `json:\"status\"`\n    Detail string `json:\"detail,omitempty\"`\n    Code   string `json:\"code,omitempty\"`\n}\nfunc (p *Problem) Error() string { \u002F* ... *\u002F }\n",[380,1825,1826,1831,1843,1861,1875,1891,1905,1919,1923],{"__ignoreMap":424},[428,1827,1828],{"class":430,"line":431},[428,1829,1830],{"class":434},"\u002F\u002F httperr\u002Fproblem.go\n",[428,1832,1833,1835,1838,1841],{"class":430,"line":438},[428,1834,442],{"class":441},[428,1836,1837],{"class":445}," Problem",[428,1839,1840],{"class":441}," struct",[428,1842,514],{"class":441},[428,1844,1845,1848,1851,1854,1858],{"class":430,"line":458},[428,1846,1847],{"class":539},"    Type   ",[428,1849,1850],{"class":482},"string",[428,1852,1853],{"class":441}," `",[428,1855,1857],{"class":1856},"sfazB","json:\"type\"",[428,1859,1860],{"class":441},"`\n",[428,1862,1863,1866,1868,1870,1873],{"class":430,"line":465},[428,1864,1865],{"class":539},"    Title  ",[428,1867,1850],{"class":482},[428,1869,1853],{"class":441},[428,1871,1872],{"class":1856},"json:\"title\"",[428,1874,1860],{"class":441},[428,1876,1877,1880,1883,1886,1889],{"class":430,"line":517},[428,1878,1879],{"class":539},"    Status ",[428,1881,1882],{"class":482},"int",[428,1884,1885],{"class":441},"    `",[428,1887,1888],{"class":1856},"json:\"status\"",[428,1890,1860],{"class":441},[428,1892,1893,1896,1898,1900,1903],{"class":430,"line":600},[428,1894,1895],{"class":539},"    Detail ",[428,1897,1850],{"class":482},[428,1899,1853],{"class":441},[428,1901,1902],{"class":1856},"json:\"detail,omitempty\"",[428,1904,1860],{"class":441},[428,1906,1907,1910,1912,1914,1917],{"class":430,"line":606},[428,1908,1909],{"class":539},"    Code   ",[428,1911,1850],{"class":482},[428,1913,1853],{"class":441},[428,1915,1916],{"class":1856},"json:\"code,omitempty\"",[428,1918,1860],{"class":441},[428,1920,1921],{"class":430,"line":636},[428,1922,603],{"class":441},[428,1924,1925,1927,1929,1932,1934,1937,1939,1942,1945,1947,1949,1951],{"class":430,"line":683},[428,1926,468],{"class":441},[428,1928,704],{"class":441},[428,1930,1931],{"class":478},"p ",[428,1933,1519],{"class":441},[428,1935,1936],{"class":445},"Problem",[428,1938,504],{"class":441},[428,1940,1941],{"class":471}," Error",[428,1943,1944],{"class":441},"()",[428,1946,483],{"class":482},[428,1948,536],{"class":441},[428,1950,982],{"class":434},[428,1952,597],{"class":441},[348,1954,1955,691,1957,1962,1963,498],{},[352,1956,690],{},[360,1958,1959],{"href":119},[380,1960,1961],{},"httperr.Problem",", the full ",[360,1964,1965],{"href":79},"error model",[348,1967,1968,691,1970,498],{},[352,1969,756],{},[360,1971,1974],{"href":1972,"rel":1973},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc9457",[761],"RFC 9457: Problem Details for HTTP APIs",[369,1976,1978,1979,1982],{"id":1977},"cascading-errorsas-mapping","Cascading ",[380,1980,1981],{},"errors.As"," mapping",[348,1984,1985,1987,1988,1990,1991,1994,1995,1998],{},[352,1986,377],{}," a type switch that uses ",[380,1989,1981],{}," to try matching increasingly general error types in sequence (own ",[380,1992,1993],{},"*Problem"," → framework-specific bind error → generic ",[380,1996,1997],{},"*errs.Error"," → everything else), deciding the HTTP response at the first match.",[419,2000,2002],{"className":421,"code":2001,"language":423,"meta":424,"style":424},"\u002F\u002F httperr\u002Fhandler.go: mapProblem\nvar p *Problem\nif errors.As(err, &p) { cp := *p; return &cp }\nvar be *fiber.BindError\nif errors.As(err, &be) { return BadRequest(be.Error()) }\nvar ee *errs.Error\nif errors.As(err, &ee) { \u002F* extract status\u002Fcode *\u002F }\nreturn New(http.StatusInternalServerError, \"\", detail)\n",[380,2003,2004,2009,2022,2072,2088,2132,2149,2179],{"__ignoreMap":424},[428,2005,2006],{"class":430,"line":431},[428,2007,2008],{"class":434},"\u002F\u002F httperr\u002Fhandler.go: mapProblem\n",[428,2010,2011,2014,2017,2019],{"class":430,"line":438},[428,2012,2013],{"class":441},"var",[428,2015,2016],{"class":539}," p ",[428,2018,1519],{"class":441},[428,2020,2021],{"class":445},"Problem\n",[428,2023,2024,2026,2029,2031,2034,2036,2039,2041,2044,2046,2048,2050,2053,2055,2057,2059,2062,2065,2067,2070],{"class":430,"line":458},[428,2025,792],{"class":520},[428,2027,2028],{"class":539}," errors",[428,2030,498],{"class":441},[428,2032,2033],{"class":471},"As",[428,2035,475],{"class":441},[428,2037,2038],{"class":539},"err",[428,2040,486],{"class":441},[428,2042,2043],{"class":441}," &",[428,2045,348],{"class":539},[428,2047,504],{"class":441},[428,2049,536],{"class":441},[428,2051,2052],{"class":539}," cp ",[428,2054,1002],{"class":441},[428,2056,529],{"class":441},[428,2058,348],{"class":539},[428,2060,2061],{"class":441},";",[428,2063,2064],{"class":520}," return",[428,2066,2043],{"class":441},[428,2068,2069],{"class":539},"cp ",[428,2071,603],{"class":441},[428,2073,2074,2076,2079,2081,2083,2085],{"class":430,"line":465},[428,2075,2013],{"class":441},[428,2077,2078],{"class":539}," be ",[428,2080,1519],{"class":441},[428,2082,622],{"class":445},[428,2084,498],{"class":441},[428,2086,2087],{"class":445},"BindError\n",[428,2089,2090,2092,2094,2096,2098,2100,2102,2104,2106,2109,2111,2113,2115,2118,2120,2122,2124,2127,2130],{"class":430,"line":517},[428,2091,792],{"class":520},[428,2093,2028],{"class":539},[428,2095,498],{"class":441},[428,2097,2033],{"class":471},[428,2099,475],{"class":441},[428,2101,2038],{"class":539},[428,2103,486],{"class":441},[428,2105,2043],{"class":441},[428,2107,2108],{"class":539},"be",[428,2110,504],{"class":441},[428,2112,536],{"class":441},[428,2114,2064],{"class":520},[428,2116,2117],{"class":471}," BadRequest",[428,2119,475],{"class":441},[428,2121,2108],{"class":539},[428,2123,498],{"class":441},[428,2125,2126],{"class":471},"Error",[428,2128,2129],{"class":441},"())",[428,2131,597],{"class":441},[428,2133,2134,2136,2139,2141,2144,2146],{"class":430,"line":600},[428,2135,2013],{"class":441},[428,2137,2138],{"class":539}," ee ",[428,2140,1519],{"class":441},[428,2142,2143],{"class":445},"errs",[428,2145,498],{"class":441},[428,2147,2148],{"class":445},"Error\n",[428,2150,2151,2153,2155,2157,2159,2161,2163,2165,2167,2170,2172,2174,2177],{"class":430,"line":606},[428,2152,792],{"class":520},[428,2154,2028],{"class":539},[428,2156,498],{"class":441},[428,2158,2033],{"class":471},[428,2160,475],{"class":441},[428,2162,2038],{"class":539},[428,2164,486],{"class":441},[428,2166,2043],{"class":441},[428,2168,2169],{"class":539},"ee",[428,2171,504],{"class":441},[428,2173,536],{"class":441},[428,2175,2176],{"class":434}," \u002F* extract status\u002Fcode *\u002F",[428,2178,597],{"class":441},[428,2180,2181,2184,2187,2189,2191,2193,2196,2198,2201,2203,2206],{"class":430,"line":636},[428,2182,2183],{"class":520},"return",[428,2185,2186],{"class":471}," New",[428,2188,475],{"class":441},[428,2190,1133],{"class":539},[428,2192,498],{"class":441},[428,2194,2195],{"class":539},"StatusInternalServerError",[428,2197,486],{"class":441},[428,2199,2200],{"class":441}," \"\"",[428,2202,486],{"class":441},[428,2204,2205],{"class":539}," detail",[428,2207,455],{"class":441},[348,2209,2210,691,2212,2217],{},[352,2211,690],{},[360,2213,2214],{"href":119},[380,2215,2216],{},"httperr.mapProblem",": a single mapping point for both engines.",[348,2219,2220,691,2222,498],{},[352,2221,756],{},[360,2223,2226],{"href":2224,"rel":2225},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fgo1.13-errors",[761],"Go blog: Working with errors",[369,2228,2230,2231],{"id":2229},"sentinel-definition-with-a-custom-is","Sentinel definition with a custom ",[380,2232,2233],{},"Is",[348,2235,2236,2238,2239,2242,2243,2245,2246,2249,2250,2253],{},[352,2237,377],{}," an immutable, stackless ",[380,2240,2241],{},"Definition"," describes a code\u002Fpublic message\u002FHTTP status as a package-level ",[380,2244,2013],{},"; every error instantiated from it stamps a back-reference to the definition, and the error's ",[380,2247,2248],{},"Is(target)"," method compares that reference, so ",[380,2251,2252],{},"errors.Is(err, ErrX)"," matches at any depth in the chain, while every occurrence still gets its own call-site stack.",[419,2255,2257],{"className":421,"code":2256,"language":423,"meta":424,"style":424},"\u002F\u002F errs\u002Fdefine.go + errs\u002Ferrs.go\nfunc Define(code string, attrs ...Attr) *Definition { \u002F* ... *\u002F }\n\nfunc (e *Error) Is(target error) bool {\n    d, ok := target.(*Definition)\n    return ok && e.def != nil && e.def == d\n}\n",[380,2258,2259,2264,2299,2303,2335,2357,2395],{"__ignoreMap":424},[428,2260,2261],{"class":430,"line":431},[428,2262,2263],{"class":434},"\u002F\u002F errs\u002Fdefine.go + errs\u002Ferrs.go\n",[428,2265,2266,2268,2271,2273,2275,2277,2279,2282,2284,2287,2289,2291,2293,2295,2297],{"class":430,"line":438},[428,2267,468],{"class":441},[428,2269,2270],{"class":471}," Define",[428,2272,475],{"class":441},[428,2274,380],{"class":478},[428,2276,483],{"class":482},[428,2278,486],{"class":441},[428,2280,2281],{"class":478}," attrs",[428,2283,619],{"class":441},[428,2285,2286],{"class":445},"Attr",[428,2288,504],{"class":441},[428,2290,529],{"class":441},[428,2292,2241],{"class":445},[428,2294,536],{"class":441},[428,2296,982],{"class":434},[428,2298,597],{"class":441},[428,2300,2301],{"class":430,"line":458},[428,2302,462],{"emptyLinePlaceholder":461},[428,2304,2305,2307,2309,2312,2314,2316,2318,2321,2323,2326,2328,2330,2333],{"class":430,"line":465},[428,2306,468],{"class":441},[428,2308,704],{"class":441},[428,2310,2311],{"class":478},"e ",[428,2313,1519],{"class":441},[428,2315,2126],{"class":445},[428,2317,504],{"class":441},[428,2319,2320],{"class":471}," Is",[428,2322,475],{"class":441},[428,2324,2325],{"class":478},"target",[428,2327,507],{"class":482},[428,2329,504],{"class":441},[428,2331,2332],{"class":482}," bool",[428,2334,514],{"class":441},[428,2336,2337,2340,2342,2345,2347,2350,2353,2355],{"class":430,"line":517},[428,2338,2339],{"class":539},"    d",[428,2341,486],{"class":441},[428,2343,2344],{"class":539}," ok ",[428,2346,1002],{"class":441},[428,2348,2349],{"class":539}," target",[428,2351,2352],{"class":441},".(*",[428,2354,2241],{"class":445},[428,2356,455],{"class":441},[428,2358,2359,2361,2363,2366,2369,2371,2374,2377,2380,2383,2385,2387,2389,2392],{"class":430,"line":600},[428,2360,521],{"class":520},[428,2362,2344],{"class":539},[428,2364,2365],{"class":441},"&&",[428,2367,2368],{"class":539}," e",[428,2370,498],{"class":441},[428,2372,2373],{"class":539},"def ",[428,2375,2376],{"class":441},"!=",[428,2378,2379],{"class":441}," nil",[428,2381,2382],{"class":441}," &&",[428,2384,2368],{"class":539},[428,2386,498],{"class":441},[428,2388,2373],{"class":539},[428,2390,2391],{"class":441},"==",[428,2393,2394],{"class":539}," d\n",[428,2396,2397],{"class":430,"line":606},[428,2398,603],{"class":441},[348,2400,2401,691,2403,2409,2410,2413],{},[352,2402,690],{},[360,2404,2406],{"href":2405},"\u002Fen\u002Fconcepts\u002Ferror-model#errsdefine-named-error-modes",[380,2407,2408],{},"errs.Define",": the full rationale and the shop ",[380,2411,2412],{},"ErrOutOfStock"," example are on the Error model page.",[348,2415,2416,691,2418,704,2422,2424],{},[352,2417,756],{},[360,2419,2421],{"href":2224,"rel":2420},[761],"Go blog: sentinel errors",[380,2423,2241],{}," generalizes this into an attribute-carrying variant).",[369,2426,2428],{"id":2427},"lazy-once-per-chain-stack-capture","Lazy, once-per-chain stack capture",[348,2430,2431,2433,2434,2437,2438,2441,2442,2445],{},[352,2432,377],{}," the full ",[380,2435,2436],{},"runtime.Callers"," stack is captured only at the ",[352,2439,2440],{},"deepest"," error in the chain (",[380,2443,2444],{},"hasStack"," checks the cause first); every further wrap records only its own wrap site. So logging a deeply wrapped error doesn't get linearly more expensive with the number of wrap levels.",[419,2447,2449],{"className":421,"code":2448,"language":423,"meta":424,"style":424},"\u002F\u002F errs\u002Fstack.go\nconst callerSkip = 4 \u002F\u002F pinned by TestCaptureSkip\n\nfunc newError(cause error, msg, full string) *Error {\n    e := &Error{msg: msg, full: full, err: cause, frame: caller(callerSkip)}\n    if !hasStack(cause) {\n        e.stack = callers(callerSkip)\n    }\n    return e\n}\n",[380,2450,2451,2456,2473,2477,2511,2567,2584,2605,2610,2617],{"__ignoreMap":424},[428,2452,2453],{"class":430,"line":431},[428,2454,2455],{"class":434},"\u002F\u002F errs\u002Fstack.go\n",[428,2457,2458,2461,2464,2466,2470],{"class":430,"line":438},[428,2459,2460],{"class":441},"const",[428,2462,2463],{"class":539}," callerSkip ",[428,2465,548],{"class":441},[428,2467,2469],{"class":2468},"sbssI"," 4",[428,2471,2472],{"class":434}," \u002F\u002F pinned by TestCaptureSkip\n",[428,2474,2475],{"class":430,"line":458},[428,2476,462],{"emptyLinePlaceholder":461},[428,2478,2479,2481,2484,2486,2489,2491,2493,2496,2498,2501,2503,2505,2507,2509],{"class":430,"line":465},[428,2480,468],{"class":441},[428,2482,2483],{"class":471}," newError",[428,2485,475],{"class":441},[428,2487,2488],{"class":478},"cause",[428,2490,507],{"class":482},[428,2492,486],{"class":441},[428,2494,2495],{"class":478}," msg",[428,2497,486],{"class":441},[428,2499,2500],{"class":478}," full",[428,2502,483],{"class":482},[428,2504,504],{"class":441},[428,2506,529],{"class":441},[428,2508,2126],{"class":445},[428,2510,514],{"class":441},[428,2512,2513,2516,2518,2520,2522,2524,2527,2529,2531,2533,2535,2537,2539,2541,2544,2546,2549,2551,2554,2556,2559,2561,2564],{"class":430,"line":517},[428,2514,2515],{"class":539},"    e ",[428,2517,1002],{"class":441},[428,2519,2043],{"class":441},[428,2521,2126],{"class":445},[428,2523,573],{"class":441},[428,2525,2526],{"class":539},"msg",[428,2528,579],{"class":441},[428,2530,2495],{"class":539},[428,2532,486],{"class":441},[428,2534,2500],{"class":539},[428,2536,579],{"class":441},[428,2538,2500],{"class":539},[428,2540,486],{"class":441},[428,2542,2543],{"class":539}," err",[428,2545,579],{"class":441},[428,2547,2548],{"class":539}," cause",[428,2550,486],{"class":441},[428,2552,2553],{"class":539}," frame",[428,2555,579],{"class":441},[428,2557,2558],{"class":471}," caller",[428,2560,475],{"class":441},[428,2562,2563],{"class":539},"callerSkip",[428,2565,2566],{"class":441},")}\n",[428,2568,2569,2572,2574,2576,2578,2580,2582],{"class":430,"line":600},[428,2570,2571],{"class":520},"    if",[428,2573,836],{"class":441},[428,2575,2444],{"class":471},[428,2577,475],{"class":441},[428,2579,2488],{"class":539},[428,2581,504],{"class":441},[428,2583,514],{"class":441},[428,2585,2586,2589,2591,2594,2596,2599,2601,2603],{"class":430,"line":606},[428,2587,2588],{"class":539},"        e",[428,2590,498],{"class":441},[428,2592,2593],{"class":539},"stack ",[428,2595,548],{"class":441},[428,2597,2598],{"class":471}," callers",[428,2600,475],{"class":441},[428,2602,2563],{"class":539},[428,2604,455],{"class":441},[428,2606,2607],{"class":430,"line":636},[428,2608,2609],{"class":441},"    }\n",[428,2611,2612,2614],{"class":430,"line":683},[428,2613,521],{"class":520},[428,2615,2616],{"class":539}," e\n",[428,2618,2619],{"class":430,"line":954},[428,2620,603],{"class":441},[348,2622,2623,691,2625,2630,2631,395,2634,2637],{},[352,2624,690],{},[360,2626,2628],{"href":2627},"\u002Fen\u002Fconcepts\u002Ferror-model#stack-semantics",[380,2629,2143],{},": the ",[380,2632,2633],{},"Frames",[380,2635,2636],{},"Chain"," accessors read through this mechanism.",[348,2639,2640,691,2642,498],{},[352,2641,756],{},[360,2643,2646],{"href":2644,"rel":2645},"https:\u002F\u002Fpkg.go.dev\u002Fruntime#Callers",[761],"pkg.dev: runtime.Callers",[364,2648,133],{"id":2649},"database",[369,2651,2653],{"id":2652},"narrow-repository-interface-sqlc-compatible","Narrow repository interface (sqlc-compatible)",[348,2655,2656,2658,2659,395,2662,395,2665,2668],{},[352,2657,377],{}," repositories depend on a minimal, 3-method interface (",[380,2660,2661],{},"Exec",[380,2663,2664],{},"Query",[380,2666,2667],{},"QueryRow",") instead of the concrete pool, the interface is defined by the consumer's (the repository's) needs, not by the driver.",[419,2670,2672],{"className":421,"code":2671,"language":423,"meta":424,"style":424},"\u002F\u002F dbx\u002Fpg\u002Fdb.go\ntype DBTX interface {\n    Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)\n    Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)\n    QueryRow(ctx context.Context, sql string, args ...any) pgx.Row\n}\n",[380,2673,2674,2679,2691,2740,2786,2825],{"__ignoreMap":424},[428,2675,2676],{"class":430,"line":431},[428,2677,2678],{"class":434},"\u002F\u002F dbx\u002Fpg\u002Fdb.go\n",[428,2680,2681,2683,2686,2689],{"class":430,"line":438},[428,2682,442],{"class":441},[428,2684,2685],{"class":445}," DBTX",[428,2687,2688],{"class":441}," interface",[428,2690,514],{"class":441},[428,2692,2693,2696,2698,2700,2702,2704,2706,2708,2711,2713,2715,2718,2720,2722,2724,2726,2729,2731,2734,2736,2738],{"class":430,"line":458},[428,2694,2695],{"class":471},"    Exec",[428,2697,475],{"class":441},[428,2699,1103],{"class":478},[428,2701,1106],{"class":445},[428,2703,498],{"class":441},[428,2705,501],{"class":445},[428,2707,486],{"class":441},[428,2709,2710],{"class":478}," sql",[428,2712,483],{"class":482},[428,2714,486],{"class":441},[428,2716,2717],{"class":478}," args",[428,2719,619],{"class":441},[428,2721,1153],{"class":445},[428,2723,504],{"class":441},[428,2725,704],{"class":441},[428,2727,2728],{"class":445},"pgconn",[428,2730,498],{"class":441},[428,2732,2733],{"class":445},"CommandTag",[428,2735,486],{"class":441},[428,2737,507],{"class":482},[428,2739,455],{"class":441},[428,2741,2742,2745,2747,2749,2751,2753,2755,2757,2759,2761,2763,2765,2767,2769,2771,2773,2775,2777,2780,2782,2784],{"class":430,"line":465},[428,2743,2744],{"class":471},"    Query",[428,2746,475],{"class":441},[428,2748,1103],{"class":478},[428,2750,1106],{"class":445},[428,2752,498],{"class":441},[428,2754,501],{"class":445},[428,2756,486],{"class":441},[428,2758,2710],{"class":478},[428,2760,483],{"class":482},[428,2762,486],{"class":441},[428,2764,2717],{"class":478},[428,2766,619],{"class":441},[428,2768,1153],{"class":445},[428,2770,504],{"class":441},[428,2772,704],{"class":441},[428,2774,143],{"class":445},[428,2776,498],{"class":441},[428,2778,2779],{"class":445},"Rows",[428,2781,486],{"class":441},[428,2783,507],{"class":482},[428,2785,455],{"class":441},[428,2787,2788,2791,2793,2795,2797,2799,2801,2803,2805,2807,2809,2811,2813,2815,2817,2820,2822],{"class":430,"line":517},[428,2789,2790],{"class":471},"    QueryRow",[428,2792,475],{"class":441},[428,2794,1103],{"class":478},[428,2796,1106],{"class":445},[428,2798,498],{"class":441},[428,2800,501],{"class":445},[428,2802,486],{"class":441},[428,2804,2710],{"class":478},[428,2806,483],{"class":482},[428,2808,486],{"class":441},[428,2810,2717],{"class":478},[428,2812,619],{"class":441},[428,2814,1153],{"class":445},[428,2816,504],{"class":441},[428,2818,2819],{"class":445}," pgx",[428,2821,498],{"class":441},[428,2823,2824],{"class":445},"Row\n",[428,2826,2827],{"class":430,"line":600},[428,2828,603],{"class":441},[348,2830,2831,2833,2834,2837,2838,2843,2844,2847],{},[352,2832,404],{}," this interface is ",[352,2835,2836],{},"method-for-method identical"," to what ",[360,2839,2842],{"href":2840,"rel":2841},"https:\u002F\u002Fsqlc.dev",[761],"sqlc"," generates, so a sqlc-generated repository accepts the kit's ",[380,2845,2846],{},"pg.DB"," unchanged, and joins the same context-carried transaction as hand-written code.",[348,2849,2850,691,2852,498],{},[352,2851,690],{},[360,2853,2855],{"href":2854},"\u002Fen\u002Fdatabase\u002Fpgx#dbtx-the-repository-query-surface",[380,2856,2857],{},"dbx\u002Fpg.DBTX",[348,2859,2860,691,2862,2867],{},[352,2861,756],{},[360,2863,2866],{"href":2864,"rel":2865},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Feffective_go#interfaces",[761],"Go, Effective Go: Interfaces"," (the principle of small, consumer-defined interfaces).",[369,2869,2871],{"id":2870},"unit-of-work-transaction-in-context","Unit of Work \u002F transaction in context",[348,2873,2874,2876,2877,2880,2881,2883,2884,2887],{},[352,2875,377],{}," a ",[380,2878,2879],{},"Transactor.WithinTransaction(ctx, fn)"," opens a transaction, places it into ",[380,2882,1103],{},", and commits after ",[380,2885,2886],{},"fn"," returns (or rolls back on error or panic). A nested call (when the context already carries a transaction) joins the existing one instead of opening a new one: nesting is flat, and commit\u002Frollback always belongs to the outermost call.",[419,2889,2891],{"className":421,"code":2890,"language":423,"meta":424,"style":424},"\u002F\u002F dbx\u002Fpg\u002Ftransactor.go\nfunc (t *transactor) WithinTransaction(ctx context.Context, fn func(ctx context.Context) error) error {\n    if _, ok := TxFromContext(ctx); ok {\n        return fn(ctx) \u002F\u002F join the existing one\n    }\n    tx, err := t.pool.Begin(ctx)\n    \u002F\u002F ...\n    committed := false\n    defer func() {\n        if !committed {\n            _ = tx.Rollback(context.WithoutCancel(ctx)) \u002F\u002F panic-safe\n        }\n    }()\n    if err := fn(ContextWithTx(ctx, tx)); err != nil {\n        return err\n    }\n    if err := tx.Commit(ctx); err != nil { \u002F* ... *\u002F }\n    committed = true\n    return nil\n}\n",[380,2892,2893,2898,2951,2977,2993,2997,3028,3032,3043,3053,3065,3099,3104,3109,3144,3152,3157,3191,3201,3209],{"__ignoreMap":424},[428,2894,2895],{"class":430,"line":431},[428,2896,2897],{"class":434},"\u002F\u002F dbx\u002Fpg\u002Ftransactor.go\n",[428,2899,2900,2902,2904,2907,2909,2912,2914,2917,2919,2921,2923,2925,2927,2929,2931,2933,2935,2937,2939,2941,2943,2945,2947,2949],{"class":430,"line":438},[428,2901,468],{"class":441},[428,2903,704],{"class":441},[428,2905,2906],{"class":478},"t ",[428,2908,1519],{"class":441},[428,2910,2911],{"class":445},"transactor",[428,2913,504],{"class":441},[428,2915,2916],{"class":471}," WithinTransaction",[428,2918,475],{"class":441},[428,2920,1103],{"class":478},[428,2922,1106],{"class":445},[428,2924,498],{"class":441},[428,2926,501],{"class":445},[428,2928,486],{"class":441},[428,2930,489],{"class":478},[428,2932,492],{"class":441},[428,2934,1103],{"class":478},[428,2936,1106],{"class":445},[428,2938,498],{"class":441},[428,2940,501],{"class":445},[428,2942,504],{"class":441},[428,2944,507],{"class":482},[428,2946,504],{"class":441},[428,2948,507],{"class":482},[428,2950,514],{"class":441},[428,2952,2953,2955,2957,2959,2961,2963,2966,2968,2970,2973,2975],{"class":430,"line":458},[428,2954,2571],{"class":520},[428,2956,994],{"class":539},[428,2958,486],{"class":441},[428,2960,2344],{"class":539},[428,2962,1002],{"class":441},[428,2964,2965],{"class":471}," TxFromContext",[428,2967,475],{"class":441},[428,2969,1103],{"class":539},[428,2971,2972],{"class":441},");",[428,2974,2344],{"class":539},[428,2976,846],{"class":441},[428,2978,2979,2982,2984,2986,2988,2990],{"class":430,"line":465},[428,2980,2981],{"class":520},"        return",[428,2983,489],{"class":471},[428,2985,475],{"class":441},[428,2987,1103],{"class":539},[428,2989,504],{"class":441},[428,2991,2992],{"class":434}," \u002F\u002F join the existing one\n",[428,2994,2995],{"class":430,"line":517},[428,2996,2609],{"class":441},[428,2998,2999,3002,3004,3007,3009,3012,3014,3017,3019,3022,3024,3026],{"class":430,"line":600},[428,3000,3001],{"class":539},"    tx",[428,3003,486],{"class":441},[428,3005,3006],{"class":539}," err ",[428,3008,1002],{"class":441},[428,3010,3011],{"class":539}," t",[428,3013,498],{"class":441},[428,3015,3016],{"class":539},"pool",[428,3018,498],{"class":441},[428,3020,3021],{"class":471},"Begin",[428,3023,475],{"class":441},[428,3025,1103],{"class":539},[428,3027,455],{"class":441},[428,3029,3030],{"class":430,"line":606},[428,3031,1207],{"class":434},[428,3033,3034,3037,3039],{"class":430,"line":636},[428,3035,3036],{"class":539},"    committed ",[428,3038,1002],{"class":441},[428,3040,3042],{"class":3041},"sfNiH"," false\n",[428,3044,3045,3048,3051],{"class":430,"line":683},[428,3046,3047],{"class":520},"    defer",[428,3049,3050],{"class":441}," func()",[428,3052,514],{"class":441},[428,3054,3055,3058,3060,3063],{"class":430,"line":954},[428,3056,3057],{"class":520},"        if",[428,3059,836],{"class":441},[428,3061,3062],{"class":539},"committed ",[428,3064,846],{"class":441},[428,3066,3067,3070,3072,3075,3077,3080,3082,3084,3086,3089,3091,3093,3096],{"class":430,"line":988},[428,3068,3069],{"class":539},"            _ ",[428,3071,548],{"class":441},[428,3073,3074],{"class":539}," tx",[428,3076,498],{"class":441},[428,3078,3079],{"class":471},"Rollback",[428,3081,475],{"class":441},[428,3083,495],{"class":539},[428,3085,498],{"class":441},[428,3087,3088],{"class":471},"WithoutCancel",[428,3090,475],{"class":441},[428,3092,1103],{"class":539},[428,3094,3095],{"class":441},"))",[428,3097,3098],{"class":434}," \u002F\u002F panic-safe\n",[428,3100,3101],{"class":430,"line":1016},[428,3102,3103],{"class":441},"        }\n",[428,3105,3106],{"class":430,"line":1031},[428,3107,3108],{"class":441},"    }()\n",[428,3110,3112,3114,3116,3118,3120,3122,3125,3127,3129,3131,3133,3136,3138,3140,3142],{"class":430,"line":3111},14,[428,3113,2571],{"class":520},[428,3115,3006],{"class":539},[428,3117,1002],{"class":441},[428,3119,489],{"class":471},[428,3121,475],{"class":441},[428,3123,3124],{"class":471},"ContextWithTx",[428,3126,475],{"class":441},[428,3128,1103],{"class":539},[428,3130,486],{"class":441},[428,3132,3074],{"class":539},[428,3134,3135],{"class":441},"));",[428,3137,3006],{"class":539},[428,3139,2376],{"class":441},[428,3141,2379],{"class":441},[428,3143,514],{"class":441},[428,3145,3147,3149],{"class":430,"line":3146},15,[428,3148,2981],{"class":520},[428,3150,3151],{"class":539}," err\n",[428,3153,3155],{"class":430,"line":3154},16,[428,3156,2609],{"class":441},[428,3158,3160,3162,3164,3166,3168,3170,3173,3175,3177,3179,3181,3183,3185,3187,3189],{"class":430,"line":3159},17,[428,3161,2571],{"class":520},[428,3163,3006],{"class":539},[428,3165,1002],{"class":441},[428,3167,3074],{"class":539},[428,3169,498],{"class":441},[428,3171,3172],{"class":471},"Commit",[428,3174,475],{"class":441},[428,3176,1103],{"class":539},[428,3178,2972],{"class":441},[428,3180,3006],{"class":539},[428,3182,2376],{"class":441},[428,3184,2379],{"class":441},[428,3186,536],{"class":441},[428,3188,982],{"class":434},[428,3190,597],{"class":441},[428,3192,3194,3196,3198],{"class":430,"line":3193},18,[428,3195,3036],{"class":539},[428,3197,548],{"class":441},[428,3199,3200],{"class":3041}," true\n",[428,3202,3204,3206],{"class":430,"line":3203},19,[428,3205,521],{"class":520},[428,3207,3208],{"class":441}," nil\n",[428,3210,3212],{"class":430,"line":3211},20,[428,3213,603],{"class":441},[348,3215,3216,3218,3219,3222],{},[352,3217,404],{}," the service layer doesn't want to know about pgx or bun, it just wants to say \"these steps run in one transaction,\" with repositories (which depend on ",[380,3220,3221],{},"DBTX",", not on the transaction) joining automatically.",[348,3224,3225,691,3227,3232,3233,698,3237,498],{},[352,3226,690],{},[360,3228,3229],{"href":153},[380,3230,3231],{},"dbx.Transactor",", implementations: ",[360,3234,3235],{"href":144},[380,3236,1444],{},[360,3238,3239],{"href":149},[380,3240,1447],{},[348,3242,3243,691,3245,498],{},[352,3244,756],{},[360,3246,3249],{"href":3247,"rel":3248},"https:\u002F\u002Fmartinfowler.com\u002FeaaCatalog\u002FunitOfWork.html",[761],"martinfowler.com: Unit of Work",[369,3251,3253],{"id":3252},"opaque-tamper-resistant-cursor","Opaque, tamper-resistant cursor",[348,3255,3256,3258,3259,3262,3263,3266],{},[352,3257,377],{}," the \"where I left off\" state for keyset pagination is a base64url-encoded JSON envelope carrying the last row's sort keys ",[352,3260,3261],{},"and"," a sha256 fingerprint of the normalized ",[380,3264,3265],{},"ORDER BY",", if a client replays the cursor against a different sort order, the fingerprint mismatch rejects it, instead of silently returning a wrong page.",[419,3268,3270],{"className":421,"code":3269,"language":423,"meta":424,"style":424},"\u002F\u002F paginate\u002Fcursor.go\nfunc sortFingerprint(sort string) string {\n    \u002F\u002F normalized ORDER BY → short sha256 hex\n}\nif env.S != sortFingerprint(sort) {\n    return CursorParams{}, ErrInvalidCursor.New(\"cursor issued for a different sort order\")\n}\n",[380,3271,3272,3277,3297,3302,3306,3330,3358],{"__ignoreMap":424},[428,3273,3274],{"class":430,"line":431},[428,3275,3276],{"class":434},"\u002F\u002F paginate\u002Fcursor.go\n",[428,3278,3279,3281,3284,3286,3289,3291,3293,3295],{"class":430,"line":438},[428,3280,468],{"class":441},[428,3282,3283],{"class":471}," sortFingerprint",[428,3285,475],{"class":441},[428,3287,3288],{"class":478},"sort",[428,3290,483],{"class":482},[428,3292,504],{"class":441},[428,3294,483],{"class":482},[428,3296,514],{"class":441},[428,3298,3299],{"class":430,"line":458},[428,3300,3301],{"class":434},"    \u002F\u002F normalized ORDER BY → short sha256 hex\n",[428,3303,3304],{"class":430,"line":465},[428,3305,603],{"class":441},[428,3307,3308,3310,3313,3315,3318,3320,3322,3324,3326,3328],{"class":430,"line":517},[428,3309,792],{"class":520},[428,3311,3312],{"class":539}," env",[428,3314,498],{"class":441},[428,3316,3317],{"class":539},"S ",[428,3319,2376],{"class":441},[428,3321,3283],{"class":471},[428,3323,475],{"class":441},[428,3325,3288],{"class":539},[428,3327,504],{"class":441},[428,3329,514],{"class":441},[428,3331,3332,3334,3337,3339,3342,3344,3346,3348,3351,3354,3356],{"class":430,"line":600},[428,3333,521],{"class":520},[428,3335,3336],{"class":445}," CursorParams",[428,3338,1347],{"class":441},[428,3340,3341],{"class":539}," ErrInvalidCursor",[428,3343,498],{"class":441},[428,3345,394],{"class":471},[428,3347,475],{"class":441},[428,3349,3350],{"class":441},"\"",[428,3352,3353],{"class":1856},"cursor issued for a different sort order",[428,3355,3350],{"class":441},[428,3357,455],{"class":441},[428,3359,3360],{"class":430,"line":606},[428,3361,603],{"class":441},[348,3363,3364,691,3366,3374,3375,395,3378,3381],{},[352,3365,690],{},[360,3367,3368,395,3371],{"href":129},[380,3369,3370],{},"paginate.CursorParams",[380,3372,3373],{},"NormalizeCursor",": together with the generic ",[380,3376,3377],{},"Page[T]",[380,3379,3380],{},"CursorPage[T]"," wrappers.",[348,3383,3384,691,3386,3391,3392,3395],{},[352,3385,756],{},[360,3387,3390],{"href":3388,"rel":3389},"https:\u002F\u002Fuse-the-index-luke.com\u002Fno-offset",[761],"use-the-index-luke.com: keyset pagination"," (why keyset pagination beats ",[380,3393,3394],{},"OFFSET"," on large tables).",[364,3397,3399],{"id":3398},"async-processing","Async processing",[369,3401,3403],{"id":3402},"transactional-outbox","Transactional outbox",[348,3405,3406,3408,3409,3412],{},[352,3407,377],{}," the event isn't sent directly to the message broker but written to a database table ",[352,3410,3411],{},"in the same transaction"," as the business write, a separate relay process then forwards the committed rows to the queue. This closes the gap where the DB write happens but the event send is lost (or vice versa).",[419,3414,3418],{"className":3415,"code":3416,"language":3417,"meta":424,"style":424},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- events\u002Foutbox\u002Frelay.go\nSELECT id, event_id, event_name, payload, metadata, created_at\nFROM outbox_events\nWHERE published_at IS NULL\nORDER BY id\nLIMIT $1\nFOR UPDATE SKIP LOCKED\n","sql",[380,3419,3420,3425,3433,3441,3455,3461,3472],{"__ignoreMap":424},[428,3421,3422],{"class":430,"line":431},[428,3423,3424],{"class":434},"-- events\u002Foutbox\u002Frelay.go\n",[428,3426,3427,3430],{"class":430,"line":438},[428,3428,3429],{"class":2468},"SELECT",[428,3431,3432],{"class":539}," id, event_id, event_name, payload, metadata, created_at\n",[428,3434,3435,3438],{"class":430,"line":458},[428,3436,3437],{"class":2468},"FROM",[428,3439,3440],{"class":539}," outbox_events\n",[428,3442,3443,3446,3449,3452],{"class":430,"line":465},[428,3444,3445],{"class":2468},"WHERE",[428,3447,3448],{"class":539}," published_at ",[428,3450,3451],{"class":2468},"IS",[428,3453,3454],{"class":2468}," NULL\n",[428,3456,3457,3459],{"class":430,"line":517},[428,3458,3265],{"class":2468},[428,3460,1419],{"class":539},[428,3462,3463,3466,3469],{"class":430,"line":600},[428,3464,3465],{"class":2468},"LIMIT",[428,3467,3468],{"class":539}," $",[428,3470,3471],{"class":2468},"1\n",[428,3473,3474,3477,3480,3483],{"class":430,"line":606},[428,3475,3476],{"class":2468},"FOR",[428,3478,3479],{"class":2468}," UPDATE",[428,3481,3482],{"class":2468}," SKIP",[428,3484,3485],{"class":539}," LOCKED\n",[348,3487,3488,3490,3491,3494],{},[352,3489,404],{}," N worker replicas can concurrently poll the same table, ",[380,3492,3493],{},"FOR UPDATE SKIP LOCKED"," guarantees each replica claims a disjoint batch without blocking.",[348,3496,3497,691,3499,3504,3505,3508],{},[352,3498,690],{},[360,3500,3501],{"href":183},[380,3502,3503],{},"events\u002Foutbox",": the full dual-write problem and the shop ",[380,3506,3507],{},"PlaceOrder"," example live there.",[348,3510,3511,691,3513,498],{},[352,3512,756],{},[360,3514,3517],{"href":3515,"rel":3516},"https:\u002F\u002Fmicroservices.io\u002Fpatterns\u002Fdata\u002Ftransactional-outbox.html",[761],"microservices.io: Transactional outbox",[369,3519,3521],{"id":3520},"at-least-once-delivery-idempotent-listener","At-least-once delivery + idempotent listener",[348,3523,3524,3526,3527,3530,3531,3534],{},[352,3525,377],{}," the delivery chain (outbox → relay → queue → listener) is ",[352,3528,3529],{},"at-least-once"," end to end: retries and crash-recovery mean an event can reach a listener more than once. The contract is therefore explicit: listeners must be idempotent, using a stable per-event identifier (",[380,3532,3533],{},"Meta.ID",") as the dedup key.",[419,3536,3538],{"className":421,"code":3537,"language":423,"meta":424,"style":424},"\u002F\u002F events\u002Fevents.go\n\u002F\u002F Delivery is at-least-once end to end (outbox → relay → asynq → listener), so\n\u002F\u002F LISTENERS MUST BE IDEMPOTENT. Use Meta(ctx).ID as an idempotency key.\n",[380,3539,3540,3545,3550],{"__ignoreMap":424},[428,3541,3542],{"class":430,"line":431},[428,3543,3544],{"class":434},"\u002F\u002F events\u002Fevents.go\n",[428,3546,3547],{"class":430,"line":438},[428,3548,3549],{"class":434},"\u002F\u002F Delivery is at-least-once end to end (outbox → relay → asynq → listener), so\n",[428,3551,3552],{"class":430,"line":458},[428,3553,3554],{"class":434},"\u002F\u002F LISTENERS MUST BE IDEMPOTENT. Use Meta(ctx).ID as an idempotency key.\n",[348,3556,3557,691,3559,3564,3565,698,3567,3570,3571,3574],{},[352,3558,690],{},[360,3560,3561],{"href":178},[380,3562,3563],{},"events",": ",[380,3566,3533],{},[380,3568,3569],{},"FanoutTasks","'s deterministic ",[380,3572,3573],{},"TaskID"," generation.",[348,3576,3577,691,3579,498],{},[352,3578,756],{},[360,3580,3583],{"href":3581,"rel":3582},"https:\u002F\u002Fmicroservices.io\u002Fpatterns\u002Fcommunication-style\u002Fidempotent-consumer.html",[761],"microservices.io: Idempotent Consumer",[369,3585,3587],{"id":3586},"redis-lease-leader-election","Redis-lease leader election",[348,3589,3590,3592,3593,3596,3597,3600],{},[352,3591,377],{}," a single-holder lock in Redis (",[380,3594,3595],{},"SETNX"," to acquire, a Lua script to renew that only allows ",[380,3598,3599],{},"PEXPIRE"," when the caller's identity still matches the lock value), so exactly one \"leader\" runs a given scheduled task out of a set of replicas.",[419,3602,3604],{"className":421,"code":3603,"language":423,"meta":424,"style":424},"\u002F\u002F scheduler\u002Flease.go\nvar renewScript = redis.NewScript(`\nif redis.call(\"get\", KEYS[1]) == ARGV[1] then\n  return redis.call(\"pexpire\", KEYS[1], ARGV[2])\nelse\n  return 0\nend`)\n",[380,3605,3606,3611,3632,3637,3642,3647,3652],{"__ignoreMap":424},[428,3607,3608],{"class":430,"line":431},[428,3609,3610],{"class":434},"\u002F\u002F scheduler\u002Flease.go\n",[428,3612,3613,3615,3618,3620,3623,3625,3628,3630],{"class":430,"line":438},[428,3614,2013],{"class":441},[428,3616,3617],{"class":539}," renewScript ",[428,3619,548],{"class":441},[428,3621,3622],{"class":539}," redis",[428,3624,498],{"class":441},[428,3626,3627],{"class":471},"NewScript",[428,3629,475],{"class":441},[428,3631,1860],{"class":441},[428,3633,3634],{"class":430,"line":458},[428,3635,3636],{"class":1856},"if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n",[428,3638,3639],{"class":430,"line":465},[428,3640,3641],{"class":1856},"  return redis.call(\"pexpire\", KEYS[1], ARGV[2])\n",[428,3643,3644],{"class":430,"line":517},[428,3645,3646],{"class":1856},"else\n",[428,3648,3649],{"class":430,"line":600},[428,3650,3651],{"class":1856},"  return 0\n",[428,3653,3654,3657,3660],{"class":430,"line":606},[428,3655,3656],{"class":1856},"end",[428,3658,3659],{"class":441},"`",[428,3661,455],{"class":441},[348,3663,3664,691,3666,3564,3671,3674],{},[352,3665,690],{},[360,3667,3668],{"href":188},[380,3669,3670],{},"scheduler",[380,3672,3673],{},"Runner.lead"," renews at a third of the TTL, and steps down if it loses the lease.",[348,3676,3677,691,3679,498],{},[352,3678,756],{},[360,3680,3683],{"href":3681,"rel":3682},"https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Fdevelop\u002Fuse\u002Fpatterns\u002Fdistributed-locks\u002F",[761],"Redis docs: Distributed locks",[364,3685,197],{"id":3686},"security",[369,3688,3690],{"id":3689},"generics-over-the-claims-type","Generics over the claims type",[348,3692,3693,3695,3696,3699,3700,3703],{},[352,3694,377],{}," the JWT issuer and its middleware are generic over ",[380,3697,3698],{},"[C jwt.Claims]",", alongside the built-in ",[380,3701,3702],{},"DefaultClaims",", a custom claims struct can be used with the same API and the same middleware guarantees (algorithm pinning, expiry checking).",[419,3705,3707],{"className":421,"code":3706,"language":423,"meta":424,"style":424},"\u002F\u002F auth\u002Ftoken.go\ntype TokenIssuer[C jwt.Claims] struct{ cfg Config }\n\n\u002F\u002F auth\u002Fmiddleware.go\nfunc MiddlewareFor[C jwt.Claims](issuer *TokenIssuer[C], toIdentity func(C) (*rbac.Identity, error)) fiber.Handler\n",[380,3708,3709,3714,3746,3750,3755],{"__ignoreMap":424},[428,3710,3711],{"class":430,"line":431},[428,3712,3713],{"class":434},"\u002F\u002F auth\u002Ftoken.go\n",[428,3715,3716,3718,3721,3723,3726,3729,3731,3734,3737,3739,3742,3744],{"class":430,"line":438},[428,3717,442],{"class":441},[428,3719,3720],{"class":445}," TokenIssuer",[428,3722,1094],{"class":441},[428,3724,3725],{"class":478},"C",[428,3727,3728],{"class":445}," jwt",[428,3730,498],{"class":441},[428,3732,3733],{"class":445},"Claims",[428,3735,3736],{"class":441},"]",[428,3738,1513],{"class":441},[428,3740,3741],{"class":539}," cfg ",[428,3743,416],{"class":445},[428,3745,597],{"class":441},[428,3747,3748],{"class":430,"line":458},[428,3749,462],{"emptyLinePlaceholder":461},[428,3751,3752],{"class":430,"line":465},[428,3753,3754],{"class":434},"\u002F\u002F auth\u002Fmiddleware.go\n",[428,3756,3757,3759,3762,3764,3766,3768,3770,3772,3775,3778,3780,3783,3785,3787,3790,3793,3795,3797,3799,3802,3805,3807,3809,3811,3813,3815,3817,3819],{"class":430,"line":517},[428,3758,468],{"class":441},[428,3760,3761],{"class":471}," MiddlewareFor",[428,3763,1094],{"class":441},[428,3765,3725],{"class":478},[428,3767,3728],{"class":445},[428,3769,498],{"class":441},[428,3771,3733],{"class":445},[428,3773,3774],{"class":441},"](",[428,3776,3777],{"class":478},"issuer",[428,3779,529],{"class":441},[428,3781,3782],{"class":445},"TokenIssuer",[428,3784,1094],{"class":441},[428,3786,3725],{"class":445},[428,3788,3789],{"class":441},"],",[428,3791,3792],{"class":478}," toIdentity",[428,3794,492],{"class":441},[428,3796,3725],{"class":445},[428,3798,504],{"class":441},[428,3800,3801],{"class":441}," (*",[428,3803,3804],{"class":445},"rbac",[428,3806,498],{"class":441},[428,3808,1315],{"class":445},[428,3810,486],{"class":441},[428,3812,507],{"class":482},[428,3814,3095],{"class":441},[428,3816,889],{"class":445},[428,3818,498],{"class":441},[428,3820,3821],{"class":445},"Handler\n",[348,3823,3824,691,3826,498],{},[352,3825,690],{},[360,3827,3829,708,3832],{"href":3828},"\u002Fen\u002Fsecurity\u002Fauthentication#custom-claims",[380,3830,3831],{},"auth.TokenIssuer[C]",[380,3833,3834],{},"MiddlewareFor[C]",[348,3836,3837,691,3839,498],{},[352,3838,756],{},[360,3840,3842],{"href":1242,"rel":3841},[761],"Go generics tutorial",[369,3844,3846],{"id":3845},"higher-order-authorization-guard","Higher-order authorization guard",[348,3848,3849,3851,3852,708,3855,3858,3859,3862,3863,3866],{},[352,3850,377],{}," the role and permission guard middleware (",[380,3853,3854],{},"RequireRole",[380,3856,3857],{},"RequirePermission",") both call a single ",[380,3860,3861],{},"requireFn(allowed func(*Identity) bool) fiber.Handler"," factory that owns the 401\u002F403 decision: the only difference between them is the ",[380,3864,3865],{},"allowed"," predicate.",[419,3868,3870],{"className":421,"code":3869,"language":423,"meta":424,"style":424},"\u002F\u002F rbac\u002Frbac.go\nfunc RequireRole(roles ...string) fiber.Handler {\n    return requireFn(func(id *Identity) bool { return id.HasRole(roles...) })\n}\nfunc requireFn(allowed func(*Identity) bool) fiber.Handler {\n    return func(c fiber.Ctx) error {\n        id := FromContext(c.Context())\n        if id == nil { return httperr.Unauthorized(\"authentication required\") }\n        if !allowed(id) { return httperr.Forbidden(\"insufficient privileges\") }\n        return c.Next()\n    }\n}\n",[380,3871,3872,3877,3903,3943,3947,3975,3995,4014,4050,4088,4100,4104],{"__ignoreMap":424},[428,3873,3874],{"class":430,"line":431},[428,3875,3876],{"class":434},"\u002F\u002F rbac\u002Frbac.go\n",[428,3878,3879,3881,3884,3886,3889,3891,3893,3895,3897,3899,3901],{"class":430,"line":438},[428,3880,468],{"class":441},[428,3882,3883],{"class":471}," RequireRole",[428,3885,475],{"class":441},[428,3887,3888],{"class":478},"roles",[428,3890,619],{"class":441},[428,3892,1850],{"class":482},[428,3894,504],{"class":441},[428,3896,889],{"class":445},[428,3898,498],{"class":441},[428,3900,627],{"class":445},[428,3902,514],{"class":441},[428,3904,3905,3907,3910,3912,3915,3917,3919,3921,3923,3925,3927,3929,3931,3934,3936,3938,3940],{"class":430,"line":458},[428,3906,521],{"class":520},[428,3908,3909],{"class":471}," requireFn",[428,3911,883],{"class":441},[428,3913,3914],{"class":478},"id",[428,3916,529],{"class":441},[428,3918,1315],{"class":445},[428,3920,504],{"class":441},[428,3922,2332],{"class":482},[428,3924,536],{"class":441},[428,3926,2064],{"class":520},[428,3928,1310],{"class":539},[428,3930,498],{"class":441},[428,3932,3933],{"class":471},"HasRole",[428,3935,475],{"class":441},[428,3937,3888],{"class":539},[428,3939,678],{"class":441},[428,3941,3942],{"class":441}," })\n",[428,3944,3945],{"class":430,"line":465},[428,3946,603],{"class":441},[428,3948,3949,3951,3953,3955,3957,3959,3961,3963,3965,3967,3969,3971,3973],{"class":430,"line":517},[428,3950,468],{"class":441},[428,3952,3909],{"class":471},[428,3954,475],{"class":441},[428,3956,3865],{"class":478},[428,3958,449],{"class":441},[428,3960,1315],{"class":445},[428,3962,504],{"class":441},[428,3964,2332],{"class":482},[428,3966,504],{"class":441},[428,3968,889],{"class":445},[428,3970,498],{"class":441},[428,3972,627],{"class":445},[428,3974,514],{"class":441},[428,3976,3977,3979,3981,3983,3985,3987,3989,3991,3993],{"class":430,"line":600},[428,3978,521],{"class":520},[428,3980,492],{"class":441},[428,3982,886],{"class":478},[428,3984,889],{"class":445},[428,3986,498],{"class":441},[428,3988,894],{"class":445},[428,3990,504],{"class":441},[428,3992,507],{"class":482},[428,3994,514],{"class":441},[428,3996,3997,4000,4002,4004,4006,4008,4010,4012],{"class":430,"line":606},[428,3998,3999],{"class":539},"        id ",[428,4001,1002],{"class":441},[428,4003,1362],{"class":471},[428,4005,475],{"class":441},[428,4007,886],{"class":539},[428,4009,498],{"class":441},[428,4011,501],{"class":471},[428,4013,868],{"class":441},[428,4015,4016,4018,4021,4023,4025,4027,4029,4032,4034,4037,4039,4041,4044,4046,4048],{"class":430,"line":636},[428,4017,3057],{"class":520},[428,4019,4020],{"class":539}," id ",[428,4022,2391],{"class":441},[428,4024,2379],{"class":441},[428,4026,536],{"class":441},[428,4028,2064],{"class":520},[428,4030,4031],{"class":539}," httperr",[428,4033,498],{"class":441},[428,4035,4036],{"class":471},"Unauthorized",[428,4038,475],{"class":441},[428,4040,3350],{"class":441},[428,4042,4043],{"class":1856},"authentication required",[428,4045,3350],{"class":441},[428,4047,504],{"class":441},[428,4049,597],{"class":441},[428,4051,4052,4054,4056,4058,4060,4062,4064,4066,4068,4070,4072,4075,4077,4079,4082,4084,4086],{"class":430,"line":683},[428,4053,3057],{"class":520},[428,4055,836],{"class":441},[428,4057,3865],{"class":471},[428,4059,475],{"class":441},[428,4061,3914],{"class":539},[428,4063,504],{"class":441},[428,4065,536],{"class":441},[428,4067,2064],{"class":520},[428,4069,4031],{"class":539},[428,4071,498],{"class":441},[428,4073,4074],{"class":471},"Forbidden",[428,4076,475],{"class":441},[428,4078,3350],{"class":441},[428,4080,4081],{"class":1856},"insufficient privileges",[428,4083,3350],{"class":441},[428,4085,504],{"class":441},[428,4087,597],{"class":441},[428,4089,4090,4092,4094,4096,4098],{"class":430,"line":954},[428,4091,2981],{"class":520},[428,4093,938],{"class":539},[428,4095,498],{"class":441},[428,4097,943],{"class":471},[428,4099,946],{"class":441},[428,4101,4102],{"class":430,"line":988},[428,4103,2609],{"class":441},[428,4105,4106],{"class":430,"line":1016},[428,4107,603],{"class":441},[348,4109,4110,691,4112,498],{},[352,4111,690],{},[360,4113,4114,395,4117],{"href":209},[380,4115,4116],{},"rbac.RequireRole",[380,4118,3857],{},[348,4120,4121,691,4123,4128],{},[352,4122,756],{},[360,4124,4127],{"href":4125,"rel":4126},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Fcodewalk\u002Ffunctions\u002F",[761],"Go blog: Function values"," (higher-order functions in Go).",[369,4130,4132],{"id":4131},"fail-closed-policy-enforcement","Fail-closed policy enforcement",[348,4134,4135,4137,4138,395,4141,4144,4145,4148],{},[352,4136,377],{}," the enforcer middleware generated from the ",[380,4139,4140],{},"@permission",[380,4142,4143],{},"@policy"," TypeSpec decorators ",[352,4146,4147],{},"rejects"," a marked operation if there is no registered policy for it in the registry: missing configuration is an error, not a silent pass-through.",[348,4150,4151,691,4153,4161,4162,498],{},[352,4152,690],{},[360,4154,4155,395,4158],{"href":214},[380,4156,4157],{},"policy.FiberEnforcer",[380,4159,4160],{},"HTTPEnforcer",", the generation chain: ",[360,4163,87],{"href":4164},"\u002Fen\u002Fconcepts\u002Fcodegen-pipeline#declarative-authorization-permission-and-policy",[348,4166,4167,691,4169,4174],{},[352,4168,756],{},[360,4170,4173],{"href":4171,"rel":4172},"https:\u002F\u002Fowasp.org\u002Fwww-community\u002FFail_securely",[761],"OWASP: Fail securely"," principle.",[364,4176,4178],{"id":4177},"codegen-and-tooling","Codegen and tooling",[369,4180,4182],{"id":4181},"anchor-comment-code-injection","Anchor-comment code injection",[348,4184,4185,4187,4188,4191],{},[352,4186,377],{}," the generators never overwrite an existing file: they only insert new code before a line marked with ",[380,4189,4190],{},"\u002F\u002F gpsystem:\u003Cname>",", keeping the anchor line in place so the next run can find it again. Idempotency is guaranteed by a whitespace-normalized comparison: if gofmt later re-aligned the inserted block, the generator still recognizes it in normalized form and doesn't duplicate it.",[419,4193,4195],{"className":421,"code":4194,"language":423,"meta":424,"style":424},"\u002F\u002F internal\u002Fgenerator\u002Fanchors.go\nfunc normalizeLine(s string) string { return strings.Join(strings.Fields(s), \" \") }\n\nfunc InsertIntoContent(content, anchor, snippet string) (updated string, inserted bool, err error) {\n    \u002F\u002F ... find the anchor line, adopt its indentation\n    if containsNormalizedBlock(content, block) {\n        return content, false, nil \u002F\u002F already present, no duplication\n    }\n    \u002F\u002F ... insert before the anchor\n}\n",[380,4196,4197,4202,4258,4262,4312,4317,4337,4356,4360,4365],{"__ignoreMap":424},[428,4198,4199],{"class":430,"line":431},[428,4200,4201],{"class":434},"\u002F\u002F internal\u002Fgenerator\u002Fanchors.go\n",[428,4203,4204,4206,4209,4211,4214,4216,4218,4220,4222,4224,4227,4229,4232,4234,4237,4239,4242,4244,4246,4249,4252,4254,4256],{"class":430,"line":438},[428,4205,468],{"class":441},[428,4207,4208],{"class":471}," normalizeLine",[428,4210,475],{"class":441},[428,4212,4213],{"class":478},"s",[428,4215,483],{"class":482},[428,4217,504],{"class":441},[428,4219,483],{"class":482},[428,4221,536],{"class":441},[428,4223,2064],{"class":520},[428,4225,4226],{"class":539}," strings",[428,4228,498],{"class":441},[428,4230,4231],{"class":471},"Join",[428,4233,475],{"class":441},[428,4235,4236],{"class":539},"strings",[428,4238,498],{"class":441},[428,4240,4241],{"class":471},"Fields",[428,4243,475],{"class":441},[428,4245,4213],{"class":539},[428,4247,4248],{"class":441},"),",[428,4250,4251],{"class":441}," \"",[428,4253,4251],{"class":441},[428,4255,504],{"class":441},[428,4257,597],{"class":441},[428,4259,4260],{"class":430,"line":458},[428,4261,462],{"emptyLinePlaceholder":461},[428,4263,4264,4266,4269,4271,4274,4276,4279,4281,4284,4286,4288,4290,4293,4295,4297,4300,4302,4304,4306,4308,4310],{"class":430,"line":465},[428,4265,468],{"class":441},[428,4267,4268],{"class":471}," InsertIntoContent",[428,4270,475],{"class":441},[428,4272,4273],{"class":478},"content",[428,4275,486],{"class":441},[428,4277,4278],{"class":478}," anchor",[428,4280,486],{"class":441},[428,4282,4283],{"class":478}," snippet",[428,4285,483],{"class":482},[428,4287,504],{"class":441},[428,4289,704],{"class":441},[428,4291,4292],{"class":478},"updated",[428,4294,483],{"class":482},[428,4296,486],{"class":441},[428,4298,4299],{"class":478}," inserted",[428,4301,2332],{"class":482},[428,4303,486],{"class":441},[428,4305,2543],{"class":478},[428,4307,507],{"class":482},[428,4309,504],{"class":441},[428,4311,514],{"class":441},[428,4313,4314],{"class":430,"line":517},[428,4315,4316],{"class":434},"    \u002F\u002F ... find the anchor line, adopt its indentation\n",[428,4318,4319,4321,4324,4326,4328,4330,4333,4335],{"class":430,"line":600},[428,4320,2571],{"class":520},[428,4322,4323],{"class":471}," containsNormalizedBlock",[428,4325,475],{"class":441},[428,4327,4273],{"class":539},[428,4329,486],{"class":441},[428,4331,4332],{"class":539}," block",[428,4334,504],{"class":441},[428,4336,514],{"class":441},[428,4338,4339,4341,4344,4346,4349,4351,4353],{"class":430,"line":606},[428,4340,2981],{"class":520},[428,4342,4343],{"class":539}," content",[428,4345,486],{"class":441},[428,4347,4348],{"class":3041}," false",[428,4350,486],{"class":441},[428,4352,2379],{"class":441},[428,4354,4355],{"class":434}," \u002F\u002F already present, no duplication\n",[428,4357,4358],{"class":430,"line":636},[428,4359,2609],{"class":441},[428,4361,4362],{"class":430,"line":683},[428,4363,4364],{"class":434},"    \u002F\u002F ... insert before the anchor\n",[428,4366,4367],{"class":430,"line":954},[428,4368,603],{"class":441},[348,4370,4371,691,4373,4381],{},[352,4372,690],{},[360,4374,4376,4377,4380],{"href":4375},"\u002Fen\u002Fconcepts\u002Fcodegen-pipeline#the-gpsystem-anchors","Codegen pipeline: the ",[380,4378,4379],{},"\u002F\u002F gpsystem:*"," anchors",": the full anchor list and which command inserts what.",[348,4383,4384,4386],{},[352,4385,756],{}," the pattern is a cousin of the \"marker comment\" technique used by scaffolding-generator ecosystems in other languages; there's no direct Go-specific precedent, this is the kit's own solution.",[369,4388,4390],{"id":4389},"plan-then-apply-generator-writes","Plan-then-apply generator writes",[348,4392,4393,4395,4396,4399,4400,4403,4404,4407],{},[352,4394,377],{}," the file generator first computes ",[352,4397,4398],{},"the entire plan"," of which files would conflict with existing ones, and only then writes anything: without ",[380,4401,4402],{},"--force",", a single conflict halts the whole run before a single byte hits disk. With ",[380,4405,4406],{},"--dry-run",", only the plan is printed; nothing happens.",[419,4409,4411],{"className":421,"code":4410,"language":423,"meta":424,"style":424},"\u002F\u002F internal\u002Fgenerator\u002Fengine.go: Apply\nvar conflicts []string\nfor _, f := range files {\n    if _, err := os.Stat(f.Path); err == nil {\n        conflicts = append(conflicts, f.Path)\n    }\n}\nif len(conflicts) > 0 && !e.Force {\n    return fmt.Errorf(\"refusing to overwrite existing files (use --force):\\n  %s\", ...)\n}\n",[380,4412,4413,4418,4431,4451,4490,4515,4519,4523,4556,4589],{"__ignoreMap":424},[428,4414,4415],{"class":430,"line":431},[428,4416,4417],{"class":434},"\u002F\u002F internal\u002Fgenerator\u002Fengine.go: Apply\n",[428,4419,4420,4422,4425,4428],{"class":430,"line":438},[428,4421,2013],{"class":441},[428,4423,4424],{"class":539}," conflicts ",[428,4426,4427],{"class":441},"[]",[428,4429,4430],{"class":482},"string\n",[428,4432,4433,4435,4437,4439,4442,4444,4446,4449],{"class":430,"line":458},[428,4434,991],{"class":520},[428,4436,994],{"class":539},[428,4438,486],{"class":441},[428,4440,4441],{"class":539}," f ",[428,4443,1002],{"class":441},[428,4445,1005],{"class":520},[428,4447,4448],{"class":539}," files ",[428,4450,846],{"class":441},[428,4452,4453,4455,4457,4459,4461,4463,4466,4468,4471,4473,4475,4477,4480,4482,4484,4486,4488],{"class":430,"line":465},[428,4454,2571],{"class":520},[428,4456,994],{"class":539},[428,4458,486],{"class":441},[428,4460,3006],{"class":539},[428,4462,1002],{"class":441},[428,4464,4465],{"class":539}," os",[428,4467,498],{"class":441},[428,4469,4470],{"class":471},"Stat",[428,4472,475],{"class":441},[428,4474,1186],{"class":539},[428,4476,498],{"class":441},[428,4478,4479],{"class":539},"Path",[428,4481,2972],{"class":441},[428,4483,3006],{"class":539},[428,4485,2391],{"class":441},[428,4487,2379],{"class":441},[428,4489,514],{"class":441},[428,4491,4492,4495,4497,4499,4501,4504,4506,4509,4511,4513],{"class":430,"line":517},[428,4493,4494],{"class":539},"        conflicts ",[428,4496,548],{"class":441},[428,4498,551],{"class":471},[428,4500,475],{"class":441},[428,4502,4503],{"class":539},"conflicts",[428,4505,486],{"class":441},[428,4507,4508],{"class":539}," f",[428,4510,498],{"class":441},[428,4512,4479],{"class":539},[428,4514,455],{"class":441},[428,4516,4517],{"class":430,"line":600},[428,4518,2609],{"class":441},[428,4520,4521],{"class":430,"line":606},[428,4522,603],{"class":441},[428,4524,4525,4527,4530,4532,4534,4536,4539,4542,4544,4546,4549,4551,4554],{"class":430,"line":636},[428,4526,792],{"class":520},[428,4528,4529],{"class":471}," len",[428,4531,475],{"class":441},[428,4533,4503],{"class":539},[428,4535,504],{"class":441},[428,4537,4538],{"class":441}," >",[428,4540,4541],{"class":2468}," 0",[428,4543,2382],{"class":441},[428,4545,836],{"class":441},[428,4547,4548],{"class":539},"e",[428,4550,498],{"class":441},[428,4552,4553],{"class":539},"Force ",[428,4555,846],{"class":441},[428,4557,4558,4560,4563,4565,4568,4570,4572,4575,4578,4582,4584,4586],{"class":430,"line":683},[428,4559,521],{"class":520},[428,4561,4562],{"class":539}," fmt",[428,4564,498],{"class":441},[428,4566,4567],{"class":471},"Errorf",[428,4569,475],{"class":441},[428,4571,3350],{"class":441},[428,4573,4574],{"class":1856},"refusing to overwrite existing files (use --force):",[428,4576,4577],{"class":539},"\\n",[428,4579,4581],{"class":4580},"swJcz","  %s",[428,4583,3350],{"class":441},[428,4585,486],{"class":441},[428,4587,4588],{"class":441}," ...)\n",[428,4590,4591],{"class":430,"line":954},[428,4592,603],{"class":441},[348,4594,4595,691,4597,4602,4603,395,4606,4609],{},[352,4596,690],{},[360,4598,4599],{"href":272},[380,4600,4601],{},"internal\u002Fgenerator.Engine.Apply",": every ",[380,4604,4605],{},"new",[380,4607,4608],{},"add"," command writes through this.",[348,4611,4612,4614,4615,395,4618,4621,4622,498],{},[352,4613,756],{}," the pattern is a cousin of Terraform's ",[380,4616,4617],{},"plan",[380,4619,4620],{},"apply"," split: ",[360,4623,4626],{"href":4624,"rel":4625},"https:\u002F\u002Fdeveloper.hashicorp.com\u002Fterraform\u002Fcli\u002Fcommands\u002Fplan",[761],"terraform.io: Plan",[369,4628,4630],{"id":4629},"spec-first-codegen","Spec-first codegen",[348,4632,4633,4635],{},[352,4634,377],{}," the single source of truth for the API contract is a declarative specification (TypeSpec), from which OpenAPI is deterministically compiled, and from that a typed, \"strict\" server interface: a spec mismatch is a compile error, not a runtime surprise.",[348,4637,4638,691,4640,4642,4643,4646],{},[352,4639,690],{},[360,4641,87],{"href":88},": the full ",[380,4644,4645],{},"TypeSpec → OpenAPI 3.0 → oapi-codegen"," chain, with a per-engine template set.",[348,4648,4649,691,4651,708,4656,498],{},[352,4650,756],{},[360,4652,4655],{"href":4653,"rel":4654},"https:\u002F\u002Ftypespec.io\u002Fdocs",[761],"typespec.io: What is TypeSpec",[360,4657,4660],{"href":4658,"rel":4659},"https:\u002F\u002Fgithub.com\u002Foapi-codegen\u002Foapi-codegen",[761],"oapi-codegen",[364,4662,4664],{"id":4663},"architectural-principles","Architectural principles",[348,4666,4667,4668,4670],{},"Two patterns aren't tied to a single package: they explain how the kit as a whole is put together, and the ",[360,4669,68],{"href":69}," page covers them in full; here only their name and link:",[4672,4673,4674,4683],"ul",{},[4675,4676,4677,4682],"li",{},[352,4678,4679],{},[360,4680,4681],{"href":1732},"\"Centralize, don't abstract\"",": the kit concentrates access to third-party dependencies (Fiber, pgx, bun, asynq) into individual packages, but their types flow freely through the public API; there's no kit-specific wrapper abstraction to learn.",[4675,4684,4685,3564,4688,708,4693,708,4697,4702,4703,4706,4707,4710],{},[352,4686,4687],{},"Interface segregation \u002F dependency inversion at the three stated exceptions",[360,4689,4690],{"href":249},[380,4691,4692],{},"storage.Store",[360,4694,4695],{"href":238},[380,4696,915],{},[360,4698,4699],{"href":253},[380,4700,4701],{},"mail.Mailer",", wherever the kit does hide a dependency behind an interface, it's always for ",[352,4704,4705],{},"dispensability"," or ",[352,4708,4709],{},"replaceability",", with a documented reason.",[364,4712,4714],{"id":4713},"where-to-next","Where to next",[4672,4716,4717,4722,4727],{},[4675,4718,4719,4721],{},[360,4720,57],{"href":58}," lists every underlying library with its version and official documentation.",[4675,4723,4724,4726],{},[360,4725,68],{"href":69}," gives the package map and the design principle this page breaks down at the pattern level.",[4675,4728,4729,698,4731,4733],{},[360,4730,78],{"href":79},[360,4732,87],{"href":88}," cover the two most deeply detailed pattern groups (errs\u002Fhttperr, and anchor injection\u002Fspec-first codegen) in full.",[4735,4736,4737],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":424,"searchDepth":438,"depth":438,"links":4739},[4740,4747,4755,4760,4765,4770,4775,4776],{"id":366,"depth":438,"text":367,"children":4741},[4742,4743,4744,4745,4746],{"id":371,"depth":458,"text":372},{"id":796,"depth":458,"text":797},{"id":1060,"depth":458,"text":1061},{"id":1247,"depth":458,"text":1248},{"id":1464,"depth":458,"text":1465},{"id":1795,"depth":438,"text":1796,"children":4748},[4749,4750,4752,4754],{"id":1799,"depth":458,"text":1800},{"id":1977,"depth":458,"text":4751},"Cascading errors.As mapping",{"id":2229,"depth":458,"text":4753},"Sentinel definition with a custom Is",{"id":2427,"depth":458,"text":2428},{"id":2649,"depth":438,"text":133,"children":4756},[4757,4758,4759],{"id":2652,"depth":458,"text":2653},{"id":2870,"depth":458,"text":2871},{"id":3252,"depth":458,"text":3253},{"id":3398,"depth":438,"text":3399,"children":4761},[4762,4763,4764],{"id":3402,"depth":458,"text":3403},{"id":3520,"depth":458,"text":3521},{"id":3586,"depth":458,"text":3587},{"id":3686,"depth":438,"text":197,"children":4766},[4767,4768,4769],{"id":3689,"depth":458,"text":3690},{"id":3845,"depth":458,"text":3846},{"id":4131,"depth":458,"text":4132},{"id":4177,"depth":438,"text":4178,"children":4771},[4772,4773,4774],{"id":4181,"depth":458,"text":4182},{"id":4389,"depth":458,"text":4390},{"id":4629,"depth":458,"text":4630},{"id":4663,"depth":438,"text":4664},{"id":4713,"depth":438,"text":4714},"A catalog of recurring design patterns in the kit: with code, where to find them, and the canonical external source.","md",null,{},{"icon":95},{"title":92,"description":4777},"mWSF8nNfgXeh4ekZBf2KqGgmyQGRAMrHWtdihRi865U",[4785,4787],{"title":87,"path":88,"stem":89,"description":4786,"icon":90,"children":-1},"TypeSpec → OpenAPI 3.0 → strict, typed server code: for Fiber and chi alike.",{"title":103,"path":104,"stem":105,"description":4788,"icon":106,"children":-1},"The engine-neutral server.Config (timeouts, CORS, body limit) and the shared lifecycle both engines build on.",1784668706945]