[{"data":1,"prerenderedAt":2934},["ShallowReactive",2],{"navigation_docs":3,"-guide-kyo":93,"-guide-kyo-surround":2929},[4,71],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":70},"Guide","i-lucide-book-open","\u002Fguide","1.guide",[10,14,18,22,26,30,34,38,42,46,50,54,58,62,66],{"title":11,"path":12,"stem":13},"Getting started","\u002Fguide\u002Fgetting-started","1.guide\u002F01.getting-started",{"title":15,"path":16,"stem":17},"Adoption paths","\u002Fguide\u002Fadoption","1.guide\u002F02.adoption",{"title":19,"path":20,"stem":21},"Routes and typed $fetch","\u002Fguide\u002Froutes","1.guide\u002F02.routes",{"title":23,"path":24,"stem":25},"Kyo handlers","\u002Fguide\u002Fkyo","1.guide\u002F03.kyo",{"title":27,"path":28,"stem":29},"ZIO handlers","\u002Fguide\u002Fzio","1.guide\u002F04.zio",{"title":31,"path":32,"stem":33},"Configuration and secrets","\u002Fguide\u002Fconfiguration","1.guide\u002F05.configuration",{"title":35,"path":36,"stem":37},"Application and request lifetimes","\u002Fguide\u002Flifetimes","1.guide\u002F06.lifetimes",{"title":39,"path":40,"stem":41},"Schemas","\u002Fguide\u002Fschema","1.guide\u002F07.schema",{"title":43,"path":44,"stem":45},"PostgreSQL","\u002Fguide\u002Fdatabase","1.guide\u002F08.database",{"title":47,"path":48,"stem":49},"Streaming and cancellation","\u002Fguide\u002Fstreaming","1.guide\u002F09.streaming",{"title":51,"path":52,"stem":53},"Testing","\u002Fguide\u002Ftesting","1.guide\u002F10.testing",{"title":55,"path":56,"stem":57},"Nitro storage and NuxtHub KV","\u002Fguide\u002Fstorage","1.guide\u002F11.storage",{"title":59,"path":60,"stem":61},"Debug Scala handlers","\u002Fguide\u002Fdebugging","1.guide\u002F12.debugging",{"title":63,"path":64,"stem":65},"npm build tooling","\u002Fguide\u002Fnpm-tooling","1.guide\u002F13.npm-tooling",{"title":67,"path":68,"stem":69},"Set up h34s with a coding assistant","\u002Fguide\u002Fagent-setup","1.guide\u002F14.agent-setup",false,{"title":72,"icon":73,"path":74,"stem":75,"children":76,"page":70},"Reference","i-lucide-library","\u002Freference","2.reference",[77,81,85,89],{"title":78,"path":79,"stem":80},"Architecture","\u002Freference\u002Farchitecture","2.reference\u002F1.architecture",{"title":82,"path":83,"stem":84},"Releases","\u002Freference\u002Freleases","2.reference\u002F2.releases",{"title":86,"path":87,"stem":88},"Compatibility","\u002Freference\u002Fcompatibility","2.reference\u002F3.compatibility",{"title":90,"path":91,"stem":92},"Migration guide","\u002Freference\u002Fmigration","2.reference\u002F4.migration",{"id":94,"title":23,"body":95,"description":2923,"extension":2924,"links":2925,"meta":2926,"navigation":289,"path":24,"seo":2927,"stem":25,"__hash__":2928},"docs\u002F1.guide\u002F03.kyo.md",{"type":96,"value":97,"toc":2905},"minimark",[98,107,131,136,171,200,204,207,464,475,500,504,509,647,657,668,690,783,823,828,848,881,885,888,947,952,965,969,986,1153,1320,1323,1327,1337,1413,1499,1535,1542,1548,1552,1562,1708,1714,1732,1736,1743,1786,1795,1948,1962,1965,2070,2107,2126,2132,2142,2177,2358,2365,2576,2589,2593,2596,2707,2711,2718,2746,2749,2753,2764,2786,2789,2809,2831,2861,2865,2901],[99,100,101,102,106],"p",{},"Kyo is the h34s core. ",[103,104,105],"code",{},"KHandler.define"," turns a Kyo function into an h3 handler: it takes the request snapshot, runs your program once per request, owns its scope and cancellation, and writes the response once. This guide covers everything a handler does. Your Kyo application never depends on ZIO.",[99,108,109,113,114,117,118,121,122,126,127,130],{},[110,111,112],"strong",{},"Status."," This guide describes ",[103,115,116],{},"0.2.0-alpha.1",". The older ",[103,119,120],{},"0.1.0-alpha.1"," has a different Kyo API (see the ",[123,124,125],"a",{"href":91},"migration guide","). See ",[123,128,129],{"href":83},"releases"," for installation.",[132,133,135],"h2",{"id":134},"prerequisites","Prerequisites",[137,138,139,147,162],"ul",{},[140,141,142,143,146],"li",{},"Scala 3.9.0 and Scala.js 1.22.0. Kyo 1.0.0-RC7 needs both, and its macros need ",[110,144,145],{},"JDK 25"," to run sbt. Java is a build tool only; your handlers run in Node.",[140,148,149,150,153,154,157,158,161],{},"Node 24, ",[103,151,152],{},"h3@1.15.4"," as a runtime dependency of your Nuxt app, and the ",[103,155,156],{},"@yuri4n\u002Fh34s-nuxt"," module. See ",[123,159,160],{"href":64},"npm tooling",".",[140,163,164,165,168,169,161],{},"The library dependency ",[103,166,167],{},"\"dev.yuri4n\" %%% \"kyo-h3-ext\" % \"\u003Cversion>\"",". Registry access is in ",[123,170,82],{"href":83},[99,172,173,174,177,178,181,182,185,186,189,190,189,193,189,196,199],{},"Import the API with ",[103,175,176],{},"import dev.yuri4n.exts.kyoh3.*"," and Kyo with ",[103,179,180],{},"import kyo.*",". The ",[103,183,184],{},"kyoh3"," package re-exports the shared values (",[103,187,188],{},"Request",", ",[103,191,192],{},"ResponseHead",[103,194,195],{},"SafeError",[103,197,198],{},"Secret",", ...).",[132,201,203],{"id":202},"write-your-first-handler","Write your first handler",[99,205,206],{},"A handler is a function from the request snapshot to the response body:",[208,209,214],"pre",{"className":210,"code":211,"language":212,"meta":213,"style":213},"language-scala shiki shiki-themes github-light github-light github-dark","import dev.yuri4n.exts.kyoh3.*\nimport kyo.*\nimport org.getshaka.nativeconverter.NativeConverter\n\nimport scala.scalajs.js.annotation.JSExportTopLevel\n\nfinal case class Greeting(message: String) derives NativeConverter\n\n@JSExportTopLevel(\"default\", \"hello-get\")\nval hello: EventHandler = KHandler\n  .define: request =>\n      Greeting(s\"Hello from ${request.path}\")\n  .at(Route.get(\"\u002Fapi\u002Fhello\"))\n","scala","",[103,215,216,249,261,284,291,319,324,361,366,389,410,424,446],{"__ignoreMap":213},[217,218,221,225,229,232,235,237,240,242,244,246],"span",{"class":219,"line":220},"line",1,[217,222,224],{"class":223},"s8jYJ","import",[217,226,228],{"class":227},"snPdu"," dev",[217,230,161],{"class":231},"sxrX7",[217,233,234],{"class":227},"yuri4n",[217,236,161],{"class":231},[217,238,239],{"class":227},"exts",[217,241,161],{"class":231},[217,243,184],{"class":227},[217,245,161],{"class":231},[217,247,248],{"class":227},"*\n",[217,250,252,254,257,259],{"class":219,"line":251},2,[217,253,224],{"class":223},[217,255,256],{"class":227}," kyo",[217,258,161],{"class":231},[217,260,248],{"class":227},[217,262,264,266,269,271,274,276,279,281],{"class":219,"line":263},3,[217,265,224],{"class":223},[217,267,268],{"class":227}," org",[217,270,161],{"class":231},[217,272,273],{"class":227},"getshaka",[217,275,161],{"class":231},[217,277,278],{"class":227},"nativeconverter",[217,280,161],{"class":231},[217,282,283],{"class":227},"NativeConverter\n",[217,285,287],{"class":219,"line":286},4,[217,288,290],{"emptyLinePlaceholder":289},true,"\n",[217,292,294,296,299,301,304,306,309,311,314,316],{"class":219,"line":293},5,[217,295,224],{"class":223},[217,297,298],{"class":227}," scala",[217,300,161],{"class":231},[217,302,303],{"class":227},"scalajs",[217,305,161],{"class":231},[217,307,308],{"class":227},"js",[217,310,161],{"class":231},[217,312,313],{"class":227},"annotation",[217,315,161],{"class":231},[217,317,318],{"class":227},"JSExportTopLevel\n",[217,320,322],{"class":219,"line":321},6,[217,323,290],{"emptyLinePlaceholder":289},[217,325,327,330,333,336,339,342,346,349,352,355,358],{"class":219,"line":326},7,[217,328,329],{"class":223},"final",[217,331,332],{"class":223}," case",[217,334,335],{"class":223}," class",[217,337,338],{"class":227}," Greeting",[217,340,341],{"class":231},"(",[217,343,345],{"class":344},"sP4rz","message",[217,347,348],{"class":231},": ",[217,350,351],{"class":227},"String",[217,353,354],{"class":231},") ",[217,356,357],{"class":223},"derives",[217,359,360],{"class":227}," NativeConverter\n",[217,362,364],{"class":219,"line":363},8,[217,365,290],{"emptyLinePlaceholder":289},[217,367,369,372,375,377,381,383,386],{"class":219,"line":368},9,[217,370,371],{"class":223},"@",[217,373,374],{"class":227},"JSExportTopLevel",[217,376,341],{"class":231},[217,378,380],{"class":379},"sIIMD","\"default\"",[217,382,189],{"class":231},[217,384,385],{"class":379},"\"hello-get\"",[217,387,388],{"class":231},")\n",[217,390,392,395,398,401,404,407],{"class":219,"line":391},10,[217,393,394],{"class":223},"val",[217,396,397],{"class":344}," hello",[217,399,400],{"class":223},":",[217,402,403],{"class":227}," EventHandler",[217,405,406],{"class":223}," =",[217,408,409],{"class":227}," KHandler\n",[217,411,413,416,418,421],{"class":219,"line":412},11,[217,414,415],{"class":231},"  .define",[217,417,400],{"class":223},[217,419,420],{"class":231}," request ",[217,422,423],{"class":223},"=>\n",[217,425,427,430,432,435,438,441,444],{"class":219,"line":426},12,[217,428,429],{"class":227},"      Greeting",[217,431,341],{"class":231},[217,433,434],{"class":223},"s",[217,436,437],{"class":379},"\"Hello from ",[217,439,440],{"class":231},"${request.path}",[217,442,443],{"class":379},"\"",[217,445,388],{"class":231},[217,447,449,452,455,458,461],{"class":219,"line":448},13,[217,450,451],{"class":231},"  .at(",[217,453,454],{"class":227},"Route",[217,456,457],{"class":231},".get(",[217,459,460],{"class":379},"\"\u002Fapi\u002Fhello\"",[217,462,463],{"class":231},"))\n",[99,465,466,467,470,471,474],{},"The response is ",[103,468,469],{},"200"," with ",[103,472,473],{},"{\"data\":{\"message\":\"Hello from \u003Cpath>\"}}",". The function runs once per request, never when the module loads.",[99,476,477,478,481,482,485,486,489,490,493,494,161],{},"Export each handler as the ",[103,479,480],{},"default"," export of its own Scala.js module, as above, and give it a route with ",[103,483,484],{},".at",". After ",[103,487,488],{},"h34s-scala"," links your modules, the Nuxt module mounts the route, and Nuxt types ",[103,491,492],{},"$fetch('\u002Fapi\u002Fhello')"," from the generated declaration. You write no route file. See ",[123,495,496,497],{"href":20},"routes and typed ",[103,498,499],{},"$fetch",[132,501,503],{"id":502},"read-the-request","Read the request",[99,505,506,508],{},[103,507,188],{}," is an immutable snapshot, taken once when the handler starts:",[510,511,512,525],"table",{},[513,514,515],"thead",{},[516,517,518,522],"tr",{},[519,520,521],"th",{},"Member",[519,523,524],{},"Value",[526,527,528,542,558,568,581,594,607,624,637],"tbody",{},[516,529,530,539],{},[531,532,533,189,536],"td",{},[103,534,535],{},"method",[103,537,538],{},"path",[531,540,541],{},"Upper-case method; path and query as h3 reports them",[516,543,544,552],{},[531,545,546,189,549],{},[103,547,548],{},"header(name)",[103,550,551],{},"headers",[531,553,554,555],{},"Case-insensitive lookup; duplicate headers joined with ",[103,556,557],{},"\", \"",[516,559,560,565],{},[531,561,562],{},[103,563,564],{},"headerValues(name)",[531,566,567],{},"Every value of a header, in request order",[516,569,570,578],{},[531,571,572,189,575],{},[103,573,574],{},"query",[103,576,577],{},"queryAll",[531,579,580],{},"First value per key; all values per key",[516,582,583,591],{},[531,584,585,189,588],{},[103,586,587],{},"routerParam(name)",[103,589,590],{},"routerParams",[531,592,593],{},"Decoded router parameters",[516,595,596,604],{},[531,597,598,189,601],{},[103,599,600],{},"cookie(name)",[103,602,603],{},"cookies",[531,605,606],{},"Parsed cookies",[516,608,609,614],{},[531,610,611],{},[103,612,613],{},"bearerToken",[531,615,616,617,620,621],{},"The token of exactly one ",[103,618,619],{},"Authorization: Bearer \u003Ctoken>"," header, else ",[103,622,623],{},"None",[516,625,626,634],{},[531,627,628,189,631],{},[103,629,630],{},"development",[103,632,633],{},"debug",[531,635,636],{},"Set only by the h34s Nitro plugin; a request cannot set them",[516,638,639,644],{},[531,640,641],{},[103,642,643],{},"body",[531,645,646],{},"The one-shot request body",[99,648,649,652,653,656],{},[103,650,651],{},"request.param(name)"," reads a required router parameter. It fails with a 400 ",[103,654,655],{},"HttpError"," when the parameter is absent.",[99,658,659,660,663,664,667],{},"Read the body with ",[103,661,662],{},"request.body.json[A]"," or ",[103,665,666],{},"request.body.text()",". The first read fixes the outcome; a second read returns the same text.",[99,669,670,671,674,675,677,678,681,682,685,686,689],{},"By default, ",[103,672,673],{},"json[A]"," needs a JSON content type when the request has a body. ",[103,676,499],{}," with an object body and ",[103,679,680],{},"curl --json"," send ",[103,683,684],{},"application\u002Fjson",". ",[103,687,688],{},"text()"," never checks the content type.",[510,691,692,702],{},[513,693,694],{},[516,695,696,699],{},[519,697,698],{},"Situation",[519,700,701],{},"Result",[526,703,704,727,741,751,762,775],{},[516,705,706,722],{},[531,707,708,711,712,189,714,717,718,721],{},[103,709,710],{},"Content-Type"," is not JSON (",[103,713,684],{},[103,715,716],{},"application\u002F*+json","), or is absent while the request has a body; with ",[103,719,720],{},"HandlerOptions(requireJsonContentType = false)",", an absent one is accepted",[531,723,724,726],{},[103,725,655],{}," 415",[516,728,729,736],{},[531,730,731,732,735],{},"Body larger than the limit (checked from ",[103,733,734],{},"Content-Length",", then while reading)",[531,737,738,740],{},[103,739,655],{}," 413; h34s never buffers more than the limit. A body without a content type is read up to the limit before the 415, so one above the limit answers 413",[516,742,743,746],{},[531,744,745],{},"Text is not valid JSON",[531,747,748,750],{},[103,749,655],{}," 400",[516,752,753,756],{},[531,754,755],{},"JSON does not match the converter's shape",[531,757,758,761],{},[103,759,760],{},"InvalidInput"," 422 with a generic message",[516,763,764,767],{},[531,765,766],{},"A schema-backed converter rejects the value",[531,768,769,771,772],{},[103,770,760],{}," 422 with your schema's messages; see ",[123,773,774],{"href":40},"schemas",[516,776,777,780],{},[531,778,779],{},"Any other exception from the converter",[531,781,782],{},"A defect: opaque 500",[99,784,785,786,789,790,793,794,797,798,801,802,804,805,807,808,811,812,815,816,818,819,822],{},"The default limit is ",[103,787,788],{},"BodyLimit.default"," (1 MiB). Change it for one handler with ",[103,791,792],{},"HandlerOptions(bodyLimit = ...)",", or for one read with ",[103,795,796],{},"request.body.json[A](BodyLimit(bytes))",". An absent body is ",[103,799,800],{},"\"\""," for ",[103,803,688],{},". For ",[103,806,673],{},", an absent or empty body with a JSON content type decodes like JSON ",[103,809,810],{},"null",": a model answers 422, and ",[103,813,814],{},"json[Option[A]]"," gives ",[103,817,623],{},". Without a content type, a request without a body reads the same way; a request with a body answers 415 first, unless you turned ",[103,820,821],{},"requireJsonContentType"," off.",[824,825,827],"h3",{"id":826},"cross-site-requests","Cross-site requests",[99,829,830,831,833,834,837,838,841,842,844,845,847],{},"A browser sends no ",[103,832,710],{}," for a cross-site ",[103,835,836],{},"fetch"," with a byte body or for ",[103,839,840],{},"navigator.sendBeacon",", and such requests need no CORS preflight. That is why ",[103,843,673],{}," answers 415 for a body without a content type by default: such a request fails the JSON read before your code sees its body. A cross-site request that sends ",[103,846,684],{}," needs a CORS preflight first. For routes that change state:",[137,849,850,864,871],{},[140,851,852,853,856,857,860,861,161],{},"Keep session cookies ",[103,854,855],{},"SameSite=Lax"," (the ",[103,858,859],{},"SetCookie"," default) or ",[103,862,863],{},"Strict",[140,865,866,867,870],{},"Keep the default ",[103,868,869],{},"HandlerOptions(requireJsonContentType = true)",". Turn it off only for a handler whose clients cannot send a content type, and only when that handler does not change state for a cookie-authenticated user.",[140,872,873,874,663,877,880],{},"Check ",[103,875,876],{},"request.header(\"sec-fetch-site\")",[103,878,879],{},"request.header(\"origin\")"," on routes that change state.",[132,882,884],{"id":883},"return-a-body","Return a body",[99,886,887],{},"What your function returns decides the response body:",[510,889,890,900],{},[513,891,892],{},[516,893,894,897],{},[519,895,896],{},"Return type",[519,898,899],{},"Response",[526,901,902,920,930],{},[516,903,904,914],{},[531,905,906,907,910,911],{},"Any ",[103,908,909],{},"A"," with a ",[103,912,913],{},"NativeConverter",[531,915,916,917],{},"200 (or the status you set) with ",[103,918,919],{},"{\"data\": \u003Cvalue>}",[516,921,922,927],{},[531,923,924],{},[103,925,926],{},"Unit",[531,928,929],{},"204 with no body, unless you set another status",[516,931,932,941],{},[531,933,934,937,938],{},[103,935,936],{},"Sent",", from ",[103,939,940],{},"Response.sendStream(stream)",[531,942,943,944],{},"The stream's strings, written as UTF-8 text as they come; see ",[123,945,946],{"href":48},"streaming",[99,948,949,951],{},[103,950,913],{}," converts directly between Scala and JavaScript values. No JSON text is made on the way; h3 writes the HTTP body.",[99,953,954,955,958,959,961,962,964],{},"A ",[103,956,957],{},"kyo.Stream"," is not a body. To stream, end your handler with ",[103,960,940],{}," and return its ",[103,963,936],{},"; a handler that returns a stream does not compile.",[132,966,968],{"id":967},"set-status-headers-and-cookies","Set status, headers and cookies",[99,970,971,972,181,975,977,978,981,982,985],{},"Status, headers and cookies are local state of your program, a ",[103,973,974],{},"Var[ResponseHead]",[103,976,899],{}," operations change that state; h34s writes the head once, after your program and its finalizers finish. A streaming handler is the one exception: ",[103,979,980],{},"Response.sendStream"," writes the head as it stands when you call it. ",[103,983,984],{},"GreetingRequest"," here is a request model like the demo's:",[208,987,989],{"className":210,"code":988,"language":212,"meta":213,"style":213},"val created: EventHandler = KHandler.define: request =>\n    for\n      input \u003C- request.body.json[GreetingRequest]\n      _     \u003C- Response.status(Status.Created)\n      _     \u003C- Response.header(\"x-request-id\", \"abc\", OnFailure.Keep)\n      _     \u003C- Response.cookie(SetCookie(\"session\", \"abc\", maxAge = Some(3600)))\n    yield Greeting(s\"Hello, ${input.name}!\")\n",[103,990,991,1016,1021,1037,1060,1091,1131],{"__ignoreMap":213},[217,992,993,995,998,1000,1002,1004,1007,1010,1012,1014],{"class":219,"line":220},[217,994,394],{"class":223},[217,996,997],{"class":344}," created",[217,999,400],{"class":223},[217,1001,403],{"class":227},[217,1003,406],{"class":223},[217,1005,1006],{"class":227}," KHandler",[217,1008,1009],{"class":231},".define",[217,1011,400],{"class":223},[217,1013,420],{"class":231},[217,1015,423],{"class":223},[217,1017,1018],{"class":219,"line":251},[217,1019,1020],{"class":223},"    for\n",[217,1022,1023,1026,1029,1032,1034],{"class":219,"line":263},[217,1024,1025],{"class":231},"      input ",[217,1027,1028],{"class":223},"\u003C-",[217,1030,1031],{"class":231}," request.body.json[",[217,1033,984],{"class":227},[217,1035,1036],{"class":231},"]\n",[217,1038,1039,1042,1044,1047,1050,1053,1055,1058],{"class":219,"line":286},[217,1040,1041],{"class":231},"      _     ",[217,1043,1028],{"class":223},[217,1045,1046],{"class":227}," Response",[217,1048,1049],{"class":231},".status(",[217,1051,1052],{"class":227},"Status",[217,1054,161],{"class":231},[217,1056,1057],{"class":227},"Created",[217,1059,388],{"class":231},[217,1061,1062,1064,1066,1068,1071,1074,1076,1079,1081,1084,1086,1089],{"class":219,"line":293},[217,1063,1041],{"class":231},[217,1065,1028],{"class":223},[217,1067,1046],{"class":227},[217,1069,1070],{"class":231},".header(",[217,1072,1073],{"class":379},"\"x-request-id\"",[217,1075,189],{"class":231},[217,1077,1078],{"class":379},"\"abc\"",[217,1080,189],{"class":231},[217,1082,1083],{"class":227},"OnFailure",[217,1085,161],{"class":231},[217,1087,1088],{"class":227},"Keep",[217,1090,388],{"class":231},[217,1092,1093,1095,1097,1099,1102,1104,1106,1109,1111,1113,1116,1119,1122,1124,1128],{"class":219,"line":321},[217,1094,1041],{"class":231},[217,1096,1028],{"class":223},[217,1098,1046],{"class":227},[217,1100,1101],{"class":231},".cookie(",[217,1103,859],{"class":227},[217,1105,341],{"class":231},[217,1107,1108],{"class":379},"\"session\"",[217,1110,189],{"class":231},[217,1112,1078],{"class":379},[217,1114,1115],{"class":231},", maxAge ",[217,1117,1118],{"class":223},"=",[217,1120,1121],{"class":227}," Some",[217,1123,341],{"class":231},[217,1125,1127],{"class":1126},"sBjJW","3600",[217,1129,1130],{"class":231},")))\n",[217,1132,1133,1136,1138,1140,1142,1145,1148,1151],{"class":219,"line":326},[217,1134,1135],{"class":223},"    yield",[217,1137,338],{"class":227},[217,1139,341],{"class":231},[217,1141,434],{"class":223},[217,1143,1144],{"class":379},"\"Hello, ",[217,1146,1147],{"class":231},"${input.name}",[217,1149,1150],{"class":379},"!\"",[217,1152,388],{"class":231},[510,1154,1155,1165],{},[513,1156,1157],{},[516,1158,1159,1162],{},[519,1160,1161],{},"Operation",[519,1163,1164],{},"Effect",[526,1166,1167,1184,1194,1204,1228,1259,1275,1285,1299],{},[516,1168,1169,1174],{},[531,1170,1171],{},[103,1172,1173],{},"Response.status(status)",[531,1175,1176,1177,663,1180,1183],{},"Last write wins. Use ",[103,1178,1179],{},"Status.Created",[103,1181,1182],{},"Status.unsafe(code)"," for literals",[516,1185,1186,1191],{},[531,1187,1188],{},[103,1189,1190],{},"Response.header(name, value)",[531,1192,1193],{},"Replaces earlier values of that header",[516,1195,1196,1201],{},[531,1197,1198],{},[103,1199,1200],{},"Response.appendHeader(name, value)",[531,1202,1203],{},"Adds another value",[516,1205,1206,1211],{},[531,1207,1208],{},[103,1209,1210],{},"Response.cookie(SetCookie(...))",[531,1212,1213,1216,1217,189,1220,189,1223,189,1226],{},[103,1214,1215],{},"Set-Cookie",". Defaults: ",[103,1218,1219],{},"Path=\u002F",[103,1221,1222],{},"HttpOnly",[103,1224,1225],{},"Secure",[103,1227,855],{},[516,1229,1230,1235],{},[531,1231,1232],{},[103,1233,1234],{},"Response.deleteCookie(name, path, domain, secure, onFailure)",[531,1236,1237,1238,1240,1241,1244,1245,1248,1249,1251,1252,663,1255,1258],{},"Expires a cookie. ",[103,1239,538],{}," (default ",[103,1242,1243],{},"\u002F",") and ",[103,1246,1247],{},"domain"," must match the cookie; ",[103,1250,1225],{}," is sent by default, which browsers need to delete a ",[103,1253,1254],{},"__Host-",[103,1256,1257],{},"__Secure-"," cookie",[516,1260,1261,1266],{},[531,1262,1263],{},[103,1264,1265],{},"Response.redirect(location, status)",[531,1267,1268,1269,1244,1272],{},"A 3xx status (default ",[103,1270,1271],{},"Status.Found",[103,1273,1274],{},"Location",[516,1276,1277,1282],{},[531,1278,1279],{},[103,1280,1281],{},"Response.head",[531,1283,1284],{},"What your program has set so far, without the handler defaults",[516,1286,1287,1291],{},[531,1288,1289],{},[103,1290,940],{},[531,1292,1293,1294,1296,1297],{},"Writes the head now, then the stream, and gives ",[103,1295,936],{},"; make it the last step. See ",[123,1298,946],{"href":48},[516,1300,1301,1306],{},[531,1302,1303],{},[103,1304,1305],{},"Response.sendEvents(events)",[531,1307,1308,1311,1312,1315,1316],{},[103,1309,1310],{},"sendStream"," for server-sent events: sets the event-stream headers, then writes one frame per ",[103,1313,1314],{},"ServerEvent",". See ",[123,1317,1319],{"href":1318},"\u002Fguide\u002Fstreaming#server-sent-events","server-sent events",[99,1321,1322],{},"An invalid header name or value, cookie name, or a non-3xx redirect status is a programming error. The check runs when the operation runs, not when you build the value; the handler then fails, and h34s answers with a 500.",[132,1324,1326],{"id":1325},"fail-safely","Fail safely",[99,1328,1329,1330,1332,1333,1336],{},"Fail with a ",[103,1331,195],{}," through Kyo's typed channel, ",[103,1334,1335],{},"Abort.fail",". Only these errors reach the client:",[208,1338,1340],{"className":210,"code":1339,"language":212,"meta":213,"style":213},"final case class NoteNotFound(id: String) extends SafeError:\n    def message: String     = \"Note not found\"\n    override def status: Int = 404\n",[103,1341,1342,1373,1392],{"__ignoreMap":213},[217,1343,1344,1346,1348,1350,1353,1355,1358,1360,1362,1364,1367,1370],{"class":219,"line":220},[217,1345,329],{"class":223},[217,1347,332],{"class":223},[217,1349,335],{"class":223},[217,1351,1352],{"class":227}," NoteNotFound",[217,1354,341],{"class":231},[217,1356,1357],{"class":344},"id",[217,1359,348],{"class":231},[217,1361,351],{"class":227},[217,1363,354],{"class":231},[217,1365,1366],{"class":223},"extends",[217,1368,1369],{"class":227}," SafeError",[217,1371,1372],{"class":223},":\n",[217,1374,1375,1378,1381,1383,1386,1389],{"class":219,"line":251},[217,1376,1377],{"class":223},"    def",[217,1379,1380],{"class":227}," message",[217,1382,400],{"class":223},[217,1384,1385],{"class":227}," String",[217,1387,1388],{"class":223},"     =",[217,1390,1391],{"class":379}," \"Note not found\"\n",[217,1393,1394,1397,1400,1403,1405,1408,1410],{"class":219,"line":263},[217,1395,1396],{"class":223},"    override",[217,1398,1399],{"class":223}," def",[217,1401,1402],{"class":227}," status",[217,1404,400],{"class":223},[217,1406,1407],{"class":227}," Int",[217,1409,406],{"class":223},[217,1411,1412],{"class":1126}," 404\n",[510,1414,1415,1427],{},[513,1416,1417],{},[516,1418,1419,1422,1424],{},[519,1420,1421],{},"Failure",[519,1423,1052],{},[519,1425,1426],{},"Body (default options)",[526,1428,1429,1447,1467,1483],{},[516,1430,1431,1436,1442],{},[531,1432,1433],{},[103,1434,1435],{},"Abort.fail(safeError)",[531,1437,1438,1441],{},[103,1439,1440],{},"safeError.status"," (400 by default)",[531,1443,1444],{},[103,1445,1446],{},"{\"errors\":[{\"message\":\"...\"}]}",[516,1448,1449,1459,1464],{},[531,1450,1451,1454,1455,1458],{},[103,1452,1453],{},"KHandler.failWith(status, message)"," (",[103,1456,1457],{},"Nothing \u003C Abort[HttpError]",")",[531,1460,1461],{},[103,1462,1463],{},"status",[531,1465,1466],{},"the same shape",[516,1468,1469,1474,1477],{},[531,1470,1471,1473],{},[103,1472,760],{}," from a body read",[531,1475,1476],{},"422",[531,1478,1479,1480,1482],{},"one entry per schema message, with ",[103,1481,538],{}," when known, never input values",[516,1484,1485,1491,1494],{},[531,1486,1487,1488,1490],{},"Anything else: thrown exceptions, panics, a ",[103,1489,195],{}," that is thrown",[531,1492,1493],{},"500",[531,1495,1496],{},[103,1497,1498],{},"{\"errors\":[{\"message\":\"Internal server error\"}]}",[99,1500,1501,1504,1505,1507,1508,189,1511,189,1514,189,1517,189,1520,1523,1524,1527,1528,1531,1532,1534],{},[103,1502,1503],{},"HttpError(status, message)"," is a ready-made ",[103,1506,195],{},". Helpers: ",[103,1509,1510],{},"HttpError.badRequest",[103,1512,1513],{},"unauthorized",[103,1515,1516],{},"notFound",[103,1518,1519],{},"conflict",[103,1521,1522],{},"unprocessable",". Add a header with ",[103,1525,1526],{},".header(name, value)",", for example ",[103,1529,1530],{},"WWW-Authenticate"," on a 401. A ",[103,1533,195],{}," whose status is outside 400-599 is treated as a defect.",[99,1536,1537,1538,1541],{},"The failure rule protects the response. On failure, the status always comes from the error, so a 2xx never leaks. The handler defaults, the entries you marked ",[103,1539,1540],{},"OnFailure.Keep",", and the error's own headers survive. Everything else your program set is discarded.",[99,1543,1544,1545,1547],{},"Never put secrets, stack traces or input values in a ",[103,1546,195],{}," message. Clients see it in production.",[132,1549,1551],{"id":1550},"choose-the-wire-format","Choose the wire format",[99,1553,1554,1555,1558,1559,400],{},"Pass ",[103,1556,1557],{},"HandlerOptions"," to ",[103,1560,1561],{},"KHandler.define(options)(program)",[510,1563,1564,1577],{},[513,1565,1566],{},[516,1567,1568,1571,1574],{},[519,1569,1570],{},"Option",[519,1572,1573],{},"Default",[519,1575,1576],{},"Alternatives",[526,1578,1579,1617,1645,1667,1684],{},[516,1580,1581,1586,1594],{},[531,1582,1583],{},[103,1584,1585],{},"success",[531,1587,1588,348,1591],{},[103,1589,1590],{},"SuccessEnvelope.Data",[103,1592,1593],{},"{\"data\": value}",[531,1595,1596,1599,1600,1603,1604,1606,1607,1610,1611,663,1613,1616],{},[103,1597,1598],{},"SuccessEnvelope.Raw",": the converted value without the envelope. A string is sent as ",[103,1601,1602],{},"text\u002Fplain; charset=utf-8"," and any other value as JSON (",[103,1605,684],{},"), unless you set a content type; ",[103,1608,1609],{},"X-Content-Type-Options: nosniff"," comes with the default. A ",[103,1612,810],{},[103,1614,1615],{},"undefined"," value becomes 204",[516,1618,1619,1624,1633],{},[531,1620,1621],{},[103,1622,1623],{},"errors",[531,1625,1626,1629,1630],{},[103,1627,1628],{},"ErrorEnvelope.List",": always ",[103,1631,1632],{},"{\"errors\":[...]}",[531,1634,1635,348,1638,1641,1642,1644],{},[103,1636,1637],{},"ErrorEnvelope.SingleOrList",[103,1639,1640],{},"{\"error\":{...}}"," for one entry, ",[103,1643,1632],{}," for several",[516,1646,1647,1652,1657],{},[531,1648,1649],{},[103,1650,1651],{},"defaults",[531,1653,1654],{},[103,1655,1656],{},"ResponseHead.empty",[531,1658,1659,1660,348,1663,1666],{},"For example ",[103,1661,1662],{},"ResponseHead.noStore",[103,1664,1665],{},"Cache-Control: no-store"," on success and failure",[516,1668,1669,1674,1679],{},[531,1670,1671],{},[103,1672,1673],{},"bodyLimit",[531,1675,1676,1678],{},[103,1677,788],{}," (1 MiB)",[531,1680,906,1681],{},[103,1682,1683],{},"BodyLimit(bytes)",[516,1685,1686,1690,1698],{},[531,1687,1688],{},[103,1689,821],{},[531,1691,1692,348,1695,1697],{},[103,1693,1694],{},"true",[103,1696,673],{}," answers 415 for a body without a content type",[531,1699,1700,1703,1704],{},[103,1701,1702],{},"false",": accept such a body as JSON; see ",[123,1705,1707],{"href":1706},"#cross-site-requests","cross-site requests",[99,1709,1710,1711,1713],{},"Sent streams and ",[103,1712,926],{}," bodies are never wrapped.",[99,1715,1716,1717,1720,1721,1724,1725,1727,1728,1731],{},"JSON has no ",[103,1718,1719],{},"NaN"," or infinity, and ",[103,1722,1723],{},"JSON.stringify"," would send them as ",[103,1726,810],{},". So h34s answers a value body that contains one with the opaque 500, in both envelopes, and a ",[103,1729,1730],{},"number"," in the generated type is always a finite number.",[132,1733,1735],{"id":1734},"compose-services-in-your-apphandler","Compose services in your AppHandler",[99,1737,1738,1739,1742],{},"Write one wiring point that provides your services to every handler. The demo calls it ",[103,1740,1741],{},"AppHandler","; you choose the name. Define your service first:",[208,1744,1746],{"className":210,"code":1745,"language":212,"meta":213,"style":213},"trait Greetings:\n    def greet(name: String): Greeting \u003C Sync\n",[103,1747,1748,1758],{"__ignoreMap":213},[217,1749,1750,1753,1756],{"class":219,"line":220},[217,1751,1752],{"class":223},"trait",[217,1754,1755],{"class":227}," Greetings",[217,1757,1372],{"class":223},[217,1759,1760,1762,1765,1767,1770,1772,1774,1776,1778,1780,1783],{"class":219,"line":251},[217,1761,1377],{"class":223},[217,1763,1764],{"class":227}," greet",[217,1766,341],{"class":231},[217,1768,1769],{"class":344},"name",[217,1771,348],{"class":231},[217,1773,351],{"class":227},[217,1775,1458],{"class":231},[217,1777,400],{"class":223},[217,1779,338],{"class":227},[217,1781,1782],{"class":223}," \u003C",[217,1784,1785],{"class":227}," Sync\n",[99,1787,1788,1789,1454,1791,1794],{},"Then provide it around each handler function. This is the demo's ",[103,1790,1741],{},[103,1792,1793],{},"examples\u002Fnuxt\u002Fscala\u002Fkyo\u002F...\u002FAppHandler.scala","):",[208,1796,1798],{"className":210,"code":1797,"language":212,"meta":213,"style":213},"object AppHandler:\n    \u002F** Every response is `Cache-Control: no-store`; bodies use the default `{ \"data\": ... }` envelope. *\u002F\n    private val options = HandlerOptions(defaults = ResponseHead.noStore)\n\n    def define[B: ResponseBody](\n        program: Request => B \u003C (HandlerEffects & Env[Greetings]),\n    )(using Frame): EventHandler =\n      KHandler.define(options)(request => Services.greetings.get.map(greetings => Env.run(greetings)(program(request))))\n",[103,1799,1800,1810,1816,1843,1847,1868,1904,1924],{"__ignoreMap":213},[217,1801,1802,1805,1808],{"class":219,"line":220},[217,1803,1804],{"class":223},"object",[217,1806,1807],{"class":227}," AppHandler",[217,1809,1372],{"class":223},[217,1811,1812],{"class":219,"line":251},[217,1813,1815],{"class":1814},"sCsY4","    \u002F** Every response is `Cache-Control: no-store`; bodies use the default `{ \"data\": ... }` envelope. *\u002F\n",[217,1817,1818,1821,1824,1827,1829,1832,1835,1837,1840],{"class":219,"line":263},[217,1819,1820],{"class":223},"    private",[217,1822,1823],{"class":223}," val",[217,1825,1826],{"class":344}," options",[217,1828,406],{"class":223},[217,1830,1831],{"class":227}," HandlerOptions",[217,1833,1834],{"class":231},"(defaults ",[217,1836,1118],{"class":223},[217,1838,1839],{"class":227}," ResponseHead",[217,1841,1842],{"class":231},".noStore)\n",[217,1844,1845],{"class":219,"line":286},[217,1846,290],{"emptyLinePlaceholder":289},[217,1848,1849,1851,1854,1857,1860,1862,1865],{"class":219,"line":293},[217,1850,1377],{"class":223},[217,1852,1853],{"class":227}," define",[217,1855,1856],{"class":231},"[",[217,1858,1859],{"class":227},"B",[217,1861,400],{"class":223},[217,1863,1864],{"class":227}," ResponseBody",[217,1866,1867],{"class":231},"](\n",[217,1869,1870,1873,1875,1877,1880,1883,1885,1887,1890,1893,1896,1898,1901],{"class":219,"line":321},[217,1871,1872],{"class":344},"        program",[217,1874,348],{"class":231},[217,1876,188],{"class":227},[217,1878,1879],{"class":223}," =>",[217,1881,1882],{"class":227}," B",[217,1884,1782],{"class":223},[217,1886,1454],{"class":231},[217,1888,1889],{"class":227},"HandlerEffects",[217,1891,1892],{"class":223}," &",[217,1894,1895],{"class":227}," Env",[217,1897,1856],{"class":231},[217,1899,1900],{"class":227},"Greetings",[217,1902,1903],{"class":231},"]),\n",[217,1905,1906,1909,1912,1915,1917,1919,1921],{"class":219,"line":326},[217,1907,1908],{"class":231},"    )(",[217,1910,1911],{"class":223},"using",[217,1913,1914],{"class":227}," Frame",[217,1916,1458],{"class":231},[217,1918,400],{"class":223},[217,1920,403],{"class":227},[217,1922,1923],{"class":223}," =\n",[217,1925,1926,1929,1932,1935,1938,1941,1943,1945],{"class":219,"line":363},[217,1927,1928],{"class":227},"      KHandler",[217,1930,1931],{"class":231},".define(options)(request ",[217,1933,1934],{"class":223},"=>",[217,1936,1937],{"class":227}," Services",[217,1939,1940],{"class":231},".greetings.get.map(greetings ",[217,1942,1934],{"class":223},[217,1944,1895],{"class":227},[217,1946,1947],{"class":231},".run(greetings)(program(request))))\n",[99,1949,1950,1953,1954,1957,1958,161],{},[103,1951,1952],{},"Services.greetings"," is a ",[103,1955,1956],{},"Shared[Greetings]"," built from the startup runtime config; see ",[123,1959,1961],{"href":1960},"\u002Fguide\u002Flifetimes#application-resources","lifetimes",[99,1963,1964],{},"Endpoints then ask for the service:",[208,1966,1968],{"className":210,"code":1967,"language":212,"meta":213,"style":213},"@JSExportTopLevel(\"default\", \"kyo-greeting-post\")\nval personalizedGreeting: EventHandler = AppHandler\n  .define: request =>\n      for\n        input    \u003C- request.body.json[GreetingRequest]\n        greeting \u003C- Env.use[Greetings](_.greet(input.name))\n      yield greeting\n  .at(Route.post(\"\u002Fapi\u002Fkyo\u002Fgreeting\"))\n",[103,1969,1970,1987,2003,2013,2018,2031,2048,2056],{"__ignoreMap":213},[217,1971,1972,1974,1976,1978,1980,1982,1985],{"class":219,"line":220},[217,1973,371],{"class":223},[217,1975,374],{"class":227},[217,1977,341],{"class":231},[217,1979,380],{"class":379},[217,1981,189],{"class":231},[217,1983,1984],{"class":379},"\"kyo-greeting-post\"",[217,1986,388],{"class":231},[217,1988,1989,1991,1994,1996,1998,2000],{"class":219,"line":251},[217,1990,394],{"class":223},[217,1992,1993],{"class":344}," personalizedGreeting",[217,1995,400],{"class":223},[217,1997,403],{"class":227},[217,1999,406],{"class":223},[217,2001,2002],{"class":227}," AppHandler\n",[217,2004,2005,2007,2009,2011],{"class":219,"line":263},[217,2006,415],{"class":231},[217,2008,400],{"class":223},[217,2010,420],{"class":231},[217,2012,423],{"class":223},[217,2014,2015],{"class":219,"line":286},[217,2016,2017],{"class":223},"      for\n",[217,2019,2020,2023,2025,2027,2029],{"class":219,"line":293},[217,2021,2022],{"class":231},"        input    ",[217,2024,1028],{"class":223},[217,2026,1031],{"class":231},[217,2028,984],{"class":227},[217,2030,1036],{"class":231},[217,2032,2033,2036,2038,2040,2043,2045],{"class":219,"line":321},[217,2034,2035],{"class":231},"        greeting ",[217,2037,1028],{"class":223},[217,2039,1895],{"class":227},[217,2041,2042],{"class":231},".use[",[217,2044,1900],{"class":227},[217,2046,2047],{"class":231},"](_.greet(input.name))\n",[217,2049,2050,2053],{"class":219,"line":326},[217,2051,2052],{"class":223},"      yield",[217,2054,2055],{"class":231}," greeting\n",[217,2057,2058,2060,2062,2065,2068],{"class":219,"line":363},[217,2059,451],{"class":231},[217,2061,454],{"class":227},[217,2063,2064],{"class":231},".post(",[217,2066,2067],{"class":379},"\"\u002Fapi\u002Fkyo\u002Fgreeting\"",[217,2069,463],{"class":231},[99,2071,2072,2074,2075,2078,2079,2082,2083,2085,2086,2088,2089,2092,2093,2096,2097,2100,2101,2104,2105,161],{},[103,2073,1889],{}," is ",[103,2076,2077],{},"Async & Scope & Abort[Throwable] & Var[ResponseHead] & Env[Fetch & HandlerTransport]",": the effects the core handles. ",[103,2080,2081],{},"Env[Fetch & HandlerTransport]"," holds the request's server-side ",[103,2084,499],{}," and the transport that ",[103,2087,980],{}," writes. h34s provides it for each request, and ",[103,2090,2091],{},"KHandler.test"," provides it in a test, so you never build it. The two services share one ",[103,2094,2095],{},"Env"," entry because, with two entries, Kyo 1.0.0-RC7 cannot reduce what is left after your own ",[103,2098,2099],{},"Env.run(service)",", and the wiring above would stop compiling. Resolve every other effect, such as ",[103,2102,2103],{},"Env[Greetings]",", before you pass the program to ",[103,2106,105],{},[99,2108,2109,2112,2113,2116,2117,2119,2120,2116,2123,161],{},[103,2110,2111],{},"Env.run"," provides a value. It does not acquire or release anything. For a database pool or another resource that lives as long as the application, use ",[103,2114,2115],{},"Shared","; see ",[123,2118,1961],{"href":36},". Read configuration with ",[103,2121,2122],{},"RuntimeConfig",[123,2124,2125],{"href":32},"configuration",[132,2127,2129,2130],{"id":2128},"call-other-routes-with-fetch","Call other routes with ",[103,2131,499],{},[99,2133,2134,2135,2138,2139,2141],{},"A handler can call another route of your app, Scala or JavaScript, with ",[103,2136,2137],{},"KHandler.$fetch",". Nitro answers a path that starts with ",[103,2140,1243],{}," in-process, without a socket, and your route rules, middleware and Nitro plugins run as they do for a browser request.",[99,2143,2144,2145,2148,2149,2152,2153,2156,2157,685,2159,2162,2163,2166,2167,2170,2171,2173,2174,400],{},"In this example, ",[103,2146,2147],{},"\u002Fapi\u002Fgreeting"," is an h34s route that returns ",[103,2150,2151],{},"Greeting(\"hi\")",", and ",[103,2154,2155],{},"\u002Fapi\u002Fevents"," sends NDJSON lines with ",[103,2158,980],{},[103,2160,2161],{},"GreetingData"," is the example's model of the ",[103,2164,2165],{},"{\"data\": ...}"," envelope: ",[103,2168,2169],{},"final case class GreetingData(data: Greeting) derives NativeConverter",". The code is the guide's ",[103,2172,499],{}," test of ",[103,2175,2176],{},"NitroLocalFetchSuite",[208,2178,2180],{"className":210,"code":2179,"language":212,"meta":213,"style":213},"val summary: Request => String \u003C HandlerEffects = _ =>\n  for\n    greeting \u003C- KHandler.$fetch.get[GreetingData](\"\u002Fapi\u002Fgreeting\")\n    events   \u003C- KHandler.$fetch.raw(\"\u002Fapi\u002Fevents\")\n    first    \u003C- events match\n        case FetchResponse.Streamed(_, _, body) =>\n          Abort.run[FetchError](body.take(2).run).map(_.foldError(_.mkString, _ => \"the stream broke\"))\n        case plain: FetchResponse.Plain => Kyo.lift(plain.text)\n  yield s\"${greeting.data.message}: $first\"\n",[103,2181,2182,2210,2215,2237,2254,2267,2285,2312,2337],{"__ignoreMap":213},[217,2183,2184,2186,2189,2191,2194,2196,2198,2200,2203,2205,2208],{"class":219,"line":220},[217,2185,394],{"class":223},[217,2187,2188],{"class":344}," summary",[217,2190,400],{"class":223},[217,2192,2193],{"class":227}," Request",[217,2195,1879],{"class":223},[217,2197,1385],{"class":227},[217,2199,1782],{"class":223},[217,2201,2202],{"class":227}," HandlerEffects",[217,2204,406],{"class":223},[217,2206,2207],{"class":231}," _ ",[217,2209,423],{"class":223},[217,2211,2212],{"class":219,"line":251},[217,2213,2214],{"class":223},"  for\n",[217,2216,2217,2220,2222,2224,2227,2229,2232,2235],{"class":219,"line":263},[217,2218,2219],{"class":231},"    greeting ",[217,2221,1028],{"class":223},[217,2223,1006],{"class":227},[217,2225,2226],{"class":231},".$fetch.get[",[217,2228,2161],{"class":227},[217,2230,2231],{"class":231},"](",[217,2233,2234],{"class":379},"\"\u002Fapi\u002Fgreeting\"",[217,2236,388],{"class":231},[217,2238,2239,2242,2244,2246,2249,2252],{"class":219,"line":286},[217,2240,2241],{"class":231},"    events   ",[217,2243,1028],{"class":223},[217,2245,1006],{"class":227},[217,2247,2248],{"class":231},".$fetch.raw(",[217,2250,2251],{"class":379},"\"\u002Fapi\u002Fevents\"",[217,2253,388],{"class":231},[217,2255,2256,2259,2261,2264],{"class":219,"line":293},[217,2257,2258],{"class":231},"    first    ",[217,2260,1028],{"class":223},[217,2262,2263],{"class":231}," events ",[217,2265,2266],{"class":223},"match\n",[217,2268,2269,2272,2275,2277,2280,2283],{"class":219,"line":321},[217,2270,2271],{"class":223},"        case",[217,2273,2274],{"class":227}," FetchResponse",[217,2276,161],{"class":231},[217,2278,2279],{"class":227},"Streamed",[217,2281,2282],{"class":231},"(_, _, body) ",[217,2284,423],{"class":223},[217,2286,2287,2290,2293,2296,2299,2302,2305,2307,2310],{"class":219,"line":326},[217,2288,2289],{"class":227},"          Abort",[217,2291,2292],{"class":231},".run[",[217,2294,2295],{"class":227},"FetchError",[217,2297,2298],{"class":231},"](body.take(",[217,2300,2301],{"class":1126},"2",[217,2303,2304],{"class":231},").run).map(_.foldError(_.mkString, _ ",[217,2306,1934],{"class":223},[217,2308,2309],{"class":379}," \"the stream broke\"",[217,2311,463],{"class":231},[217,2313,2314,2316,2319,2321,2324,2326,2329,2331,2334],{"class":219,"line":363},[217,2315,2271],{"class":223},[217,2317,2318],{"class":344}," plain",[217,2320,348],{"class":231},[217,2322,2323],{"class":227},"FetchResponse",[217,2325,161],{"class":231},[217,2327,2328],{"class":227},"Plain",[217,2330,1879],{"class":223},[217,2332,2333],{"class":227}," Kyo",[217,2335,2336],{"class":231},".lift(plain.text)\n",[217,2338,2339,2342,2345,2347,2350,2352,2355],{"class":219,"line":368},[217,2340,2341],{"class":223},"  yield",[217,2343,2344],{"class":223}," s",[217,2346,443],{"class":379},[217,2348,2349],{"class":231},"${greeting.data.message}",[217,2351,348],{"class":379},[217,2353,2354],{"class":231},"$first",[217,2356,2357],{"class":379},"\"\n",[99,2359,2360,2361,2364],{},"The handler answers ",[103,2362,2363],{},"\"hi: 1\\n2\\n\"",". Keep these rules in mind:",[137,2366,2367,2397,2428,2450,2460,2482,2498,2522,2534,2551],{},[140,2368,2369,2372,2373,189,2376,2379,2380,2383,2384,2386,2387,2389,2390,2396],{},[110,2370,2371],{},"Model the envelope."," ",[103,2374,2375],{},"get",[103,2377,2378],{},"post"," and ",[103,2381,2382],{},"as"," decode the JSON as it arrives. An h34s route with the default options answers ",[103,2385,2165],{},", so decode it with a model of the envelope, such as ",[103,2388,2161],{},". The typed route call (",[123,2391,2395],{"href":2392,"rel":2393},"https:\u002F\u002Fgithub.com\u002Fyuri4n\u002Fh34s\u002Fissues\u002F46",[2394],"nofollow","#46",") will remove the envelope for you, because only the route knows its options.",[140,2398,2399,2372,2402,2405,2406,2409,2410,2413,2414,2416,2417,189,2420,2423,2424,2427],{},[110,2400,2401],{},"Plain or streamed.",[103,2403,2404],{},"raw"," returns as soon as the head arrives. It gives ",[103,2407,2408],{},"FetchResponse.Streamed"," when the response carries the ",[103,2411,2412],{},"x-h34s-stream"," marker (every ",[103,2415,980],{}," sets it) or a stream content type (",[103,2418,2419],{},"text\u002Fevent-stream",[103,2421,2422],{},"application\u002Fx-ndjson","), and ",[103,2425,2426],{},"FetchResponse.Plain"," otherwise.",[140,2429,2430,2433,2434,2437,2438,2441,2442,2445,2446,2449],{},[110,2431,2432],{},"Read a streamed body once."," Its type is ",[103,2435,2436],{},"Stream[String, Async & Scope & Abort[FetchError]]",". When the callee's stream fails after its head, or the connection closes, the read fails with ",[103,2439,2440],{},"FetchError.BodyFailed(status, reason)",". Handle it with ",[103,2443,2444],{},"Abort.run[FetchError]",", as above. The ",[103,2447,2448],{},"reason"," is h34s' own text; it never quotes the callee's error.",[140,2451,2452,2455,2456,161],{},[110,2453,2454],{},"Your scope cancels the rest."," A body that you do not read to its end is cancelled when your handler's scope closes. The cancel waits until the callee's stream and request finalizers have run. See ",[123,2457,2459],{"href":2458},"\u002Fguide\u002Fstreaming#in-process-calls","in-process calls",[140,2461,2462,2372,2465,2467,2468,189,2471,189,2473,2379,2475,2477,2478,2481],{},[110,2463,2464],{},"Statuses do not throw.",[103,2466,2404],{}," gives any status as a response. ",[103,2469,2470],{},"text",[103,2472,2382],{},[103,2474,2375],{},[103,2476,2378],{}," fail with ",[103,2479,2480],{},"FetchError.Status(status, body)"," unless the status is 2xx. There is no retry.",[140,2483,2484,2487,2488,2491,2492,2494,2495,2497],{},[110,2485,2486],{},"The base URL is added."," h34s puts your app's ",[103,2489,2490],{},"app.baseURL"," in front of a path that starts with ",[103,2493,1243],{},", as Nuxt's ",[103,2496,499],{}," does. A path that already starts with the base stays as it is.",[140,2499,2500,2503,2504,2506,2507,2510,2511,2379,2514,2517,2518,2521],{},[110,2501,2502],{},"Credentials stay in-process."," A path that starts with ",[103,2505,1243],{}," gets the incoming request's headers and cookies, as in Nuxt. An absolute URL gets only the headers that you pass, so the user's cookies and ",[103,2508,2509],{},"authorization"," never reach another host. The called route gets only the ",[103,2512,2513],{},"_platform",[103,2515,2516],{},"waitUntil"," entries of your request's ",[103,2519,2520],{},"event.context",", never the rest.",[140,2523,2524,2527,2528,2530,2531,2533],{},[110,2525,2526],{},"Errors are not public."," A ",[103,2529,2295],{}," is not a ",[103,2532,195],{},". If your handler does not handle it, the client gets the opaque 500.",[140,2535,2536,2372,2539,2541,2542,685,2544,2546,2547,2550],{},[110,2537,2538],{},"Repeated headers.",[103,2540,548],{}," joins repeated values with ",[103,2543,557],{},[103,2545,564],{}," gives each ",[103,2548,2549],{},"set-cookie"," value as its own entry.",[140,2552,2553,2372,2556,2559,2560,2562,2563,2566,2567,2570,2571,1315,2574,685],{},[110,2554,2555],{},"Server-sent events.",[103,2557,2558],{},"KHandler.$fetch.events[E](url)"," reads a ",[103,2561,2419],{}," route as a ",[103,2564,2565],{},"Stream[E, Async & Scope & Abort[FetchError]]",", each event's ",[103,2568,2569],{},"data"," decoded as JSON for ",[103,2572,2573],{},"E",[123,2575,1319],{"href":1318},[99,2577,2578,2579,2116,2582,161],{},"Test such a handler with ",[103,2580,2581],{},"Fetch.fake",[123,2583,2585,2586,2588],{"href":2584},"\u002Fguide\u002Ftesting#answer-fetch-calls","answer ",[103,2587,499],{}," calls",[132,2590,2592],{"id":2591},"kyo-details-to-keep-in-mind","Kyo details to keep in mind",[99,2594,2595],{},"These come from Kyo 1.0.0-RC7 semantics. They matter once your handlers grow:",[137,2597,2598,2617,2630,2659,2692],{},[140,2599,2600,2606,2607,2609,2610,2613,2614,2616],{},[110,2601,2602,2605],{},[103,2603,2604],{},"Abort.run[Throwable]"," also catches thrown exceptions."," If your own code runs ",[103,2608,2604],{}," (or recovers ",[103,2611,2612],{},"Throwable","), a thrown exception becomes a typed failure there. h34s itself keeps thrown exceptions as defects. Recover narrower types, such as your own ",[103,2615,195],{}," subtypes.",[140,2618,2619,2629],{},[110,2620,2621,2624,2625,2628],{},[103,2622,2623],{},"Abort.recover"," does not roll back ",[103,2626,2627],{},"Var"," changes."," A status or header you set before a recovered failure stays set. Set response entries after the risky step, or set them again in the recovery branch.",[140,2631,2632,2637,2638,2379,2641,2644,2645,2648,2649,2651,2652,2654,2655,2658],{},[110,2633,2634,2636],{},[103,2635,2627],{}," needs an isolate across forks."," Forking operations such as ",[103,2639,2640],{},"Fiber.init",[103,2642,2643],{},"Async.race"," require an ",[103,2646,2647],{},"Isolate"," for a ",[103,2650,2627],{}," they carry, so code that forks while it needs ",[103,2653,974],{}," does not compile without one. Call ",[103,2656,2657],{},"Response.*"," in the request fiber, before or after the parallel part.",[140,2660,2661,2668,2669,2672,2673,2676,2677,2680,2681,2684,2685,2687,2688,2691],{},[110,2662,2663,2664,2667],{},"Kyo ",[103,2665,2666],{},"Scope"," finalizer errors are logged and do not change the response."," Do work that must affect the response in the main program, not in a finalizer. The ZIO adapter behaves the same since the next release (provisionally ",[103,2670,2671],{},"0.3.0-alpha.1","); its log line is ",[103,2674,2675],{},"h34s: a ZIO finalizer failed",". Kyo differs here: a failing finalizer inside your own nested ",[103,2678,2679],{},"Scope.run"," is also logged (",[103,2682,2683],{},"Scope finalizer failed",") and keeps the response, because Kyo's ",[103,2686,2679],{}," itself logs finalizer failures. In ZIO, a release that dies inside your own ",[103,2689,2690],{},"ZIO.scoped"," block fails the program and answers 500. This is an accepted difference: inner-scope semantics belong to each runtime.",[140,2693,2694,2700,2701,2116,2704,161],{},[110,2695,2696,2699],{},[103,2697,2698],{},"Async.uninterruptible"," does not make the request wait."," It runs the step on a fiber that nothing owns. In a handler, use ",[103,2702,2703],{},"KHandler.uninterruptible(step)",[123,2705,1961],{"href":2706},"\u002Fguide\u002Flifetimes#when-the-client-disconnects",[132,2708,2710],{"id":2709},"choose-locale-data-in-your-build","Choose locale data in your build",[99,2712,2713,2714,2717],{},"Kyo brings a full currency database through scala-java-locales. It adds tens of MB to the linked JavaScript. The demo excludes it in ",[103,2715,2716],{},"build.sbt",", because its English-only greeting does not need currency data:",[208,2719,2721],{"className":210,"code":2720,"language":212,"meta":213,"style":213},"excludeDependencies += ExclusionRule(\"io.github.cquiroz\", \"locales-full-currencies-db_sjs1_3\")\n",[103,2722,2723],{"__ignoreMap":213},[217,2724,2725,2728,2731,2734,2736,2739,2741,2744],{"class":219,"line":220},[217,2726,2727],{"class":231},"excludeDependencies ",[217,2729,2730],{"class":223},"+=",[217,2732,2733],{"class":227}," ExclusionRule",[217,2735,341],{"class":231},[217,2737,2738],{"class":379},"\"io.github.cquiroz\"",[217,2740,189],{"class":231},[217,2742,2743],{"class":379},"\"locales-full-currencies-db_sjs1_3\"",[217,2745,388],{"class":231},[99,2747,2748],{},"This is application policy. Keep the database if you format currencies or other locales.",[132,2750,2752],{"id":2751},"run-the-demos-kyo-routes","Run the demo's Kyo routes",[99,2754,2755,2756,2759,2760,2763],{},"The demo in ",[103,2757,2758],{},"examples\u002Fnuxt\u002F"," has a Kyo greeting with no database. The ",[123,2761,2762],{"href":12},"getting-started tutorial"," walks through it step by step. From the repository root, with JDK 25 selected for sbt:",[208,2765,2769],{"className":2766,"code":2767,"language":2768,"meta":213,"style":213},"language-sh shiki shiki-themes github-light github-light github-dark","pnpm install --frozen-lockfile\npnpm run scala:fast\npnpm dev\n","sh",[103,2770,2771,2776,2781],{"__ignoreMap":213},[217,2772,2773],{"class":219,"line":220},[217,2774,2775],{},"pnpm install --frozen-lockfile\n",[217,2777,2778],{"class":219,"line":251},[217,2779,2780],{},"pnpm run scala:fast\n",[217,2782,2783],{"class":219,"line":263},[217,2784,2785],{},"pnpm dev\n",[99,2787,2788],{},"Then, in another terminal:",[208,2790,2792],{"className":2766,"code":2791,"language":2768,"meta":213,"style":213},"curl http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\ncurl -H 'content-type: application\u002Fjson' --data '{\"name\":\"Scala\"}' http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\ncurl -H 'content-type: application\u002Fjson' --data '{\"name\":\"  \"}' http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\n",[103,2793,2794,2799,2804],{"__ignoreMap":213},[217,2795,2796],{"class":219,"line":220},[217,2797,2798],{},"curl http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\n",[217,2800,2801],{"class":219,"line":251},[217,2802,2803],{},"curl -H 'content-type: application\u002Fjson' --data '{\"name\":\"Scala\"}' http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\n",[217,2805,2806],{"class":219,"line":263},[217,2807,2808],{},"curl -H 'content-type: application\u002Fjson' --data '{\"name\":\"  \"}' http:\u002F\u002F127.0.0.1:3000\u002Fapi\u002Fkyo\u002Fgreeting\n",[99,2810,2811,2812,189,2815,2818,2819,2822,2823,2826,2827,2830],{},"Expected bodies: ",[103,2813,2814],{},"{\"data\":{\"message\":\"Hello, Kyo!\"}}",[103,2816,2817],{},"{\"data\":{\"message\":\"Hello, Scala!\"}}",", and a 422 with ",[103,2820,2821],{},"{\"errors\":[{\"message\":\"must not be blank\",\"path\":\"name\"}]}",". The salutation comes from ",[103,2824,2825],{},"runtimeConfig.greeting.salutation","; start the server with ",[103,2828,2829],{},"NUXT_GREETING_SALUTATION=Hi"," to change it.",[99,2832,2833,2836,2837,2840,2841,2844,2845,2848,2849,2379,2852,2855,2856,1315,2858,161],{},[110,2834,2835],{},"Evidence."," On macOS x86_64 with Node 24.19.0, these responses came back from a production build of the demo (",[103,2838,2839],{},"nuxt build",", then the Node server), and under ",[103,2842,2843],{},"nuxt dev"," (Nuxt 4.3.1, after ",[103,2846,2847],{},"pnpm run scala:fast","): GET 200 with ",[103,2850,2851],{},"{data}",[103,2853,2854],{},"no-store",", a blank name 422 with path ",[103,2857,1769],{},[123,2859,2860],{"href":87},"compatibility",[132,2862,2864],{"id":2863},"next-steps","Next steps",[137,2866,2867,2871,2875,2880,2886,2890,2895],{},[140,2868,2869],{},[123,2870,31],{"href":32},[140,2872,2873],{},[123,2874,35],{"href":36},[140,2876,2877,2879],{},[123,2878,39],{"href":40}," for validated models",[140,2881,2882,2885],{},[123,2883,2884],{"href":44},"Databases"," with kyo-sql",[140,2887,2888],{},[123,2889,47],{"href":48},[140,2891,2892,2894],{},[123,2893,51],{"href":52}," handlers without h3",[140,2896,2897,2900],{},[123,2898,2899],{"href":60},"Debugging"," and source maps",[2902,2903,2904],"style",{},"html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":213,"searchDepth":251,"depth":251,"links":2906},[2907,2908,2909,2912,2913,2914,2915,2916,2917,2919,2920,2921,2922],{"id":134,"depth":251,"text":135},{"id":202,"depth":251,"text":203},{"id":502,"depth":251,"text":503,"children":2910},[2911],{"id":826,"depth":263,"text":827},{"id":883,"depth":251,"text":884},{"id":967,"depth":251,"text":968},{"id":1325,"depth":251,"text":1326},{"id":1550,"depth":251,"text":1551},{"id":1734,"depth":251,"text":1735},{"id":2128,"depth":251,"text":2918},"Call other routes with $fetch",{"id":2591,"depth":251,"text":2592},{"id":2709,"depth":251,"text":2710},{"id":2751,"depth":251,"text":2752},{"id":2863,"depth":251,"text":2864},"Write handlers as Kyo functions from the request to the body, set the response head, fail safely, and wire services in your AppHandler.","md",null,{},{"title":23,"description":2923},"-WSCq0Q4mUq3AeqiridIzMmAlmpc7U2qR-EB8hUc8aM",[2930,2932],{"title":19,"path":20,"stem":21,"description":2931,"children":-1},"Mount Scala handlers from Scala with .at(Route...), and let Nuxt type $fetch from the generated declarations.",{"title":27,"path":28,"stem":29,"description":2933,"children":-1},"Write ordinary ZIO programs; the hand-written ZIO adapter runs them over the Kyo core.",1792533381917]