[{"data":1,"prerenderedAt":2426},["ShallowReactive",2],{"navigation_docs_en":3,"-en-concepts-codegen-pipeline":342,"-en-concepts-codegen-pipeline-surround":2421},[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":87,"body":344,"description":2414,"extension":2415,"links":2416,"meta":2417,"navigation":2418,"path":88,"seo":2419,"stem":89,"__hash__":2420},"docs_en\u002Fen\u002F2.concepts\u002F5.codegen-pipeline.md",{"type":345,"value":346,"toc":2399},"minimark",[347,356,367,438,443,513,532,536,556,684,695,716,796,809,1023,1032,1612,1631,1635,1640,1659,1712,1716,1751,1755,1776,1784,1788,1809,1815,1818,1922,1936,1940,1947,2081,2138,2145,2152,2306,2317,2321,2371,2375,2395],[348,349,350,351,355],"p",{},"The API contract of a gpsystem project is ",[352,353,354],"strong",{},"spec-first",": the source of truth is TypeSpec, compiled to OpenAPI, from which a strict, typed server interface is generated. So a spec deviation is not a runtime surprise but a compile error. The pipeline is the same for both engines; only the template set differs:",[357,358,363],"pre",{"className":359,"code":361,"language":362},[360],"language-text","spec\u002Ftypespec\u002F**.tsp ──tsp compile──▶ api\u002Fopenapi\u002F\u003CNamespace>.yaml\n        │                                     │\n        │ source of truth                     │ oapi-codegen v2.8.x\n        │ (by hand + generators)              │ + engine-specific templates\n        ▼                                     ▼\n  gpsystem CLI                internal\u002Fmodules\u002F\u003Cm>\u002Fsurfaces\u002F\u003Cs>\u002Fhttp\u002Fgen\u002Fapi.gen.go\n","text",[364,365,361],"code",{"__ignoreMap":366},"",[348,368,369,372,373,376,377,376,380,376,383,386,387,390,391,394,395,398,399,402,403,406,407,410,411,414,415,418,419,422,423,426,427,430,431,433,434,437],{},[364,370,371],{},"mise run generate"," runs the whole chain: ",[364,374,375],{},"tsp compile"," → ",[364,378,379],{},"go generate .\u002F...",[364,381,382],{},"go mod tidy",[364,384,385],{},"go build .\u002F...",". The ",[364,388,389],{},"go:generate"," directive lives in the surface's ",[364,392,393],{},"http\u002Fhandler.go"," and drives the ",[364,396,397],{},"api\u002Foapi-codegen\u002F\u003Cmodule>-\u003Csurface>.yaml"," config (because of the ",[364,400,401],{},"surfaces\u002F"," wrapper, the relative paths it references go six levels up: ",[364,404,405],{},"..\u002F..\u002F..\u002F..\u002F..\u002F..\u002F","). That file only holds the ",[364,408,409],{},"Handler"," struct, ",[364,412,413],{},"New"," and the interface assertion; the handler methods themselves live in one ",[352,416,417],{},"category file per group"," (",[364,420,421],{},"\u003Cmodule>_handler.go",", or ",[364,424,425],{},"\u003Cmodule>_\u003Cgroup>_handler.go"," for a category like ",[364,428,429],{},"notifications","), so an ",[364,432,324],{}," run groups by operation rather than scattering one file per function (see ",[435,436,324],"a",{"href":325},").",[439,440,442],"h2",{"id":441},"typespec-conventions","TypeSpec conventions",[444,445,446,481,506],"ul",{},[447,448,449,450,453,454,457,458,461,462,465,466,469,470,473,474,477,478,437],"li",{},"Each surface is its own ",[364,451,452],{},"@service"," with ",[364,455,456],{},"@server(\"\u002Fapi\u002Fv1\u003Cbase>\")",". The emitter names the output file after the ",[352,459,460],{},"namespace",", so namespaces are deterministic: ",[364,463,464],{},"Project.Module"," for the ",[364,467,468],{},"api"," surface, ",[364,471,472],{},"Project.Module\u003CSurface>"," for everything else (in the shop: ",[364,475,476],{},"Shop.Shop"," and ",[364,479,480],{},"Shop.ShopAdmin",[447,482,483,486,487,418,490,492,493,486,496,499,500,418,503,505],{},[364,484,485],{},"shared\u002Ferrors.tsp"," mirrors ",[364,488,489],{},"httperr.Problem",[435,491,78],{"href":79},"); ",[364,494,495],{},"shared\u002Fpaginator.tsp",[364,497,498],{},"paginate.Page[T]"," \u002F ",[364,501,502],{},"paginate.CursorPage[T]",[435,504,128],{"href":129},"). Keep them in sync with the Go side.",[447,507,508,509,512],{},"The OpenAPI output is pinned to ",[352,510,511],{},"3.0"," (oapi-codegen does not read 3.1).",[514,515,517,518,520,521,524,525,527,528,531],"warning",{"title":516},"Namespaces must be siblings","Never nest one ",[364,519,452],{}," namespace inside another (e.g. ",[364,522,523],{},"Shop.Shop.Admin"," under ",[364,526,476],{},"). TypeSpec merges their routes and fails with ",[364,529,530],{},"duplicate-operation"," errors. The generators always emit sibling namespaces.",[439,533,535],{"id":534},"one-operations-journey-the-shops-product-list","One operation's journey: the shop's product list",[348,537,538,541,542,544,545,548,549,551,552,555],{},[352,539,540],{},"1. The contract."," In the shop ",[364,543,468],{}," surface's ",[364,546,547],{},".tsp",", ",[364,550,324],{}," inserts at the ",[364,553,554],{},"\u002F\u002F gpsystem:operations"," anchor, but you can also write to the same spot by hand:",[357,557,562],{"className":558,"code":559,"filename":560,"language":561,"meta":366,"style":366},"language-tsp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","@tag(\"shop\")\ninterface ShopApi {\n  \u002F\u002F gpsystem:operations\n  @get\n  @route(\"\u002Fproducts\")\n  @operationId(\"ListProducts\")\n  @summary(\"List products\")\n  listProducts(...CursorQuery): CursorPage\u003CProduct>;\n}\n","spec\u002Ftypespec\u002Fmodules\u002Fshop\u002Fapi.tsp","tsp",[364,563,564,584,598,605,611,624,637,650,678],{"__ignoreMap":366},[565,566,569,573,577,581],"span",{"class":567,"line":568},"line",1,[565,570,572],{"class":571},"swJcz","@tag",[565,574,576],{"class":575},"sMK4o","(",[565,578,580],{"class":579},"sfazB","\"shop\"",[565,582,583],{"class":575},")\n",[565,585,587,591,595],{"class":567,"line":586},2,[565,588,590],{"class":589},"sbssI","interface",[565,592,594],{"class":593},"sBMFI"," ShopApi",[565,596,597],{"class":575}," {\n",[565,599,601],{"class":567,"line":600},3,[565,602,604],{"class":603},"sHwdD","  \u002F\u002F gpsystem:operations\n",[565,606,608],{"class":567,"line":607},4,[565,609,610],{"class":571},"  @get\n",[565,612,614,617,619,622],{"class":567,"line":613},5,[565,615,616],{"class":571},"  @route",[565,618,576],{"class":575},[565,620,621],{"class":579},"\"\u002Fproducts\"",[565,623,583],{"class":575},[565,625,627,630,632,635],{"class":567,"line":626},6,[565,628,629],{"class":571},"  @operationId",[565,631,576],{"class":575},[565,633,634],{"class":579},"\"ListProducts\"",[565,636,583],{"class":575},[565,638,640,643,645,648],{"class":567,"line":639},7,[565,641,642],{"class":571},"  @summary",[565,644,576],{"class":575},[565,646,647],{"class":579},"\"List products\"",[565,649,583],{"class":575},[565,651,653,657,660,663,666,669,672,675],{"class":567,"line":652},8,[565,654,656],{"class":655},"s2Zo4","  listProducts",[565,658,659],{"class":575},"(...",[565,661,662],{"class":593},"CursorQuery",[565,664,665],{"class":575},"):",[565,667,668],{"class":593}," CursorPage",[565,670,671],{"class":575},"\u003C",[565,673,674],{"class":593},"Product",[565,676,677],{"class":575},">;\n",[565,679,681],{"class":567,"line":680},9,[565,682,683],{"class":575},"}\n",[348,685,686,499,688,691,692,694],{},[364,687,662],{},[364,689,690],{},"CursorPage\u003CT>"," come from the scaffolded ",[364,693,495],{},": a cursor-paginated list in a single line.",[348,696,697,700,701,703,704,707,708,711,712,715],{},[352,698,699],{},"2. The generated interface."," After ",[364,702,375],{},", oapi-codegen emits ",[352,705,706],{},"the same"," strict interface into the ",[364,709,710],{},"gen"," package on both engines: the request\u002Fresponse type names carry no ",[364,713,714],{},"Object"," suffix, and the signature is engine-independent:",[357,717,722],{"className":718,"code":719,"filename":720,"language":721,"meta":366,"style":366},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","type StrictServerInterface interface {\n    \u002F\u002F (GET \u002Fproducts)\n    ListProducts(ctx context.Context, request ListProductsRequest) (ListProductsResponse, error)\n    \u002F\u002F ...\n}\n","internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fgen\u002Fapi.gen.go (generated, do not edit)","go",[364,723,724,737,742,787,792],{"__ignoreMap":366},[565,725,726,729,732,735],{"class":567,"line":568},[565,727,728],{"class":575},"type",[565,730,731],{"class":593}," StrictServerInterface",[565,733,734],{"class":575}," interface",[565,736,597],{"class":575},[565,738,739],{"class":567,"line":586},[565,740,741],{"class":603},"    \u002F\u002F (GET \u002Fproducts)\n",[565,743,744,747,749,753,756,759,762,765,768,771,774,776,779,781,785],{"class":567,"line":600},[565,745,746],{"class":655},"    ListProducts",[565,748,576],{"class":575},[565,750,752],{"class":751},"sHdIc","ctx",[565,754,755],{"class":593}," context",[565,757,758],{"class":575},".",[565,760,761],{"class":593},"Context",[565,763,764],{"class":575},",",[565,766,767],{"class":751}," request",[565,769,770],{"class":593}," ListProductsRequest",[565,772,773],{"class":575},")",[565,775,418],{"class":575},[565,777,778],{"class":593},"ListProductsResponse",[565,780,764],{"class":575},[565,782,784],{"class":783},"spNyl"," error",[565,786,583],{"class":575},[565,788,789],{"class":567,"line":607},[565,790,791],{"class":603},"    \u002F\u002F ...\n",[565,793,794],{"class":567,"line":613},[565,795,683],{"class":575},[348,797,798,801,802,804,805,808],{},[352,799,800],{},"3. The handler."," This one you write (the skeleton comes from ",[364,803,324],{},"), and because the interface is engine-independent, ",[352,806,807],{},"so is the handler",":",[357,810,813],{"className":718,"code":811,"filename":812,"language":721,"meta":366,"style":366},"func (h *Handler) ListProducts(ctx context.Context, req gen.ListProductsRequest) (gen.ListProductsResponse, error) {\n    page, err := h.svc.ListProducts(ctx, req.Params.Cursor, req.Params.Limit)\n    if err != nil {\n        return nil, service.ErrListProducts.Wrap(err, \"api: list products\")\n    }\n    return gen.ListProducts200JSONResponse(page), nil\n}\n","internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fhttp\u002Fshop_handler.go",[364,814,815,876,936,952,991,996,1019],{"__ignoreMap":366},[565,816,817,820,822,825,828,830,832,835,837,839,841,843,845,847,850,853,855,858,860,862,864,866,868,870,872,874],{"class":567,"line":568},[565,818,819],{"class":575},"func",[565,821,418],{"class":575},[565,823,824],{"class":751},"h ",[565,826,827],{"class":575},"*",[565,829,409],{"class":593},[565,831,773],{"class":575},[565,833,834],{"class":655}," ListProducts",[565,836,576],{"class":575},[565,838,752],{"class":751},[565,840,755],{"class":593},[565,842,758],{"class":575},[565,844,761],{"class":593},[565,846,764],{"class":575},[565,848,849],{"class":751}," req",[565,851,852],{"class":593}," gen",[565,854,758],{"class":575},[565,856,857],{"class":593},"ListProductsRequest",[565,859,773],{"class":575},[565,861,418],{"class":575},[565,863,710],{"class":593},[565,865,758],{"class":575},[565,867,778],{"class":593},[565,869,764],{"class":575},[565,871,784],{"class":783},[565,873,773],{"class":575},[565,875,597],{"class":575},[565,877,878,882,884,887,890,893,895,898,900,903,905,907,909,911,913,916,918,921,923,925,927,929,931,934],{"class":567,"line":586},[565,879,881],{"class":880},"sTEyZ","    page",[565,883,764],{"class":575},[565,885,886],{"class":880}," err ",[565,888,889],{"class":575},":=",[565,891,892],{"class":880}," h",[565,894,758],{"class":575},[565,896,897],{"class":880},"svc",[565,899,758],{"class":575},[565,901,902],{"class":655},"ListProducts",[565,904,576],{"class":575},[565,906,752],{"class":880},[565,908,764],{"class":575},[565,910,849],{"class":880},[565,912,758],{"class":575},[565,914,915],{"class":880},"Params",[565,917,758],{"class":575},[565,919,920],{"class":880},"Cursor",[565,922,764],{"class":575},[565,924,849],{"class":880},[565,926,758],{"class":575},[565,928,915],{"class":880},[565,930,758],{"class":575},[565,932,933],{"class":880},"Limit",[565,935,583],{"class":575},[565,937,938,942,944,947,950],{"class":567,"line":600},[565,939,941],{"class":940},"s7zQu","    if",[565,943,886],{"class":880},[565,945,946],{"class":575},"!=",[565,948,949],{"class":575}," nil",[565,951,597],{"class":575},[565,953,954,957,960,963,965,968,970,973,975,978,980,983,986,989],{"class":567,"line":607},[565,955,956],{"class":940},"        return",[565,958,959],{"class":575}," nil,",[565,961,962],{"class":880}," service",[565,964,758],{"class":575},[565,966,967],{"class":880},"ErrListProducts",[565,969,758],{"class":575},[565,971,972],{"class":655},"Wrap",[565,974,576],{"class":575},[565,976,977],{"class":880},"err",[565,979,764],{"class":575},[565,981,982],{"class":575}," \"",[565,984,985],{"class":579},"api: list products",[565,987,988],{"class":575},"\"",[565,990,583],{"class":575},[565,992,993],{"class":567,"line":613},[565,994,995],{"class":575},"    }\n",[565,997,998,1001,1003,1005,1008,1010,1013,1016],{"class":567,"line":626},[565,999,1000],{"class":940},"    return",[565,1002,852],{"class":880},[565,1004,758],{"class":575},[565,1006,1007],{"class":655},"ListProducts200JSONResponse",[565,1009,576],{"class":575},[565,1011,1012],{"class":880},"page",[565,1014,1015],{"class":575},"),",[565,1017,1018],{"class":575}," nil\n",[565,1020,1021],{"class":567,"line":639},[565,1022,683],{"class":575},[348,1024,1025,1028,1029,808],{},[352,1026,1027],{},"4. The wiring."," The one place where the two engines genuinely differ is the generated wiring in ",[364,1030,1031],{},"register.go",[1033,1034,1035,1265],"code-group",{},[357,1036,1039],{"className":718,"code":1037,"filename":1038,"language":721,"meta":366,"style":366},"func RegisterApi(router fiber.Router, deps Dependencies) {\n    apiSvc := apiservice.New()\n    apiHandler := apihttp.New(apiSvc)\n    apiPolicies := apipolicy.New()\n    apiGroup := router.Group(\"\u002Fshop\")\n    shopapigen.RegisterHandlersWithOptions(apiGroup, shopapigen.NewStrictHandler(apiHandler, []shopapigen.StrictMiddlewareFunc{\n        kitpolicy.FiberEnforcer[shopapigen.StrictHandlerFunc](apiPolicies, shopapigen.PermissionByOperation, shopapigen.PolicyByOperation),\n    }), shopapigen.FiberServerOptions{})\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go (Fiber)",[364,1040,1041,1073,1090,1111,1127,1153,1199,1246,1261],{"__ignoreMap":366},[565,1042,1043,1045,1048,1050,1053,1056,1058,1061,1063,1066,1069,1071],{"class":567,"line":568},[565,1044,819],{"class":575},[565,1046,1047],{"class":655}," RegisterApi",[565,1049,576],{"class":575},[565,1051,1052],{"class":751},"router",[565,1054,1055],{"class":593}," fiber",[565,1057,758],{"class":575},[565,1059,1060],{"class":593},"Router",[565,1062,764],{"class":575},[565,1064,1065],{"class":751}," deps",[565,1067,1068],{"class":593}," Dependencies",[565,1070,773],{"class":575},[565,1072,597],{"class":575},[565,1074,1075,1078,1080,1083,1085,1087],{"class":567,"line":586},[565,1076,1077],{"class":880},"    apiSvc ",[565,1079,889],{"class":575},[565,1081,1082],{"class":880}," apiservice",[565,1084,758],{"class":575},[565,1086,413],{"class":655},[565,1088,1089],{"class":575},"()\n",[565,1091,1092,1095,1097,1100,1102,1104,1106,1109],{"class":567,"line":600},[565,1093,1094],{"class":880},"    apiHandler ",[565,1096,889],{"class":575},[565,1098,1099],{"class":880}," apihttp",[565,1101,758],{"class":575},[565,1103,413],{"class":655},[565,1105,576],{"class":575},[565,1107,1108],{"class":880},"apiSvc",[565,1110,583],{"class":575},[565,1112,1113,1116,1118,1121,1123,1125],{"class":567,"line":607},[565,1114,1115],{"class":880},"    apiPolicies ",[565,1117,889],{"class":575},[565,1119,1120],{"class":880}," apipolicy",[565,1122,758],{"class":575},[565,1124,413],{"class":655},[565,1126,1089],{"class":575},[565,1128,1129,1132,1134,1137,1139,1142,1144,1146,1149,1151],{"class":567,"line":613},[565,1130,1131],{"class":880},"    apiGroup ",[565,1133,889],{"class":575},[565,1135,1136],{"class":880}," router",[565,1138,758],{"class":575},[565,1140,1141],{"class":655},"Group",[565,1143,576],{"class":575},[565,1145,988],{"class":575},[565,1147,1148],{"class":579},"\u002Fshop",[565,1150,988],{"class":575},[565,1152,583],{"class":575},[565,1154,1155,1158,1160,1163,1165,1168,1170,1173,1175,1178,1180,1183,1185,1188,1191,1193,1196],{"class":567,"line":626},[565,1156,1157],{"class":880},"    shopapigen",[565,1159,758],{"class":575},[565,1161,1162],{"class":655},"RegisterHandlersWithOptions",[565,1164,576],{"class":575},[565,1166,1167],{"class":880},"apiGroup",[565,1169,764],{"class":575},[565,1171,1172],{"class":880}," shopapigen",[565,1174,758],{"class":575},[565,1176,1177],{"class":655},"NewStrictHandler",[565,1179,576],{"class":575},[565,1181,1182],{"class":880},"apiHandler",[565,1184,764],{"class":575},[565,1186,1187],{"class":575}," []",[565,1189,1190],{"class":593},"shopapigen",[565,1192,758],{"class":575},[565,1194,1195],{"class":593},"StrictMiddlewareFunc",[565,1197,1198],{"class":575},"{\n",[565,1200,1201,1204,1206,1209,1212,1214,1216,1219,1222,1225,1227,1229,1231,1234,1236,1238,1240,1243],{"class":567,"line":639},[565,1202,1203],{"class":880},"        kitpolicy",[565,1205,758],{"class":575},[565,1207,1208],{"class":655},"FiberEnforcer",[565,1210,1211],{"class":575},"[",[565,1213,1190],{"class":593},[565,1215,758],{"class":575},[565,1217,1218],{"class":593},"StrictHandlerFunc",[565,1220,1221],{"class":575},"](",[565,1223,1224],{"class":880},"apiPolicies",[565,1226,764],{"class":575},[565,1228,1172],{"class":880},[565,1230,758],{"class":575},[565,1232,1233],{"class":880},"PermissionByOperation",[565,1235,764],{"class":575},[565,1237,1172],{"class":880},[565,1239,758],{"class":575},[565,1241,1242],{"class":880},"PolicyByOperation",[565,1244,1245],{"class":575},"),\n",[565,1247,1248,1251,1253,1255,1258],{"class":567,"line":652},[565,1249,1250],{"class":575},"    }),",[565,1252,1172],{"class":593},[565,1254,758],{"class":575},[565,1256,1257],{"class":593},"FiberServerOptions",[565,1259,1260],{"class":575},"{})\n",[565,1262,1263],{"class":567,"line":680},[565,1264,683],{"class":575},[357,1266,1269],{"className":718,"code":1267,"filename":1268,"language":721,"meta":366,"style":366},"func RegisterApi(router chi.Router, deps Dependencies) {\n    apiSvc := apiservice.New()\n    apiHandler := apihttp.New(apiSvc)\n    apiPolicies := apipolicy.New()\n    router.Route(\"\u002Fshop\", func(r chi.Router) {\n        shopapigen.HandlerWithOptions(shopapigen.NewStrictHandlerWithOptions(\n            apiHandler,\n            []shopapigen.StrictMiddlewareFunc{\n                chix.StrictValidator[shopapigen.StrictHandlerFunc](nil),\n                \u002F\u002F Last = outermost: authorization runs before validation.\n                kitpolicy.HTTPEnforcer[shopapigen.StrictHandlerFunc](apiPolicies, shopapigen.PermissionByOperation, shopapigen.PolicyByOperation),\n            },\n            shopapigen.StrictHTTPServerOptions{\n                RequestErrorHandlerFunc:  httperr.WriteBadRequest,\n                ResponseErrorHandlerFunc: httperr.WriteError,\n            },\n        ), shopapigen.ChiServerOptions{\n            BaseRouter:       r,\n            ErrorHandlerFunc: httperr.WriteBadRequest,\n        })\n    })\n}\n","internal\u002Fmodules\u002Fshop\u002Fregister.go (chi)",[364,1270,1271,1298,1312,1330,1344,1380,1402,1410,1423,1444,1450,1491,1497,1510,1528,1546,1551,1566,1579,1595,1601,1607],{"__ignoreMap":366},[565,1272,1273,1275,1277,1279,1281,1284,1286,1288,1290,1292,1294,1296],{"class":567,"line":568},[565,1274,819],{"class":575},[565,1276,1047],{"class":655},[565,1278,576],{"class":575},[565,1280,1052],{"class":751},[565,1282,1283],{"class":593}," chi",[565,1285,758],{"class":575},[565,1287,1060],{"class":593},[565,1289,764],{"class":575},[565,1291,1065],{"class":751},[565,1293,1068],{"class":593},[565,1295,773],{"class":575},[565,1297,597],{"class":575},[565,1299,1300,1302,1304,1306,1308,1310],{"class":567,"line":586},[565,1301,1077],{"class":880},[565,1303,889],{"class":575},[565,1305,1082],{"class":880},[565,1307,758],{"class":575},[565,1309,413],{"class":655},[565,1311,1089],{"class":575},[565,1313,1314,1316,1318,1320,1322,1324,1326,1328],{"class":567,"line":600},[565,1315,1094],{"class":880},[565,1317,889],{"class":575},[565,1319,1099],{"class":880},[565,1321,758],{"class":575},[565,1323,413],{"class":655},[565,1325,576],{"class":575},[565,1327,1108],{"class":880},[565,1329,583],{"class":575},[565,1331,1332,1334,1336,1338,1340,1342],{"class":567,"line":607},[565,1333,1115],{"class":880},[565,1335,889],{"class":575},[565,1337,1120],{"class":880},[565,1339,758],{"class":575},[565,1341,413],{"class":655},[565,1343,1089],{"class":575},[565,1345,1346,1349,1351,1354,1356,1358,1360,1362,1364,1367,1370,1372,1374,1376,1378],{"class":567,"line":613},[565,1347,1348],{"class":880},"    router",[565,1350,758],{"class":575},[565,1352,1353],{"class":655},"Route",[565,1355,576],{"class":575},[565,1357,988],{"class":575},[565,1359,1148],{"class":579},[565,1361,988],{"class":575},[565,1363,764],{"class":575},[565,1365,1366],{"class":575}," func(",[565,1368,1369],{"class":751},"r",[565,1371,1283],{"class":593},[565,1373,758],{"class":575},[565,1375,1060],{"class":593},[565,1377,773],{"class":575},[565,1379,597],{"class":575},[565,1381,1382,1385,1387,1390,1392,1394,1396,1399],{"class":567,"line":626},[565,1383,1384],{"class":880},"        shopapigen",[565,1386,758],{"class":575},[565,1388,1389],{"class":655},"HandlerWithOptions",[565,1391,576],{"class":575},[565,1393,1190],{"class":880},[565,1395,758],{"class":575},[565,1397,1398],{"class":655},"NewStrictHandlerWithOptions",[565,1400,1401],{"class":575},"(\n",[565,1403,1404,1407],{"class":567,"line":639},[565,1405,1406],{"class":880},"            apiHandler",[565,1408,1409],{"class":575},",\n",[565,1411,1412,1415,1417,1419,1421],{"class":567,"line":652},[565,1413,1414],{"class":575},"            []",[565,1416,1190],{"class":593},[565,1418,758],{"class":575},[565,1420,1195],{"class":593},[565,1422,1198],{"class":575},[565,1424,1425,1428,1430,1433,1435,1437,1439,1441],{"class":567,"line":680},[565,1426,1427],{"class":880},"                chix",[565,1429,758],{"class":575},[565,1431,1432],{"class":655},"StrictValidator",[565,1434,1211],{"class":575},[565,1436,1190],{"class":593},[565,1438,758],{"class":575},[565,1440,1218],{"class":593},[565,1442,1443],{"class":575},"](nil),\n",[565,1445,1447],{"class":567,"line":1446},10,[565,1448,1449],{"class":603},"                \u002F\u002F Last = outermost: authorization runs before validation.\n",[565,1451,1453,1456,1458,1461,1463,1465,1467,1469,1471,1473,1475,1477,1479,1481,1483,1485,1487,1489],{"class":567,"line":1452},11,[565,1454,1455],{"class":880},"                kitpolicy",[565,1457,758],{"class":575},[565,1459,1460],{"class":655},"HTTPEnforcer",[565,1462,1211],{"class":575},[565,1464,1190],{"class":593},[565,1466,758],{"class":575},[565,1468,1218],{"class":593},[565,1470,1221],{"class":575},[565,1472,1224],{"class":880},[565,1474,764],{"class":575},[565,1476,1172],{"class":880},[565,1478,758],{"class":575},[565,1480,1233],{"class":880},[565,1482,764],{"class":575},[565,1484,1172],{"class":880},[565,1486,758],{"class":575},[565,1488,1242],{"class":880},[565,1490,1245],{"class":575},[565,1492,1494],{"class":567,"line":1493},12,[565,1495,1496],{"class":575},"            },\n",[565,1498,1500,1503,1505,1508],{"class":567,"line":1499},13,[565,1501,1502],{"class":593},"            shopapigen",[565,1504,758],{"class":575},[565,1506,1507],{"class":593},"StrictHTTPServerOptions",[565,1509,1198],{"class":575},[565,1511,1513,1516,1518,1521,1523,1526],{"class":567,"line":1512},14,[565,1514,1515],{"class":880},"                RequestErrorHandlerFunc",[565,1517,808],{"class":575},[565,1519,1520],{"class":880},"  httperr",[565,1522,758],{"class":575},[565,1524,1525],{"class":880},"WriteBadRequest",[565,1527,1409],{"class":575},[565,1529,1531,1534,1536,1539,1541,1544],{"class":567,"line":1530},15,[565,1532,1533],{"class":880},"                ResponseErrorHandlerFunc",[565,1535,808],{"class":575},[565,1537,1538],{"class":880}," httperr",[565,1540,758],{"class":575},[565,1542,1543],{"class":880},"WriteError",[565,1545,1409],{"class":575},[565,1547,1549],{"class":567,"line":1548},16,[565,1550,1496],{"class":575},[565,1552,1554,1557,1559,1561,1564],{"class":567,"line":1553},17,[565,1555,1556],{"class":575},"        ),",[565,1558,1172],{"class":593},[565,1560,758],{"class":575},[565,1562,1563],{"class":593},"ChiServerOptions",[565,1565,1198],{"class":575},[565,1567,1569,1572,1574,1577],{"class":567,"line":1568},18,[565,1570,1571],{"class":880},"            BaseRouter",[565,1573,808],{"class":575},[565,1575,1576],{"class":880},"       r",[565,1578,1409],{"class":575},[565,1580,1582,1585,1587,1589,1591,1593],{"class":567,"line":1581},19,[565,1583,1584],{"class":880},"            ErrorHandlerFunc",[565,1586,808],{"class":575},[565,1588,1538],{"class":880},[565,1590,758],{"class":575},[565,1592,1525],{"class":880},[565,1594,1409],{"class":575},[565,1596,1598],{"class":567,"line":1597},20,[565,1599,1600],{"class":575},"        })\n",[565,1602,1604],{"class":567,"line":1603},21,[565,1605,1606],{"class":575},"    })\n",[565,1608,1610],{"class":567,"line":1609},22,[565,1611,683],{"class":575},[348,1613,1614,1615,1618,1619,1622,1623,1626,1627,1630],{},"The reasons for the differences: on Fiber, request validation is done at bind time by the app's ",[364,1616,1617],{},"StructValidator","; on chi the same runs as the ",[364,1620,1621],{},"chix.StrictValidator"," strict middleware. On Fiber, error rendering goes through the central ",[364,1624,1625],{},"ErrorHandler","; on chi the generated code's error hooks must be wired to the ",[364,1628,1629],{},"httperr"," writers. Neither touches your business code.",[439,1632,1634],{"id":1633},"the-two-template-sets","The two template sets",[1636,1637,1639],"h3",{"id":1638},"fiber-v3-the-kits-own-templates","Fiber v3: the kit's own templates",[348,1641,1642,1643,1646,1647,1650,1651,1654,1655,1658],{},"oapi-codegen's built-in ",[364,1644,1645],{},"fiber-server"," generator targets ",[352,1648,1649],{},"Fiber v2","; v3 support (upstream PR #1937) is unmerged. The kit therefore ships a v3 template set (",[364,1652,1653],{},"internal\u002Ftemplates\u002Foapi\u002F","), copied into every Fiber project under ",[364,1656,1657],{},"api\u002Foapi-codegen\u002Ftemplates\u002F",". Deltas vs the v2 baseline:",[444,1660,1661,1670,1685,1694,1701],{},[447,1662,1663,376,1666,1669],{},[364,1664,1665],{},"*fiber.Ctx",[364,1667,1668],{},"fiber.Ctx"," (v3 made the context an interface)",[447,1671,1672,376,1675,1678,1679,1681,1682],{},[364,1673,1674],{},"BodyParser",[364,1676,1677],{},"ctx.Bind().Body()",", which runs the app's ",[364,1680,1617],{},", so ",[352,1683,1684],{},"generated body binding validates for free",[447,1686,1687,376,1690,1693],{},[364,1688,1689],{},"UserContext()",[364,1691,1692],{},"Context()"," and the rest of the v3 renames",[447,1695,1696,1697,1700],{},"an ",[364,1698,1699],{},"imports.tmpl"," override swaps the hardcoded fiber v2 import for v3",[447,1702,1703,1704,1707,1708,1711],{},"handler errors propagate ",[352,1705,1706],{},"unchanged",": the upstream template wrapped every error into ",[364,1709,1710],{},"fiber.NewError(400)",", which would destroy the kit's typed problem responses",[1636,1713,1715],{"id":1714},"chi-upstream-routing-a-minimal-strict-override","chi: upstream routing + a minimal strict override",[348,1717,1718,1721,1722,1725,1726,1729,1730,1733,1734,548,1737,1740,1741,1743,1744,1746,1747,1750],{},[364,1719,1720],{},"--engine chi"," projects use oapi-codegen's ",[352,1723,1724],{},"built-in"," ",[364,1727,1728],{},"chi-server"," routing templates (current upstream, unlike fiber), plus a minimal kit override (",[364,1731,1732],{},"internal\u002Ftemplates\u002Foapichi\u002F","): the two strict templates (",[364,1735,1736],{},"strict\u002Fstrict-interface.tmpl",[364,1738,1739],{},"strict\u002Fstrict-http.tmpl",") with a single substantive delta: the ",[364,1742,714],{}," suffix is dropped from the generated request\u002Fresponse type names (",[364,1745,857],{},", not ",[364,1748,1749],{},"ListProductsRequestObject","), matching the Fiber set so the business handler templates stay engine-agnostic.",[1636,1752,1754],{"id":1753},"how-this-stays-safe","How this stays safe",[348,1756,1757,1758,1761,1762,1765,1766,1768,1769,1771,1772,1775],{},"Both sets are pinned and tested in the kit repo: the Fiber side is guarded by a ",[352,1759,1760],{},"golden test"," (byte-exact expected output) plus a ",[352,1763,1764],{},"compile test"," that builds the generated package against Fiber v3; the chi side by a contract test that pins the identifiers the register snippet uses, the shape of ",[364,1767,1218],{}," and the ",[364,1770,714],{},"-free names, and compiles too. Bumping oapi-codegen or an engine ",[352,1773,1774],{},"breaks these first",", never a consumer project.",[1777,1778,1779,1780,1783],"note",{},"If upstream merges Fiber v3 support, the migration is: drop the ",[364,1781,1782],{},"user-templates"," entries from the codegen configs, delete the local template directory, and pin the first v3-capable oapi-codegen release. The golden test tells you when output parity is reached.",[439,1785,1787],{"id":1786},"declarative-authorization-permission-and-policy","Declarative authorization: @permission and @policy",[348,1789,1790,1791,1794,1795,1798,1799,477,1802,1805,1806,1808],{},"The project scaffold ships a small decorator lib (",[364,1792,1793],{},"spec\u002Ftypespec\u002Flib\u002Fpolicy.tsp"," + ",[364,1796,1797],{},"policy.js","), making ",[364,1800,1801],{},"@permission(\"...\")",[364,1803,1804],{},"@policy(\"...\")"," available in every surface ",[364,1807,547],{},". The chain is machine-driven end to end:",[357,1810,1813],{"className":1811,"code":1812,"language":362},[360],"@permission(\"orders.place\") \u002F @policy(\"orders.owner\") on the operation\n  → x-permission \u002F x-policy extensions in the emitted OpenAPI (setExtension)\n  → the strict template generates PermissionByOperation \u002F PolicyByOperation maps into the gen package\n  → the enforcer middleware wired in register.go (FiberEnforcer \u002F HTTPEnforcer)\n    checks the RBAC permission and runs the policy from the surface's policy\u002F registry\n",[364,1814,1812],{"__ignoreMap":366},[348,1816,1817],{},"The generated maps look like this:",[357,1819,1822],{"className":718,"code":1820,"filename":1821,"language":721,"meta":366,"style":366},"var PermissionByOperation = map[string]string{\n    \"PlaceOrder\": \"orders.place\",\n}\n\nvar PolicyByOperation = map[string]string{\n    \"GetOrder\": \"orders.owner\",\n}\n","gen\u002Fapi.gen.go (generated)",[364,1823,1824,1848,1869,1873,1879,1898,1918],{"__ignoreMap":366},[565,1825,1826,1829,1832,1835,1838,1841,1844,1846],{"class":567,"line":568},[565,1827,1828],{"class":575},"var",[565,1830,1831],{"class":880}," PermissionByOperation ",[565,1833,1834],{"class":575},"=",[565,1836,1837],{"class":575}," map[",[565,1839,1840],{"class":783},"string",[565,1842,1843],{"class":575},"]",[565,1845,1840],{"class":783},[565,1847,1198],{"class":575},[565,1849,1850,1853,1856,1858,1860,1862,1865,1867],{"class":567,"line":586},[565,1851,1852],{"class":575},"    \"",[565,1854,1855],{"class":579},"PlaceOrder",[565,1857,988],{"class":575},[565,1859,808],{"class":575},[565,1861,982],{"class":575},[565,1863,1864],{"class":579},"orders.place",[565,1866,988],{"class":575},[565,1868,1409],{"class":575},[565,1870,1871],{"class":567,"line":600},[565,1872,683],{"class":575},[565,1874,1875],{"class":567,"line":607},[565,1876,1878],{"emptyLinePlaceholder":1877},true,"\n",[565,1880,1881,1883,1886,1888,1890,1892,1894,1896],{"class":567,"line":613},[565,1882,1828],{"class":575},[565,1884,1885],{"class":880}," PolicyByOperation ",[565,1887,1834],{"class":575},[565,1889,1837],{"class":575},[565,1891,1840],{"class":783},[565,1893,1843],{"class":575},[565,1895,1840],{"class":783},[565,1897,1198],{"class":575},[565,1899,1900,1902,1905,1907,1909,1911,1914,1916],{"class":567,"line":626},[565,1901,1852],{"class":575},[565,1903,1904],{"class":579},"GetOrder",[565,1906,988],{"class":575},[565,1908,808],{"class":575},[565,1910,982],{"class":575},[565,1912,1913],{"class":579},"orders.owner",[565,1915,988],{"class":575},[565,1917,1409],{"class":575},[565,1919,1920],{"class":567,"line":639},[565,1921,683],{"class":575},[348,1923,1924,1925,1928,1929,1932,1933,1935],{},"The enforcer is the outermost element of the strict middleware chain: it sees the fully bound, typed request, reads the authenticated user from the context, and short-circuits with 401\u002F403 before the business handler runs. ",[352,1926,1927],{},"Fail-closed",": an operation tagged with ",[364,1930,1931],{},"@policy"," whose policy is not registered does not pass; it errors. Semantics and policy writing are covered on the ",[435,1934,213],{"href":214}," page.",[439,1937,1939],{"id":1938},"the-gpsystemyaml-manifest","The gpsystem.yaml manifest",[348,1941,1942,1943,1946],{},"The ",[364,1944,1945],{},"gpsystem.yaml"," in the project root marks the project root for the CLI and records what has been generated so far:",[357,1948,1952],{"className":1949,"code":1950,"filename":1945,"language":1951,"meta":366,"style":366},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","version: 1\nname: shop\nmodule: github.com\u002Facme\u002Fshop\nkit: github.com\u002Fgp-system\u002Fgpsystem\nengine: fiber        # or chi; the generators follow it\ndb: pgx              # or bun\ntemplatesSHA: 3f2a…  # checksum of the copied oapi template set\nmodules:\n  shop:\n    surfaces: [api, admin]\n    events: [orderPlaced]\n","yaml",[364,1953,1954,1964,1974,1984,1994,2006,2019,2032,2040,2047,2067],{"__ignoreMap":366},[565,1955,1956,1959,1961],{"class":567,"line":568},[565,1957,1958],{"class":571},"version",[565,1960,808],{"class":575},[565,1962,1963],{"class":589}," 1\n",[565,1965,1966,1969,1971],{"class":567,"line":586},[565,1967,1968],{"class":571},"name",[565,1970,808],{"class":575},[565,1972,1973],{"class":579}," shop\n",[565,1975,1976,1979,1981],{"class":567,"line":600},[565,1977,1978],{"class":571},"module",[565,1980,808],{"class":575},[565,1982,1983],{"class":579}," github.com\u002Facme\u002Fshop\n",[565,1985,1986,1989,1991],{"class":567,"line":607},[565,1987,1988],{"class":571},"kit",[565,1990,808],{"class":575},[565,1992,1993],{"class":579}," github.com\u002Fgp-system\u002Fgpsystem\n",[565,1995,1996,1999,2001,2003],{"class":567,"line":613},[565,1997,1998],{"class":571},"engine",[565,2000,808],{"class":575},[565,2002,1055],{"class":579},[565,2004,2005],{"class":603},"        # or chi; the generators follow it\n",[565,2007,2008,2011,2013,2016],{"class":567,"line":626},[565,2009,2010],{"class":571},"db",[565,2012,808],{"class":575},[565,2014,2015],{"class":579}," pgx",[565,2017,2018],{"class":603},"              # or bun\n",[565,2020,2021,2024,2026,2029],{"class":567,"line":639},[565,2022,2023],{"class":571},"templatesSHA",[565,2025,808],{"class":575},[565,2027,2028],{"class":579}," 3f2a…",[565,2030,2031],{"class":603},"  # checksum of the copied oapi template set\n",[565,2033,2034,2037],{"class":567,"line":652},[565,2035,2036],{"class":571},"modules",[565,2038,2039],{"class":575},":\n",[565,2041,2042,2045],{"class":567,"line":680},[565,2043,2044],{"class":571},"  shop",[565,2046,2039],{"class":575},[565,2048,2049,2052,2054,2057,2059,2061,2064],{"class":567,"line":1446},[565,2050,2051],{"class":571},"    surfaces",[565,2053,808],{"class":575},[565,2055,2056],{"class":575}," [",[565,2058,468],{"class":579},[565,2060,764],{"class":575},[565,2062,2063],{"class":579}," admin",[565,2065,2066],{"class":575},"]\n",[565,2068,2069,2072,2074,2076,2079],{"class":567,"line":1452},[565,2070,2071],{"class":571},"    events",[565,2073,808],{"class":575},[565,2075,2056],{"class":575},[565,2077,2078],{"class":579},"orderPlaced",[565,2080,2066],{"class":575},[444,2082,2083,2098,2111],{},[447,2084,2085,477,2087,2089,2090,548,2092,2094,2095,758],{},[364,2086,1998],{},[364,2088,2010],{}," decide which template tree the generators render (",[364,2091,314],{},[364,2093,324],{},", ...); the choice is made at ",[435,2096,2097],{"href":305},"project creation",[447,2099,2100,2102,2103,2107,2108,758],{},[364,2101,2023],{}," is how ",[435,2104,2105],{"href":339},[364,2106,338],{}," detects that you modified the copied templates locally: a modified set is only overwritten with ",[364,2109,2110],{},"--force",[447,2112,1942,2113,2115,2116,2118,2119,2122,2123,2126,2127,2130,2131,2126,2134,2137],{},[364,2114,2036],{}," entries tell ",[364,2117,314],{}," what already exists, and ",[364,2120,2121],{},"add listener"," whether you are subscribing to an existing event. The pre-rename ",[364,2124,2125],{},"domains:","\u002F",[364,2128,2129],{},"domainDBs:"," keys are still read and migrated to ",[364,2132,2133],{},"surfaces:",[364,2135,2136],{},"surfaceDBs:"," automatically on the next save.",[439,2139,1942,2141,2144],{"id":2140},"the-gpsystem-anchors",[364,2142,2143],{},"\u002F\u002F gpsystem:*"," anchors",[348,2146,2147,2148,2151],{},"The generators ",[352,2149,2150],{},"never overwrite"," an existing file; they only insert at marked anchor comments. The important ones:",[2153,2154,2155,2171],"table",{},[2156,2157,2158],"thead",{},[2159,2160,2161,2165,2168],"tr",{},[2162,2163,2164],"th",{},"Anchor",[2162,2166,2167],{},"File",[2162,2169,2170],{},"Who inserts here",[2172,2173,2174,2193,2209,2229,2245,2266,2291],"tbody",{},[2159,2175,2176,2182,2187],{},[2177,2178,2179],"td",{},[364,2180,2181],{},"gpsystem:modules",[2177,2183,2184],{},[364,2185,2186],{},"spec\u002Ftypespec\u002Fmain.tsp",[2177,2188,2189,499,2191],{},[364,2190,309],{},[364,2192,314],{},[2159,2194,2195,2200,2205],{},[2177,2196,2197],{},[364,2198,2199],{},"gpsystem:operations",[2177,2201,2202],{},[364,2203,2204],{},"\u003Csurface>.tsp",[2177,2206,2207],{},[364,2208,324],{},[2159,2210,2211,2219,2223],{},[2177,2212,2213,548,2216],{},[364,2214,2215],{},"gpsystem:surfaces",[364,2217,2218],{},"gpsystem:imports",[2177,2220,2221],{},[364,2222,1031],{},[2177,2224,2225,499,2227],{},[364,2226,309],{},[364,2228,314],{},[2159,2230,2231,2236,2241],{},[2177,2232,2233],{},[364,2234,2235],{},"gpsystem:registrations",[2177,2237,2238],{},[364,2239,2240],{},"cmd\u002F\u003Cmodule>\u002Fmain.go",[2177,2242,2243],{},[364,2244,314],{},[2159,2246,2247,2255,2260],{},[2177,2248,2249,548,2252],{},[364,2250,2251],{},"gpsystem:dependencies",[364,2253,2254],{},"gpsystem:dependencies-init",[2177,2256,2257,2259],{},[364,2258,1031],{}," \u002F entry points",[2177,2261,2262,2265],{},[364,2263,2264],{},"add event"," (dispatcher wiring)",[2159,2267,2268,2276,2284],{},[2177,2269,2270,548,2273],{},[364,2271,2272],{},"gpsystem:listeners",[364,2274,2275],{},"gpsystem:jobs",[2177,2277,2278,499,2281],{},[364,2279,2280],{},"listeners\u002Fregister.go",[364,2282,2283],{},"jobs\u002Fregister.go",[2177,2285,2286,499,2288],{},[364,2287,2121],{},[364,2289,2290],{},"add job",[2159,2292,2293,2298,2303],{},[2177,2294,2295],{},[364,2296,2297],{},"gpsystem:config",[2177,2299,2300],{},[364,2301,2302],{},"internal\u002Fplatform\u002Fconfig\u002Fconfig.go",[2177,2304,2305],{},"future config additions",[348,2307,2308,2309,2312,2313,2316],{},"Insertions are idempotent (running twice does not duplicate), and the file is reformatted with goimports afterwards. If you delete an anchor, the affected generator ",[352,2310,2311],{},"fails loudly"," instead of guessing: before the error, it prints the snippet that would have been inserted and the anchor line to restore, so you can paste it in by hand at the right spot. Leave the anchors in place, as the ",[435,2314,2315],{"href":41},"sample app"," does.",[439,2318,2320],{"id":2319},"patterns-used","Patterns used",[444,2322,2323,2345,2356],{},[447,2324,2325,2328,2329,2332,2333,2339,2340,758],{},[352,2326,2327],{},"Spec-first codegen"," (TypeSpec → OpenAPI 3.0 → strict oapi-codegen server): ",[435,2330,92],{"href":2331},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#spec-first-codegen",". TypeSpec's official documentation: ",[435,2334,2338],{"href":2335,"rel":2336},"https:\u002F\u002Ftypespec.io\u002Fdocs",[2337],"nofollow","typespec.io\u002Fdocs","; the oapi-codegen project: ",[435,2341,2344],{"href":2342,"rel":2343},"https:\u002F\u002Fgithub.com\u002Foapi-codegen\u002Foapi-codegen",[2337],"github.com\u002Foapi-codegen\u002Foapi-codegen",[447,2346,2347,418,2350,2352,2353,758],{},[352,2348,2349],{},"Anchor-comment code injection",[364,2351,2143],{},", idempotent, whitespace-normalized): ",[435,2354,92],{"href":2355},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#anchor-comment-code-injection",[447,2357,2358,2361,2362,2126,2365,2367,2368,758],{},[352,2359,2360],{},"Fail-closed policy enforcement"," in the ",[364,2363,2364],{},"@permission",[364,2366,1931],{}," enforcer chain: ",[435,2369,92],{"href":2370},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#fail-closed-policy-enforcement",[439,2372,2374],{"id":2373},"related-pages","Related pages",[444,2376,2377,2385,2390],{},[447,2378,2379,2381,2382,2384],{},[435,2380,324],{"href":325},": scaffolding an operation into the ",[364,2383,547],{}," and the handler.",[447,2386,2387,2389],{},[435,2388,123],{"href":124},": how generated binding validates.",[447,2391,2392,2394],{},[435,2393,213],{"href":214},": the enforcer and the policy registry.",[2396,2397,2398],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}",{"title":366,"searchDepth":586,"depth":586,"links":2400},[2401,2402,2403,2408,2409,2410,2412,2413],{"id":441,"depth":586,"text":442},{"id":534,"depth":586,"text":535},{"id":1633,"depth":586,"text":1634,"children":2404},[2405,2406,2407],{"id":1638,"depth":600,"text":1639},{"id":1714,"depth":600,"text":1715},{"id":1753,"depth":600,"text":1754},{"id":1786,"depth":586,"text":1787},{"id":1938,"depth":586,"text":1939},{"id":2140,"depth":586,"text":2411},"The \u002F\u002F gpsystem:* anchors",{"id":2319,"depth":586,"text":2320},{"id":2373,"depth":586,"text":2374},"TypeSpec → OpenAPI 3.0 → strict, typed server code: for Fiber and chi alike.","md",null,{},{"icon":90},{"title":87,"description":2414},"OOyTyW-w1AyIBoW0U6K1izTuthdlWhDX4K1NvTZ-9ok",[2422,2424],{"title":52,"path":83,"stem":84,"description":2423,"icon":85,"children":-1},"Typed configuration from environment variables: envconf, struct composition and the .env file.",{"title":92,"path":93,"stem":94,"description":2425,"icon":95,"children":-1},"A catalog of recurring design patterns in the kit: with code, where to find them, and the canonical external source.",1784668702982]