[{"data":1,"prerenderedAt":1419},["ShallowReactive",2],{"navigation_docs":3,"-cross-cutting":36,"-cross-cutting-surround":1414},[4,8,12,16,20,24,28,32],{"title":5,"path":6,"stem":7},"Getting started","\u002Fgetting-started","1.getting-started",{"title":9,"path":10,"stem":11},"Core concepts","\u002Fcore-concepts","2.core-concepts",{"title":13,"path":14,"stem":15},"Cross-cutting concerns","\u002Fcross-cutting","3.cross-cutting",{"title":17,"path":18,"stem":19},"Tenancy & RLS","\u002Ftenancy-and-rls","4.tenancy-and-rls",{"title":21,"path":22,"stem":23},"How the codegen works","\u002Fhow-the-codegen-works","5.how-the-codegen-works",{"title":25,"path":26,"stem":27},"vs. NestJS","\u002Fvs-nestjs","6.vs-nestjs",{"title":29,"path":30,"stem":31},"Before \u002F after","\u002Fbefore-after","7.before-after",{"title":33,"path":34,"stem":35},"Modules","\u002Fmodules","8.modules",{"id":37,"title":13,"body":38,"description":1408,"extension":1409,"links":1410,"meta":1411,"navigation":277,"path":14,"seo":1412,"stem":15,"__hash__":1413},"docs\u002F3.cross-cutting.md",{"type":39,"value":40,"toc":1397},"minimark",[41,59,69,72,80,83,326,332,339,348,587,594,609,737,752,756,781,939,955,1075,1110,1232,1238,1242,1245,1341,1352,1356,1393],[42,43,44,45,53,54,58],"p",{},"Guards, interceptors, and filters are all ",[46,47,48,52],"strong",{},[49,50,51],"code",{},"@Injectable"," providers",", resolved from the\nper-request scope. They share one model and differ only in ",[55,56,57],"em",{},"when"," they run in the pipeline:",[60,61,66],"pre",{"className":62,"code":64,"language":65},[63],"language-text","guards → interceptors (pre) → validate → handler → interceptors (post) → [catch] filters\n","text",[49,67,64],{"__ignoreMap":68},"",[42,70,71],{},"Because they're DI'd, each can inject services — a DB-backed permission guard, a logging\ninterceptor, an audit filter. You reference the class on a decorator; roost maps it to its DI\ntoken and resolves it per request.",[73,74,76,77],"h2",{"id":75},"guards-useguards","Guards — ",[49,78,79],{},"@UseGuards",[42,81,82],{},"A guard throws to reject (preserving the exact status), or returns to allow.",[60,84,88],{"className":85,"code":86,"language":87,"meta":68,"style":68},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","@Injectable()\nexport class MemberGuard implements RoostGuard {\n  canActivate(ctx: Ctx): void {\n    if (!ctx.user) throw createError({ statusCode: 401 })\n    if (ctx.user.role !== 'member') throw createError({ statusCode: 403 })\n  }\n}\n\n@Controller('projects')\n@UseGuards(MemberGuard) \u002F\u002F applies to every method; method-level guards merge on top\nexport class ProjectsController {}\n","ts",[49,89,90,107,131,159,208,260,266,272,279,298,313],{"__ignoreMap":68},[91,92,95,99,103],"span",{"class":93,"line":94},"line",1,[91,96,98],{"class":97},"sMK4o","@",[91,100,102],{"class":101},"s2Zo4","Injectable",[91,104,106],{"class":105},"sTEyZ","()\n",[91,108,110,114,118,122,125,128],{"class":93,"line":109},2,[91,111,113],{"class":112},"s7zQu","export",[91,115,117],{"class":116},"spNyl"," class",[91,119,121],{"class":120},"sBMFI"," MemberGuard",[91,123,124],{"class":116}," implements",[91,126,127],{"class":120}," RoostGuard",[91,129,130],{"class":97}," {\n",[91,132,134,138,141,145,148,151,154,157],{"class":93,"line":133},3,[91,135,137],{"class":136},"swJcz","  canActivate",[91,139,140],{"class":97},"(",[91,142,144],{"class":143},"sHdIc","ctx",[91,146,147],{"class":97},":",[91,149,150],{"class":120}," Ctx",[91,152,153],{"class":97},"):",[91,155,156],{"class":120}," void",[91,158,130],{"class":97},[91,160,162,165,168,171,173,176,179,182,185,188,190,193,196,198,202,205],{"class":93,"line":161},4,[91,163,164],{"class":112},"    if",[91,166,167],{"class":136}," (",[91,169,170],{"class":97},"!",[91,172,144],{"class":105},[91,174,175],{"class":97},".",[91,177,178],{"class":105},"user",[91,180,181],{"class":136},") ",[91,183,184],{"class":112},"throw",[91,186,187],{"class":101}," createError",[91,189,140],{"class":136},[91,191,192],{"class":97},"{",[91,194,195],{"class":136}," statusCode",[91,197,147],{"class":97},[91,199,201],{"class":200},"sbssI"," 401",[91,203,204],{"class":97}," }",[91,206,207],{"class":136},")\n",[91,209,211,213,215,217,219,221,223,226,229,232,236,239,241,243,245,247,249,251,253,256,258],{"class":93,"line":210},5,[91,212,164],{"class":112},[91,214,167],{"class":136},[91,216,144],{"class":105},[91,218,175],{"class":97},[91,220,178],{"class":105},[91,222,175],{"class":97},[91,224,225],{"class":105},"role",[91,227,228],{"class":97}," !==",[91,230,231],{"class":97}," '",[91,233,235],{"class":234},"sfazB","member",[91,237,238],{"class":97},"'",[91,240,181],{"class":136},[91,242,184],{"class":112},[91,244,187],{"class":101},[91,246,140],{"class":136},[91,248,192],{"class":97},[91,250,195],{"class":136},[91,252,147],{"class":97},[91,254,255],{"class":200}," 403",[91,257,204],{"class":97},[91,259,207],{"class":136},[91,261,263],{"class":93,"line":262},6,[91,264,265],{"class":97},"  }\n",[91,267,269],{"class":93,"line":268},7,[91,270,271],{"class":97},"}\n",[91,273,275],{"class":93,"line":274},8,[91,276,278],{"emptyLinePlaceholder":277},true,"\n",[91,280,282,284,287,289,291,294,296],{"class":93,"line":281},9,[91,283,98],{"class":97},[91,285,286],{"class":101},"Controller",[91,288,140],{"class":105},[91,290,238],{"class":97},[91,292,293],{"class":234},"projects",[91,295,238],{"class":97},[91,297,207],{"class":105},[91,299,301,303,306,309],{"class":93,"line":300},10,[91,302,98],{"class":97},[91,304,305],{"class":101},"UseGuards",[91,307,308],{"class":105},"(MemberGuard) ",[91,310,312],{"class":311},"sHwdD","\u002F\u002F applies to every method; method-level guards merge on top\n",[91,314,316,318,320,323],{"class":93,"line":315},11,[91,317,113],{"class":112},[91,319,117],{"class":116},[91,321,322],{"class":120}," ProjectsController",[91,324,325],{"class":97}," {}\n",[42,327,328,329,175],{},"Class + method guards are ",[46,330,331],{},"merged class-first and de-duped",[73,333,335,336],{"id":334},"interceptors-useinterceptors","Interceptors — ",[49,337,338],{},"@UseInterceptors",[42,340,341,342,345,346,175],{},"\"Around\" advice. ",[49,343,344],{},"next()"," runs the next interceptor (or the handler). Transform the result,\ntime it, short-circuit by not calling ",[49,347,344],{},[60,349,351],{"className":85,"code":350,"language":87,"meta":68,"style":68},"@Injectable()\nexport class AuditInterceptor implements RoostInterceptor {\n  constructor(private readonly ctx: RequestContext) {}\n  async intercept(ctx: Ctx, next: () => Promise\u003Cunknown>) {\n    const started = Date.now()\n    try {\n      return await next()\n    } finally {\n      console.log(`${ctx.event.path} tenant=${this.ctx.tenantId} ${Date.now() - started}ms`)\n    }\n  }\n}\n",[49,352,353,361,377,403,447,468,475,487,497,573,578,582],{"__ignoreMap":68},[91,354,355,357,359],{"class":93,"line":94},[91,356,98],{"class":97},[91,358,102],{"class":101},[91,360,106],{"class":105},[91,362,363,365,367,370,372,375],{"class":93,"line":109},[91,364,113],{"class":112},[91,366,117],{"class":116},[91,368,369],{"class":120}," AuditInterceptor",[91,371,124],{"class":116},[91,373,374],{"class":120}," RoostInterceptor",[91,376,130],{"class":97},[91,378,379,382,384,387,390,393,395,398,401],{"class":93,"line":133},[91,380,381],{"class":116},"  constructor",[91,383,140],{"class":97},[91,385,386],{"class":116},"private",[91,388,389],{"class":116}," readonly",[91,391,392],{"class":143}," ctx",[91,394,147],{"class":97},[91,396,397],{"class":120}," RequestContext",[91,399,400],{"class":97},")",[91,402,325],{"class":97},[91,404,405,408,411,413,415,417,419,422,425,427,430,433,436,439,442,445],{"class":93,"line":161},[91,406,407],{"class":116},"  async",[91,409,410],{"class":136}," intercept",[91,412,140],{"class":97},[91,414,144],{"class":143},[91,416,147],{"class":97},[91,418,150],{"class":120},[91,420,421],{"class":97},",",[91,423,424],{"class":101}," next",[91,426,147],{"class":97},[91,428,429],{"class":97}," ()",[91,431,432],{"class":116}," =>",[91,434,435],{"class":120}," Promise",[91,437,438],{"class":97},"\u003C",[91,440,441],{"class":120},"unknown",[91,443,444],{"class":97},">)",[91,446,130],{"class":97},[91,448,449,452,455,458,461,463,466],{"class":93,"line":210},[91,450,451],{"class":116},"    const",[91,453,454],{"class":105}," started",[91,456,457],{"class":97}," =",[91,459,460],{"class":105}," Date",[91,462,175],{"class":97},[91,464,465],{"class":101},"now",[91,467,106],{"class":136},[91,469,470,473],{"class":93,"line":262},[91,471,472],{"class":112},"    try",[91,474,130],{"class":97},[91,476,477,480,483,485],{"class":93,"line":268},[91,478,479],{"class":112},"      return",[91,481,482],{"class":112}," await",[91,484,424],{"class":101},[91,486,106],{"class":136},[91,488,489,492,495],{"class":93,"line":274},[91,490,491],{"class":97},"    }",[91,493,494],{"class":112}," finally",[91,496,130],{"class":97},[91,498,499,502,504,507,509,512,514,516,519,521,524,527,530,533,536,538,540,543,545,548,551,553,555,558,561,563,565,568,571],{"class":93,"line":281},[91,500,501],{"class":105},"      console",[91,503,175],{"class":97},[91,505,506],{"class":101},"log",[91,508,140],{"class":136},[91,510,511],{"class":97},"`${",[91,513,144],{"class":105},[91,515,175],{"class":97},[91,517,518],{"class":105},"event",[91,520,175],{"class":97},[91,522,523],{"class":105},"path",[91,525,526],{"class":97},"}",[91,528,529],{"class":234}," tenant=",[91,531,532],{"class":97},"${",[91,534,535],{"class":97},"this.",[91,537,144],{"class":105},[91,539,175],{"class":97},[91,541,542],{"class":105},"tenantId",[91,544,526],{"class":97},[91,546,547],{"class":97}," ${",[91,549,550],{"class":105},"Date",[91,552,175],{"class":97},[91,554,465],{"class":101},[91,556,557],{"class":105},"() ",[91,559,560],{"class":97},"-",[91,562,454],{"class":105},[91,564,526],{"class":97},[91,566,567],{"class":234},"ms",[91,569,570],{"class":97},"`",[91,572,207],{"class":136},[91,574,575],{"class":93,"line":300},[91,576,577],{"class":97},"    }\n",[91,579,580],{"class":93,"line":315},[91,581,265],{"class":97},[91,583,585],{"class":93,"line":584},12,[91,586,271],{"class":97},[73,588,590,591],{"id":589},"filters-usefilters","Filters — ",[49,592,593],{},"@UseFilters",[42,595,596,597,600,601,604,605,608],{},"Map a thrown error to a response. Return ",[49,598,599],{},"undefined"," to pass to the next filter, an ",[49,602,603],{},"Error","\n(use ",[49,606,607],{},"createError",") to throw with its status, or any value to send as the body.",[60,610,612],{"className":85,"code":611,"language":87,"meta":68,"style":68},"@Injectable()\nexport class DomainErrorFilter implements RoostFilter {\n  catch(err: unknown, _ctx: Ctx) {\n    if (err instanceof NotFoundError)\n      return createError({ statusCode: 404, statusMessage: err.message })\n    return undefined \u002F\u002F not ours\n  }\n}\n",[49,613,614,622,638,666,682,718,729,733],{"__ignoreMap":68},[91,615,616,618,620],{"class":93,"line":94},[91,617,98],{"class":97},[91,619,102],{"class":101},[91,621,106],{"class":105},[91,623,624,626,628,631,633,636],{"class":93,"line":109},[91,625,113],{"class":112},[91,627,117],{"class":116},[91,629,630],{"class":120}," DomainErrorFilter",[91,632,124],{"class":116},[91,634,635],{"class":120}," RoostFilter",[91,637,130],{"class":97},[91,639,640,643,645,648,650,653,655,658,660,662,664],{"class":93,"line":133},[91,641,642],{"class":136},"  catch",[91,644,140],{"class":97},[91,646,647],{"class":143},"err",[91,649,147],{"class":97},[91,651,652],{"class":120}," unknown",[91,654,421],{"class":97},[91,656,657],{"class":143}," _ctx",[91,659,147],{"class":97},[91,661,150],{"class":120},[91,663,400],{"class":97},[91,665,130],{"class":97},[91,667,668,670,672,674,677,680],{"class":93,"line":161},[91,669,164],{"class":112},[91,671,167],{"class":136},[91,673,647],{"class":105},[91,675,676],{"class":97}," instanceof",[91,678,679],{"class":120}," NotFoundError",[91,681,207],{"class":136},[91,683,684,686,688,690,692,694,696,699,701,704,706,709,711,714,716],{"class":93,"line":210},[91,685,479],{"class":112},[91,687,187],{"class":101},[91,689,140],{"class":136},[91,691,192],{"class":97},[91,693,195],{"class":136},[91,695,147],{"class":97},[91,697,698],{"class":200}," 404",[91,700,421],{"class":97},[91,702,703],{"class":136}," statusMessage",[91,705,147],{"class":97},[91,707,708],{"class":105}," err",[91,710,175],{"class":97},[91,712,713],{"class":105},"message",[91,715,204],{"class":97},[91,717,207],{"class":136},[91,719,720,723,726],{"class":93,"line":262},[91,721,722],{"class":112},"    return",[91,724,725],{"class":97}," undefined",[91,727,728],{"class":311}," \u002F\u002F not ours\n",[91,730,731],{"class":93,"line":268},[91,732,265],{"class":97},[91,734,735],{"class":93,"line":274},[91,736,271],{"class":97},[42,738,739,740,743,744,747,748,751],{},"Filters dispatch ",[46,741,742],{},"method-before-class"," (a route can override a controller default), then global\nfilters, then a built-in fallback that maps ",[49,745,746],{},"HttpError"," (see below) and ",[49,749,750],{},"ZodError → 400",". So a bad\nrequest body returns 400 out of the box, no filter required.",[73,753,755],{"id":754},"exceptions-the-leaf-case","Exceptions — the leaf case",[42,757,758,759,762,763,766,767,770,771,774,775,777,778,147],{},"A filter is for the ",[55,760,761],{},"cross-cutting"," case: map a whole class of errors in one place, with injected\nservices to log or audit. For the ",[55,764,765],{},"leaf"," case — reject with a status right where you are — throw a\n",[46,768,769],{},"prebuilt exception"," instead. They're auto-imported, so feature code needs no ",[49,772,773],{},"import"," and never\nreaches for ",[49,776,607],{}," from ",[49,779,780],{},"h3",[60,782,784],{"className":85,"code":783,"language":87,"meta":68,"style":68},"@Scoped()\nexport class ProjectsService {\n  constructor(private readonly repo: ProjectsRepo) {}\n\n  get(id: string): ProjectRecord {\n    const found = this.repo.find(id)\n    if (!found) throw new NotFoundException(`project ${id} not found`) \u002F\u002F -> 404, no filter needed\n    return found\n  }\n}\n",[49,785,786,795,806,828,832,854,880,924,931,935],{"__ignoreMap":68},[91,787,788,790,793],{"class":93,"line":94},[91,789,98],{"class":97},[91,791,792],{"class":101},"Scoped",[91,794,106],{"class":105},[91,796,797,799,801,804],{"class":93,"line":109},[91,798,113],{"class":112},[91,800,117],{"class":116},[91,802,803],{"class":120}," ProjectsService",[91,805,130],{"class":97},[91,807,808,810,812,814,816,819,821,824,826],{"class":93,"line":133},[91,809,381],{"class":116},[91,811,140],{"class":97},[91,813,386],{"class":116},[91,815,389],{"class":116},[91,817,818],{"class":143}," repo",[91,820,147],{"class":97},[91,822,823],{"class":120}," ProjectsRepo",[91,825,400],{"class":97},[91,827,325],{"class":97},[91,829,830],{"class":93,"line":161},[91,831,278],{"emptyLinePlaceholder":277},[91,833,834,837,839,842,844,847,849,852],{"class":93,"line":210},[91,835,836],{"class":136},"  get",[91,838,140],{"class":97},[91,840,841],{"class":143},"id",[91,843,147],{"class":97},[91,845,846],{"class":120}," string",[91,848,153],{"class":97},[91,850,851],{"class":120}," ProjectRecord",[91,853,130],{"class":97},[91,855,856,858,861,863,866,869,871,874,876,878],{"class":93,"line":262},[91,857,451],{"class":116},[91,859,860],{"class":105}," found",[91,862,457],{"class":97},[91,864,865],{"class":97}," this.",[91,867,868],{"class":105},"repo",[91,870,175],{"class":97},[91,872,873],{"class":101},"find",[91,875,140],{"class":136},[91,877,841],{"class":105},[91,879,207],{"class":136},[91,881,882,884,886,888,891,893,895,898,901,903,905,908,910,912,914,917,919,921],{"class":93,"line":268},[91,883,164],{"class":112},[91,885,167],{"class":136},[91,887,170],{"class":97},[91,889,890],{"class":105},"found",[91,892,181],{"class":136},[91,894,184],{"class":112},[91,896,897],{"class":97}," new",[91,899,900],{"class":101}," NotFoundException",[91,902,140],{"class":136},[91,904,570],{"class":97},[91,906,907],{"class":234},"project ",[91,909,532],{"class":97},[91,911,841],{"class":105},[91,913,526],{"class":97},[91,915,916],{"class":234}," not found",[91,918,570],{"class":97},[91,920,181],{"class":136},[91,922,923],{"class":311},"\u002F\u002F -> 404, no filter needed\n",[91,925,926,928],{"class":93,"line":274},[91,927,722],{"class":112},[91,929,930],{"class":105}," found\n",[91,932,933],{"class":93,"line":281},[91,934,265],{"class":97},[91,936,937],{"class":93,"line":300},[91,938,271],{"class":97},[42,940,941,943,944,947,948,950,951,954],{},[49,942,746],{}," is the base — ",[49,945,946],{},"statusCode",", ",[49,949,713],{},", and an optional ",[49,952,953],{},"data"," payload. The built-in set\ncovers the common statuses:",[956,957,958,971],"table",{},[959,960,961],"thead",{},[962,963,964,968],"tr",{},[965,966,967],"th",{},"Exception",[965,969,970],{},"Status",[972,973,974,985,995,1005,1015,1025,1035,1045,1055,1065],"tbody",{},[962,975,976,982],{},[977,978,979],"td",{},[49,980,981],{},"BadRequestException",[977,983,984],{},"400",[962,986,987,992],{},[977,988,989],{},[49,990,991],{},"UnauthorizedException",[977,993,994],{},"401",[962,996,997,1002],{},[977,998,999],{},[49,1000,1001],{},"ForbiddenException",[977,1003,1004],{},"403",[962,1006,1007,1012],{},[977,1008,1009],{},[49,1010,1011],{},"NotFoundException",[977,1013,1014],{},"404",[962,1016,1017,1022],{},[977,1018,1019],{},[49,1020,1021],{},"ConflictException",[977,1023,1024],{},"409",[962,1026,1027,1032],{},[977,1028,1029],{},[49,1030,1031],{},"GoneException",[977,1033,1034],{},"410",[962,1036,1037,1042],{},[977,1038,1039],{},[49,1040,1041],{},"UnprocessableEntityException",[977,1043,1044],{},"422",[962,1046,1047,1052],{},[977,1048,1049],{},[49,1050,1051],{},"TooManyRequestsException",[977,1053,1054],{},"429",[962,1056,1057,1062],{},[977,1058,1059],{},[49,1060,1061],{},"InternalServerErrorException",[977,1063,1064],{},"500",[962,1066,1067,1072],{},[977,1068,1069],{},[49,1070,1071],{},"ServiceUnavailableException",[977,1073,1074],{},"503",[42,1076,1077,1078,1081,1082,1085,1086,1089,1090,1093,1094,1097,1098,1100,1101,1103,1104,1107,1108,147],{},"The set stops at the common app-thrown statuses on purpose (infra\u002Fproxy statuses like ",[49,1079,1080],{},"502","\u002F",[49,1083,1084],{},"504","\nare set by load balancers, not domain code). For anything else, throw the base directly —\n",[49,1087,1088],{},"throw new HttpError(418, \"I'm a teapot\")"," — or ",[49,1091,1092],{},"extend HttpError"," for an app-specific exception you\nreuse. The built-in fallback maps ",[46,1095,1096],{},"any"," ",[49,1099,746],{}," to its status — exactly like ",[49,1102,750],{}," —\nso guards, services, and handlers all reject the same way, named alias or not. A guard, for\ninstance, throws ",[49,1105,1106],{},"new ForbiddenException()"," rather than importing ",[49,1109,607],{},[60,1111,1113],{"className":85,"code":1112,"language":87,"meta":68,"style":68},"@Injectable()\nexport class MemberGuard implements RoostGuard {\n  canActivate(ctx: Ctx): void {\n    if (!ctx.user) throw new UnauthorizedException()\n    if (ctx.user.role !== 'member') throw new ForbiddenException('Members only')\n  }\n}\n",[49,1114,1115,1123,1137,1155,1180,1224,1228],{"__ignoreMap":68},[91,1116,1117,1119,1121],{"class":93,"line":94},[91,1118,98],{"class":97},[91,1120,102],{"class":101},[91,1122,106],{"class":105},[91,1124,1125,1127,1129,1131,1133,1135],{"class":93,"line":109},[91,1126,113],{"class":112},[91,1128,117],{"class":116},[91,1130,121],{"class":120},[91,1132,124],{"class":116},[91,1134,127],{"class":120},[91,1136,130],{"class":97},[91,1138,1139,1141,1143,1145,1147,1149,1151,1153],{"class":93,"line":133},[91,1140,137],{"class":136},[91,1142,140],{"class":97},[91,1144,144],{"class":143},[91,1146,147],{"class":97},[91,1148,150],{"class":120},[91,1150,153],{"class":97},[91,1152,156],{"class":120},[91,1154,130],{"class":97},[91,1156,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1178],{"class":93,"line":161},[91,1158,164],{"class":112},[91,1160,167],{"class":136},[91,1162,170],{"class":97},[91,1164,144],{"class":105},[91,1166,175],{"class":97},[91,1168,178],{"class":105},[91,1170,181],{"class":136},[91,1172,184],{"class":112},[91,1174,897],{"class":97},[91,1176,1177],{"class":101}," UnauthorizedException",[91,1179,106],{"class":136},[91,1181,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208,1210,1213,1215,1217,1220,1222],{"class":93,"line":210},[91,1183,164],{"class":112},[91,1185,167],{"class":136},[91,1187,144],{"class":105},[91,1189,175],{"class":97},[91,1191,178],{"class":105},[91,1193,175],{"class":97},[91,1195,225],{"class":105},[91,1197,228],{"class":97},[91,1199,231],{"class":97},[91,1201,235],{"class":234},[91,1203,238],{"class":97},[91,1205,181],{"class":136},[91,1207,184],{"class":112},[91,1209,897],{"class":97},[91,1211,1212],{"class":101}," ForbiddenException",[91,1214,140],{"class":136},[91,1216,238],{"class":97},[91,1218,1219],{"class":234},"Members only",[91,1221,238],{"class":97},[91,1223,207],{"class":136},[91,1225,1226],{"class":93,"line":262},[91,1227,265],{"class":97},[91,1229,1230],{"class":93,"line":268},[91,1231,271],{"class":97},[42,1233,1234,1237],{},[46,1235,1236],{},"Filter or exception?"," Throw an exception when you're setting a status at one call site; write a\nfilter when the mapping is cross-cutting. They compose — a filter gets first crack, the built-in\nfallback catches the rest.",[73,1239,1241],{"id":1240},"globals","Globals",[42,1243,1244],{},"Apply a chain to every route at runtime — no decorator, no regeneration:",[60,1246,1249],{"className":85,"code":1247,"filename":1248,"language":87,"meta":68,"style":68},"roost: {\n  globals: {\n    guards: ['authGuard'],\n    interceptors: ['auditInterceptor'],\n    filters: ['sentryFilter'],\n  },\n}\n","nuxt.config.ts",[49,1250,1251,1260,1269,1292,1312,1332,1337],{"__ignoreMap":68},[91,1252,1253,1256,1258],{"class":93,"line":94},[91,1254,1255],{"class":120},"roost",[91,1257,147],{"class":97},[91,1259,130],{"class":97},[91,1261,1262,1265,1267],{"class":93,"line":109},[91,1263,1264],{"class":120},"  globals",[91,1266,147],{"class":97},[91,1268,130],{"class":97},[91,1270,1271,1274,1276,1279,1281,1284,1286,1289],{"class":93,"line":133},[91,1272,1273],{"class":120},"    guards",[91,1275,147],{"class":97},[91,1277,1278],{"class":136}," [",[91,1280,238],{"class":97},[91,1282,1283],{"class":234},"authGuard",[91,1285,238],{"class":97},[91,1287,1288],{"class":136},"]",[91,1290,1291],{"class":97},",\n",[91,1293,1294,1297,1299,1301,1303,1306,1308,1310],{"class":93,"line":161},[91,1295,1296],{"class":120},"    interceptors",[91,1298,147],{"class":97},[91,1300,1278],{"class":136},[91,1302,238],{"class":97},[91,1304,1305],{"class":234},"auditInterceptor",[91,1307,238],{"class":97},[91,1309,1288],{"class":136},[91,1311,1291],{"class":97},[91,1313,1314,1317,1319,1321,1323,1326,1328,1330],{"class":93,"line":210},[91,1315,1316],{"class":120},"    filters",[91,1318,147],{"class":97},[91,1320,1278],{"class":136},[91,1322,238],{"class":97},[91,1324,1325],{"class":234},"sentryFilter",[91,1327,238],{"class":97},[91,1329,1288],{"class":136},[91,1331,1291],{"class":97},[91,1333,1334],{"class":93,"line":262},[91,1335,1336],{"class":97},"  },\n",[91,1338,1339],{"class":93,"line":268},[91,1340,271],{"class":97},[42,1342,1343,1344,1347,1348,1351],{},"Globals are runtime-applied (not baked into each route file), but they still show up in\n",[49,1345,1346],{},".roost\u002Fmanifest.json"," annotated ",[49,1349,1350],{},"(global)",", so the full effective chain per route stays visible.",[73,1353,1355],{"id":1354},"async-everywhere","Async everywhere",[42,1357,1358,1359,1362,1363,1366,1367,1370,1371,1373,1374,1377,1378,1377,1381,1384,1385,1388,1389,1392],{},"Every stage is ",[46,1360,1361],{},"awaited",", so guards, interceptors, filters, and handlers can all be ",[49,1364,1365],{},"async"," —\na guard can ",[49,1368,1369],{},"await"," a DB membership check, a handler can ",[49,1372,1369],{}," a service, an interceptor can\ntime the awaited work. ",[49,1375,1376],{},"canActivate"," \u002F ",[49,1379,1380],{},"intercept",[49,1382,1383],{},"catch"," \u002F the handler each return\n",[49,1386,1387],{},"T | Promise\u003CT>","; sync and async compose identically. The playground's ",[49,1390,1391],{},"reports"," feature shows it\nend to end: an async guard, an async controller method, and an async service doing concurrent I\u002FO.",[1394,1395,1396],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .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}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":68,"searchDepth":109,"depth":109,"links":1398},[1399,1401,1403,1405,1406,1407],{"id":75,"depth":109,"text":1400},"Guards — @UseGuards",{"id":334,"depth":109,"text":1402},"Interceptors — @UseInterceptors",{"id":589,"depth":109,"text":1404},"Filters — @UseFilters",{"id":754,"depth":109,"text":755},{"id":1240,"depth":109,"text":1241},{"id":1354,"depth":109,"text":1355},"Guards, interceptors, and filters — all DI'd providers, differing only in when they run.","md",null,{},{"title":13,"description":1408},"KkL2pZ2XZjbFfWlIsXGPFKWw-9d6HMLKSzGfO_p6J_8",[1415,1417],{"title":9,"path":10,"stem":11,"description":1416,"children":-1},"Decorators as build-time annotations, awilix DI, and the two generated artifacts.",{"title":17,"path":18,"stem":19,"description":1418,"children":-1},"Tenant isolation by construction — no tenantId threading — and the same pattern against real Postgres RLS.",1780506501961]