[{"data":1,"prerenderedAt":2914},["ShallowReactive",2],{"navigation_docs":3,"-guide-streaming":93,"-guide-streaming-surround":2909},[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":47,"body":95,"description":2903,"extension":2904,"links":2905,"meta":2906,"navigation":1266,"path":48,"seo":2907,"stem":49,"__hash__":2908},"docs\u002F1.guide\u002F09.streaming.md",{"type":96,"value":97,"toc":2884},"minimark",[98,107,142,147,160,175,304,318,403,417,420,428,435,439,577,581,586,602,614,618,621,653,659,663,666,669,682,697,701,794,797,801,806,820,831,913,925,947,995,1017,1021,1041,1055,1060,1066,1155,1176,1204,1208,1214,1452,1455,1629,1640,1654,1678,1688,1712,1718,1722,1732,2216,2226,2236,2240,2260,2333,2339,2416,2429,2545,2559,2563,2575,2711,2726,2730,2745,2749,2829,2833,2880],[99,100,101,102,106],"p",{},"Send a stream with ",[103,104,105],"code",{},"Response.sendStream",", and h34s writes its elements as they are produced. The request scope stays open until the stream ends, so the resources that the stream needs stay alive. When the client goes away, h34s stops the stream and waits for its cleanup.",[99,108,109,113,114,116,117,120,121,124,125,128,129,132,133,136,137,141],{},[110,111,112],"strong",{},"Status."," ",[103,115,105],{}," and ",[103,118,119],{},"Sent"," are new in ",[103,122,123],{},"0.3.0-alpha.1",", in both runtimes. They replace the stream bodies of ",[103,126,127],{},"0.2.0-alpha.1",", which you returned from the handler, and ",[103,130,131],{},"ZHandler.sendStream"," of ",[103,134,135],{},"0.1.0-alpha.1",", which had another signature. See the ",[138,139,140],"a",{"href":91},"migration guide",".",[143,144,146],"h2",{"id":145},"send-a-stream","Send a stream",[99,148,149,150,153,154,156,157,159],{},"Make ",[103,151,152],{},"Response.sendStream(stream)"," the last step of your handler, and return the ",[103,155,119],{}," value that it gives. ",[103,158,119],{}," is the handler's body: it tells h34s that the response is already on its way, so h34s writes nothing more.",[99,161,162,163,166,167,170,171,174],{},"Kyo: send a ",[103,164,165],{},"kyo.Stream[String, S]",", where ",[103,168,169],{},"S"," needs at most ",[103,172,173],{},"Async & Abort[Throwable] & Scope",":",[176,177,182],"pre",{"className":178,"code":179,"language":180,"meta":181,"style":181},"language-scala shiki shiki-themes github-light github-light github-dark","val letters: EventHandler = KHandler.define: _ =>\n    Response.status(Status.Accepted)\n      .andThen(Response.header(\"content-type\", \"text\u002Fplain\"))\n      .andThen(Response.sendStream(Stream.init(Seq(\"a\", \"b\", \"c\"))))\n","scala","",[103,183,184,221,241,266],{"__ignoreMap":181},[185,186,189,193,197,199,203,206,209,213,215,218],"span",{"class":187,"line":188},"line",1,[185,190,192],{"class":191},"s8jYJ","val",[185,194,196],{"class":195},"sP4rz"," letters",[185,198,174],{"class":191},[185,200,202],{"class":201},"snPdu"," EventHandler",[185,204,205],{"class":191}," =",[185,207,208],{"class":201}," KHandler",[185,210,212],{"class":211},"sxrX7",".define",[185,214,174],{"class":191},[185,216,217],{"class":211}," _ ",[185,219,220],{"class":191},"=>\n",[185,222,224,227,230,233,235,238],{"class":187,"line":223},2,[185,225,226],{"class":201},"    Response",[185,228,229],{"class":211},".status(",[185,231,232],{"class":201},"Status",[185,234,141],{"class":211},[185,236,237],{"class":201},"Accepted",[185,239,240],{"class":211},")\n",[185,242,244,247,250,253,257,260,263],{"class":187,"line":243},3,[185,245,246],{"class":211},"      .andThen(",[185,248,249],{"class":201},"Response",[185,251,252],{"class":211},".header(",[185,254,256],{"class":255},"sIIMD","\"content-type\"",[185,258,259],{"class":211},", ",[185,261,262],{"class":255},"\"text\u002Fplain\"",[185,264,265],{"class":211},"))\n",[185,267,269,271,273,276,279,282,285,288,291,293,296,298,301],{"class":187,"line":268},4,[185,270,246],{"class":211},[185,272,249],{"class":201},[185,274,275],{"class":211},".sendStream(",[185,277,278],{"class":201},"Stream",[185,280,281],{"class":211},".init(",[185,283,284],{"class":201},"Seq",[185,286,287],{"class":211},"(",[185,289,290],{"class":255},"\"a\"",[185,292,259],{"class":211},[185,294,295],{"class":255},"\"b\"",[185,297,259],{"class":211},[185,299,300],{"class":255},"\"c\"",[185,302,303],{"class":211},"))))\n",[99,305,306,307,166,310,313,314,317],{},"ZIO: send a ",[103,308,309],{},"ZStream[Any, E, String]",[103,311,312],{},"E \u003C: Throwable"," (or ",[103,315,316],{},"Nothing","):",[176,319,321],{"className":178,"code":320,"language":180,"meta":181,"style":181},"val letters: EventHandler = ZHandler.define(_ =>\n  Response.status(Status.Accepted) *> Response.header(\"content-type\", \"text\u002Fplain\") *> Response.sendStream(ZStream(\"a\", \"b\", \"c\")),\n)\n",[103,322,323,343,399],{"__ignoreMap":181},[185,324,325,327,329,331,333,335,338,341],{"class":187,"line":188},[185,326,192],{"class":191},[185,328,196],{"class":195},[185,330,174],{"class":191},[185,332,202],{"class":201},[185,334,205],{"class":191},[185,336,337],{"class":201}," ZHandler",[185,339,340],{"class":211},".define(_ ",[185,342,220],{"class":191},[185,344,345,348,350,352,354,356,359,362,365,367,369,371,373,375,377,379,381,384,386,388,390,392,394,396],{"class":187,"line":223},[185,346,347],{"class":201},"  Response",[185,349,229],{"class":211},[185,351,232],{"class":201},[185,353,141],{"class":211},[185,355,237],{"class":201},[185,357,358],{"class":211},") ",[185,360,361],{"class":191},"*>",[185,363,364],{"class":201}," Response",[185,366,252],{"class":211},[185,368,256],{"class":255},[185,370,259],{"class":211},[185,372,262],{"class":255},[185,374,358],{"class":211},[185,376,361],{"class":191},[185,378,364],{"class":201},[185,380,275],{"class":211},[185,382,383],{"class":201},"ZStream",[185,385,287],{"class":211},[185,387,290],{"class":255},[185,389,259],{"class":211},[185,391,295],{"class":255},[185,393,259],{"class":211},[185,395,300],{"class":255},[185,397,398],{"class":211},")),\n",[185,400,401],{"class":187,"line":243},[185,402,240],{"class":211},[99,404,405,406,409,410,116,413,416],{},"Both send ",[103,407,408],{},"abc"," with status 202. These snippets come from the stream tests (\"the guide's example sends abc with status 202\" in ",[103,411,412],{},"HttpStreamSuite",[103,414,415],{},"HttpStreamSpec",").",[99,418,419],{},"A returned stream does not compile. The compiler names the fix: ",[176,421,426],{"className":422,"code":424,"language":425,"meta":181},[423],"language-text","h34s cannot send kyo.Stream[String, Any] as a response body. Return a value with a NativeConverter,\nreturn Unit for a 204, or end the handler with Response.sendStream(stream) to stream text or\nResponse.sendEvents(events) to stream server-sent events\n","text",[103,427,424],{"__ignoreMap":181},[99,429,430,431,434],{},"Call ",[103,432,433],{},"sendStream"," once. A second call is a defect: the first stream stays the response, and the server logs the failure.",[143,436,438],{"id":437},"know-what-is-written","Know what is written",[440,441,442,455,476,486,503,540,571],"ul",{},[443,444,445,113,448,450,451,416],"li",{},[110,446,447],{},"The head leaves at the call.",[103,449,433],{}," writes the head as your program set it when you call it. Over a Node socket, h34s also flushes it at once, so the client has the status and the headers before the first element. Without a socket, h34s writes the head first too (see ",[138,452,454],{"href":453},"#in-process-calls","in-process calls",[443,456,457,460,461,463,464,116,467,470,471,259,473,475],{},[110,458,459],{},"Set the head first."," Set the status, headers and cookies before you call ",[103,462,433],{},". After the call, the head is fixed: a status, header or cookie that you set later does not reach the client, and ",[103,465,466],{},"KHandler.test",[103,468,469],{},"ZHandler.test"," show the head as it was at the call. The tests \"a status, header or cookie set after sendStream is not on the wire\" (",[103,472,412],{},[103,474,415],{},") prove it.",[443,477,478,481,482,485],{},[110,479,480],{},"Elements are strings."," Each one is written as UTF-8 text, as is: no envelope and no separator. Node can write only strings and bytes to a response, so h34s does not accept other element types. A ",[103,483,484],{},"null"," element fails the stream.",[443,487,488,491,492,116,495,498,499,502],{},[110,489,490],{},"Default content type."," Without a content type, h34s sends ",[103,493,494],{},"text\u002Fplain; charset=utf-8",[103,496,497],{},"X-Content-Type-Options: nosniff",". Set ",[103,500,501],{},"content-type"," yourself for another format.",[443,504,505,508,509,512,513,516,517,520,521,524,525,259,528,531,532,535,536,539],{},[110,506,507],{},"The marker."," Every streamed response carries ",[103,510,511],{},"x-h34s-stream: 1",", so a server-side ",[103,514,515],{},"$fetch"," can tell a stream from a buffered body. ",[103,518,519],{},"KHandler.$fetch.raw"," gives ",[103,522,523],{},"FetchResponse.Streamed"," for a response with this marker or a stream content type (",[103,526,527],{},"text\u002Fevent-stream",[103,529,530],{},"application\u002Fx-ndjson","), and ",[103,533,534],{},"FetchResponse.Plain"," for every other response, in-process and over the network alike. A missing ",[103,537,538],{},"content-length"," does not make a response streamed: no in-process response has one.",[443,541,542,545,546,549,550,553,554,557,558,561,562,565,566,570],{},[110,543,544],{},"Your encoding."," Encode values yourself, one string per element. For newline-delimited JSON: ",[103,547,548],{},"stream.map(value => value.toJson + \"\\n\")"," (",[103,551,552],{},"toJson"," comes with ",[103,555,556],{},"NativeConverter",") and ",[103,559,560],{},"Response.header(\"content-type\", \"application\u002Fx-ndjson\")",". For server-sent events, use ",[103,563,564],{},"Response.sendEvents",", which encodes the frames for you; see ",[138,567,569],{"href":568},"#server-sent-events","server-sent events",". ",[443,572,573,576],{},[110,574,575],{},"Chunks."," HTTP clients can merge or split writes. Do not rely on one client-side chunk per element.",[143,578,580],{"id":579},"validate-and-acquire-before-the-call","Validate and acquire before the call",[99,582,583,584,174],{},"Because the head leaves at the call, a failure after it cannot change the status. Do the work that can fail with a proper error before you call ",[103,585,433],{},[587,588,589,592,595,598],"ol",{},[443,590,591],{},"Read and validate the request body.",[443,593,594],{},"Acquire the resources that the stream needs, such as a database session or a file.",[443,596,597],{},"Set the status and the headers.",[443,599,430,600,141],{},[103,601,433],{},[99,603,604,605,609,610,613],{},"A failure in steps 1 to 3 uses the normal ",[138,606,608],{"href":607},"\u002Fguide\u002Fkyo#fail-safely","failure rule",": the client gets an approved ",[103,611,612],{},"SafeError"," response or an opaque 500.",[143,615,617],{"id":616},"keep-resources-alive-for-the-transfer","Keep resources alive for the transfer",[99,619,620],{},"Acquire what the stream needs in the request scope, then send the stream. h34s keeps the scope open until the stream ends, fails or is cancelled:",[440,622,623,630],{},[443,624,625,626,629],{},"Kyo: ",[103,627,628],{},"Scope.acquireRelease(open)(close).map(source => Response.sendStream(...stream over source...))",", or use a stream that manages its own resources.",[443,631,632,633,636,637,640,641,644,645,648,649,652],{},"ZIO: ",[103,634,635],{},"ZIO.acquireRelease(open)(close).flatMap(source => Response.sendStream(ZStream...))"," in the handler (the request ",[103,638,639],{},"Scope"," is in ",[103,642,643],{},"HandlerEnv","), or ",[103,646,647],{},"ZStream.unwrapScoped(...)"," \u002F ",[103,650,651],{},"ZStream.acquireReleaseWith(...)"," for resources the stream owns.",[99,654,655,656,658],{},"A resource that you close before you call ",[103,657,433],{}," is gone. h34s cannot restore it.",[143,660,662],{"id":661},"cancellation","Cancellation",[99,664,665],{},"The bridge is pull-driven. One producer fiber owns the stream and advances only when the HTTP side asks for the next chunk. No chunk is computed before it is requested.",[99,667,668],{},"When the client disconnects:",[587,670,671,674,679],{},[443,672,673],{},"h34s interrupts the producer and the handler.",[443,675,676,677,141],{},"It waits for the stream's finalizers, including asynchronous ones, and for the finalizers of every scope around ",[103,678,433],{},[443,680,681],{},"It closes the request scope and its resources, once, writes nothing more, and only then settles the request.",[99,683,684,685,688,689,692,693,141],{},"A request whose response closed before the stream started never starts the stream. Interrupted work can roll back. To make a step finish anyway, use ",[103,686,687],{},"KHandler.uninterruptible(step)"," (Kyo) or ",[103,690,691],{},"ZIO.uninterruptible"," (ZIO); see ",[138,694,696],{"href":695},"\u002Fguide\u002Flifetimes#when-the-client-disconnects","when the client disconnects",[143,698,700],{"id":699},"failures","Failures",[702,703,704,717],"table",{},[705,706,707],"thead",{},[708,709,710,714],"tr",{},[711,712,713],"th",{},"When it fails",[711,715,716],{},"Result",[718,719,720,737,748,759,772,780],"tbody",{},[708,721,722,728],{},[723,724,725,726],"td",{},"Before ",[103,727,433],{},[723,729,730,731,733,734,736],{},"The normal ",[138,732,608],{"href":607},": an approved ",[103,735,612],{}," response, or an opaque 500",[708,738,739,742],{},[723,740,741],{},"Writing the head, at the call",[723,743,744,745,747],{},"For example an invalid cookie domain. Nothing reached the client: h34s clears the head, the stream never starts, ",[103,746,433],{}," fails, and the failure rule answers with an opaque 500. If part of the head had already left, h34s closes the connection instead",[708,749,750,753],{},[723,751,752],{},"The stream, before or after its first element",[723,754,755,756,758],{},"The head is already out. h34s closes the connection without a final chunk, so the client sees an incomplete transfer; the elements already written stay. ",[103,757,433],{}," fails with the stream's error, your program sees it, and the server logs it once. Nothing more is written",[708,760,761,769],{},[723,762,763,764,766,767],{},"The stream throws, even a ",[103,765,612],{},", or emits ",[103,768,484],{},[723,770,771],{},"A defect of the stream: the same as above. The client never sees the error's text",[708,773,774,777],{},[723,775,776],{},"Writing to the response",[723,778,779],{},"h34s closes the connection. The log names the write error, not the producer's interruption",[708,781,782,788],{},[723,783,784,785,787],{},"The stream, without a socket (an in-process ",[103,786,515],{},")",[723,789,790,791,793],{},"The caller's read of the body fails after the elements it already read. ",[103,792,433],{}," fails with the stream's error",[99,795,796],{},"Clients should treat an incomplete transfer as a failure, even when the status was successful.",[143,798,800],{"id":799},"roll-back-when-a-stream-fails","Roll back when a stream fails",[99,802,803,805],{},[103,804,433],{}," fails with the stream's error, so the code around it sees the failure. Put a transaction, or a finalizer that checks the outcome, around the step.",[99,807,808,809,116,812,815,816,819],{},"In these snippets, ",[103,810,811],{},"commit",[103,813,814],{},"rollback"," are your own steps. With kyo-sql, ",[103,817,818],{},"DB.transaction"," around the step does this for you.",[99,821,822,823,826,827,830],{},"Kyo: a ",[103,824,825],{},"Scope.run"," around the step gives its finalizers the outcome, ",[103,828,829],{},"Absent"," on success:",[176,832,834],{"className":178,"code":833,"language":180,"meta":181,"style":181},"Scope.run(\n  Scope.ensure((outcome: Maybe[Result.Error[Any]]) => if outcome.isEmpty then commit else rollback)\n    .andThen(Response.sendStream(stream)),\n)\n",[103,835,836,843,899,909],{"__ignoreMap":181},[185,837,838,840],{"class":187,"line":188},[185,839,639],{"class":201},[185,841,842],{"class":211},".run(\n",[185,844,845,848,851,854,857,860,863,865,867,870,872,875,878,881,884,887,890,893,896],{"class":187,"line":223},[185,846,847],{"class":201},"  Scope",[185,849,850],{"class":211},".ensure((",[185,852,853],{"class":195},"outcome",[185,855,856],{"class":211},": ",[185,858,859],{"class":201},"Maybe",[185,861,862],{"class":211},"[",[185,864,716],{"class":201},[185,866,141],{"class":211},[185,868,869],{"class":201},"Error",[185,871,862],{"class":211},[185,873,874],{"class":201},"Any",[185,876,877],{"class":211},"]]) ",[185,879,880],{"class":191},"=>",[185,882,883],{"class":191}," if",[185,885,886],{"class":211}," outcome.isEmpty ",[185,888,889],{"class":191},"then",[185,891,892],{"class":211}," commit ",[185,894,895],{"class":191},"else",[185,897,898],{"class":211}," rollback)\n",[185,900,901,904,906],{"class":187,"line":243},[185,902,903],{"class":211},"    .andThen(",[185,905,249],{"class":201},[185,907,908],{"class":211},".sendStream(stream)),\n",[185,910,911],{"class":187,"line":268},[185,912,240],{"class":211},[99,914,915,916,918,919,921,922,924],{},"Write that ",[103,917,825],{}," inside your handler. A finalizer that you add directly to the request scope gets ",[103,920,829],{}," even after a failure, because h34s closes the request scope after your program, without its outcome. Such a finalizer would commit. The test \"after a stream failure, a finalizer of the request scope gets Absent, and one of a Scope.run around sendStream gets the failure\" (",[103,923,412],{},") shows the difference.",[99,926,632,927,930,931,934,935,937,938,941,942,944,945,317],{},[103,928,929],{},"ZIO.addFinalizerExit",", inside ",[103,932,933],{},"ZIO.scoped"," or in the request ",[103,936,639],{},", gets the ",[103,939,940],{},"Exit"," (the request ",[103,943,639],{}," case is the test \"a stream failure after the head: a finalizer of the request Scope sees the failure\" in ",[103,946,415],{},[176,948,950],{"className":178,"code":949,"language":180,"meta":181,"style":181},"ZIO.scoped(\n  ZIO.addFinalizerExit(exit => if exit.isSuccess then commit else rollback) *> Response.sendStream(stream),\n)\n",[103,951,952,960,991],{"__ignoreMap":181},[185,953,954,957],{"class":187,"line":188},[185,955,956],{"class":201},"ZIO",[185,958,959],{"class":211},".scoped(\n",[185,961,962,965,968,970,972,975,977,979,981,984,986,988],{"class":187,"line":223},[185,963,964],{"class":201},"  ZIO",[185,966,967],{"class":211},".addFinalizerExit(exit ",[185,969,880],{"class":191},[185,971,883],{"class":191},[185,973,974],{"class":211}," exit.isSuccess ",[185,976,889],{"class":191},[185,978,892],{"class":211},[185,980,895],{"class":191},[185,982,983],{"class":211}," rollback) ",[185,985,361],{"class":191},[185,987,364],{"class":201},[185,989,990],{"class":211},".sendStream(stream),\n",[185,992,993],{"class":187,"line":243},[185,994,240],{"class":211},[99,996,997,998,1001,1002,1004,1005,1008,1009,1011,1012,1014,1015,416],{},"The stream's own cause reaches your ZIO program: a ",[103,999,1000],{},"ZStream[Any, MyError, String]"," fails ",[103,1003,433],{}," with ",[103,1006,1007],{},"MyError",". The tests \"a stream failure after two elements fails the sendStream step; a Scope finalizer around it sees the failure\" (",[103,1010,412],{},") and \"a stream failure after two elements fails the sendStream effect; a finalizer of ZIO.scoped around it sees the failure\" (",[103,1013,415],{},") prove both. A client disconnect still waits for the finalizers of such a scope: see the test \"a disconnect waits for the asynchronous finalizers of a Scope.run around sendStream before the request settles\" (",[103,1016,412],{},[143,1018,1020],{"id":1019},"server-sent-events","Server-sent events",[99,1022,1023,1024,166,1027,1030,1031,1034,1035,1037,1038,1040],{},"Send server-sent events with ",[103,1025,1026],{},"Response.sendEvents(events)",[103,1028,1029],{},"events"," is a stream of ",[103,1032,1033],{},"ServerEvent"," values. It is ",[103,1036,105],{}," for the ",[103,1039,527],{}," format: h34s sets the event-stream headers and writes one frame per event. Everything above holds for it. The head leaves at the call, the request scope stays open until the stream ends, a disconnect waits for the stream's cleanup, and a failure after the head ends the response and fails the step.",[99,1042,1043,113,1045,259,1047,116,1049,120,1052,1054],{},[110,1044,112],{},[103,1046,564],{},[103,1048,1033],{},[103,1050,1051],{},"$fetch.events",[103,1053,123],{},", in both runtimes.",[1056,1057,1059],"h3",{"id":1058},"build-the-events","Build the events",[99,1061,1062,1063,1065],{},"A ",[103,1064,1033],{}," has four fields:",[702,1067,1068,1078],{},[705,1069,1070],{},[708,1071,1072,1075],{},[711,1073,1074],{},"Field",[711,1076,1077],{},"Meaning",[718,1079,1080,1098,1123,1145],{},[708,1081,1082,1087],{},[723,1083,1084],{},[103,1085,1086],{},"data: String",[723,1088,1089,1090,1093,1094,1097],{},"The event's text. Each line becomes one ",[103,1091,1092],{},"data:"," line, and the client joins them with LF, so a CR or CRLF in ",[103,1095,1096],{},"data"," arrives as LF",[708,1099,1100,1105],{},[723,1101,1102],{},[103,1103,1104],{},"event: Option[String]",[723,1106,1107,1108,1111,1112,1115,1116,1119,1120],{},"The event type. ",[103,1109,1110],{},"None"," is the default type, which a browser's ",[103,1113,1114],{},"EventSource"," calls ",[103,1117,1118],{},"message",". A named type reaches only ",[103,1121,1122],{},"addEventListener(type)",[708,1124,1125,1130],{},[723,1126,1127],{},[103,1128,1129],{},"id: Option[String]",[723,1131,1132,1133,1136,1137,1140,1141,1144],{},"The event's ID. A browser keeps it as ",[103,1134,1135],{},"lastEventId"," and sends it back in the ",[103,1138,1139],{},"Last-Event-ID"," header when it reconnects. ",[103,1142,1143],{},"Some(\"\")"," clears it",[708,1146,1147,1152],{},[723,1148,1149],{},[103,1150,1151],{},"retry: Option[Int]",[723,1153,1154],{},"The time in milliseconds that a browser waits before it reconnects",[99,1156,1157,1160,1161,1164,1165,1167,1168,1171,1172,1175],{},[103,1158,1159],{},"ServerEvent.json(value, event, id)"," writes ",[103,1162,1163],{},"value"," as one line of JSON text with its ",[103,1166,556],{},". Read it back with ",[103,1169,1170],{},"$fetch.events[A]"," in Scala, or with ",[103,1173,1174],{},"JSON.parse(message.data)"," in the browser.",[99,1177,1178,1179,1182,1183,1186,1187,1189,1190,1192,1193,1196,1197,1199,1200,1203],{},"A line break inside ",[103,1180,1181],{},"event"," or ",[103,1184,1185],{},"id",", U+0000 inside ",[103,1188,1185],{},", an empty ",[103,1191,1181],{}," or a negative ",[103,1194,1195],{},"retry"," would change the meaning of the frame. The ",[103,1198,1033],{}," constructor throws an ",[103,1201,1202],{},"IllegalArgumentException"," for them, so h34s never writes such a frame. Inside a stream, the throw fails the stream like any other defect.",[1056,1205,1207],{"id":1206},"send-them","Send them",[99,1209,808,1210,1213],{},[103,1211,1212],{},"Countdown"," is the example's event model. Each route sends 3, 2, 1 and 0, one event per second. Kyo:",[176,1215,1217],{"className":178,"code":1216,"language":180,"meta":181,"style":181},"\u002F** One event of the countdown: the seconds that remain. *\u002F\nfinal case class Countdown(remaining: Int) derives Schema, NativeSchema\n\n\u002F** `GET \u002Fapi\u002Fkyo\u002Fcountdown`: the events 3, 2, 1 and 0, one per second, as `tick` events. A chunk of\n  * one element sends each event when it is ready, not the whole chunk at its end.\n  *\u002F\n@JSExportTopLevel(\"default\", \"kyo-countdown\")\nval countdown: EventHandler = KHandler\n  .define: _ =>\n      val ticks = Stream.init(3 to 0 by -1, chunkSize = 1).map: remaining =>\n          Async.sleep(1.second).andThen(ServerEvent.json(Countdown(remaining), event = Some(\"tick\")))\n      Response.sendEvents(ticks)\n  .at(Route.get(\"\u002Fapi\u002Fkyo\u002Fcountdown\"))\n",[103,1218,1219,1225,1262,1268,1273,1279,1285,1306,1323,1335,1389,1426,1435],{"__ignoreMap":181},[185,1220,1221],{"class":187,"line":188},[185,1222,1224],{"class":1223},"sCsY4","\u002F** One event of the countdown: the seconds that remain. *\u002F\n",[185,1226,1227,1230,1233,1236,1239,1241,1244,1246,1249,1251,1254,1257,1259],{"class":187,"line":223},[185,1228,1229],{"class":191},"final",[185,1231,1232],{"class":191}," case",[185,1234,1235],{"class":191}," class",[185,1237,1238],{"class":201}," Countdown",[185,1240,287],{"class":211},[185,1242,1243],{"class":195},"remaining",[185,1245,856],{"class":211},[185,1247,1248],{"class":201},"Int",[185,1250,358],{"class":211},[185,1252,1253],{"class":191},"derives",[185,1255,1256],{"class":201}," Schema",[185,1258,259],{"class":211},[185,1260,1261],{"class":201},"NativeSchema\n",[185,1263,1264],{"class":187,"line":243},[185,1265,1267],{"emptyLinePlaceholder":1266},true,"\n",[185,1269,1270],{"class":187,"line":268},[185,1271,1272],{"class":1223},"\u002F** `GET \u002Fapi\u002Fkyo\u002Fcountdown`: the events 3, 2, 1 and 0, one per second, as `tick` events. A chunk of\n",[185,1274,1276],{"class":187,"line":1275},5,[185,1277,1278],{"class":1223},"  * one element sends each event when it is ready, not the whole chunk at its end.\n",[185,1280,1282],{"class":187,"line":1281},6,[185,1283,1284],{"class":1223},"  *\u002F\n",[185,1286,1288,1291,1294,1296,1299,1301,1304],{"class":187,"line":1287},7,[185,1289,1290],{"class":191},"@",[185,1292,1293],{"class":201},"JSExportTopLevel",[185,1295,287],{"class":211},[185,1297,1298],{"class":255},"\"default\"",[185,1300,259],{"class":211},[185,1302,1303],{"class":255},"\"kyo-countdown\"",[185,1305,240],{"class":211},[185,1307,1309,1311,1314,1316,1318,1320],{"class":187,"line":1308},8,[185,1310,192],{"class":191},[185,1312,1313],{"class":195}," countdown",[185,1315,174],{"class":191},[185,1317,202],{"class":201},[185,1319,205],{"class":191},[185,1321,1322],{"class":201}," KHandler\n",[185,1324,1326,1329,1331,1333],{"class":187,"line":1325},9,[185,1327,1328],{"class":211},"  .define",[185,1330,174],{"class":191},[185,1332,217],{"class":211},[185,1334,220],{"class":191},[185,1336,1338,1341,1344,1346,1349,1351,1355,1358,1361,1364,1367,1370,1373,1376,1379,1382,1384,1387],{"class":187,"line":1337},10,[185,1339,1340],{"class":191},"      val",[185,1342,1343],{"class":195}," ticks",[185,1345,205],{"class":191},[185,1347,1348],{"class":201}," Stream",[185,1350,281],{"class":211},[185,1352,1354],{"class":1353},"sBjJW","3",[185,1356,1357],{"class":211}," to ",[185,1359,1360],{"class":1353},"0",[185,1362,1363],{"class":211}," by ",[185,1365,1366],{"class":191},"-",[185,1368,1369],{"class":1353},"1",[185,1371,1372],{"class":211},", chunkSize ",[185,1374,1375],{"class":191},"=",[185,1377,1378],{"class":1353}," 1",[185,1380,1381],{"class":211},").map",[185,1383,174],{"class":191},[185,1385,1386],{"class":211}," remaining ",[185,1388,220],{"class":191},[185,1390,1392,1395,1398,1400,1403,1405,1408,1410,1413,1415,1418,1420,1423],{"class":187,"line":1391},11,[185,1393,1394],{"class":201},"          Async",[185,1396,1397],{"class":211},".sleep(",[185,1399,1369],{"class":1353},[185,1401,1402],{"class":211},".second).andThen(",[185,1404,1033],{"class":201},[185,1406,1407],{"class":211},".json(",[185,1409,1212],{"class":201},[185,1411,1412],{"class":211},"(remaining), event ",[185,1414,1375],{"class":191},[185,1416,1417],{"class":201}," Some",[185,1419,287],{"class":211},[185,1421,1422],{"class":255},"\"tick\"",[185,1424,1425],{"class":211},")))\n",[185,1427,1429,1432],{"class":187,"line":1428},12,[185,1430,1431],{"class":201},"      Response",[185,1433,1434],{"class":211},".sendEvents(ticks)\n",[185,1436,1438,1441,1444,1447,1450],{"class":187,"line":1437},13,[185,1439,1440],{"class":211},"  .at(",[185,1442,1443],{"class":201},"Route",[185,1445,1446],{"class":211},".get(",[185,1448,1449],{"class":255},"\"\u002Fapi\u002Fkyo\u002Fcountdown\"",[185,1451,265],{"class":211},[99,1453,1454],{},"ZIO:",[176,1456,1458],{"className":178,"code":1457,"language":180,"meta":181,"style":181},"\u002F** One event of the countdown: the seconds that remain. *\u002F\nfinal case class Countdown(remaining: Int) derives Schema, NativeSchema\n\n\u002F** `GET \u002Fapi\u002Fzio\u002Fcountdown`: the events 3, 2, 1 and 0, one per second, as `tick` events. *\u002F\n@JSExportTopLevel(\"default\", \"zio-countdown\")\nval countdown: EventHandler = ZHandler\n  .define: _ =>\n      val ticks = ZStream(3, 2, 1, 0).mapZIO: remaining =>\n          ZIO.sleep(1.second).as(ServerEvent.json(Countdown(remaining), event = Some(\"tick\")))\n      Response.sendEvents(ticks)\n  .at(Route.get(\"\u002Fapi\u002Fzio\u002Fcountdown\"))\n",[103,1459,1460,1464,1492,1496,1501,1518,1533,1543,1580,1610,1616],{"__ignoreMap":181},[185,1461,1462],{"class":187,"line":188},[185,1463,1224],{"class":1223},[185,1465,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490],{"class":187,"line":223},[185,1467,1229],{"class":191},[185,1469,1232],{"class":191},[185,1471,1235],{"class":191},[185,1473,1238],{"class":201},[185,1475,287],{"class":211},[185,1477,1243],{"class":195},[185,1479,856],{"class":211},[185,1481,1248],{"class":201},[185,1483,358],{"class":211},[185,1485,1253],{"class":191},[185,1487,1256],{"class":201},[185,1489,259],{"class":211},[185,1491,1261],{"class":201},[185,1493,1494],{"class":187,"line":243},[185,1495,1267],{"emptyLinePlaceholder":1266},[185,1497,1498],{"class":187,"line":268},[185,1499,1500],{"class":1223},"\u002F** `GET \u002Fapi\u002Fzio\u002Fcountdown`: the events 3, 2, 1 and 0, one per second, as `tick` events. *\u002F\n",[185,1502,1503,1505,1507,1509,1511,1513,1516],{"class":187,"line":1275},[185,1504,1290],{"class":191},[185,1506,1293],{"class":201},[185,1508,287],{"class":211},[185,1510,1298],{"class":255},[185,1512,259],{"class":211},[185,1514,1515],{"class":255},"\"zio-countdown\"",[185,1517,240],{"class":211},[185,1519,1520,1522,1524,1526,1528,1530],{"class":187,"line":1281},[185,1521,192],{"class":191},[185,1523,1313],{"class":195},[185,1525,174],{"class":191},[185,1527,202],{"class":201},[185,1529,205],{"class":191},[185,1531,1532],{"class":201}," ZHandler\n",[185,1534,1535,1537,1539,1541],{"class":187,"line":1287},[185,1536,1328],{"class":211},[185,1538,174],{"class":191},[185,1540,217],{"class":211},[185,1542,220],{"class":191},[185,1544,1545,1547,1549,1551,1554,1556,1558,1560,1563,1565,1567,1569,1571,1574,1576,1578],{"class":187,"line":1308},[185,1546,1340],{"class":191},[185,1548,1343],{"class":195},[185,1550,205],{"class":191},[185,1552,1553],{"class":201}," ZStream",[185,1555,287],{"class":211},[185,1557,1354],{"class":1353},[185,1559,259],{"class":211},[185,1561,1562],{"class":1353},"2",[185,1564,259],{"class":211},[185,1566,1369],{"class":1353},[185,1568,259],{"class":211},[185,1570,1360],{"class":1353},[185,1572,1573],{"class":211},").mapZIO",[185,1575,174],{"class":191},[185,1577,1386],{"class":211},[185,1579,220],{"class":191},[185,1581,1582,1585,1587,1589,1592,1594,1596,1598,1600,1602,1604,1606,1608],{"class":187,"line":1325},[185,1583,1584],{"class":201},"          ZIO",[185,1586,1397],{"class":211},[185,1588,1369],{"class":1353},[185,1590,1591],{"class":211},".second).as(",[185,1593,1033],{"class":201},[185,1595,1407],{"class":211},[185,1597,1212],{"class":201},[185,1599,1412],{"class":211},[185,1601,1375],{"class":191},[185,1603,1417],{"class":201},[185,1605,287],{"class":211},[185,1607,1422],{"class":255},[185,1609,1425],{"class":211},[185,1611,1612,1614],{"class":187,"line":1337},[185,1613,1431],{"class":201},[185,1615,1434],{"class":211},[185,1617,1618,1620,1622,1624,1627],{"class":187,"line":1391},[185,1619,1440],{"class":211},[185,1621,1443],{"class":201},[185,1623,1446],{"class":211},[185,1625,1626],{"class":255},"\"\u002Fapi\u002Fzio\u002Fcountdown\"",[185,1628,265],{"class":211},[99,1630,1631,1632,1635,1636,1639],{},"These are the countdown routes of the example application (",[103,1633,1634],{},"examples\u002Fnuxt","), and docs\u002Ftest checks that the snippets equal them. In Kyo, ",[103,1637,1638],{},"Stream.map"," with an effect emits a chunk only when every element of the chunk is mapped. A one-element chunk sends each event when it is ready; with the default chunk size, all four events would leave together after four seconds.",[99,1641,1642,1645,1646,1649,1650,1653],{},[103,1643,1644],{},"sendEvents"," sets the headers of h3's ",[103,1647,1648],{},"createEventStream",", as ",[103,1651,1652],{},"Response.header"," would:",[440,1655,1656,1661,1666,1672],{},[443,1657,1658],{},[103,1659,1660],{},"content-type: text\u002Fevent-stream",[443,1662,1663],{},[103,1664,1665],{},"cache-control: private, no-cache, no-store, no-transform, must-revalidate, max-age=0",[443,1667,1668,1671],{},[103,1669,1670],{},"x-accel-buffering: no",", so that nginx does not buffer the stream",[443,1673,1674,1677],{},[103,1675,1676],{},"connection: keep-alive",", except over HTTP\u002F2, where the header is not allowed",[99,1679,1680,1681,1684,1685,1687],{},"They replace a value of the same name that you or your handler defaults set, such as ",[103,1682,1683],{},"ResponseHead.noStore",". Your other headers, your cookies and your status stay. Keep the status 200: a browser's ",[103,1686,1114],{}," fails the connection on any other status.",[99,1689,1690,1691,259,1694,116,1697,1700,1701,1703,1704,1707,1708,1711],{},"Each frame has an ",[103,1692,1693],{},"id:",[103,1695,1696],{},"event:",[103,1698,1699],{},"retry:"," line when the field is set, then the ",[103,1702,1092],{}," lines, then an empty line: ",[103,1705,1706],{},"ServerEvent.json(Countdown(3), event = Some(\"tick\"))"," is ",[103,1709,1710],{},"event: tick\\ndata: {\"remaining\":3}\\n\\n",". h34s sends no heartbeat comments yet, so a proxy with an idle timeout can close a stream that stays quiet for a long time.",[99,1713,1714,1715,1717],{},"h34s does not wrap h3's ",[103,1716,1648],{},": in-process, it leaks two unhandled rejections per cancel, and it notices a disconnect only through an event that never fires there.",[1056,1719,1721],{"id":1720},"read-the-events-in-the-browser","Read the events in the browser",[99,1723,1724,1725,1727,1728,1731],{},"The example reads both routes with the browser's ",[103,1726,1114],{},", in its ",[103,1729,1730],{},"useCountdown"," composable:",[176,1733,1737],{"className":1734,"code":1735,"language":1736,"meta":181,"style":181},"language-ts shiki shiki-themes github-light github-light github-dark","\u002F** One `tick` event: the JSON that `ServerEvent.json(Countdown(n))` writes. *\u002F\ninterface Countdown {\n  remaining: number\n}\n\ntype CountdownState = 'idle' | 'running' | 'done' | 'failed'\n\n\u002F** The countdown of `GET \u002Fapi\u002F\u003Cruntime>\u002Fcountdown`, read with `EventSource`. *\u002F\nexport function useCountdown(runtime: 'kyo' | 'zio') {\n  const remaining = ref\u003Cnumber | null>(null)\n  const state = ref\u003CCountdownState>('idle')\n  let source: EventSource | undefined\n\n  function stop() {\n    source?.close()\n    source = undefined\n  }\n\n  function start() {\n    stop()\n    remaining.value = null\n    state.value = 'running'\n    source = new EventSource(`\u002Fapi\u002F${runtime}\u002Fcountdown`)\n    source.addEventListener('tick', (message) => {\n      const tick: Countdown = JSON.parse(message.data)\n      remaining.value = tick.remaining\n      if (tick.remaining === 0) {\n        state.value = 'done'\n        stop()\n      }\n    })\n    \u002F\u002F EventSource reconnects after an error. Stop instead: the run failed.\n    source.addEventListener('error', () => {\n      state.value = 'failed'\n      stop()\n    })\n  }\n\n  onScopeDispose(stop)\n  return { remaining, state, start }\n}\n","ts",[103,1738,1739,1744,1754,1764,1769,1773,1802,1806,1811,1840,1871,1894,1912,1916,1928,1940,1950,1956,1961,1971,1979,1990,2001,2025,2050,2076,2087,2104,2115,2123,2129,2135,2141,2160,2170,2178,2183,2188,2193,2202,2211],{"__ignoreMap":181},[185,1740,1741],{"class":187,"line":188},[185,1742,1743],{"class":1223},"\u002F** One `tick` event: the JSON that `ServerEvent.json(Countdown(n))` writes. *\u002F\n",[185,1745,1746,1749,1751],{"class":187,"line":223},[185,1747,1748],{"class":191},"interface",[185,1750,1238],{"class":201},[185,1752,1753],{"class":211}," {\n",[185,1755,1756,1759,1761],{"class":187,"line":243},[185,1757,1758],{"class":195},"  remaining",[185,1760,174],{"class":191},[185,1762,1763],{"class":1353}," number\n",[185,1765,1766],{"class":187,"line":268},[185,1767,1768],{"class":211},"}\n",[185,1770,1771],{"class":187,"line":1275},[185,1772,1267],{"emptyLinePlaceholder":1266},[185,1774,1775,1778,1781,1783,1786,1789,1792,1794,1797,1799],{"class":187,"line":1281},[185,1776,1777],{"class":191},"type",[185,1779,1780],{"class":201}," CountdownState",[185,1782,205],{"class":191},[185,1784,1785],{"class":255}," 'idle'",[185,1787,1788],{"class":191}," |",[185,1790,1791],{"class":255}," 'running'",[185,1793,1788],{"class":191},[185,1795,1796],{"class":255}," 'done'",[185,1798,1788],{"class":191},[185,1800,1801],{"class":255}," 'failed'\n",[185,1803,1804],{"class":187,"line":1287},[185,1805,1267],{"emptyLinePlaceholder":1266},[185,1807,1808],{"class":187,"line":1308},[185,1809,1810],{"class":1223},"\u002F** The countdown of `GET \u002Fapi\u002F\u003Cruntime>\u002Fcountdown`, read with `EventSource`. *\u002F\n",[185,1812,1813,1816,1819,1822,1824,1827,1829,1832,1834,1837],{"class":187,"line":1325},[185,1814,1815],{"class":191},"export",[185,1817,1818],{"class":191}," function",[185,1820,1821],{"class":201}," useCountdown",[185,1823,287],{"class":211},[185,1825,1826],{"class":195},"runtime",[185,1828,174],{"class":191},[185,1830,1831],{"class":255}," 'kyo'",[185,1833,1788],{"class":191},[185,1835,1836],{"class":255}," 'zio'",[185,1838,1839],{"class":211},") {\n",[185,1841,1842,1845,1848,1850,1853,1856,1859,1861,1864,1867,1869],{"class":187,"line":1337},[185,1843,1844],{"class":191},"  const",[185,1846,1847],{"class":1353}," remaining",[185,1849,205],{"class":191},[185,1851,1852],{"class":201}," ref",[185,1854,1855],{"class":211},"\u003C",[185,1857,1858],{"class":1353},"number",[185,1860,1788],{"class":191},[185,1862,1863],{"class":1353}," null",[185,1865,1866],{"class":211},">(",[185,1868,484],{"class":1353},[185,1870,240],{"class":211},[185,1872,1873,1875,1878,1880,1882,1884,1887,1889,1892],{"class":187,"line":1391},[185,1874,1844],{"class":191},[185,1876,1877],{"class":1353}," state",[185,1879,205],{"class":191},[185,1881,1852],{"class":201},[185,1883,1855],{"class":211},[185,1885,1886],{"class":201},"CountdownState",[185,1888,1866],{"class":211},[185,1890,1891],{"class":255},"'idle'",[185,1893,240],{"class":211},[185,1895,1896,1899,1902,1904,1907,1909],{"class":187,"line":1428},[185,1897,1898],{"class":191},"  let",[185,1900,1901],{"class":211}," source",[185,1903,174],{"class":191},[185,1905,1906],{"class":201}," EventSource",[185,1908,1788],{"class":191},[185,1910,1911],{"class":1353}," undefined\n",[185,1913,1914],{"class":187,"line":1437},[185,1915,1267],{"emptyLinePlaceholder":1266},[185,1917,1919,1922,1925],{"class":187,"line":1918},14,[185,1920,1921],{"class":191},"  function",[185,1923,1924],{"class":201}," stop",[185,1926,1927],{"class":211},"() {\n",[185,1929,1931,1934,1937],{"class":187,"line":1930},15,[185,1932,1933],{"class":211},"    source?.",[185,1935,1936],{"class":201},"close",[185,1938,1939],{"class":211},"()\n",[185,1941,1943,1946,1948],{"class":187,"line":1942},16,[185,1944,1945],{"class":211},"    source ",[185,1947,1375],{"class":191},[185,1949,1911],{"class":1353},[185,1951,1953],{"class":187,"line":1952},17,[185,1954,1955],{"class":211},"  }\n",[185,1957,1959],{"class":187,"line":1958},18,[185,1960,1267],{"emptyLinePlaceholder":1266},[185,1962,1964,1966,1969],{"class":187,"line":1963},19,[185,1965,1921],{"class":191},[185,1967,1968],{"class":201}," start",[185,1970,1927],{"class":211},[185,1972,1974,1977],{"class":187,"line":1973},20,[185,1975,1976],{"class":201},"    stop",[185,1978,1939],{"class":211},[185,1980,1982,1985,1987],{"class":187,"line":1981},21,[185,1983,1984],{"class":211},"    remaining.value ",[185,1986,1375],{"class":191},[185,1988,1989],{"class":1353}," null\n",[185,1991,1993,1996,1998],{"class":187,"line":1992},22,[185,1994,1995],{"class":211},"    state.value ",[185,1997,1375],{"class":191},[185,1999,2000],{"class":255}," 'running'\n",[185,2002,2004,2006,2008,2011,2013,2015,2018,2020,2023],{"class":187,"line":2003},23,[185,2005,1945],{"class":211},[185,2007,1375],{"class":191},[185,2009,2010],{"class":191}," new",[185,2012,1906],{"class":201},[185,2014,287],{"class":211},[185,2016,2017],{"class":255},"`\u002Fapi\u002F${",[185,2019,1826],{"class":211},[185,2021,2022],{"class":255},"}\u002Fcountdown`",[185,2024,240],{"class":211},[185,2026,2028,2031,2034,2036,2039,2042,2044,2046,2048],{"class":187,"line":2027},24,[185,2029,2030],{"class":211},"    source.",[185,2032,2033],{"class":201},"addEventListener",[185,2035,287],{"class":211},[185,2037,2038],{"class":255},"'tick'",[185,2040,2041],{"class":211},", (",[185,2043,1118],{"class":195},[185,2045,358],{"class":211},[185,2047,880],{"class":191},[185,2049,1753],{"class":211},[185,2051,2053,2056,2059,2061,2063,2065,2068,2070,2073],{"class":187,"line":2052},25,[185,2054,2055],{"class":191},"      const",[185,2057,2058],{"class":1353}," tick",[185,2060,174],{"class":191},[185,2062,1238],{"class":201},[185,2064,205],{"class":191},[185,2066,2067],{"class":1353}," JSON",[185,2069,141],{"class":211},[185,2071,2072],{"class":201},"parse",[185,2074,2075],{"class":211},"(message.data)\n",[185,2077,2079,2082,2084],{"class":187,"line":2078},26,[185,2080,2081],{"class":211},"      remaining.value ",[185,2083,1375],{"class":191},[185,2085,2086],{"class":211}," tick.remaining\n",[185,2088,2090,2093,2096,2099,2102],{"class":187,"line":2089},27,[185,2091,2092],{"class":191},"      if",[185,2094,2095],{"class":211}," (tick.remaining ",[185,2097,2098],{"class":191},"===",[185,2100,2101],{"class":1353}," 0",[185,2103,1839],{"class":211},[185,2105,2107,2110,2112],{"class":187,"line":2106},28,[185,2108,2109],{"class":211},"        state.value ",[185,2111,1375],{"class":191},[185,2113,2114],{"class":255}," 'done'\n",[185,2116,2118,2121],{"class":187,"line":2117},29,[185,2119,2120],{"class":201},"        stop",[185,2122,1939],{"class":211},[185,2124,2126],{"class":187,"line":2125},30,[185,2127,2128],{"class":211},"      }\n",[185,2130,2132],{"class":187,"line":2131},31,[185,2133,2134],{"class":211},"    })\n",[185,2136,2138],{"class":187,"line":2137},32,[185,2139,2140],{"class":1223},"    \u002F\u002F EventSource reconnects after an error. Stop instead: the run failed.\n",[185,2142,2144,2146,2148,2150,2153,2156,2158],{"class":187,"line":2143},33,[185,2145,2030],{"class":211},[185,2147,2033],{"class":201},[185,2149,287],{"class":211},[185,2151,2152],{"class":255},"'error'",[185,2154,2155],{"class":211},", () ",[185,2157,880],{"class":191},[185,2159,1753],{"class":211},[185,2161,2163,2166,2168],{"class":187,"line":2162},34,[185,2164,2165],{"class":211},"      state.value ",[185,2167,1375],{"class":191},[185,2169,1801],{"class":255},[185,2171,2173,2176],{"class":187,"line":2172},35,[185,2174,2175],{"class":201},"      stop",[185,2177,1939],{"class":211},[185,2179,2181],{"class":187,"line":2180},36,[185,2182,2134],{"class":211},[185,2184,2186],{"class":187,"line":2185},37,[185,2187,1955],{"class":211},[185,2189,2191],{"class":187,"line":2190},38,[185,2192,1267],{"emptyLinePlaceholder":1266},[185,2194,2196,2199],{"class":187,"line":2195},39,[185,2197,2198],{"class":201},"  onScopeDispose",[185,2200,2201],{"class":211},"(stop)\n",[185,2203,2205,2208],{"class":187,"line":2204},40,[185,2206,2207],{"class":191},"  return",[185,2209,2210],{"class":211}," { remaining, state, start }\n",[185,2212,2214],{"class":187,"line":2213},41,[185,2215,1768],{"class":211},[99,2217,2218,2219,2222,2223,2225],{},"The example's countdown page (",[103,2220,2221],{},"\u002Fcountdown",") shows one card per runtime. ",[103,2224,1114],{}," reconnects after an error and after the server ends the stream, so close it when you have what you need, as the composable does at 0.",[99,2227,2228,2229,2232,2233,2235],{},"The generated TypeScript type of an event route is ",[103,2230,2231],{},"string",", the type of a sent stream. The generator does not describe the events' data yet, so the composable declares ",[103,2234,1212],{}," itself.",[1056,2237,2239],{"id":2238},"read-the-events-in-a-handler","Read the events in a handler",[99,2241,2242,2243,2246,2247,2249,2250,2253,2254,2256,2257,174],{},"Read another route's events with ",[103,2244,2245],{},"$fetch.events[E]",". Each event's ",[103,2248,1096],{}," is decoded as JSON for ",[103,2251,2252],{},"E"," with its ",[103,2255,556],{},". In Kyo, the result is a ",[103,2258,2259],{},"Stream[E, Async & Scope & Abort[FetchError]]",[176,2261,2263],{"className":178,"code":2262,"language":180,"meta":181,"style":181},"val total: EventHandler = KHandler.define: _ =>\n    for\n      ticks \u003C- KHandler.$fetch.events[Countdown](\"\u002Fapi\u002Fkyo\u002Fcountdown\")\n      seen  \u003C- ticks.run\n    yield seen.map(_.remaining).sum\n",[103,2264,2265,2288,2293,2315,2325],{"__ignoreMap":181},[185,2266,2267,2269,2272,2274,2276,2278,2280,2282,2284,2286],{"class":187,"line":188},[185,2268,192],{"class":191},[185,2270,2271],{"class":195}," total",[185,2273,174],{"class":191},[185,2275,202],{"class":201},[185,2277,205],{"class":191},[185,2279,208],{"class":201},[185,2281,212],{"class":211},[185,2283,174],{"class":191},[185,2285,217],{"class":211},[185,2287,220],{"class":191},[185,2289,2290],{"class":187,"line":223},[185,2291,2292],{"class":191},"    for\n",[185,2294,2295,2298,2301,2303,2306,2308,2311,2313],{"class":187,"line":243},[185,2296,2297],{"class":211},"      ticks ",[185,2299,2300],{"class":191},"\u003C-",[185,2302,208],{"class":201},[185,2304,2305],{"class":211},".$fetch.events[",[185,2307,1212],{"class":201},[185,2309,2310],{"class":211},"](",[185,2312,1449],{"class":255},[185,2314,240],{"class":211},[185,2316,2317,2320,2322],{"class":187,"line":268},[185,2318,2319],{"class":211},"      seen  ",[185,2321,2300],{"class":191},[185,2323,2324],{"class":211}," ticks.run\n",[185,2326,2327,2330],{"class":187,"line":1275},[185,2328,2329],{"class":191},"    yield",[185,2331,2332],{"class":211}," seen.map(_.remaining).sum\n",[99,2334,2335,2336,174],{},"In ZIO, it is a ",[103,2337,2338],{},"ZStream[Any, FetchError, E]",[176,2340,2342],{"className":178,"code":2341,"language":180,"meta":181,"style":181},"val total: EventHandler = ZHandler.define: _ =>\n    for\n      ticks \u003C- ZHandler.$fetch.events[Countdown](\"\u002Fapi\u002Fzio\u002Fcountdown\")\n      sum   \u003C- ticks.runFold(0)(_ + _.remaining)\n    yield sum\n",[103,2343,2344,2366,2370,2388,2409],{"__ignoreMap":181},[185,2345,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364],{"class":187,"line":188},[185,2347,192],{"class":191},[185,2349,2271],{"class":195},[185,2351,174],{"class":191},[185,2353,202],{"class":201},[185,2355,205],{"class":191},[185,2357,337],{"class":201},[185,2359,212],{"class":211},[185,2361,174],{"class":191},[185,2363,217],{"class":211},[185,2365,220],{"class":191},[185,2367,2368],{"class":187,"line":223},[185,2369,2292],{"class":191},[185,2371,2372,2374,2376,2378,2380,2382,2384,2386],{"class":187,"line":243},[185,2373,2297],{"class":211},[185,2375,2300],{"class":191},[185,2377,337],{"class":201},[185,2379,2305],{"class":211},[185,2381,1212],{"class":201},[185,2383,2310],{"class":211},[185,2385,1626],{"class":255},[185,2387,240],{"class":211},[185,2389,2390,2393,2395,2398,2400,2403,2406],{"class":187,"line":268},[185,2391,2392],{"class":211},"      sum   ",[185,2394,2300],{"class":191},[185,2396,2397],{"class":211}," ticks.runFold(",[185,2399,1360],{"class":1353},[185,2401,2402],{"class":211},")(_ ",[185,2404,2405],{"class":191},"+",[185,2407,2408],{"class":211}," _.remaining)\n",[185,2410,2411,2413],{"class":187,"line":1275},[185,2412,2329],{"class":191},[185,2414,2415],{"class":211}," sum\n",[99,2417,2418,2419,2422,2423,259,2426,416],{},"Both answer ",[103,2420,2421],{},"{\"data\": 6}",". These snippets come from the tests \"the guide's events example: a handler reads the countdown's typed events with $fetch.events\" (",[103,2424,2425],{},"NitroLocalFetchSuite",[103,2427,2428],{},"NitroLocalFetchSpec",[440,2430,2431,2454,2478,2499,2521,2536],{},[443,2432,2433,113,2436,2438,2439,2442,2443,2446,2447,2450,2451,2453],{},[110,2434,2435],{},"Head first.",[103,2437,1029],{}," sends the request with ",[103,2440,2441],{},"accept: text\u002Fevent-stream",", unless you pass an ",[103,2444,2445],{},"accept"," header, and returns as soon as the head arrives, as ",[103,2448,2449],{},"raw"," does. The body lives in your ",[103,2452,639],{},": when the scope closes, h34s cancels a body that you did not read to its end.",[443,2455,2456,2459,2460,2463,2464,2467,2468,2470,2471,2473,2474,2477],{},[110,2457,2458],{},"Typed failures."," A status that is not 2xx fails with ",[103,2461,2462],{},"FetchError.Status",". A 2xx response with another content type fails with ",[103,2465,2466],{},"FetchError.NotEvents(status, contentType)",", and h34s does not read its body. The first event whose ",[103,2469,1096],{}," is not JSON for ",[103,2472,2252],{}," fails the stream with ",[103,2475,2476],{},"FetchError.Decode",", which names the event by its position and type, never by its data; the events before it reach you.",[443,2479,2480,113,2483,2486,2487,2489,2490,2492,2493,2495,2496,2498],{},[110,2481,2482],{},"Events without a model.",[103,2484,2485],{},"$fetch.events(FetchRequest(url))"," gives the ",[103,2488,1033],{}," values themselves. Use it for a stream that mixes types, or for a JavaScript route that uses h3's ",[103,2491,1648],{},": such a route reaches you through its ",[103,2494,527],{}," content type. In-process, stopping before the end of such a route's body makes h3 leak the unhandled rejections described above, so read a ",[103,2497,1648],{}," route to its end.",[443,2500,2501,2504,2505,2507,2508,2510,2511,2513,2514,2516,2517,2520],{},[110,2502,2503],{},"One frame, one event."," Each ",[103,2506,1033],{}," has the fields of its own frame. Its ",[103,2509,1185],{}," is the frame's own ",[103,2512,1693],{}," line; a browser's ",[103,2515,1135],{}," carries over to later events, but ",[103,2518,2519],{},"ServerEvent.id"," does not.",[443,2522,2523,2526,2527,2529,2530,2532,2533,2535],{},[110,2524,2525],{},"The parsing rules."," h34s decodes the body as UTF-8 once, which removes one leading byte order mark, as a browser does. Then it reads the text with the WHATWG ",[103,2528,1114],{}," rules: lines end with CRLF, CR or LF, comment lines (",[103,2531,174],{},") are ignored, and one space after the colon is removed. A second byte order mark is part of the first line, which then names an unknown field. An event without a ",[103,2534,1092],{}," line, and an event that the stream does not end with an empty line, are not dispatched. A frame may be split anywhere between chunks.",[443,2537,2538,2541,2542,2544],{},[110,2539,2540],{},"NDJSON."," There is no typed decoder for newline-delimited JSON yet. Read the body of ",[103,2543,2449],{}," and split it into lines yourself.",[99,2546,2547,116,2549,2551,2552,2554,2555,2558],{},[103,2548,2425],{},[103,2550,2428],{}," test this: \"sendEvents in-process: $fetch.events",[185,2553,2252],{}," returns head-first and decodes the typed events as they arrive\", \"a JavaScript route with h3's createEventStream: the untyped $fetch.events reads its events by the content type\", \"events of a route that is not an event stream fails with FetchError.NotEvents; an error status with FetchError.Status\", \"a typed event whose data is not JSON for E fails the stream with FetchError.Decode, which names the event\" and \"over real HTTP (absolute URL), events split inside lines and inside a CRLF decode to the same typed events\" and \"events: the body is decoded as UTF-8 once, so of two leading byte order marks only the first is removed\". ",[103,2556,2557],{},"ServerEventSuite"," checks the frame and the parsing rules, every split point of a text with several events, and that every event's frame decodes to the same event.",[143,2560,2562],{"id":2561},"in-process-calls","In-process calls",[99,2564,2565,2566,549,2568,1182,2571,2574],{},"Nitro answers a server-side ",[103,2567,515],{},[103,2569,2570],{},"KHandler.$fetch",[103,2572,2573],{},"ZHandler.$fetch",") to your own route in-process, without a socket. A stream works there too:",[440,2576,2577,2582,2595,2601,2619,2640,2646,2674,2693],{},[443,2578,2579,2581],{},[110,2580,2435],{}," h34s writes the head, hands the stream to h3, and settles h3's handler promise at once, so the caller gets the head before the first element. With the h34s test copy of Nitro 2.13.1's local fetch, a caller had the head after 16 to 23 ms and the elements one per 200 ms after that.",[443,2583,2584,2587,2588,2590,2591,2594],{},[110,2585,2586],{},"Bytes."," Chunks are UTF-8 bytes, because a Web ",[103,2589,249],{}," body accepts only ",[103,2592,2593],{},"Uint8Array"," chunks.",[443,2596,2597,2600],{},[110,2598,2599],{},"Lifetime."," Your program and the request scope stay open until the caller reads the body to its end or cancels it.",[443,2602,2603,2606,2607,2609,2610,2612,2613,2615,2616,2618],{},[110,2604,2605],{},"A cancel waits for cleanup."," A ",[103,2608,515],{}," caller cancels an unread body when its ",[103,2611,639],{}," closes: the request scope, a Kyo ",[103,2614,825],{}," or a ZIO ",[103,2617,933],{}," block. The cancel waits until the callee's stream and request finalizers have run, so the caller settles after the callee's cleanup.",[443,2620,2621,2624,2625,2628,2629,2632,2633,2635,2636,2639],{},[110,2622,2623],{},"A failure after the head is typed."," When the callee's stream fails after its head, the caller's read fails with ",[103,2626,2627],{},"FetchError.BodyFailed(status, reason)",": in Kyo, through ",[103,2630,2631],{},"Abort[FetchError]"," in the body's effect type; in ZIO, as the error of the body's ",[103,2634,383],{},". The ",[103,2637,2638],{},"reason"," is h34s' own text and never quotes the callee's error.",[443,2641,2642,2645],{},[110,2643,2644],{},"An unread body stays open."," A JavaScript caller, or a socketless Nitro preset, that drops the body without cancelling it keeps the callee's program and request scope open. h34s has no idle timeout for this case yet.",[443,2647,2648,2651,2652,2655,2656,2659,2660,2662,2663,116,2666,2669,2670,2673],{},[110,2649,2650],{},"The base URL and the context."," h34s puts your app's ",[103,2653,2654],{},"app.baseURL"," in front of a path that starts with ",[103,2657,2658],{},"\u002F",", where Nitro mounts your routes, as Nuxt's ",[103,2661,515],{}," does; a path that already starts with the base stays as it is. The call passes only the ",[103,2664,2665],{},"_platform",[103,2667,2668],{},"waitUntil"," entries of the caller's ",[103,2671,2672],{},"event.context",", so the called route cannot read what your middleware put there, such as a session.",[443,2675,2676,2606,2679,2681,2682,2684,2685,2688,2689,2692],{},[110,2677,2678],{},"Credentials stay in-process.",[103,2680,515],{}," call to a path that starts with ",[103,2683,2658],{}," gets the incoming request's headers and cookies, as in Nuxt. A call to an absolute URL gets only the headers that you pass and the content type and length of the body that you send, so the user's cookies and ",[103,2686,2687],{},"authorization"," never reach another host. Nuxt's ",[103,2690,2691],{},"event.$fetch"," forwards them to any host; h34s does not. To call another service with a credential, pass that header yourself.",[443,2694,2695,2698,2699,2702,2703,2705,2706,1182,2708,2710],{},[110,2696,2697],{},"A JavaScript stream needs a stream content type."," A JavaScript route that streams without the marker and without a stream content type is ",[103,2700,2701],{},"Plain",", so ",[103,2704,2449],{}," reads it to its end, and never returns for an endless stream. Set ",[103,2707,527],{},[103,2709,530],{}," on such a route.",[99,2712,2713,2715,2716,2718,2719,2722,2723,2725],{},[103,2714,2425],{}," measures this behavior: \"(b) streamed Scala route in-process: $fetch returns head-first; the body is pulled in the caller's Scope\", \"(c) a caller that drops a Streamed body: its Scope cancels the callee, whose finalizers run before the caller settles\" and \"(c) a caller that reads part of a Streamed body: the rest is cancelled when its Scope closes\". The same suite shows the head arriving before the first element over real HTTP: \"(b) the same streamed route over real HTTP: the head arrives before the first element\". Two more tests prove the ",[103,2717,2701],{}," rule and the credential rule: \"(a) JSON route in-process: no marker and no stream content type, so Plain without content-length; as",[185,2720,2721],{},"A"," decodes it\" and \"an absolute URL gets only the headers the caller passes: the incoming cookie, authorization and entity headers stay behind\". The base URL, the context and the typed failure have their own tests: \"app.baseURL: an in-process $fetch reaches a route under a non-root base; a path that carries the base is not prefixed again\", \"an in-process call passes only the caller's _platform and waitUntil: the callee cannot read the caller's other context entries\" and \"a callee stream that fails after its head: the caller's read fails with a typed FetchError.BodyFailed, not a panic\". ",[103,2724,2428],{}," repeats them for ZIO callers, with \"a caller that drops a Streamed body: its Scope cancels the callee and waits for the callee's finalizers (Q16)\".",[143,2727,2729],{"id":2728},"test-a-streaming-handler","Test a streaming handler",[99,2731,2732,116,2734,2736,2737,2740,2741,141],{},[103,2733,466],{},[103,2735,469],{}," collect what your handler sends. The result's body is ",[103,2738,2739],{},"TestResponse.Body.Streamed(elements)","; see ",[138,2742,2744],{"href":2743},"\u002Fguide\u002Ftesting#test-a-streaming-handler","testing",[143,2746,2748],{"id":2747},"limits","Limits",[702,2750,2751,2761],{},[705,2752,2753],{},[708,2754,2755,2758],{},[711,2756,2757],{},"Area",[711,2759,2760],{},"Behavior",[718,2762,2763,2777,2796,2808,2816],{},[708,2764,2765,2768],{},[723,2766,2767],{},"Direct Node HTTP responses",[723,2769,2770,2771,2774,2775],{},"Supported. Tested with real h3 and mocked Node responses, and with a real ",[103,2772,2773],{},"node:http"," server in ",[103,2776,2425],{},[708,2778,2779,2785],{},[723,2780,2781,2782,2784],{},"No socket (Nitro's in-process local fetch, for example a server-side ",[103,2783,515],{}," to your own route)",[723,2786,2787,2788,2791,2792,2795],{},"Supported. Tested with a copy of Nitro 2.13.1's local-fetch wiring (",[103,2789,2790],{},"NitroLocal","); not yet tested against ",[103,2793,2794],{},"nuxt dev"," or a built Nitro server. An unread body that nobody cancels keeps the callee open",[708,2797,2798,2801],{},[723,2799,2800],{},"Back pressure",[723,2802,2803,2804,2807],{},"One chunk at a time from your stream. h3's sink does not wait for Node's ",[103,2805,2806],{},"drain",", so a slow client can still grow Node's output buffers. There is no end-to-end memory bound",[708,2809,2810,2813],{},[723,2811,2812],{},"Other Nitro presets (serverless, edge)",[723,2814,2815],{},"Not tested. Many of them serve every request without a socket, so no disconnect reaches h34s unless the platform cancels the body",[708,2817,2818,2820],{},[723,2819,1020],{},[723,2821,2822,2823,2825,2826,2828],{},"No heartbeat comments yet: a proxy with an idle timeout can close a quiet stream. No typed decoder for NDJSON in ",[103,2824,515],{},". In-process, stopping before the end of a JavaScript ",[103,2827,1648],{}," route makes h3 leak unhandled rejections",[143,2830,2832],{"id":2831},"what-is-tested","What is tested",[99,2834,2835,2836,259,2839,116,2841,2843,2844,259,2847,116,2849,2851,2852,2854,2855,2857,2858,2860,2861,2863,2864,2867,2868,2870,2871,2873,2874,2876,2877,2879],{},"Kyo ",[103,2837,2838],{},"StreamSuite",[103,2840,412],{},[103,2842,2425],{},", ZIO ",[103,2845,2846],{},"StreamSpec",[103,2848,415],{},[103,2850,2428],{},": pull-driven delivery, the head written and flushed at the call before the first element, empty streams, order of effectful, concatenated, filtered and mapped streams, finalization exactly once after the end, cancellation waiting for asynchronous finalizers, disconnects releasing stream and request resources once (also with a ",[103,2853,825],{}," around ",[103,2856,433],{},"), failures before and after the first element (the step fails, the connection closes, one log line), thrown errors and ",[103,2859,484],{}," elements, a failed write, the default content type, the socketless path (head first, UTF-8 bytes, a cancel that waits for the request, a failure that errors the caller's reads and reaches a ",[103,2862,515],{}," caller as ",[103,2865,2866],{},"FetchError.BodyFailed","), and the compile error for a returned stream. For server-sent events, ",[103,2869,2557],{}," adds the frame encoder and the parsing rules, and the suites above add ",[103,2872,1644],{}," (headers, frames, HTTP\u002F2, a disconnect) and ",[103,2875,1051],{}," (head first, a JavaScript ",[103,2878,1648],{}," route, the typed failures, frames split over real HTTP). A real client abort against a running Nuxt server has not been tested on this branch.",[2881,2882,2883],"style",{},"html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .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 .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":181,"searchDepth":223,"depth":223,"links":2885},[2886,2887,2888,2889,2890,2891,2892,2893,2899,2900,2901,2902],{"id":145,"depth":223,"text":146},{"id":437,"depth":223,"text":438},{"id":579,"depth":223,"text":580},{"id":616,"depth":223,"text":617},{"id":661,"depth":223,"text":662},{"id":699,"depth":223,"text":700},{"id":799,"depth":223,"text":800},{"id":1019,"depth":223,"text":1020,"children":2894},[2895,2896,2897,2898],{"id":1058,"depth":243,"text":1059},{"id":1206,"depth":243,"text":1207},{"id":1720,"depth":243,"text":1721},{"id":2238,"depth":243,"text":2239},{"id":2561,"depth":223,"text":2562},{"id":2728,"depth":223,"text":2729},{"id":2747,"depth":223,"text":2748},{"id":2831,"depth":223,"text":2832},"Send a Kyo Stream or a ZStream with Response.sendStream; h34s writes the head at once, keeps the request scope open and waits for cleanup when the client goes away.","md",null,{},{"title":47,"description":2903},"OrEWC7PRv80vXgGyJKZI6qplN8hE2dME3dWZyyvXMzU",[2910,2912],{"title":43,"path":44,"stem":45,"description":2911,"children":-1},"Use kyo-sql or Skunk directly inside Nitro, with one pool per application and one transaction or session per request.",{"title":51,"path":52,"stem":53,"description":2913,"children":-1},"Test handler functions with Request.fake and KHandler.test or ZHandler.test, without h3, Nitro or a server.",1792533381917]