From e645a6c485ccfb67feb656641d7e445c6572ffc6 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:37:48 +0300 Subject: [PATCH 1/4] re-record wasm, diagnostics, the agent loop, highlighting and the stdlib at 0.20.1 --- records/agent.json | 10 +-- records/diagnostics.json | 6 +- records/highlight.json | 8 +- records/stdlib.json | 16 ++-- records/wasm/arrays.wasm | Bin 145881 -> 145881 bytes records/wasm/blocks.wasm | Bin 127866 -> 127866 bytes records/wasm/bounds.wasm | Bin 129541 -> 129541 bytes records/wasm/case.wasm | Bin 127806 -> 127806 bytes records/wasm/classes.wasm | Bin 124398 -> 124398 bytes records/wasm/conditions.wasm | Bin 124828 -> 124828 bytes records/wasm/constants.wasm | Bin 160443 -> 160443 bytes records/wasm/conversions.wasm | Bin 187416 -> 187416 bytes records/wasm/defaults.wasm | Bin 127100 -> 127100 bytes records/wasm/defer.wasm | Bin 128138 -> 128138 bytes records/wasm/dispatch.wasm | Bin 136069 -> 136069 bytes records/wasm/errors.wasm | Bin 160371 -> 160371 bytes records/wasm/exports.wasm | Bin 124838 -> 124838 bytes records/wasm/functions.wasm | Bin 125184 -> 125184 bytes records/wasm/generic-functions.wasm | Bin 151699 -> 151699 bytes records/wasm/generic-types.wasm | Bin 135597 -> 135597 bytes records/wasm/hashes.wasm | Bin 165179 -> 165179 bytes records/wasm/hello.wasm | Bin 123087 -> 123087 bytes records/wasm/iyi-basics.wasm | Bin 213443 -> 213443 bytes records/wasm/iyi-calc.wasm | Bin 200618 -> 200618 bytes records/wasm/iyi-collections.wasm | Bin 228588 -> 228588 bytes records/wasm/iyi-config.wasm | Bin 177376 -> 177376 bytes records/wasm/iyi-derive.wasm | Bin 128268 -> 128268 bytes records/wasm/iyi-enums.wasm | Bin 151146 -> 151146 bytes records/wasm/iyi-errors.wasm | Bin 138685 -> 138685 bytes records/wasm/iyi-files.wasm | Bin 125156 -> 125156 bytes records/wasm/iyi-format.wasm | Bin 308041 -> 308041 bytes records/wasm/iyi-formatting.wasm | Bin 143143 -> 143143 bytes records/wasm/iyi-generics.wasm | Bin 143824 -> 143824 bytes records/wasm/iyi-grid.wasm | Bin 187747 -> 187747 bytes records/wasm/iyi-hello.wasm | Bin 130696 -> 130696 bytes records/wasm/iyi-immutable.wasm | Bin 197267 -> 197267 bytes records/wasm/iyi-init_order.wasm | Bin 124809 -> 124809 bytes records/wasm/iyi-inventory.wasm | Bin 218027 -> 218027 bytes records/wasm/iyi-io.wasm | Bin 131609 -> 131609 bytes records/wasm/iyi-modules.wasm | Bin 126751 -> 126751 bytes records/wasm/iyi-sessions.wasm | Bin 139540 -> 139540 bytes records/wasm/iyi-shapes.wasm | Bin 197147 -> 197147 bytes records/wasm/iyi-socket.wasm | Bin 413767 -> 413767 bytes records/wasm/iyi-std_iterator.wasm | Bin 208688 -> 208688 bytes records/wasm/iyi-std_text.wasm | Bin 195805 -> 195805 bytes records/wasm/iyi-std_time.wasm | Bin 275376 -> 275376 bytes records/wasm/iyi-visited.wasm | Bin 176116 -> 176116 bytes records/wasm/iyi-webapp.wasm | Bin 171353 -> 171353 bytes records/wasm/loops.wasm | Bin 126424 -> 126424 bytes records/wasm/manifest.json | 122 ++++++++++++++-------------- records/wasm/methods.wasm | Bin 161051 -> 161051 bytes records/wasm/modules.wasm | Bin 124842 -> 124842 bytes records/wasm/nil.wasm | Bin 125476 -> 125476 bytes records/wasm/ranges.wasm | Bin 141760 -> 141760 bytes records/wasm/returns.wasm | Bin 127402 -> 127402 bytes records/wasm/strings.wasm | Bin 160621 -> 160621 bytes records/wasm/structs.wasm | Bin 125572 -> 125572 bytes records/wasm/traits.wasm | Bin 160535 -> 160535 bytes records/wasm/tuples.wasm | Bin 127759 -> 127759 bytes records/wasm/types.wasm | Bin 158793 -> 158793 bytes records/wasm/unions.wasm | Bin 135190 -> 135190 bytes records/wasm/variables.wasm | Bin 156061 -> 156061 bytes 62 files changed, 81 insertions(+), 81 deletions(-) diff --git a/records/agent.json b/records/agent.json index 40e4ea6..6447e61 100644 --- a/records/agent.json +++ b/records/agent.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", - "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "compiler": "iyi 0.20.1 (built on Crystal 1.22.0-dev [91d012b1e] (2026-11-04))", + "commit": "91d012b1ed978c7df5cc033624ec161a2daf01b6", "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:agent", - "when": "2026-11-02T07:19:31.432Z" + "when": "2026-11-04T04:32:53.960Z" }, "project": [ { @@ -104,7 +104,7 @@ "command": "iyi test --json .", "exitCode": 0, "payload": "stdout", - "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.394}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", + "stdout": "{\"tests\":[{\"file\":\"./app_test.iyi\",\"status\":\"pass\",\"seconds\":0.281}],\"passed\":1,\"failed\":0,\"skipped\":0}\n", "stderr": "" }, { @@ -167,7 +167,7 @@ }, "serverInfo": { "name": "iyi", - "version": "0.20.0" + "version": "0.20.1" } }, "tools": [ diff --git a/records/diagnostics.json b/records/diagnostics.json index 2d10ef1..029a842 100644 --- a/records/diagnostics.json +++ b/records/diagnostics.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", - "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "compiler": "iyi 0.20.1 (built on Crystal 1.22.0-dev [91d012b1e] (2026-11-04))", + "commit": "91d012b1ed978c7df5cc033624ec161a2daf01b6", "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:diagnostics", - "when": "2026-11-02T07:19:23.048Z" + "when": "2026-11-04T04:32:47.944Z" }, "cases": [ { diff --git a/records/highlight.json b/records/highlight.json index e4b069a..f2a119a 100644 --- a/records/highlight.json +++ b/records/highlight.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", - "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "compiler": "iyi 0.20.1 (built on Crystal 1.22.0-dev [91d012b1e] (2026-11-04))", + "commit": "91d012b1ed978c7df5cc033624ec161a2daf01b6", "machine": "Linux 7.0.0-38-generic x86_64, QEMU Virtual CPU version 2.5+", "command": "npm run record:highlight", - "when": "2026-11-02T07:19:35.714Z" + "when": "2026-11-04T04:32:57.045Z" }, "files": { "samples/iyi/app/formal.iyi": "# A second module, exporting a `title` of its own. See `../modules.iyi`.\nmodule app/formal\n\npub def title : String\n "esteemed colleague"\nend\n\npub def address(name : String) : String\n "Good day, #{name}."\nend\n", @@ -15,7 +15,7 @@ "samples/iyi/calc/ast.iyi": "module calc/ast\n\n# What an evaluated program can go wrong with. Both are ordinary members of the\n# union an evaluation returns (SPEC.md III.1), so a caller either handles them\n# or propagates with `!`.\npub struct UnknownName\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Error for UnknownName\n def message : String\n "unknown name '#{name}'"\n end\nend\n\npub struct DividedByZero\n def initialize\n end\nend\n\nimpl Error for DividedByZero\n def message : String\n "divided by zero"\n end\nend\n\n# The one thing every node can do. A trait rather than a base class, because\n# there is nothing to inherit and R-3 has no opinion to break here.\npub trait Node\n abstract def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\nend\n\npub struct Number\n getter value : Int32\n\n def initialize(@value : Int32)\n end\nend\n\nimpl Node for Number\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n value\n end\nend\n\npub struct Name\n getter name : String\n\n def initialize(@name : String)\n end\nend\n\nimpl Node for Name\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n return UnknownName.new(name) unless scope.has_key?(name)\n scope[name]\n end\nend\n\npub class Binary\n getter op : Char\n getter left : Number | Name | Binary\n getter right : Number | Name | Binary\n\n def initialize(@op : Char, @left : Number | Name | Binary, @right : Number | Name | Binary)\n end\nend\n\nimpl Node for Binary\n def evaluate(scope : Hash(String, Int32)) : Int32 | UnknownName | DividedByZero\n a = left.evaluate(scope)!\n b = right.evaluate(scope)!\n\n if op == '+'\n a + b\n elsif op == '-'\n a - b\n elsif op == '*'\n a * b\n else\n return DividedByZero.new if b == 0\n a // b\n end\n end\nend\n", "samples/iyi/calc/lexer.iyi": "module calc/lexer\n\n# What the scanner produces. A token is its kind, the text it covers and where\n# it started, because an error that cannot say where is half an error.\npub struct Token\n getter kind : String\n getter text : String\n getter at : Int32\n\n def initialize(@kind : String, @text : String, @at : Int32)\n end\nend\n\npub struct BadCharacter\n getter at : Int32\n getter found : Char\n\n def initialize(@at : Int32, @found : Char)\n end\nend\n\nimpl Error for BadCharacter\n def message : String\n "unexpected character at #{at}"\n end\nend\n\npub def scan(source : String) : Array(Token) | BadCharacter\n tokens = [] of Token\n index = 0\n\n while index < source.size\n c = source[index]\n\n # A line ends in CRLF on Windows: `echo 2 + 3 * 4| iyi run\n # samples\\iyi\\calc.iyi` in cmd answered "unexpected character at 9",\n # the `\\r`.\n if c == ' ' || c == '\\n' || c == '\\t' || c == '\\r'\n index = index + 1\n elsif digit?(c)\n start = index\n while index < source.size && digit?(source[index])\n index = index + 1\n end\n tokens << Token.new("number", source[start, index - start], start)\n elsif letter?(c)\n start = index\n while index < source.size && (letter?(source[index]) || digit?(source[index]))\n index = index + 1\n end\n tokens << Token.new("name", source[start, index - start], start)\n elsif operator?(c)\n tokens << Token.new("op", source[index, 1], index)\n index = index + 1\n else\n return BadCharacter.new(index, c)\n end\n end\n\n tokens << Token.new("end", "", index)\n tokens\nend\n\ndef digit?(c : Char) : Bool\n c >= '0' && c <= '9'\nend\n\ndef letter?(c : Char) : Bool\n (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'\nend\n\ndef operator?(c : Char) : Bool\n c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '='\nend\n", "samples/iyi/calc/parser.iyi": "module calc/parser\n\nimport calc/lexer::{Token}\nimport calc/ast::{Number, Name, Binary}\n\npub struct SyntaxError\n getter at : Int32\n getter wanted : String\n\n def initialize(@at : Int32, @wanted : String)\n end\nend\n\nimpl Error for SyntaxError\n def message : String\n "expected #{wanted} at #{at}"\n end\nend\n\n# One statement: `name = expression`, or an expression on its own, which the\n# runner prints. Kept as a pair rather than a node type, because a statement is\n# not something this language evaluates twice.\npub struct Statement\n getter target : String\n getter body : Number | Name | Binary\n\n def initialize(@target : String, @body : Number | Name | Binary)\n end\nend\n\n# A cursor over the tokens. A struct with an index rather than a shifting array,\n# because backing up one token is the parser's whole trick.\npub class Reader\n getter tokens : Array(Token)\n getter at : Int32\n\n def initialize(@tokens : Array(Token))\n @at = 0\n end\n\n def peek : Token\n tokens[at]\n end\n\n def advance : Token\n token = tokens[at]\n @at = at + 1\n token\n end\nend\n\npub def parse(tokens : Array(Token)) : Array(Statement) | SyntaxError\n reader = Reader.new(tokens)\n statements = [] of Statement\n\n while reader.peek.kind != "end"\n statements << statement(reader)!\n end\n\n statements\nend\n\ndef statement(reader : Reader) : Statement | SyntaxError\n first = reader.peek\n\n if first.kind == "name" && reader.tokens[reader.at + 1].text == "="\n reader.advance\n reader.advance\n Statement.new(first.text, expression(reader)!)\n else\n Statement.new("", expression(reader)!)\n end\nend\n\ndef expression(reader : Reader) : Number | Name | Binary | SyntaxError\n left = term(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "+" || reader.peek.text == "-")\n op = reader.advance.text[0]\n left = Binary.new(op, left, term(reader)!)\n end\n\n left\nend\n\ndef term(reader : Reader) : Number | Name | Binary | SyntaxError\n left = factor(reader)!\n\n while reader.peek.kind == "op" && (reader.peek.text == "*" || reader.peek.text == "/")\n op = reader.advance.text[0]\n left = Binary.new(op, left, factor(reader)!)\n end\n\n left\nend\n\ndef factor(reader : Reader) : Number | Name | Binary | SyntaxError\n token = reader.advance\n\n if token.kind == "number"\n Number.new(token.text.to_i)\n elsif token.kind == "name"\n Name.new(token.text)\n elsif token.text == "("\n inside = expression(reader)!\n closing = reader.advance\n return SyntaxError.new(closing.at, "')'") unless closing.text == ")"\n inside\n else\n SyntaxError.new(token.at, "a number, a name or '('")\n end\nend\n", - "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 25,725-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer::{scan, BadCharacter}\nimport calc/parser::{parse, Statement, SyntaxError}\nimport calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way. Its last two lines are errors on\n# purpose, and the demo exits 0 with them.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\n# Whether every statement answered. An error goes to standard error, and\n# input with one makes the program exit 1: all of them went to standard\n# output at exit 0, so `echo 1 / 0 | iyi run samples/iyi/calc.iyi && next`\n# ran `next`.\npub def run(source : String) : Bool\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n stderr.puts "error: #{tokens.message}"\n return false\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n stderr.puts "error: #{program.message}"\n return false\n end\n\n answered = true\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n stderr.puts "error: #{value.message}"\n answered = false\n end\n end\n answered\nend\n\n# Input that is only line ends is nothing piped in: PowerShell's `"" |`\n# sends a CRLF. And its pipe starts what it sends with a byte order mark,\n# which is not part of the program: "unexpected character at 0".\ntyped = read_input.lchop('\\uFEFF')\nif typed.strip.empty?\n run(DEMO)\nelsif !run(typed)\n exit 1\nend\n", + "samples/iyi/calc.iyi": "# A language, in the language — the biggest program this prelude can hold.\n#\n# echo "x = 2 + 3 * 4\n# x * 10" | ./bin/iyi run samples/iyi/calc.iyi\n#\n# The other samples each document a rule. This one documents nothing and is\n# here for a different reason: everything else in `samples/iyi` is a page long,\n# and a language that has only been used for pages has not been used. This is\n# three modules and a scanner, a parser and an evaluator, the shape of every\n# real program that reads something, written against iyi's own 25,763-line\n# library and nothing else.\n#\n# What it cost to write is recorded where it landed. `String#[]`, `String#[](start,\n# count)`, `String#to_i` and `read_input` are in the prelude because this file\n# asked for them, which is the rule the prelude grows by (SPEC.md "0.1.0",\n# item 3). Nothing else was missing.\nmodule samples/calc\n\nimport calc/lexer::{scan, BadCharacter}\nimport calc/parser::{parse, Statement, SyntaxError}\nimport calc/ast::{Number, Name, Binary, UnknownName, DividedByZero}\n\n# What runs when nothing is piped in, so that the sample is a program rather\n# than a prompt. CI runs it this way. Its last two lines are errors on\n# purpose, and the demo exits 0 with them.\nDEMO = "x = 2 + 3 * 4\ny = (x - 4) / 2\nx + y * 10\nmissing + 1\n1 / 0"\n\n# Whether every statement answered. An error goes to standard error, and\n# input with one makes the program exit 1: all of them went to standard\n# output at exit 0, so `echo 1 / 0 | iyi run samples/iyi/calc.iyi && next`\n# ran `next`.\npub def run(source : String) : Bool\n tokens = scan(source)\n if tokens.is_a?(BadCharacter)\n stderr.puts "error: #{tokens.message}"\n return false\n end\n\n program = parse(tokens)\n if program.is_a?(SyntaxError)\n stderr.puts "error: #{program.message}"\n return false\n end\n\n answered = true\n scope = {} of String => Int32\n program.each do |statement|\n value = statement.body.evaluate(scope)\n\n if value.is_a?(Int32)\n if statement.target.empty?\n puts value\n else\n scope[statement.target] = value\n end\n else\n # `value` is an error member of the union, and every one of them answers\n # `message` because they implement `Error` (SPEC.md III.1).\n stderr.puts "error: #{value.message}"\n answered = false\n end\n end\n answered\nend\n\n# Input that is only line ends is nothing piped in: PowerShell's `"" |`\n# sends a CRLF. And its pipe starts what it sends with a byte order mark,\n# which is not part of the program: "unexpected character at 0".\ntyped = read_input.lchop('\\uFEFF')\nif typed.strip.empty?\n run(DEMO)\nelsif !run(typed)\n exit 1\nend\n", "samples/iyi/collections.iyi": "# The `Enumerable` port, exercised — SPEC.md II.6.\n#\n# ./bin/iyi run samples/iyi/collections.iyi\n#\n# `std/enumerable.iyi` holds the trait. This file implements it twice and calls\n# every method it defines. Both matter: a default method is only typed when it\n# is called, so a trait that merely compiles has proved nothing about whether\n# its bodies are sound.\n#\n# `Nums` and `Words` answer `Elem` differently — `Int32` and `String` — and each\n# gets the same 57 default methods from one `each`. That is the claim II.6\n# makes, running.\n\nmodule samples/collections\n\nimport std/enumerable::{Enumerable}\n\npub struct Nums\n def initialize(@a : Array(Int32))\n end\nend\n\nimpl Enumerable for Nums\n type Elem = Int32\n\n def each(& : Int32 -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\npub struct Words\n def initialize(@a : Array(String))\n end\nend\n\nimpl Enumerable for Words\n type Elem = String\n\n def each(& : String -> Nil) : Nil\n @a.each { |x| yield x }\n end\nend\n\nn = Nums.new([3, 1, 4, 1, 5])\nw = Words.new(["pear", "fig", "plum", "kiwi"])\n\nputs "to_a #{n.to_a.join(",")}"\nputs "size #{n.size}"\nputs "empty? #{n.empty?} #{Nums.new([] of Int32).empty?}"\nputs "all? #{n.all? { |x| x > 0 }}"\nputs "any? #{n.any? { |x| x > 4 }}"\nputs "none? #{n.none? { |x| x > 9 }}"\nputs "one? #{n.one? { |x| x == 4 }}"\nputs "count #{n.count { |x| x == 1 }}"\nputs "find #{n.find { |x| x > 3 }} #{n.find { |x| x > 9 }.inspect}"\nputs "index #{n.index { |x| x == 4 }} #{n.index { |x| x == 9 }.inspect}"\nputs "first?/first #{n.first?} #{n.first}"\nputs "map #{n.map { |x| x * 2 }.join(",")}"\nputs "flat_map #{n.flat_map { |x| [x, x] }.join(",")}"\nputs "map_with_index #{n.map_with_index { |x, i| x * i }.join(",")}"\nputs "select #{n.select { |x| x > 2 }.join(",")}"\nputs "reject #{n.reject { |x| x > 2 }.join(",")}"\nputs "partition #{n.partition { |x| x > 2 }.map(&.join("+")).join(" | ")}"\nputs "take/skip #{n.take(2).join(",")} #{n.skip(3).join(",")}"\nputs "take_while #{n.take_while { |x| x < 4 }.join(",")}"\nputs "skip_while #{n.skip_while { |x| x < 4 }.join(",")}"\nputs "reduce #{n.reduce(0) { |a, x| a + x }}"\nputs "each_with_index #{n.map_with_index { |x, i| i }.join(",")}"\nputs "each_with_object #{n.each_with_object([] of Int32) { |e, o| o << e * 10 }.join(",")}"\nputs "join #{n.join("-")}"\nputs "includes? #{n.includes?(4)} #{n.includes?(9)}"\nputs "max?/max #{n.max?} #{n.max}"\nputs "min?/min #{n.min?} #{n.min}"\nputs "sum/product #{n.sum} #{n.product}"\nputs "sum of nothing #{Nums.new([] of Int32).sum}"\nputs "tally #{n.tally[1]}"\nputs "sorted #{n.sorted.join(",")}"\nputs "max_by #{w.max_by { |s| s.size }}"\nputs "group_by #{w.group_by { |s| s.size }[4].join(",")}"\nputs "index_by #{w.index_by { |s| s.size }[3]}"\n\nputs "each_slice #{n.each_slice(2).map(&.join("+")).join(" ")}"\nputs "each_cons #{n.each_cons(2).map(&.join("+")).join(" ")}"\nputs "chunk_while #{n.chunk_while { |a, b| b >= a }.map(&.join("+")).join(" ")}"\nputs "first(n) #{n.first(3).join(",")}"\nputs "compact_map #{n.compact_map { |x| x > 2 ? x * 10 : nil }.join(",")}"\nputs "find_value #{n.find_value { |x| x > 3 ? "big" : nil }}"\nputs "accumulate #{n.accumulate(0) { |a, x| a + x }.join(",")}"\nputs "reduce? #{n.reduce? { |a, x| a + x }}"\nputs "in_groups_of #{n.in_groups_of(2, 0).map(&.join("+")).join(" ")}"\nputs "min_by #{w.min_by { |s| s.size }}"\nputs "max_of/min_of #{w.max_of { |s| s.size }} #{w.min_of { |s| s.size }}"\nputs "minmax #{n.minmax[0]},#{n.minmax[1]}"\nputs "tally_by #{w.tally_by { |s| s.size }[4]}"\nputs "sum_by #{w.sum_by { |s| s.size }}"\n\n# SPEC II.6 finding 6: one bound replaces `Indexable | Iterable | Iterator`,\n# and `O::Elem` names what the other collection yields. Four numbers for\n# four words: a shorter other collection is refused, as the other library\n# refuses it.\nputs "zip #{Nums.new([3, 1, 4, 1]).zip(w).map { |pair| "#{pair[0]}#{pair[1]}" }.join(" ")}"\n\ncycled = [] of Int32\nNums.new([1, 2]).cycle(2) { |x| cycled << x }\nputs "cycle #{cycled.join(",")}"\n\n# A trait with an associated type used as a type: one function, any implementer.\ndef describe(e : Enumerable) : String\n "#{e.size} items"\nend\n\nputs "as a type #{describe(n)} / #{describe(w)}"\n\n# ---------------------------------------------------------------------------\n# What this sample would report as an error\n# ---------------------------------------------------------------------------\n#\n# * `Words.new([...]).sum` — `sum` is bounded `where Elem : Num` and\n# `String` implements `Comparable` but not `Num`. The error names the element\n# type and the bound rather than failing somewhere inside the method body.\n#\n# * A second `impl Enumerable for Nums` — a trait with an associated type is\n# implementable once per type, because a second answer for `Elem` would make\n# every call on `Nums` ambiguous.\n#\n# * `impl Num for X` with no `impl Comparable for X` before it —\n# `Num : Comparable` requires it, and the impls have to be written in\n# dependency order.\n", "samples/iyi/config.iyi": "# The first program that reads text - a `key = value` file, with the line\n# number in its error.\n#\n# ./bin/iyi run samples/iyi/config.iyi\n#\n# Text comes apart into lines, a line into its two halves around a `=`, and\n# each half loses its whitespace; a line that does not parse is an error\n# value carrying where it was (SPEC.md III.1), not a printed message. Like\n# `basics` and `inventory`, this documents no rule: it is here because the\n# prelude grows only when a program in this repository needs something\n# (SPEC.md 0.1.0 item 3), and cutting text is what the third program does.\nmodule samples/config\n\nstruct ParseError\n property line : Int32\n property message : String\n\n def initialize(@line : Int32, @message : String)\n end\nend\n\nimpl Error for ParseError\n def message : String\n "line #{@line}: #{@message}"\n end\nend\n\ndef parse(text : String) : Hash(String, String) | ParseError\n config = {} of String => String\n text.lines.each_with_index do |raw, index|\n line = raw.strip\n next if line.empty? || line.starts_with?('#')\n eq = line.index('=')\n return ParseError.new(index + 1, "no '=' in #{line.inspect}") unless eq\n key = line[0, eq].strip\n return ParseError.new(index + 1, "empty key") if key.empty?\n config[key] = line[eq + 1, line.size - eq - 1].strip\n end\n config\nend\n\ngood = "# the service\\nname = iyi\\nport= 8080\\n\\nmode =fast\\n"\ncase parse(good)\nin Hash(String, String)\n puts it # => {"name" => "iyi", "port" => "8080", "mode" => "fast"}\n puts it["port"].to_i + 1 # => 8081\n puts it.keys.join(", ") # => name, port, mode\nin ParseError then puts "error: #{it.message}"\nend\n\nbad = "name = iyi\\nbroken line\\n"\ncase parse(bad)\nin Hash(String, String) then puts "parsed"\nin ParseError then puts "error: #{it.message}" # => error: line 2: no '=' in "broken line"\nend\n\n# The pieces a parser cuts, on their own.\nputs "a,,b".split(',') # => ["a", "", "b"]\nputs "one\\ntwo\\n".lines # => ["one", "two"]\nputs " padded\\t".strip.inspect # => "padded"\nputs "config.iyi".ends_with?(".iyi") # => true\n", "samples/iyi/cores.iyi": "# Work on every core - SPEC.md III.4.13, as a person writes it.\n#\n# ./bin/iyi run samples/iyi/cores.iyi\n#\n# `spawn` is a task on the thread that spawned it, and its block may touch\n# anything; `spawn_parallel` is a task a pool of workers runs on any core,\n# and its block may capture only what is `Share` (III.4.4). std/http's\n# `Server.serve` puts each connection on that pool, so its block is checked\n# the same way. When a block reaches module state through a call, the\n# compiler warns, and `@[SafeReach("why")]` on the state says why it is safe.\nmodule samples/cores\n\nimport std/http::{HTTP, Response, Server}\nimport std/socket::{IyiSocket}\nimport std/mutex::{Mutex}\n\ndef collatz_steps(n : Int64) : Int64\n steps = 0_i64\n while n != 1\n n = n % 2 == 0 ? n // 2 : 3 * n + 1\n steps += 1\n end\n steps\nend\n\n# 1. `spawn` and `spawn_parallel`.\n#\n# The tasks `spawn` starts share their spawner's thread, so they may append\n# to an `Array` they captured. The tasks `spawn_parallel` starts run on\n# whichever core is free; each answers its own value, and a task's answer\n# need not be `Share`, because it is handed over once, after the task ends.\nseen = [] of Int64\ngroup do |g|\n (1..3).each do |n|\n g.spawn do\n seen << collatz_steps(n.to_i64)\n 0\n end\n end\nend\nputs seen.sorted # => [0, 1, 7]\n\nlongest = 0_i64\ngroup do |g|\n handles = [] of IyiTask(Int64)\n 4.times do |part|\n handles << g.spawn_parallel do\n best = 0_i64\n start = part.to_i64 * 25_000 + 1\n (start..start + 24_999).each { |n| best = collatz_steps(n) if collatz_steps(n) > best }\n best\n end\n end\n handles.each do |handle|\n steps = handle.value\n longest = steps if steps.is_a?(Int64) && steps > longest\n end\nend\nputs longest # => 350, the longest chain below 100,000\n\n# 2. State the pool may share.\n#\n# A counter that every core adds to is an `Atomic`; an `Array` that every\n# core appends to sits behind a `Mutex`. Both are `Share`, so a parallel\n# block may capture them.\nhits = Atomic(Int64).new(0_i64)\npaths = Mutex.new { [] of String }\n\n# 3. Module state a call reaches.\n#\n# A parallel block that names `GREETING` is refused, because an `Array` is\n# not `Share`. One that only calls `greeting` used to compile with nothing\n# said; now the compiler warns at the block, naming the path. This constant\n# is set before the server starts and only read afterwards, so it says so.\n@[SafeReach("set once before the server starts, then only read")]\nGREETING = ["hello", "from", "every", "core"]\n\ndef greeting : String\n GREETING.join(" ")\nend\n\n# 4. A server on every core.\n#\n# `Server.serve` runs each connection on the pool's workers, so its block is\n# checked as `spawn_parallel`'s is. It stops through a channel: closing the\n# listener from inside the block would capture it, and `/stop` may arrive on\n# any worker. `workers: 1` keeps the one-thread server, whose block may\n# capture anything.\nlistener = IyiSocket.listen("127.0.0.1", 0)\nbase = "http://127.0.0.1:#{listener.local_port}"\nstop = Channel(Int32).new(1)\ngroup do |g|\n g.spawn do\n stop.receive\n listener.close\n 0\n end\n g.spawn do\n Server.serve(listener) do |req|\n hits.add(1_i64)\n path = req.path\n paths.lock { |list| list << path }\n if path == "/stop"\n stop.send(0)\n Response.new(200, "bye")\n else\n Response.new(200, greeting)\n end\n end\n 0\n end\n g.spawn do\n puts HTTP.get(base + "/").or_panic.body # => hello from every core\n HTTP.get(base + "/stop").or_panic\n 0\n end\nend\nputs hits.get # => 2\npaths.lock { |list| puts list.sorted } # => ["/", "/stop"]\n", diff --git a/records/stdlib.json b/records/stdlib.json index 41d10b5..74348df 100644 --- a/records/stdlib.json +++ b/records/stdlib.json @@ -1,10 +1,10 @@ { "recorded": { - "compiler": "iyi 0.20.0 (built on Crystal 1.22.0-dev [224ed74fc] (2026-11-02))", - "commit": "224ed74fc7ba9d78345dfd10f21ece4b499702fa", + "compiler": "iyi 0.20.1 (built on Crystal 1.22.0-dev [91d012b1e] (2026-11-04))", + "commit": "91d012b1ed978c7df5cc033624ec161a2daf01b6", "machine": "Linux 7.0.0-38-generic x64, QEMU Virtual CPU version 2.5+", "command": "npm run record:stdlib", - "when": "2026-11-02T07:19:39.580Z" + "when": "2026-11-04T04:32:59.606Z" }, "modules": [ { @@ -274,8 +274,8 @@ { "name": "std/http", "path": "src/std/http.iyi", - "sourceSha256": "c834c001d26e1a45a8e7163e78255438004bb09caad82542270e011ef7dc82c3", - "surface": "module std/http\n\n# A body as it arrives: from a connection, framed by its length, by chunks\n# or by the connection's close, or from a string already in hand. `read`\n# answers the next piece, \"\" once the body is done; what the other end\n# does wrong is an `HTTPError`, and a connection that fails or is\n# cancelled answers that. The first failure is kept: every read after it\n# answers the same. A body coded `gzip`, `deflate` or `zstd` is decoded\n# as it is read when the client asked for that.\n#\n# A body has a limit: past it a read answers an `HTTPError` with status\n# 413. The server's is `Server::MAX_BODY`; a client's is none.\npub class BodyReader\n # The body's coding undone from here on: `Envelope::Gzip` for gzip,\n # `Envelope::Zlib` for HTTP's deflate - which some servers send as the\n # bare stream instead, without zlib's two bytes of header, so the first\n # two bytes are looked at first, and a pair that is not a zlib header\n # (RFC 1950 §2.2: method 8, a multiple of 31) is read as the bare\n # stream. A body that does not decode is an `HTTPError` naming the\n # coding and why.\n def decode(envelope : Envelope) : Nil\n # The body's `zstd` coding (RFC 8878) undone from here on, by std/zstd's\n # `ZstdReader` as the pieces arrive, frames one after another. A frame\n # that asks for a window past *max_window* bytes is refused rather than\n # given the memory (a window outside 1 KiB to 512 MiB is a panic), and a\n # body that does not decode is an `HTTPError` naming the coding and why.\n def decode_zstd(max_window : Int32 = Zstd::WINDOW_LIMIT) : Nil\n # Reads past the rest of the body, *limit* bytes at most: whether it\n # ended within them. A server reads past what a block left unread, so\n # the next request on the connection starts where it should.\n def drain(limit : Int64) : Bool\n # Whether every byte of the body has been read, the framing's end too.\n def finished? : Bool\n # A body that arrives as an HTTP/2 stream's DATA: refused past *limit*.\n protected def self.frames(h2 : H2Connection, id : Int32, limit : Int64) : BodyReader\n def initialize(conn : Conn | ::Nil, kind : Int32, length : Int64, limit : Int64, text : String = \"\", hello : Proc(Bool) | ::Nil = nil)\n protected def on_frames(h2 : H2Connection, id : Int32) : Nil\n # The body's bytes as the framing delivers them, before any decoding.\n protected def raw(max : Int32) : String | Cancelled | SocketError | HTTPError\n # The next piece of the body, at most *max* bytes; \"\" when it is done.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the body as one string, refused past *limit* bytes (413).\n # The pieces are kept as they arrive and joined once: a builder of the\n # length a head declared was made before any of it had come, and a head\n # that declared 64 MB and sent two bytes cost the server 64 MB.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\n protected def remember(fault : HTTPError | Cancelled | SocketError) : HTTPError | Cancelled | SocketError\n # A body that is a string already in hand.\n def self.text(text : String) : BodyReader\n # Whether the body ends where the connection does: nothing can follow\n # it on the connection.\n def to_close? : Bool\nend\n\n# What a streamed body is written to: an answer's `Response.stream`\n# block's, and a client's upload from an `IyiIO`. Each `write` goes out as\n# one chunk (RFC 9112 §7.1), or as it is where the connection's close ends\n# the body, so a block that writes a line a second is read a line a\n# second. Under a server's `compress`, each write is a gzip piece flushed\n# whole (std/compress's `Deflater`), which decodes as it arrives too. The\n# first failure is kept, and every write after it answers it; `finish`,\n# which the server calls when the block returns, writes the end.\npub class BodyWriter\n # The end of the body: the gzip trailer, and the last chunk with the\n # blank line after it. Once; the server's call after a block that\n # finished its own body writes nothing.\n def finish : Nil | Cancelled | SocketError\n def initialize(socket : IyiSocket | TlsStream | H2Out, chunked : Bool, deflater : Deflater | ::Nil = nil)\n def write(text : String) : Nil | Cancelled | SocketError\n # Bytes of the body written so far, before any framing or coding.\n def written : Int64\nend\n\n# What `HTTP.request`'s `jar:` keeps cookies in: the `Cookie` header a\n# request to a URL carries, and an answer's `Set-Cookie` lines taken in.\n# std/cookie's `CookieJar` is one; std/http itself reads no clock, so the\n# expiry of a cookie is the store's to keep.\npub trait CookieStore\n abstract def header_for(url : String) : String | ::Nil\n abstract def update(url : String, response : Response) : Nil\nend\n\npub struct HTTP\n # For a request target in absolute form (RFC 9112 §3.2.2), where its\n # authority starts: after `http://` or `https://`, in any case.\n def self.absolute_form(target : String) : Int32 | ::Nil\n # Whether a list of codings a client accepts (`Accept-Encoding`, RFC\n # 9110 §12.5.3) takes *coding*: named, or `*`, with a `q` above 0.\n def self.accepts?(list : String, coding : String) : Bool\n # A 1xx, 204 or 304: an answer with no content (RFC 9110 §15).\n def self.bodiless?(status : Int32) : Bool\n # Where a chunked body that starts at *from* ends in `text` — the byte\n # after the last chunk's terminating CRLF (trailers included) — nil when\n # it has not all arrived, or why it is not a chunked body. What\n # `decode_chunked` will then read whole.\n def self.chunked_end(text : String, from : Int32 = 0) : Int32 | String | Nil\n # How much a list of codings a client accepts wants *coding*, as its\n # `q` in thousandths: the weight it is named with, else `*`'s, else 0 -\n # and 0 refuses it.\n def self.coding_weight(list : String, coding : String) : Int32\n # A `Content-Length` as a number, or nil when it is not one. Repeated\n # lines arrive joined, and are one length only when every one says the\n # same (RFC 9112 §6.3). A length past 2147483647 is still a length, too\n # large for either side, and is read as one past it: `to_i?` answered\n # nil for it, and 3000000000 was refused as \"not a number\".\n def self.content_length(value : String) : Int64 | ::Nil\n # A control character other than a tab, or DEL: what a field value may\n # not hold (RFC 9110 §5.5), nor a request target or a reason phrase.\n def self.control?(text : String) : Bool\n # The pairs of a request's `Cookie` header, `a=1; b=2` (RFC 6265 §4.2),\n # a value's quotes taken off; the first of a name is kept, as a server\n # reads the most specific cookie first.\n def self.cookie_pairs(value : String) : Hash(String, String)\n # A chunked body (RFC 9112 §7.1) as one string: each chunk's size line\n # is hexadecimal, extensions after `;` are ignored, and a zero-length\n # chunk ends it. What follows the last chunk is trailers, which nothing\n # here reads.\n def self.decode_chunked(text : String) : String\n def self.delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def self.find_headers_end(text : String, from : Int32 = 0) : Int32 | ::Nil\n # The request as it goes on the wire, whole. `Host`, `Connection` and\n # `Content-Length` are the request's own lines; a caller's header of\n # that name is refused rather than sent twice. A body's length is\n # always written for the methods a body belongs to, so `POST` with\n # nothing to say still says `Content-Length: 0`. A byte of the path past\n # 127 goes as `%` and two hex digits: a request target is ASCII (RFC\n # 9112 §3.2, RFC 3986 §2.1), and `/ü` went out as its UTF-8 bytes, raw.\n def self.format_request(method : String, path : String, host : String, body : String = \"\", headers : Hash(String, String) = {} of String => String) : String\n def self.get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # *text* in gzip, whole: an answer's body under a server's `compress`.\n def self.gzip(text : String) : String\n # A writer whose body goes out in gzip as it is written.\n def self.gzip_writer(socket : IyiSocket | TlsStream | H2Out, chunked : Bool) : BodyWriter\n def self.has_break?(text : String) : Bool\n # Whether a list of tokens (RFC 9110 §5.6.1), its repeated lines joined\n # with \", \", holds *token*, in any case.\n def self.has_token?(list : String, token : String) : Bool\n def self.head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The value of a chunk size's hex digits, nil for anything else or for\n # one past what an Int32 holds.\n def self.hex(text : String) : Int32 | ::Nil\n # *n* in lowercase hexadecimal: a chunk's size line.\n def self.hex_text(n : Int64) : String\n # A Host value - the `a` of `Host: a` - or an absolute-form target's\n # authority, made of what a host and its port are spelled with (RFC 3986\n # §3.2.2, §3.2.3): a name, an IPv4 address or an IPv6 one in brackets,\n # and digits after a colon. Empty is one too, for a target with no\n # authority (RFC 9112 §3.2). A blank, a `/`, an `@` - the bytes another\n # host or a path begins with - are not.\n def self.host?(text : String) : Bool\n # The endpoints *answer*'s ServiceMode records name for a client whose\n # configuration offers *alpn* by ALPN (none: it speaks http/1.1 alone),\n # to an origin on *port*, in the order to try them: a record whose\n # mandatory keys are not all ones std/dns reads, or whose protocols -\n # alpn, and http/1.1 unless no-default-alpn - hold none of the client's,\n # names none (RFC 9460 sections 7.1.2 and 8), and neither does one whose\n # target is not a host name. Each is the record's target, or its owner\n # for \".\"; its port, or *port*; its `ech` where the ECHConfigList holds\n # a config std/tls can use (RFC 9849 section 6.1), nil otherwise; and\n # *alpn* with the protocols the record's alpn lists first, in *alpn*'s\n # order otherwise.\n def self.https_endpoints(answer : SvcbAnswer, alpn : Array(String), port : Int32) : Array(HttpsEndpoint)\n def initialize\n # One request, and the block's answer to the response, its body still\n # arriving: `response.body_io.read` takes it a piece at a time, and\n # `response.body` all of it. The connection closes when the block\n # returns. A body of *body* is a string, sent with its length, or an\n # `IyiIO`, sent chunked as it is read - which a redirect that keeps the\n # method (307, 308) cannot send again, so that answer is handed to the\n # block as it is.\n #\n # The redirects followed are RFC 9110 §15.4's: 301 and 302 make a POST a\n # GET, 303 makes anything but a HEAD a GET, and 307 and 308 keep the\n # method and the body. `Authorization`, `Proxy-Authorization` and\n # `Cookie` written by the caller are not sent to another scheme, host or\n # port - `https://` to `http://` at one host included, as curl does (Go\n # strips them for another host) - and a jar's `Secure` cookies go over\n # `https://` alone. The downgrade itself is followed, as Go's client and\n # `curl -L` follow it; any other header the caller wrote, and a 307 or\n # 308's body, go on in the clear. One more than *redirects* is an\n # `HTTPError`; with 0 the first answer is the block's.\n #\n # *timeout_ms* bounds the connect, and each read and write after it -\n # a pause, not the whole exchange, as Python's `timeout` is: a body that\n # keeps arriving is read for as long as it takes. One that runs out is a\n # `SocketError` whose `timed_out?` is true. 0, the default, waits as\n # long as the server does, as Go's and Python's clients do; a client\n # that talks to servers it does not control should give one.\n #\n # *proxy* is a proxy's URL - `http://user:password@host:port`, or\n # `host:port` alone - and every request goes through it, every redirect\n # too: `http://` as an absolute-form request to the proxy (RFC 9112\n # §3.2.2), which carries `Proxy-Authorization: Basic` when the URL has\n # a user; `https://` through a tunnel the proxy opens with `CONNECT\n # host:port` (RFC 9110 §9.3.6), the credential on the CONNECT alone and\n # TLS to the server inside it, its certificate checked as without a\n # proxy. A caller's own `Proxy-Authorization` is the proxy's too: sent\n # on the CONNECT when the URL has no user, and never inside the tunnel.\n # A CONNECT answered with anything but a 2xx - 407 when the proxy wants\n # credentials, 403, 502 - is an `HTTPError` naming the proxy and its\n # answer. `http://` through a proxy is HTTP/1.1 whatever\n # *prior_knowledge* says. Which proxy, if any, a URL's environment\n # names is `proxy_for`'s, and std/http_proxy's `HTTPProxy.from_env`.\n def self.open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, client : HTTPClient | ::Nil = nil, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n # The field lines after the first, or why they are not fields. A name\n # repeated, in any case, is one field, its values joined in order with\n # \", \" (RFC 9110 §5.3): into a Hash by name, a second line spelled the\n # same replaced the first and one spelled otherwise was an entry\n # `header` never reached, so two different `Content-Length`s were read\n # as whichever came first or last. A value with a control character in\n # it - a CR or LF that ends no line, a NUL - is refused (§5.5) rather than\n # handed to whoever reads it or echoes it into a header.\n #\n # *unfold*, for an answer: a line that begins with a blank continues the\n # field above it (obs-fold, RFC 9112 §5.2), and a user agent joins it on\n # with one space; read as a field of its own, `HTTP.get` panicked with\n # \"header without a colon: \\\" b\\\"\". A server may refuse one, and this\n # one does.\n #\n # *apart*, when given, is handed each repeated field's values by the\n # name in the answer, a line each, before they are joined.\n def self.parse_fields(lines : Array(String), unfold : Bool = false, apart : Hash(String, Array(String)) | ::Nil = nil) : Hash(String, String) | String\n # The response as it came off the wire, whole. `head` says the request\n # was a `HEAD`, whose answer carries the headers of a body it does not\n # send. An interim answer - `100 Continue`, `103 Early Hints` - is read\n # past to the final one after it (RFC 9110 §15.2). A `101` switches\n # protocols, and HTTP ends with it. What is not a response panics with\n # why: this is the text a program has in hand; the client answers the\n # same sentences as `HTTPError`s.\n def self.parse_response(text : String, head : Bool = false) : Response\n # A response's head - the text before its blank line - read: status,\n # reason, the fields, and the lines of those on more than one. A line\n # that begins with a blank continues the field above it (obs-fold, RFC\n # 9112 §5.2), which a user agent joins on with one space.\n def self.parse_response_head(text : String) : ::Tuple(Int32, String, Hash(String, String), Hash(String, Array(String)) | ::Nil) | HTTPError\n # Where *text* goes on at *from* past the empty lines there: what a\n # server skips before a request line (RFC 9112 §2.2).\n def self.past_empty_lines(text : String, from : Int32) : Int32\n def self.post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The proxy a request for *url* goes through, given the values of\n # `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` - nil when it goes\n # direct. Go's `golang.org/x/net/http/httpproxy` is the rule, line for\n # line, as its documentation and tests state it:\n #\n # * `https://` takes *https_proxy*, `http://` *http_proxy*, any other\n # scheme none. A proxy written without a scheme is `http://`'s, and is\n # answered with it, ready for `open`'s *proxy*.\n # * `localhost` (in lower case: Go's comparison) and a loopback address\n # (127.0.0.0/8, ::1, the first as IPv4-mapped too) never go through a\n # proxy.\n # * *no_proxy* is a comma-separated list, each entry trimmed and read\n # without regard to case: `*` alone sends everything direct; an IP\n # address or a CIDR range (`10.0.0.0/8`, `2001:db8::/32`) matches the\n # addresses in it, IPv4 and its IPv4-mapped form alike, and a CIDR\n # range of one family never the other's; a domain matches itself and\n # every name under it (`foo.com`: `foo.com`, `bar.foo.com`, not\n # `barfoo.com`); with a leading dot, or `*.`, only the names under it\n # (`.y.com`: `x.y.com`, not `y.com`). An address or domain may carry\n # a port (`1.2.3.4:80`, `[::1]:443`, `foo.com:8080`), and matches\n # then on that port alone, the URL's own or its scheme's default.\n # An entry that is none of these (`:80`) is passed over.\n # * A host is compared as IDNA's ASCII form, as Go compares it.\n #\n # Pure: the values are the caller's, so std/http reaches no environment;\n # std/http_proxy reads them from it.\n def self.proxy_for(url : String, http_proxy : String | ::Nil, https_proxy : String | ::Nil, no_proxy : String | ::Nil = nil) : String | ::Nil\n def self.put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The reason phrase a status carries (RFC 9110 §15), for a response the\n # server writes; a status this table does not know gets its class's.\n def self.reason_for(status : Int32) : String\n # One request and its whole answer: the answer as `open` hands it, its\n # body read. Redirects are followed, *redirects* at most; *jar* keeps\n # the cookies the answers set and sends them back; *decompress* asks\n # for gzip, deflate and zstd and undoes them. A refused or reset\n # connection answers `SocketError`; a task cancelled answers\n # `Cancelled`; what the server does wrong answers `HTTPError`. `!`\n # passes any of them up, `.or_panic` refuses it.\n def self.request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Response | Cancelled | SocketError | HTTPError\n # A request's head: the request line, `Host`, `Connection` (*connection*:\n # `close`, or `Upgrade`), the caller's headers, and the body's framing -\n # *length*, chunks, or none.\n def self.request_head(method : String, path : String, host : String, headers : Hash(String, String), length : Int64 | ::Nil, chunked : Bool, connection : String) : String\n # Two field names for the same field: ASCII case is the only difference\n # a name may carry.\n def self.same_name?(a : String, b : String) : Bool\n def self.split_crlf(text : String) : Array(String)\n # A status line read: the status and its reason, or why it is not one.\n def self.status_line(line : String) : ::Tuple(Int32, String) | HTTPError\n # What an `https://` request is checked against and offers: the\n # system's roots, and ALPN's `http/1.1`, unless a program sets its own -\n # roots of its own for a server it runs, say.\n def self.tls_config : TlsConfig\n def self.tls_config=(config : TlsConfig) : TlsConfig\n # A token (RFC 9110 §5.6.2): what a method or a field name is made of.\n def self.token?(text : String) : Bool\n # Without the blanks at either end (RFC 9112 §5.1: field values are\n # trimmed of the whitespace around them).\n def self.trim(s : String) : String\n # The 101 an upgrade to *protocol* is answered with, and the connection,\n # which is the caller's from then on: std/websocket's handshake. Any\n # other answer is an `HTTPError` naming it.\n #\n # *proxy* is `open`'s, and so is its credential, on the CONNECT alone;\n # but an upgrade goes through a tunnel for `http://` as for `https://`,\n # the handshake inside it in origin form. `Upgrade` belongs to the\n # connection it is sent on (RFC 9110 §7.8), and a proxy forwarding a\n # request removes `Connection` and every field it names (§7.6.1), so a\n # handshake handed to a forward proxy in absolute form reaches the\n # server, at best, as a plain GET: the gate's proxy, which drops\n # `Connection`, leaves std/websocket's server a request it answers 400.\n def self.upgrade(url : String, protocol : String, headers : Hash(String, String) = {} of String => String, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Upgraded | Cancelled | SocketError | HTTPError\nend\n\n# Requests that keep their connections: an `HTTPClient` holds the connections\n# its answers left open, by host and port, and the next request to that\n# server goes over one of them instead of a new one - the TCP handshake,\n# and the slow start after it, paid once rather than per request. Its\n# methods are `HTTP`'s, redirects, cookies and decoding included:\n#\n# ```\n# client = HTTPClient.new\n# client.get(\"http://127.0.0.1:8080/a\")\n# client.get(\"http://127.0.0.1:8080/b\") # the same connection\n# client.close\n# ```\n#\n# A connection goes back once its answer has been read to the end (inside\n# `open`, up to 64 KiB left unread is read past for it) and the answer\n# allows it; at most *idle* wait per server, and the rest close. A server\n# may close an idle connection whenever it likes (RFC 9112 §9.3.1), so a\n# request whose kept connection turns out closed - the write fails, or it\n# ends before a byte of an answer - is sent again once on a new one, when\n# its method is idempotent (RFC 9110 §9.2.2) and its body a string;\n# anything else answers the failure. One task at a time per request: two\n# tasks sending at once take two connections, and a client is not for\n# sharing across threads. An `https://` connection is kept under the\n# `HTTP.tls_config` it was made with, and handed only to a request made\n# under the same one (`TlsMadeUnder`): after `HTTP.tls_config=` the next\n# request makes a connection of its own, checked against the new roots.\n# A client made with *tls* uses that configuration instead of\n# `HTTP.tls_config`, and one made with *resolver* finds its hosts by it\n# instead of `Resolver.system` - an encrypted resolver, or a hosts text\n# that pins a name to an address: std/secure_dns's `DnsOverHttps` is a\n# client of its own with both, so its server is checked against the roots\n# it was given and reached at the address it was given.\n#\n# A client resumes TLS sessions (RFC 8446 §2.2): the tickets an\n# `https://` server sends are kept for its origin - scheme, host and\n# port, whichever proxy's tunnel they came through - under the TLS\n# configuration they came under, the client's own *tls* or else\n# `HTTP.tls_config` (`TlsMadeUnder`, as a kept connection is), and a new\n# connection to that origin offers the newest, each once.\n# A resumed handshake checks no certificate - the ticket stands for the\n# check the first one made - so a ticket is never offered to another\n# origin, nor under another configuration: after `HTTP.tls_config=`, or\n# roots, an identity, `now`, `min_version` or `require_staple` assigned\n# in place - in `HTTP.tls_config` or in the client's own *tls* - the next\n# connection is a full handshake under the new ones, and that origin's\n# tickets from under the old one are dropped. (std/tls's `TlsSession`\n# holds each ticket to its configuration too; this client drops them\n# where that one would only pass them over.) At most *origin_tickets* are\n# kept an origin - 4 by default, so that as many connections as *idle*\n# keeps by default can resume at once, where std/tls's server and\n# OpenSSL's send 2 a handshake - and *tickets* in all, 256 by default: as\n# many origins as that leaves room for at *origin_tickets* each, the one\n# longest unused dropped first. 0 for either keeps none, and nothing\n# resumes. `close`\n# leaves them. Nothing goes early (0-RTT), whatever a ticket allows: the\n# module's header says why. `connections_resumed`, `tickets_held` and\n# `Response#tls_resumed?` say what was kept and used. What it saves, by\n# bench/std_http_resume.iyi built --release on a 12-core machine other\n# jobs shared (medians of 200, the middle of three runs): the handshake\n# took 0.96 ms full and 0.51 resumed against std/http's server, 1.03 and\n# 0.57 against OpenSSL 3.5's s_server - and a GET on a new connection,\n# which waits about 40 ms at each iyi end that is not the handshake's,\n# 82.98 ms and 82.00, and 42.04 and 40.96.\n#\n# Over HTTP/2 - `https://` where ALPN chose `h2`, or `http://` with\n# *prior_knowledge* (h2c, RFC 9113 §3.3) - a kept connection carries the\n# next request on a new stream, until the server says GOAWAY.\n#\n# HTTPS records (RFC 9460), when `use_https_records` is set: before a new\n# connection to an `https://` origin - not through a proxy, and not for\n# an upgrade - the client asks its resolver for the origin's HTTPS\n# records (`Resolver#https_records`, AliasMode followed) and connects by\n# the first ServiceMode record it can use: one whose mandatory keys it\n# knows - the seven std/dns reads, port and no-default-alpn mandatory by\n# being there (section 9) - and whose protocols (alpn, and http/1.1\n# unless no-default-alpn) hold one of the configuration's `alpn`, or\n# http/1.1 where that is empty. Its target - the record's owner for \".\"\n# - and its port, or the origin's, are where the connection goes; its\n# `ech` is the connection's `ech_configs` (RFC 9848); and the protocols\n# it lists go first in the ClientHello's ALPN, which still offers every\n# one the configuration does (section 7.1.2: a record says which\n# protocols the endpoint has, and cannot take one away). The certificate\n# is checked, and the server named inside, for the origin's host,\n# whatever the target (section 2.3). An `ech` whose ECHConfigList holds\n# no config std/tls can use is passed over, as RFC 9849 section 6.1 has\n# it. The address hints are not read: the target's addresses are asked\n# for as any host's are.\n#\n# An endpoint that does not connect gives way to the next, then to the\n# last AliasMode target and to the origin itself (section 3) - unless\n# every endpoint carries `ech`, when the request fails rather than name\n# the server in the clear (RFC 9848 section 5.1). A TLS refusal is the\n# request's failure, but for one: a server that turned the encrypted\n# ClientHello down, its certificate checked for the config's\n# public_name, is asked once more on a new connection to the same\n# address, under the retry_configs it sent, or without ECH where it sent\n# none, which turns ECH off there (RFC 9849 section 6.1.6); a refusal of\n# that is the failure. No records (NXDOMAIN, NODATA), a set section 2.2\n# refuses, an AliasMode \".\" (the service says it is not there), or no\n# record the client can use: the connection is made as without records.\n# So is one whose lookup failed through a resolver that asks in the\n# clear, as section 3.1 lets a client choose; through encrypted\n# upstreams the failure is the request's, as section 3.1 asks, for\n# there the one who can drop the answer is an attacker taking the\n# records away. `localhost` and addresses are not asked about, and an\n# `http://` URL is not made `https://` by its records (section 9.5's\n# SHOULD): the program wrote the scheme.\n#\n# A record's `ech` and protocols make a configuration of their own - a\n# copy of the client's, made once and kept for the origin while the same\n# ones come under the same configuration - so that a kept connection and\n# a ticket are handed on only under the record they were made by\n# (`TlsMadeUnder` holds `ech_configs`). Off by default: every new\n# connection then waits on one DNS answer or more before its ClientHello\n# (RFC 9848 section 5.3 forbids sending it sooner), with no cache to\n# spare the next, and the records may move a connection to another host\n# and port, which is the program's to ask for.\npub class HTTPClient\n # Every idle connection closed. The client may be used after; it opens\n # new ones.\n def close : Nil\n # Connections this client has opened, and requests sent over one it\n # kept: what a program reads to see that its connections are kept.\n def connections_made : Int32\n # Connections this client opened whose TLS handshake resumed a session\n # by a ticket it kept.\n def connections_resumed : Int32\n def connections_reused : Int32\n def delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n protected def give_back(key : String, conn : Conn) : Nil\n def head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The configuration a connection to *origin* is made under by a record\n # whose `ech` is *ech* (nil: none) and whose protocols put *alpn* in its\n # order, *base* being the client's own or `HTTP.tls_config`: *base*\n # itself where those are its own, and otherwise a copy of it with them -\n # the same object every time while *base* is the same (`TlsMadeUnder`),\n # so that a connection kept and a ticket held under it serve the next\n # connection by the same record. It becomes the one the origin's kept\n # connections are taken under (`https_current`). The origin's\n # configurations from under another *base* go.\n protected def https_config(origin : String, base : TlsConfig, ech : Array(UInt8) | ::Nil, alpn : Array(String)) : TlsConfig\n # The configuration *origin*'s last connection by HTTPS records was made\n # under, while *base* is the one it came from - *base* itself where it\n # was made as without records: what a kept connection to the origin\n # must have been made under to be taken. Nil when none was made by them\n # under *base*, and then none is taken.\n protected def https_current(origin : String, base : TlsConfig) : TlsConfig | ::Nil\n # *timeout_ms* and *proxy* are `HTTP.open`'s, for every request the\n # client sends; *tls* and *resolver* are this client's own (above);\n # *origin_tickets* and *tickets* bound the TLS tickets kept, by origin\n # and in all.\n def initialize(idle : Int32 = 4, timeout_ms : Int32 = 0, prior_knowledge : Bool = false, proxy : String | ::Nil = nil, tls : TlsConfig | ::Nil = nil, resolver : Resolver | ::Nil = nil, origin_tickets : Int32 = 4, tickets : Int32 = 256)\n protected def made : Nil\n def open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n def post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # `http://` spoken as HTTP/2 from the first byte, without asking: for a\n # server known to take it.\n def prior_knowledge : Bool\n # The proxy every request goes through, `HTTP.open`'s *proxy*; its\n # connections are kept under it.\n def proxy : String | ::Nil\n def put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true) : Response | Cancelled | SocketError | HTTPError\n # What turns a host name into addresses for this client's connections,\n # instead of `Resolver.system`: encrypted upstreams, or a hosts text\n # alone that pins a name to an address, as std/secure_dns's\n # `DnsOverHttps` pins its server's.\n def resolver : Resolver | ::Nil\n protected def resumed : Nil\n # The session a new connection to *origin* under *tls* offers a ticket\n # from and keeps the server's new ones in; nil when the client keeps\n # none. The origin's tickets from under another configuration are\n # dropped, not left to take room that `TlsSession` would never offer\n # them from. The session handed out is the newest used; past the room\n # *tickets* leaves, the oldest goes.\n protected def session_for(origin : String, tls : TlsConfig) : TlsSession | ::Nil\n # A kept connection to *key*, nil when none waits. An HTTP/2 connection\n # that can take no more requests is closed rather than handed out, and\n # so is an `https://` one made under another TLS configuration than\n # *tls*, the one the request is made under (`TlsMadeUnder`): keyed by\n # scheme, host and port alone, a connection whose server was checked\n # against one set of roots carried requests after `HTTP.tls_config=`\n # named others that would have refused it.\n protected def take(key : String, tls : TlsConfig | ::Nil = nil) : Conn | ::Nil\n # The TLS tickets held for *origin* - `https://host:port`, the host in\n # lower case and the port written even where it is 443 - or for every\n # origin when nil.\n def tickets_held(origin : String | ::Nil = nil) : Int32\n # What this client's `https://` requests are checked against and offer,\n # instead of `HTTP.tls_config`: roots of its own, or ALPN's `h2` first.\n def tls : TlsConfig | ::Nil\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records : Bool\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records=(value : Bool) : Bool\nend\n\n# What the other end did that HTTP does not allow, as a value: `reason` is\n# the sentence, `status` the answer a server gives a request that did it\n# (400, or 413 and 431 for the limits). `message` says it is HTTP's.\npub struct HTTPError\n def initialize(reason : String, status : Int32 = 400)\n def reason : String\n def status : Int32\n def to_s : String\nend\n\n# One endpoint an origin's HTTPS records name, as `HTTP.https_endpoints`\n# reads it: the host and port to connect to, the ECHConfigList to encrypt\n# the ClientHello under (nil: none), and the protocols to offer by ALPN,\n# in order.\npub struct HttpsEndpoint\n def alpn : Array(String)\n def ech : Array(UInt8) | ::Nil\n def host : String\n def initialize(host : String, port : Int32, ech : Array(UInt8) | ::Nil, alpn : Array(String))\n def port : Int32\nend\n\n# A `multipart/form-data` body (RFC 7578) read a part at a time, as it\n# arrives: `next_part` answers the next `Part`, nil after the last, and a\n# part's `read` answers its bytes up to the boundary, \"\" at its end. A part\n# not read to its end is read past by the next `next_part`. The body of a\n# request is `Request#multipart`'s; any `BodyReader` and boundary will do.\n# A part's head is refused past 16 KiB, and a body that ends before its\n# closing boundary is an `HTTPError`.\npub class Multipart\n # The boundary a `Content-Type` of `multipart/...` names, nil when it\n # names none - or one of a length RFC 2046 §5.1.1 does not allow, 1 to\n # 70 characters, which `new` refuses with a panic. The type is the\n # peer's: `Request#multipart` handed a client's 90-character boundary\n # through to that panic (found by bench/std_fuzz_exercise.sh).\n def self.boundary(content_type : String) : String | ::Nil\n # Where *pattern* is in what is held, -1 when it is not there yet.\n protected def find(pattern : String) : Int32\n def initialize(body : BodyReader, boundary : String)\n # The next part, nil after the last.\n def next_part : Part | Nil | Cancelled | SocketError | HTTPError\n # Up to *max* bytes of the part being read, \"\" at its boundary. What is\n # held back is what may yet be the start of the boundary.\n protected def part_read(max : Int32) : String | Cancelled | SocketError | HTTPError\n # One more piece of the body onto what is held: false at its end.\n protected def pull : Bool | Cancelled | SocketError | HTTPError\nend\n\n# A multipart/form-data body to send: fields and files in order. Its\n# boundary is chosen when the body is first asked for - `finish`,\n# `content_type` or `boundary`, whichever comes first, after which no\n# part is added - as `iyi-` and 32 hex digits of the parts' SHA-1, made\n# again from itself in the rare case a part holds it: so it is none of\n# the parts' bytes by construction rather than by chance, the same\n# fields make the same body, and std/http reaches no source of\n# randomness for it. A boundary given to `new` is used as it is, and a\n# part that holds it is refused.\npub class MultipartBuilder\n def boundary : String\n def content_type : String\n def field(name : String, value : String) : self\n def file(name : String, filename : String, data : String, content_type : String = MIME.from_extension(filename)) : self\n # The body, closing boundary and all.\n def finish : String\n # The boundary ends a part where it follows a CRLF, the part's first\n # bytes included, which follow the head's blank line.\n protected def self.holds?(data : String, mark : String) : Bool\n def initialize(boundary : String | ::Nil = nil)\n # A name inside quotes, as browsers write one: a quote and a line break\n # percent-encoded (the WHATWG form encoding), so no field can end the\n # parameter or the line early.\n protected def self.quoted(text : String) : String\nend\n\n# One part of a multipart body: its head's fields, its name and file name\n# from `Content-Disposition`, and its bytes, read up to its boundary.\npub class Part\n def content_type : String | ::Nil\n # The file name a browser sent the part under, `filename=`.\n def filename : String | ::Nil\n def finished? : Bool\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n def initialize(owner : Multipart, headers : Hash(String, String))\n # The form field's name, `name=` of `Content-Disposition`.\n def name : String | ::Nil\n # The part's next bytes, at most *max*; \"\" at its end.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the part as one string, refused past *limit* bytes.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\nend\n\n# What a server's block is handed: one request. Its body is `body`, read\n# whole before the block ran, or under `stream_bodies` read by the block\n# from `body_io` as it arrives; `body` then reads the rest itself, and a\n# body that cannot be read - the client went, or sent one that is not\n# HTTP - ends the connection with that sentence rather than be answered as\n# if it were whole.\npub class Request\n def body : String\n def body_io : BodyReader\n # The pairs of the request's `Cookie` header, the first of a name kept.\n def cookies : Hash(String, String)\n # The fields of a form: the query's, and an\n # `application/x-www-form-urlencoded` body's after them. The body is\n # read for it.\n def form : Params\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n # A request whose body is in hand: what `parse_request` answers.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), body : String, version : String = \"HTTP/1.1\", remote_address : String | ::Nil = nil)\n # A request whose body is still on the connection.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), version : String, reader : BodyReader, remote_address : String | ::Nil = nil)\n def method : String\n # The body as `multipart/form-data` parts, read as they arrive; nil when\n # the `Content-Type` is not multipart or names no boundary\n # (`Multipart.boundary`).\n def multipart : Multipart | ::Nil\n def path : String\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates : Array(Certificate)\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates=(value : Array(Certificate)) : Array(Certificate)\n def query : String | ::Nil\n # The client's address, as the server's socket saw it (no port): nil\n # for a request a program made, or when the client had already gone. A\n # proxy in front is what this names; `X-Forwarded-For` is the proxy's\n # word, and a header any client can write.\n def remote_address : String | ::Nil\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the TLS connection the request came over resumed a session by\n # a ticket, under `serve(tls:)`: its `peer_certificates` are then the\n # ticket's. False in the clear.\n def tls_resumed? : Bool\n def version : String\nend\n\n# A response: what a server's block answers, and what a client is handed.\n#\n# To write one: `Response.new(200, \"hello\")`, a status and a string;\n# `Response.new(200, io)`, a stream the server copies out and closes, with\n# the caller's `Content-Length` when it gives one and chunked otherwise;\n# `Response.stream(200) { |w| w.write(\"...\") }`, whose block writes as it\n# goes; `Response.upgrade(headers) { |socket, leftover| ... }`, a 101 after\n# which the connection is the block's. `add_header` writes a field on a\n# line of its own, as each `Set-Cookie` must be.\n#\n# What a client is handed: the status, reason and headers, and the body -\n# `body` whole, or `body_io` as it arrives inside `HTTP.open`'s block.\npub class Response\n # One more line of the field *name*, written on a line of its own: what\n # two `Set-Cookie`s need, since the \", \" a field's lines are joined with\n # is part of a cookie's date.\n def add_header(name : String, value : String) : Nil\n # The lines of each field written on more than one, by its name in\n # `headers`; nil when every field is on one line.\n def apart : Hash(String, Array(String)) | ::Nil\n # The body: whole. Inside `HTTP.open`'s block it is read here, the first\n # time it is asked for, and a body that cannot be read panics with why;\n # `body_io` answers that as a value.\n def body : String\n # The body as it arrives. For a response a program made, its string.\n def body_io : BodyReader\n # Whether the client undid the body's `gzip`, `deflate` or `zstd`\n # coding; the `Content-Encoding` and `Content-Length` that described it\n # are gone from the headers then.\n def decoded? : Bool\n # A header by name, as the server spelled it or otherwise: a field\n # name is case-insensitive (RFC 9110 §5.1), and `content-length` is as\n # common on the wire as `Content-Length`.\n def header(name : String) : String | ::Nil\n # A field's lines apart, in the order they came: `header` joins them\n # with \", \", and `Set-Cookie` is the field that join breaks (RFC 9110\n # §5.3), since a cookie's `Expires` date has a comma in it - two cookies\n # read as \"a=1; Expires=Wed, 21 Oct 2026 07:28:00 GMT, b=2\", which no\n # split takes back apart. Empty for a field that is not there.\n def header_values(name : String) : Array(String)\n def headers : Hash(String, String)\n # A response a client read: its body comes from *reader*, and *url* is\n # where it came from.\n def initialize(status : Int32, reason : String, headers : Hash(String, String), apart : Hash(String, Array(String)) | ::Nil, reader : BodyReader, url : String, decoded : Bool)\n def initialize(status : Int32, reason : String, headers : Hash(String, String), body : String, apart : Hash(String, Array(String)) | ::Nil = nil)\n # A response to write: the reason is the status's own, the headers the\n # caller's. `Response.new(200, \"hello\")` is the common shape.\n def initialize(status : Int32, body : String = \"\", headers : Hash(String, String) = {} of String => String)\n # A response whose body is *source*, copied out by the server and\n # closed after.\n def initialize(status : Int32, source : IyiIO, headers : Hash(String, String) = {} of String => String)\n # The body read whole into the response, which is answered; why not,\n # when it cannot be.\n def load : Response | Cancelled | SocketError | HTTPError\n def reason : String\n def source : IyiIO | ::Nil\n def status : Int32\n # A response whose body the block writes as it goes, chunked.\n def self.stream(status : Int32, headers : Hash(String, String) = {} of String => String, &writer : (BodyWriter -> Nil)) : Response\n def success? : Bool\n def takeover : Proc(IyiSocket | TlsStream, String, Nil) | ::Nil\n protected def takeover=(takeover : Proc(IyiSocket | TlsStream, String, Nil)) : Proc(IyiSocket | TlsStream, String, Nil)\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the `https://` connection the answer came over resumed a TLS\n # session by a ticket an `HTTPClient` kept, rather than a full\n # handshake; false in the clear and for one a program made.\n def tls_resumed? : Bool\n # A 101 Switching Protocols with *headers* (`Upgrade` and the\n # protocol's own), after which the connection is the block's: it is\n # handed the connection - the socket, or under `serve(tls:)` the\n # `TlsStream` over it - and the bytes the client sent after its request,\n # and the connection closes when it returns.\n def self.upgrade(headers : Hash(String, String), &takeover : (IyiSocket | TlsStream, String -> Nil)) : Response\n # The URL the answer came from, after any redirects; nil for one a\n # program made.\n def url : String | ::Nil\n def writer : Proc(BodyWriter, Nil) | ::Nil\n protected def writer=(writer : Proc(BodyWriter, Nil)) : Proc(BodyWriter, Nil)\nend\n\npub struct Server\n # The headers a coded body goes with: the coding, and `Vary` so that a\n # cache keeps the answers apart. A `Content-Length` the block gave is\n # the plain body's, and goes.\n protected def self.coded(response : Response, coding : String) : Response\n # The coding *response* goes in to the client of *req*, or nil: one the\n # client takes (RFC 9110 §12.5.3, a `q` of 0 refusing it), for an\n # answer with a body and a type worth compressing, and no coding of its\n # own. A whole body goes `zstd` when the client's `q` for it is at least\n # its `q` for gzip, and `gzip` otherwise. The tie goes to zstd for the\n # server's time: measured on this machine, release builds, on\n # src/std/http.iyi (199,134 bytes) `Gzip.compress` wrote 54,484 bytes in\n # 5 ms and `Zstd.compress` 61,753 in 1 ms, and on CHANGELOG.md (1.5 MB)\n # 552,446 in 58 ms against 589,785 in 13 ms - a quarter of the time, for\n # an eighth more bytes at most. A stream - an `IyiIO` or a\n # `Response.stream` - goes gzip or as it is: std/compress's `Deflater`\n # writes each piece as it comes, where std/zstd's compressor takes its\n # input whole, and a frame a write would code each piece alone.\n def self.coding_for(req : Request, response : Response) : String | ::Nil\n # A string body compressed whole in *coding*, `zstd` or `gzip`, with\n # the headers that say so. `Zstd.compress`'s frame needs a window of\n # 1 MiB at most, inside the 8 MB RFC 9659 §3 holds an encoder to.\n protected def self.compressed(response : Response, coding : String) : Response\n # A response's head. `Content-Length` is *length*'s - the caller's own\n # line is checked against it, and kept only where no body follows it: a\n # HEAD, or a 304 that names the length a 200 would have (RFC 9110 §8.6) -\n # or `Transfer-Encoding: chunked` when *chunked*, or neither for a body\n # the connection's close ends. `Connection` says what the connection will\n # do next, except on an *upgrade*, whose own line says it. A 1xx, 204 or\n # 304 has no content (RFC 9110 §15), and writes neither a body nor a\n # length of its own (§8.6): the body was read by the client as the start\n # of the next answer, and `Content-Length: 0` on a 304 told a cache that\n # the resource had become empty. A field added on lines of its own\n # (`add_header`) is written a line each.\n def self.format_head(response : Response, head : Bool, close : Bool, length : Int64 | ::Nil, chunked : Bool, upgrade : Bool = false) : String\n # The response as it goes on the wire, whole: `format_head` for its\n # string body, and the body.\n def self.format_response(response : Response, head : Bool, close : Bool) : String\n # One connection: requests until the client is done. A request that is\n # not HTTP is answered with why and the connection closed, since the\n # bytes after it cannot be trusted to be a request either. Requests that\n # came in one read are read where they lie, one after another. A\n # connection that opens with HTTP/2's preface is served as HTTP/2. Under\n # *tls* the handshake comes first, and the connection ends with\n # close_notify. A connection this side wrote last on - an answer, a\n # refusal, a GOAWAY - closes by lingering (`Conn#close`), so the reset\n # a close with the client's bytes unread is cannot take what was\n # written from the client before it reads it. One that timed out or was\n # cancelled, with nothing of this side's waiting to be read, closes at\n # once, and so does one whose client said `close` and sent nothing past\n # its request (`serve_h1`).\n def self.handle(client : IyiSocket, handler : Proc(Request, Response), listener : IyiSocket | ServeStop | Nil = nil, idle : Hash(UInt64, Conn) | ::Nil = nil, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 0, tls : TlsServerConfig | TlsFrozenServerConfig | Nil = nil, request_timeout_ms : Int32 = 0) : Nil\n def initialize\n # A request's head - the text before its blank line - read and checked,\n # or why it is not one.\n def self.parse_head(text : String) : RequestHead | HTTPError\n # A request at *from* in `text`, if a whole one is there. No request and\n # no error means \"not yet\". `consumed` counts from the start of `text`.\n def self.parse_request(text : String, from : Int32 = 0) : ParsedRequest\n # Serve `listener` until it is closed, on *workers* threads: each\n # connection is a task on the pool `spawn_parallel` starts (SPEC.md\n # III.4.13), started by whichever worker is free and served there to\n # its end, while the thread that called this accepts. The block runs on\n # the connection's worker, once per request, on several workers at\n # once; what it answers is written back.\n # The close ends a connection waiting on its client too, for its next\n # request or for the rest of one, and one that is answering ends when its\n # answer is written: a keep-alive client that sat idle kept `serve` from\n # returning for as long as it stayed, and so did one that had sent\n # what has arrived of a request so far - one byte of it was enough.\n #\n # *stream_bodies*: the block reads each request's body from `body_io`\n # rather than being handed it whole. *compress*: an answer whose type\n # std/mime calls compressible goes zstd or gzip to a client that\n # accepts one (`coding_for`).\n #\n # *timeout_ms*: a request's head must have arrived within it, counted\n # from when the server starts waiting for one - a kept-alive connection's\n # idle time included - and no read of a body nor write of an answer may\n # wait longer; a connection that does is closed. Without it, a client\n # that sends a byte of its head every few seconds (Slowloris) holds a\n # connection and its fiber for as long as it likes, and so does one that\n # reads none of its answer. A minute by default, nginx's\n # `client_header_timeout`; 0 for none. A connection handed over by\n # `Response.upgrade` waits as its protocol likes.\n #\n # *request_timeout_ms*: the whole request - head and body - must have\n # arrived within it, counted from the same moment as *timeout_ms*, as\n # Go's `ReadTimeout` beside its `ReadHeaderTimeout`; the head is held to\n # the sooner of the two. Without it a body sent a byte every few seconds\n # was bounded per read alone, and held its connection for as long as it\n # dripped. A block that reads the body itself (*stream_bodies*) reads\n # under it until the block returns, and what it leaves unread is read\n # past under it too. Over HTTP/2 the wait for each request and its body\n # is held to it on the connection the same way, the head to the sooner\n # of the two. Five minutes by default, which a 64 MiB body (`MAX_BODY`)\n # meets at 224 KB/s; a server taking large uploads over slow links\n # raises it. 0 for none. A connection past it is closed, unanswered, as\n # one past *timeout_ms* is.\n #\n # *max_connections*: at most this many connections are served at once;\n # 1024 by default, 0 for no limit. Past it the server stops accepting\n # until one ends, so the next connection waits in the kernel's listen\n # queue - queued, not refused, as Go's `netutil.LimitListener` does - and\n # is served in its turn; a client that gives up waiting goes unserved.\n # Each served connection holds its place until it closes, a kept-alive\n # one between requests too, which *timeout_ms* bounds. The process's\n # own limit on descriptors may come first.\n #\n # *tls*: each connection is TLS (std/tls's `TlsServer`, 1.3 or 1.2) under this\n # configuration, its handshake held to *timeout_ms* too and run on the\n # worker that serves it; one whose handshake fails is closed, the\n # client told why by an alert. Inside it a connection that opens with\n # HTTP/2's preface - as a client whose ALPN chose `h2` does, when\n # `tls.alpn` offers it - is served HTTP/2, and any other HTTP/1.1. A\n # `Response.upgrade` block over TLS is handed the `TlsStream`, so a\n # WebSocket is `wss://`. On more than one worker the config is a\n # `TlsFrozenServerConfig` - `config.frozen` - since every worker reads\n # it at once: its ticket keys are one set behind a lock, so a ticket a\n # connection on one worker was sent resumes on any other, and an\n # identity source given to `frozen` is a block the compiler checked. A\n # `TlsServerConfig` itself is refused at the call (\"Server.serve hands\n # `tls : TlsServerConfig` to other threads, several at once, and it is\n # not Share: ... Pass `config.frozen` ...\"), not frozen here: `frozen`\n # is a copy, so a property assigned after the server started would be\n # served by no worker and nothing would say so, and a config with an\n # `identity_source` cannot be frozen without its source given again as\n # a block the compiler checks. Under `workers: 1` either is served.\n #\n # *workers*: the pool's size, `IyiWorkers.count` by default - `IYI_WORKERS`\n # from the environment, else the cores this process may run on. The\n # pool is the process's one, and it does not grow or shrink once it has\n # started, so a count other than the one it started with panics. Every\n # worker runs the block at once, so the compiler checks it where it is\n # written as it checks a parallel task's (`@[Share]`, SPEC.md III.4.4):\n # every value it captures must be `Share` - a table behind a `Mutex`\n # (std/mutex), a count in an `Atomic`, a `SharedRouter` (std/router) -\n # every captured local assigned once, and the constants and class\n # variables it names `Share`. The refusal names the variable, why its\n # type is not `Share`, and the two ways out: the lock, or `workers: 1`.\n # `workers: 1`, written as the literal, serves on the calling thread\n # alone - each connection a task beside the accept loop, as `spawn`\n # makes one - and its block is not checked: a block that calls a\n # `Router`, appends to an `Array` or keeps a handle that is not `Share`\n # is served there. A count in a variable is checked whatever it holds,\n # since the compiler cannot know it is 1. A count of 1 from the default\n # (`IYI_WORKERS=1`, one core) serves on the calling thread too.\n #\n # A connection stays on the worker that started it (III.4.13, part 1):\n # connections spread over the workers free as they arrive, and one kept\n # alive is not moved later. A block that computes without parking holds\n # its worker meanwhile, as any task does. The listener is the calling\n # thread's, and a close of it is made there: a block that stops the\n # server sends on a `Channel` a task beside the server receives from,\n # and that task closes it. At the stop each connection waiting on its\n # client is closed on its own worker, sent close_notify there first\n # under *tls*.\n def self.serve(listener : IyiSocket, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count, &handler : (Request -> Response)) : Nil forall C\n # `serve` with a value for its handler rather than a block: a\n # `SharedProc`, a `SharedRouter` (std/router), or one of the program's\n # own, whose `call(request : Request) : Response` answers each request,\n # as Go's `http.Handler` does. On more than one worker it must be\n # `Share` (SPEC.md III.4.4), since every connection on every worker\n # calls the one value at once: the compiler refuses one that is not at\n # the argument, naming the field that failed and the two ways out - a\n # count in an `Atomic`, a pool or a cache behind a `Mutex`, a\n # `SharedRouter` for a `Router`, or `workers: 1`, under which any value\n # with that `call` is served on the calling thread, a `Router`\n # included. What the check does not see is the edge `spawn_parallel`\n # has too: a method the handler calls that writes a module's state. A\n # struct handler is copied into each connection's task, and an `Atomic`\n # in it with it: what the connections share is a class, or held by\n # one.\n #\n # The rest is the block form's: *stream_bodies*, *compress*,\n # *timeout_ms*, *tls* (`config.frozen` on more than one worker),\n # *request_timeout_ms*, *max_connections* and *workers*, HTTP/2 by its\n # preface, `Response.upgrade`, and a close of *listener* - on the\n # thread that called this - ending every connection waiting on its\n # client and returning once the others have answered.\n def self.serve(listener : IyiSocket, handler : H, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count) : Nil forall H, C\n protected def self.writer_for(socket : IyiSocket | TlsStream, chunked : Bool, gzip : Bool) : BodyWriter\nend\n\n# A TLS connection used as a socket is: read into a buffer, written to,\n# closed, its timeouts set - by a `Conn`, a `BodyWriter`, and a connection\n# handed over by an upgrade (`wss://` at either end). What TLS refuses is a\n# `SocketError` carrying TLS's own sentence (`layer_reason`), so answers\n# keep the types they have over a plain socket.\npub class TlsStream\n # The socket closed at once, nothing sent or read first.\n def abort : Nil\n # close_notify, the sending half shut, and what the peer still sends\n # read past for a bounded time before the socket closes\n # (`TlsConnection#close`).\n def close : Nil\n # close_notify and the sending half shut; reads go on until the peer's\n # own (`TlsConnection#close_write`).\n def close_write : SocketError | ::Nil\n def closed? : Bool\n def initialize(tls : TlsConnection, host : String)\n # The peer's certificate chain, leaf first, as the handshake checked it:\n # the server's, at a client; the client's, at a server that asked.\n def peer_certificates : Array(Certificate)\n # Up to *count* bytes into *buffer*: 0 once the peer closed with\n # close_notify.\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n # Up to *max_bytes* as a string, what is held first: \"\" once the peer\n # closed with close_notify. `IyiSocket#read`'s form, for a block an\n # upgrade hands either.\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n # Whether the handshake resumed a session (`TlsConnection#resumed?`).\n def resumed? : Bool\n def write(text : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What `HTTP.upgrade` answers: the 101, and the connection, which is the\n# caller's now - the socket, and the bytes the server sent after its head.\npub class Upgraded\n def initialize(response : Response, socket : IyiSocket | TlsStream, leftover : String)\n def leftover : String\n def response : Response\n # The connection: the socket, or for `https://` the `TlsStream` over it.\n def socket : IyiSocket | TlsStream\nend\n\nimpl Error for HTTPError\n def message : String\nend\n" + "sourceSha256": "c7ddc59f99e543868102459ccd62385e941d6ac8e85de415eb34e30e57f71dcd", + "surface": "module std/http\n\n# A body as it arrives: from a connection, framed by its length, by chunks\n# or by the connection's close, or from a string already in hand. `read`\n# answers the next piece, \"\" once the body is done; what the other end\n# does wrong is an `HTTPError`, and a connection that fails or is\n# cancelled answers that. The first failure is kept: every read after it\n# answers the same. A body coded `gzip`, `deflate` or `zstd` is decoded\n# as it is read when the client asked for that.\n#\n# A body has a limit: past it a read answers an `HTTPError` with status\n# 413. The server's is `Server::MAX_BODY`; a client's is none.\npub class BodyReader\n # The body's coding undone from here on: `Envelope::Gzip` for gzip,\n # `Envelope::Zlib` for HTTP's deflate - which some servers send as the\n # bare stream instead, without zlib's two bytes of header, so the first\n # two bytes are looked at first, and a pair that is not a zlib header\n # (RFC 1950 §2.2: method 8, a multiple of 31) is read as the bare\n # stream. A body that does not decode is an `HTTPError` naming the\n # coding and why.\n def decode(envelope : Envelope) : Nil\n # The body's `zstd` coding (RFC 8878) undone from here on, by std/zstd's\n # `ZstdReader` as the pieces arrive, frames one after another. A frame\n # that asks for a window past *max_window* bytes is refused rather than\n # given the memory (a window outside 1 KiB to 512 MiB is a panic), and a\n # body that does not decode is an `HTTPError` naming the coding and why.\n def decode_zstd(max_window : Int32 = Zstd::WINDOW_LIMIT) : Nil\n # Reads past the rest of the body, *limit* bytes at most: whether it\n # ended within them. A server reads past what a block left unread, so\n # the next request on the connection starts where it should.\n def drain(limit : Int64) : Bool\n # Whether every byte of the body has been read, the framing's end too.\n def finished? : Bool\n # A body that arrives as an HTTP/2 stream's DATA: refused past *limit*.\n protected def self.frames(h2 : H2Connection, id : Int32, limit : Int64) : BodyReader\n def initialize(conn : Conn | ::Nil, kind : Int32, length : Int64, limit : Int64, text : String = \"\", hello : Proc(Bool) | ::Nil = nil)\n # Whether a server reading past the rest of this body, so that the next\n # request starts where it should, would close instead: its length says\n # more than *limit* bytes are left, or its client still waits for the\n # `100 Continue` its `Expect` asked for - which, written after the\n # answer, the client reads as its next request's, and the body it\n # holds back is then waited for. A chunked body's rest is not known\n # until it is read.\n def left_past?(limit : Int64) : Bool\n protected def on_frames(h2 : H2Connection, id : Int32) : Nil\n # The body's bytes as the framing delivers them, before any decoding.\n protected def raw(max : Int32) : String | Cancelled | SocketError | HTTPError\n # The next piece of the body, at most *max* bytes; \"\" when it is done.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the body as one string, refused past *limit* bytes (413).\n # The pieces are kept as they arrive and joined once: a builder of the\n # length a head declared was made before any of it had come, and a head\n # that declared 64 MB and sent two bytes cost the server 64 MB.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\n protected def remember(fault : HTTPError | Cancelled | SocketError) : HTTPError | Cancelled | SocketError\n # A body that is a string already in hand.\n def self.text(text : String) : BodyReader\n # Whether the body ends where the connection does: nothing can follow\n # it on the connection.\n def to_close? : Bool\nend\n\n# What a streamed body is written to: an answer's `Response.stream`\n# block's, and a client's upload from an `IyiIO`. Each `write` goes out as\n# one chunk (RFC 9112 §7.1), or as it is where the connection's close ends\n# the body, so a block that writes a line a second is read a line a\n# second. Under a server's `compress`, each write is a gzip piece flushed\n# whole (std/compress's `Deflater`), which decodes as it arrives too. The\n# first failure is kept, and every write after it answers it; `finish`,\n# which the server calls when the block returns, writes the end.\npub class BodyWriter\n # The end of the body: the gzip trailer, and the last chunk with the\n # blank line after it. Once; the server's call after a block that\n # finished its own body writes nothing.\n def finish : Nil | Cancelled | SocketError\n def initialize(socket : IyiSocket | TlsStream | H2Out, chunked : Bool, deflater : Deflater | ::Nil = nil)\n def write(text : String) : Nil | Cancelled | SocketError\n # Bytes of the body written so far, before any framing or coding.\n def written : Int64\nend\n\n# What `HTTP.request`'s `jar:` keeps cookies in: the `Cookie` header a\n# request to a URL carries, and an answer's `Set-Cookie` lines taken in.\n# std/cookie's `CookieJar` is one; std/http itself reads no clock, so the\n# expiry of a cookie is the store's to keep.\npub trait CookieStore\n abstract def header_for(url : String) : String | ::Nil\n abstract def update(url : String, response : Response) : Nil\nend\n\npub struct HTTP\n # For a request target in absolute form (RFC 9112 §3.2.2), where its\n # authority starts: after `http://` or `https://`, in any case.\n def self.absolute_form(target : String) : Int32 | ::Nil\n # Whether a list of codings a client accepts (`Accept-Encoding`, RFC\n # 9110 §12.5.3) takes *coding*: named, or `*`, with a `q` above 0.\n def self.accepts?(list : String, coding : String) : Bool\n # A 1xx, 204 or 304: an answer with no content (RFC 9110 §15).\n def self.bodiless?(status : Int32) : Bool\n # Where a chunked body that starts at *from* ends in `text` — the byte\n # after the last chunk's terminating CRLF (trailers included) — nil when\n # it has not all arrived, or why it is not a chunked body. What\n # `decode_chunked` will then read whole.\n def self.chunked_end(text : String, from : Int32 = 0) : Int32 | String | Nil\n # How much a list of codings a client accepts wants *coding*, as its\n # `q` in thousandths: the weight it is named with, else `*`'s, else 0 -\n # and 0 refuses it.\n def self.coding_weight(list : String, coding : String) : Int32\n # A `Content-Length` as a number, or nil when it is not one. Repeated\n # lines arrive joined, and are one length only when every one says the\n # same (RFC 9112 §6.3). A length past 2147483647 is still a length, too\n # large for either side, and is read as one past it: `to_i?` answered\n # nil for it, and 3000000000 was refused as \"not a number\".\n def self.content_length(value : String) : Int64 | ::Nil\n # A control character other than a tab, or DEL: what a field value may\n # not hold (RFC 9110 §5.5), nor a request target or a reason phrase.\n def self.control?(text : String) : Bool\n # The pairs of a request's `Cookie` header, `a=1; b=2` (RFC 6265 §4.2),\n # a value's quotes taken off; the first of a name is kept, as a server\n # reads the most specific cookie first.\n def self.cookie_pairs(value : String) : Hash(String, String)\n # A chunked body (RFC 9112 §7.1) as one string: each chunk's size line\n # is hexadecimal, extensions after `;` are ignored, and a zero-length\n # chunk ends it. What follows the last chunk is trailers, which nothing\n # here reads.\n def self.decode_chunked(text : String) : String\n def self.delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def self.find_headers_end(text : String, from : Int32 = 0) : Int32 | ::Nil\n # The request as it goes on the wire, whole. `Host`, `Connection` and\n # `Content-Length` are the request's own lines; a caller's header of\n # that name is refused rather than sent twice. A body's length is\n # always written for the methods a body belongs to, so `POST` with\n # nothing to say still says `Content-Length: 0`. A byte of the path past\n # 127 goes as `%` and two hex digits: a request target is ASCII (RFC\n # 9112 §3.2, RFC 3986 §2.1), and `/ü` went out as its UTF-8 bytes, raw.\n def self.format_request(method : String, path : String, host : String, body : String = \"\", headers : Hash(String, String) = {} of String => String) : String\n def self.get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # *text* in gzip, whole: an answer's body under a server's `compress`.\n def self.gzip(text : String) : String\n # A writer whose body goes out in gzip as it is written.\n def self.gzip_writer(socket : IyiSocket | TlsStream | H2Out, chunked : Bool) : BodyWriter\n def self.has_break?(text : String) : Bool\n # Whether a list of tokens (RFC 9110 §5.6.1), its repeated lines joined\n # with \", \", holds *token*, in any case.\n def self.has_token?(list : String, token : String) : Bool\n def self.head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The value of a chunk size's hex digits, nil for anything else or for\n # one past what an Int32 holds.\n def self.hex(text : String) : Int32 | ::Nil\n # *n* in lowercase hexadecimal: a chunk's size line.\n def self.hex_text(n : Int64) : String\n # A Host value - the `a` of `Host: a` - or an absolute-form target's\n # authority, made of what a host and its port are spelled with (RFC 3986\n # §3.2.2, §3.2.3): a name, an IPv4 address or an IPv6 one in brackets,\n # and digits after a colon. Empty is one too, for a target with no\n # authority (RFC 9112 §3.2). A blank, a `/`, an `@` - the bytes another\n # host or a path begins with - are not.\n def self.host?(text : String) : Bool\n # The endpoints *answer*'s ServiceMode records name for a client whose\n # configuration offers *alpn* by ALPN (none: it speaks http/1.1 alone),\n # to an origin on *port*, in the order to try them: a record whose\n # mandatory keys are not all ones std/dns reads, or whose protocols -\n # alpn, and http/1.1 unless no-default-alpn - hold none of the client's,\n # names none (RFC 9460 sections 7.1.2 and 8), and neither does one whose\n # target is not a host name. Each is the record's target, or its owner\n # for \".\"; its port, or *port*; its `ech` where the ECHConfigList holds\n # a config std/tls can use (RFC 9849 section 6.1), nil otherwise; and\n # *alpn* with the protocols the record's alpn lists first, in *alpn*'s\n # order otherwise.\n def self.https_endpoints(answer : SvcbAnswer, alpn : Array(String), port : Int32) : Array(HttpsEndpoint)\n def initialize\n # One request, and the block's answer to the response, its body still\n # arriving: `response.body_io.read` takes it a piece at a time, and\n # `response.body` all of it. The connection closes when the block\n # returns. A body of *body* is a string, sent with its length, or an\n # `IyiIO`, sent chunked as it is read - which a redirect that keeps the\n # method (307, 308) cannot send again, so that answer is handed to the\n # block as it is.\n #\n # The redirects followed are RFC 9110 §15.4's: 301 and 302 make a POST a\n # GET, 303 makes anything but a HEAD a GET, and 307 and 308 keep the\n # method and the body. `Authorization`, `Proxy-Authorization` and\n # `Cookie` written by the caller are not sent to another scheme, host or\n # port - `https://` to `http://` at one host included, as curl does (Go\n # strips them for another host) - and a jar's `Secure` cookies go over\n # `https://` alone. The downgrade itself is followed, as Go's client and\n # `curl -L` follow it; any other header the caller wrote, and a 307 or\n # 308's body, go on in the clear. One more than *redirects* is an\n # `HTTPError`; with 0 the first answer is the block's.\n #\n # *timeout_ms* bounds the connect, and each read and write after it -\n # a pause, not the whole exchange, as Python's `timeout` is: a body that\n # keeps arriving is read for as long as it takes. One that runs out is a\n # `SocketError` whose `timed_out?` is true. 0, the default, waits as\n # long as the server does, as Go's and Python's clients do; a client\n # that talks to servers it does not control should give one.\n #\n # *proxy* is a proxy's URL - `http://user:password@host:port`, or\n # `host:port` alone - and every request goes through it, every redirect\n # too: `http://` as an absolute-form request to the proxy (RFC 9112\n # §3.2.2), which carries `Proxy-Authorization: Basic` when the URL has\n # a user; `https://` through a tunnel the proxy opens with `CONNECT\n # host:port` (RFC 9110 §9.3.6), the credential on the CONNECT alone and\n # TLS to the server inside it, its certificate checked as without a\n # proxy. A caller's own `Proxy-Authorization` is the proxy's too: sent\n # on the CONNECT when the URL has no user, and never inside the tunnel.\n # A CONNECT answered with anything but a 2xx - 407 when the proxy wants\n # credentials, 403, 502 - is an `HTTPError` naming the proxy and its\n # answer. `http://` through a proxy is HTTP/1.1 whatever\n # *prior_knowledge* says. Which proxy, if any, a URL's environment\n # names is `proxy_for`'s, and std/http_proxy's `HTTPProxy.from_env`.\n def self.open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, client : HTTPClient | ::Nil = nil, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n # The field lines after the first, or why they are not fields. A name\n # repeated, in any case, is one field, its values joined in order with\n # \", \" (RFC 9110 §5.3): into a Hash by name, a second line spelled the\n # same replaced the first and one spelled otherwise was an entry\n # `header` never reached, so two different `Content-Length`s were read\n # as whichever came first or last. A value with a control character in\n # it - a CR or LF that ends no line, a NUL - is refused (§5.5) rather than\n # handed to whoever reads it or echoes it into a header.\n #\n # *unfold*, for an answer: a line that begins with a blank continues the\n # field above it (obs-fold, RFC 9112 §5.2), and a user agent joins it on\n # with one space; read as a field of its own, `HTTP.get` panicked with\n # \"header without a colon: \\\" b\\\"\". A server may refuse one, and this\n # one does.\n #\n # *apart*, when given, is handed each repeated field's values by the\n # name in the answer, a line each, before they are joined.\n def self.parse_fields(lines : Array(String), unfold : Bool = false, apart : Hash(String, Array(String)) | ::Nil = nil) : Hash(String, String) | String\n # The response as it came off the wire, whole. `head` says the request\n # was a `HEAD`, whose answer carries the headers of a body it does not\n # send. An interim answer - `100 Continue`, `103 Early Hints` - is read\n # past to the final one after it (RFC 9110 §15.2). A `101` switches\n # protocols, and HTTP ends with it. What is not a response panics with\n # why: this is the text a program has in hand; the client answers the\n # same sentences as `HTTPError`s.\n def self.parse_response(text : String, head : Bool = false) : Response\n # A response's head - the text before its blank line - read: status,\n # reason, the fields, and the lines of those on more than one. A line\n # that begins with a blank continues the field above it (obs-fold, RFC\n # 9112 §5.2), which a user agent joins on with one space.\n def self.parse_response_head(text : String) : ::Tuple(Int32, String, Hash(String, String), Hash(String, Array(String)) | ::Nil) | HTTPError\n # Where *text* goes on at *from* past the empty lines there: what a\n # server skips before a request line (RFC 9112 §2.2).\n def self.past_empty_lines(text : String, from : Int32) : Int32\n def self.post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The proxy a request for *url* goes through, given the values of\n # `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` - nil when it goes\n # direct. Go's `golang.org/x/net/http/httpproxy` is the rule, line for\n # line, as its documentation and tests state it:\n #\n # * `https://` takes *https_proxy*, `http://` *http_proxy*, any other\n # scheme none. A proxy written without a scheme is `http://`'s, and is\n # answered with it, ready for `open`'s *proxy*.\n # * `localhost` (in lower case: Go's comparison) and a loopback address\n # (127.0.0.0/8, ::1, the first as IPv4-mapped too) never go through a\n # proxy.\n # * *no_proxy* is a comma-separated list, each entry trimmed and read\n # without regard to case: `*` alone sends everything direct; an IP\n # address or a CIDR range (`10.0.0.0/8`, `2001:db8::/32`) matches the\n # addresses in it, IPv4 and its IPv4-mapped form alike, and a CIDR\n # range of one family never the other's; a domain matches itself and\n # every name under it (`foo.com`: `foo.com`, `bar.foo.com`, not\n # `barfoo.com`); with a leading dot, or `*.`, only the names under it\n # (`.y.com`: `x.y.com`, not `y.com`). An address or domain may carry\n # a port (`1.2.3.4:80`, `[::1]:443`, `foo.com:8080`), and matches\n # then on that port alone, the URL's own or its scheme's default.\n # An entry that is none of these (`:80`) is passed over.\n # * A host is compared as IDNA's ASCII form, as Go compares it.\n #\n # Pure: the values are the caller's, so std/http reaches no environment;\n # std/http_proxy reads them from it.\n def self.proxy_for(url : String, http_proxy : String | ::Nil, https_proxy : String | ::Nil, no_proxy : String | ::Nil = nil) : String | ::Nil\n def self.put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The reason phrase a status carries (RFC 9110 §15), for a response the\n # server writes; a status this table does not know gets its class's.\n def self.reason_for(status : Int32) : String\n # One request and its whole answer: the answer as `open` hands it, its\n # body read. Redirects are followed, *redirects* at most; *jar* keeps\n # the cookies the answers set and sends them back; *decompress* asks\n # for gzip, deflate and zstd and undoes them. A refused or reset\n # connection answers `SocketError`; a task cancelled answers\n # `Cancelled`; what the server does wrong answers `HTTPError`. `!`\n # passes any of them up, `.or_panic` refuses it.\n def self.request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Response | Cancelled | SocketError | HTTPError\n # A request's head: the request line, `Host`, `Connection` (*connection*:\n # `close`, or `Upgrade`), the caller's headers, and the body's framing -\n # *length*, chunks, or none.\n def self.request_head(method : String, path : String, host : String, headers : Hash(String, String), length : Int64 | ::Nil, chunked : Bool, connection : String) : String\n # Two field names for the same field: ASCII case is the only difference\n # a name may carry.\n def self.same_name?(a : String, b : String) : Bool\n def self.split_crlf(text : String) : Array(String)\n # A status line read: the status and its reason, or why it is not one.\n def self.status_line(line : String) : ::Tuple(Int32, String) | HTTPError\n # What an `https://` request is checked against and offers: the\n # system's roots, and ALPN's `http/1.1`, unless a program sets its own -\n # roots of its own for a server it runs, say.\n def self.tls_config : TlsConfig\n def self.tls_config=(config : TlsConfig) : TlsConfig\n # A token (RFC 9110 §5.6.2): what a method or a field name is made of.\n def self.token?(text : String) : Bool\n # Without the blanks at either end (RFC 9112 §5.1: field values are\n # trimmed of the whitespace around them).\n def self.trim(s : String) : String\n # The 101 an upgrade to *protocol* is answered with, and the connection,\n # which is the caller's from then on: std/websocket's handshake. Any\n # other answer is an `HTTPError` naming it.\n #\n # *proxy* is `open`'s, and so is its credential, on the CONNECT alone;\n # but an upgrade goes through a tunnel for `http://` as for `https://`,\n # the handshake inside it in origin form. `Upgrade` belongs to the\n # connection it is sent on (RFC 9110 §7.8), and a proxy forwarding a\n # request removes `Connection` and every field it names (§7.6.1), so a\n # handshake handed to a forward proxy in absolute form reaches the\n # server, at best, as a plain GET: the gate's proxy, which drops\n # `Connection`, leaves std/websocket's server a request it answers 400.\n def self.upgrade(url : String, protocol : String, headers : Hash(String, String) = {} of String => String, timeout_ms : Int32 = 0, proxy : String | ::Nil = nil) : Upgraded | Cancelled | SocketError | HTTPError\nend\n\n# Requests that keep their connections: an `HTTPClient` holds the connections\n# its answers left open, by host and port, and the next request to that\n# server goes over one of them instead of a new one - the TCP handshake,\n# and the slow start after it, paid once rather than per request. Its\n# methods are `HTTP`'s, redirects, cookies and decoding included:\n#\n# ```\n# client = HTTPClient.new\n# client.get(\"http://127.0.0.1:8080/a\")\n# client.get(\"http://127.0.0.1:8080/b\") # the same connection\n# client.close\n# ```\n#\n# A connection goes back once its answer has been read to the end (inside\n# `open`, up to 64 KiB left unread is read past for it) and the answer\n# allows it; at most *idle* wait per server, and the rest close. A server\n# may close an idle connection whenever it likes (RFC 9112 §9.3.1), so a\n# request whose kept connection turns out closed - the write fails, or it\n# ends before a byte of an answer - is sent again once on a new one, when\n# its method is idempotent (RFC 9110 §9.2.2) and its body a string;\n# anything else answers the failure. One task at a time per request: two\n# tasks sending at once take two connections, and a client is not for\n# sharing across threads. An `https://` connection is kept under the\n# `HTTP.tls_config` it was made with, and handed only to a request made\n# under the same one (`TlsMadeUnder`): after `HTTP.tls_config=` the next\n# request makes a connection of its own, checked against the new roots.\n# A client made with *tls* uses that configuration instead of\n# `HTTP.tls_config`, and one made with *resolver* finds its hosts by it\n# instead of `Resolver.system` - an encrypted resolver, or a hosts text\n# that pins a name to an address: std/secure_dns's `DnsOverHttps` is a\n# client of its own with both, so its server is checked against the roots\n# it was given and reached at the address it was given.\n#\n# A client resumes TLS sessions (RFC 8446 §2.2): the tickets an\n# `https://` server sends are kept for its origin - scheme, host and\n# port, whichever proxy's tunnel they came through - under the TLS\n# configuration they came under, the client's own *tls* or else\n# `HTTP.tls_config` (`TlsMadeUnder`, as a kept connection is), and a new\n# connection to that origin offers the newest, each once.\n# A resumed handshake checks no certificate - the ticket stands for the\n# check the first one made - so a ticket is never offered to another\n# origin, nor under another configuration: after `HTTP.tls_config=`, or\n# roots, an identity, `now`, `min_version` or `require_staple` assigned\n# in place - in `HTTP.tls_config` or in the client's own *tls* - the next\n# connection is a full handshake under the new ones, and that origin's\n# tickets from under the old one are dropped. (std/tls's `TlsSession`\n# holds each ticket to its configuration too; this client drops them\n# where that one would only pass them over.) At most *origin_tickets* are\n# kept an origin - 4 by default, so that as many connections as *idle*\n# keeps by default can resume at once, where std/tls's server and\n# OpenSSL's send 2 a handshake - and *tickets* in all, 256 by default: as\n# many origins as that leaves room for at *origin_tickets* each, the one\n# longest unused dropped first. 0 for either keeps none, and nothing\n# resumes. `close`\n# leaves them. Nothing goes early (0-RTT), whatever a ticket allows: the\n# module's header says why. `connections_resumed`, `tickets_held` and\n# `Response#tls_resumed?` say what was kept and used. What it saves, by\n# bench/std_http_resume.iyi built --release on a 12-core machine other\n# jobs shared (medians of 200, the middle of three runs): the handshake\n# took 0.96 ms full and 0.51 resumed against std/http's server, 1.03 and\n# 0.57 against OpenSSL 3.5's s_server - and a GET on a new connection,\n# which waits about 40 ms at each iyi end that is not the handshake's,\n# 82.98 ms and 82.00, and 42.04 and 40.96.\n#\n# Over HTTP/2 - `https://` where ALPN chose `h2`, or `http://` with\n# *prior_knowledge* (h2c, RFC 9113 §3.3) - a kept connection carries the\n# next request on a new stream, until the server says GOAWAY.\n#\n# HTTPS records (RFC 9460), when `use_https_records` is set: before a new\n# connection to an `https://` origin - not through a proxy, and not for\n# an upgrade - the client asks its resolver for the origin's HTTPS\n# records (`Resolver#https_records`, AliasMode followed) and connects by\n# the first ServiceMode record it can use: one whose mandatory keys it\n# knows - the seven std/dns reads, port and no-default-alpn mandatory by\n# being there (section 9) - and whose protocols (alpn, and http/1.1\n# unless no-default-alpn) hold one of the configuration's `alpn`, or\n# http/1.1 where that is empty. Its target - the record's owner for \".\"\n# - and its port, or the origin's, are where the connection goes; its\n# `ech` is the connection's `ech_configs` (RFC 9848); and the protocols\n# it lists go first in the ClientHello's ALPN, which still offers every\n# one the configuration does (section 7.1.2: a record says which\n# protocols the endpoint has, and cannot take one away). The certificate\n# is checked, and the server named inside, for the origin's host,\n# whatever the target (section 2.3). An `ech` whose ECHConfigList holds\n# no config std/tls can use is passed over, as RFC 9849 section 6.1 has\n# it. The address hints are not read: the target's addresses are asked\n# for as any host's are.\n#\n# An endpoint that does not connect gives way to the next, then to the\n# last AliasMode target and to the origin itself (section 3) - unless\n# every endpoint carries `ech`, when the request fails rather than name\n# the server in the clear (RFC 9848 section 5.1). A TLS refusal is the\n# request's failure, but for one: a server that turned the encrypted\n# ClientHello down, its certificate checked for the config's\n# public_name, is asked once more on a new connection to the same\n# address, under the retry_configs it sent, or without ECH where it sent\n# none, which turns ECH off there (RFC 9849 section 6.1.6); a refusal of\n# that is the failure. No records (NXDOMAIN, NODATA), a set section 2.2\n# refuses, an AliasMode \".\" (the service says it is not there), or no\n# record the client can use: the connection is made as without records.\n# So is one whose lookup failed through a resolver that asks in the\n# clear, as section 3.1 lets a client choose; through encrypted\n# upstreams the failure is the request's, as section 3.1 asks, for\n# there the one who can drop the answer is an attacker taking the\n# records away. `localhost` and addresses are not asked about, and an\n# `http://` URL is not made `https://` by its records (section 9.5's\n# SHOULD): the program wrote the scheme.\n#\n# A record's `ech` and protocols make a configuration of their own - a\n# copy of the client's, made once and kept for the origin while the same\n# ones come under the same configuration - so that a kept connection and\n# a ticket are handed on only under the record they were made by\n# (`TlsMadeUnder` holds `ech_configs`). Off by default: every new\n# connection then waits on one DNS answer or more before its ClientHello\n# (RFC 9848 section 5.3 forbids sending it sooner), with no cache to\n# spare the next, and the records may move a connection to another host\n# and port, which is the program's to ask for.\npub class HTTPClient\n # Every idle connection closed. The client may be used after; it opens\n # new ones.\n def close : Nil\n # Connections this client has opened, and requests sent over one it\n # kept: what a program reads to see that its connections are kept.\n def connections_made : Int32\n # Connections this client opened whose TLS handshake resumed a session\n # by a ticket it kept.\n def connections_resumed : Int32\n def connections_reused : Int32\n def delete(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def get(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n protected def give_back(key : String, conn : Conn) : Nil\n def head(url : String, headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # The configuration a connection to *origin* is made under by a record\n # whose `ech` is *ech* (nil: none) and whose protocols put *alpn* in its\n # order, *base* being the client's own or `HTTP.tls_config`: *base*\n # itself where those are its own, and otherwise a copy of it with them -\n # the same object every time while *base* is the same (`TlsMadeUnder`),\n # so that a connection kept and a ticket held under it serve the next\n # connection by the same record. It becomes the one the origin's kept\n # connections are taken under (`https_current`). The origin's\n # configurations from under another *base* go.\n protected def https_config(origin : String, base : TlsConfig, ech : Array(UInt8) | ::Nil, alpn : Array(String)) : TlsConfig\n # The configuration *origin*'s last connection by HTTPS records was made\n # under, while *base* is the one it came from - *base* itself where it\n # was made as without records: what a kept connection to the origin\n # must have been made under to be taken. Nil when none was made by them\n # under *base*, and then none is taken.\n protected def https_current(origin : String, base : TlsConfig) : TlsConfig | ::Nil\n # *timeout_ms* and *proxy* are `HTTP.open`'s, for every request the\n # client sends; *tls* and *resolver* are this client's own (above);\n # *origin_tickets* and *tickets* bound the TLS tickets kept, by origin\n # and in all.\n def initialize(idle : Int32 = 4, timeout_ms : Int32 = 0, prior_knowledge : Bool = false, proxy : String | ::Nil = nil, tls : TlsConfig | ::Nil = nil, resolver : Resolver | ::Nil = nil, origin_tickets : Int32 = 4, tickets : Int32 = 256)\n protected def made : Nil\n def open(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true, &block : (Response -> R)) : R | Cancelled | SocketError | HTTPError forall R\n def post(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n # `http://` spoken as HTTP/2 from the first byte, without asking: for a\n # server known to take it.\n def prior_knowledge : Bool\n # The proxy every request goes through, `HTTP.open`'s *proxy*; its\n # connections are kept under it.\n def proxy : String | ::Nil\n def put(url : String, body : String | IyiIO = \"\", headers : Hash(String, String) = {} of String => String) : Response | Cancelled | SocketError | HTTPError\n def request(method : String, url : String, headers : Hash(String, String) = {} of String => String, body : String | IyiIO = \"\", redirects : Int32 = 10, jar : CookieStore | ::Nil = nil, decompress : Bool = true) : Response | Cancelled | SocketError | HTTPError\n # What turns a host name into addresses for this client's connections,\n # instead of `Resolver.system`: encrypted upstreams, or a hosts text\n # alone that pins a name to an address, as std/secure_dns's\n # `DnsOverHttps` pins its server's.\n def resolver : Resolver | ::Nil\n protected def resumed : Nil\n # The session a new connection to *origin* under *tls* offers a ticket\n # from and keeps the server's new ones in; nil when the client keeps\n # none. The origin's tickets from under another configuration are\n # dropped, not left to take room that `TlsSession` would never offer\n # them from. The session handed out is the newest used; past the room\n # *tickets* leaves, the oldest goes.\n protected def session_for(origin : String, tls : TlsConfig) : TlsSession | ::Nil\n # A kept connection to *key*, nil when none waits. An HTTP/2 connection\n # that can take no more requests is closed rather than handed out, and\n # so is an `https://` one made under another TLS configuration than\n # *tls*, the one the request is made under (`TlsMadeUnder`): keyed by\n # scheme, host and port alone, a connection whose server was checked\n # against one set of roots carried requests after `HTTP.tls_config=`\n # named others that would have refused it.\n protected def take(key : String, tls : TlsConfig | ::Nil = nil) : Conn | ::Nil\n # The TLS tickets held for *origin* - `https://host:port`, the host in\n # lower case and the port written even where it is 443 - or for every\n # origin when nil.\n def tickets_held(origin : String | ::Nil = nil) : Int32\n # What this client's `https://` requests are checked against and offer,\n # instead of `HTTP.tls_config`: roots of its own, or ALPN's `h2` first.\n def tls : TlsConfig | ::Nil\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records : Bool\n # Whether a new `https://` connection is made by the origin's HTTPS\n # records (the class's header); off by default.\n def use_https_records=(value : Bool) : Bool\nend\n\n# What the other end did that HTTP does not allow, as a value: `reason` is\n# the sentence, `status` the answer a server gives a request that did it\n# (400, or 413 and 431 for the limits). `message` says it is HTTP's.\npub struct HTTPError\n def initialize(reason : String, status : Int32 = 400)\n def reason : String\n def status : Int32\n def to_s : String\nend\n\n# One endpoint an origin's HTTPS records name, as `HTTP.https_endpoints`\n# reads it: the host and port to connect to, the ECHConfigList to encrypt\n# the ClientHello under (nil: none), and the protocols to offer by ALPN,\n# in order.\npub struct HttpsEndpoint\n def alpn : Array(String)\n def ech : Array(UInt8) | ::Nil\n def host : String\n def initialize(host : String, port : Int32, ech : Array(UInt8) | ::Nil, alpn : Array(String))\n def port : Int32\nend\n\n# A `multipart/form-data` body (RFC 7578) read a part at a time, as it\n# arrives: `next_part` answers the next `Part`, nil after the last, and a\n# part's `read` answers its bytes up to the boundary, \"\" at its end. A part\n# not read to its end is read past by the next `next_part`. The body of a\n# request is `Request#multipart`'s; any `BodyReader` and boundary will do.\n# A part's head is refused past 16 KiB, and a body that ends before its\n# closing boundary is an `HTTPError`.\npub class Multipart\n # The boundary a `Content-Type` of `multipart/...` names, nil when it\n # names none - or one of a length RFC 2046 §5.1.1 does not allow, 1 to\n # 70 characters, which `new` refuses with a panic. The type is the\n # peer's: `Request#multipart` handed a client's 90-character boundary\n # through to that panic (found by bench/std_fuzz_exercise.sh).\n def self.boundary(content_type : String) : String | ::Nil\n # Where *pattern* is in what is held, -1 when it is not there yet.\n protected def find(pattern : String) : Int32\n def initialize(body : BodyReader, boundary : String)\n # The next part, nil after the last.\n def next_part : Part | Nil | Cancelled | SocketError | HTTPError\n # Up to *max* bytes of the part being read, \"\" at its boundary. What is\n # held back is what may yet be the start of the boundary.\n protected def part_read(max : Int32) : String | Cancelled | SocketError | HTTPError\n # One more piece of the body onto what is held: false at its end.\n protected def pull : Bool | Cancelled | SocketError | HTTPError\nend\n\n# A multipart/form-data body to send: fields and files in order. Its\n# boundary is chosen when the body is first asked for - `finish`,\n# `content_type` or `boundary`, whichever comes first, after which no\n# part is added - as `iyi-` and 32 hex digits of the parts' SHA-1, made\n# again from itself in the rare case a part holds it: so it is none of\n# the parts' bytes by construction rather than by chance, the same\n# fields make the same body, and std/http reaches no source of\n# randomness for it. A boundary given to `new` is used as it is, and a\n# part that holds it is refused.\npub class MultipartBuilder\n def boundary : String\n def content_type : String\n def field(name : String, value : String) : self\n def file(name : String, filename : String, data : String, content_type : String = MIME.from_extension(filename)) : self\n # The body, closing boundary and all.\n def finish : String\n # The boundary ends a part where it follows a CRLF, the part's first\n # bytes included, which follow the head's blank line.\n protected def self.holds?(data : String, mark : String) : Bool\n def initialize(boundary : String | ::Nil = nil)\n # A name inside quotes, as browsers write one: a quote and a line break\n # percent-encoded (the WHATWG form encoding), so no field can end the\n # parameter or the line early.\n protected def self.quoted(text : String) : String\nend\n\n# One part of a multipart body: its head's fields, its name and file name\n# from `Content-Disposition`, and its bytes, read up to its boundary.\npub class Part\n def content_type : String | ::Nil\n # The file name a browser sent the part under, `filename=`.\n def filename : String | ::Nil\n def finished? : Bool\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n def initialize(owner : Multipart, headers : Hash(String, String))\n # The form field's name, `name=` of `Content-Disposition`.\n def name : String | ::Nil\n # The part's next bytes, at most *max*; \"\" at its end.\n def read(max : Int32 = 65536) : String | Cancelled | SocketError | HTTPError\n # The rest of the part as one string, refused past *limit* bytes.\n def read_all(limit : Int64 = 2147483647_i64) : String | Cancelled | SocketError | HTTPError\nend\n\n# What a server's block is handed: one request. Its body is `body`, read\n# whole before the block ran, or under `stream_bodies` read by the block\n# from `body_io` as it arrives; `body` then reads the rest itself, and a\n# body that cannot be read - the client went, or sent one that is not\n# HTTP - ends the connection with that sentence rather than be answered as\n# if it were whole.\npub class Request\n def body : String\n def body_io : BodyReader\n # The pairs of the request's `Cookie` header, the first of a name kept.\n def cookies : Hash(String, String)\n # The fields of a form: the query's, and an\n # `application/x-www-form-urlencoded` body's after them. The body is\n # read for it.\n def form : Params\n def header(name : String) : String | ::Nil\n def headers : Hash(String, String)\n # A request whose body is in hand: what `parse_request` answers.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), body : String, version : String = \"HTTP/1.1\", remote_address : String | ::Nil = nil)\n # A request whose body is still on the connection.\n def initialize(method : String, path : String, query : String | ::Nil, headers : Hash(String, String), version : String, reader : BodyReader, remote_address : String | ::Nil = nil)\n def method : String\n # The body as `multipart/form-data` parts, read as they arrive; nil when\n # the `Content-Type` is not multipart or names no boundary\n # (`Multipart.boundary`).\n def multipart : Multipart | ::Nil\n def path : String\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates : Array(Certificate)\n # The client's certificate chain, leaf first, as std/tls checked it:\n # under `serve(tls:)` with `client_roots` set, the certificate the\n # client presented; empty otherwise.\n def peer_certificates=(value : Array(Certificate)) : Array(Certificate)\n def query : String | ::Nil\n # The client's address, as the server's socket saw it (no port): nil\n # for a request a program made, or when the client had already gone. A\n # proxy in front is what this names; `X-Forwarded-For` is the proxy's\n # word, and a header any client can write.\n def remote_address : String | ::Nil\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the TLS connection the request came over resumed a session by\n # a ticket, under `serve(tls:)`: its `peer_certificates` are then the\n # ticket's. False in the clear.\n def tls_resumed? : Bool\n def version : String\nend\n\n# A response: what a server's block answers, and what a client is handed.\n#\n# To write one: `Response.new(200, \"hello\")`, a status and a string;\n# `Response.new(200, io)`, a stream the server copies out and closes, with\n# the caller's `Content-Length` when it gives one and chunked otherwise;\n# `Response.stream(200) { |w| w.write(\"...\") }`, whose block writes as it\n# goes; `Response.upgrade(headers) { |socket, leftover| ... }`, a 101 after\n# which the connection is the block's. `add_header` writes a field on a\n# line of its own, as each `Set-Cookie` must be.\n#\n# What a client is handed: the status, reason and headers, and the body -\n# `body` whole, or `body_io` as it arrives inside `HTTP.open`'s block.\npub class Response\n # One more line of the field *name*, written on a line of its own: what\n # two `Set-Cookie`s need, since the \", \" a field's lines are joined with\n # is part of a cookie's date.\n def add_header(name : String, value : String) : Nil\n # The lines of each field written on more than one, by its name in\n # `headers`; nil when every field is on one line.\n def apart : Hash(String, Array(String)) | ::Nil\n # The body: whole. Inside `HTTP.open`'s block it is read here, the first\n # time it is asked for, and a body that cannot be read panics with why;\n # `body_io` answers that as a value.\n def body : String\n # The body as it arrives. For a response a program made, its string.\n def body_io : BodyReader\n # Whether the client undid the body's `gzip`, `deflate` or `zstd`\n # coding; the `Content-Encoding` and `Content-Length` that described it\n # are gone from the headers then.\n def decoded? : Bool\n # A header by name, as the server spelled it or otherwise: a field\n # name is case-insensitive (RFC 9110 §5.1), and `content-length` is as\n # common on the wire as `Content-Length`.\n def header(name : String) : String | ::Nil\n # A field's lines apart, in the order they came: `header` joins them\n # with \", \", and `Set-Cookie` is the field that join breaks (RFC 9110\n # §5.3), since a cookie's `Expires` date has a comma in it - two cookies\n # read as \"a=1; Expires=Wed, 21 Oct 2026 07:28:00 GMT, b=2\", which no\n # split takes back apart. Empty for a field that is not there.\n def header_values(name : String) : Array(String)\n def headers : Hash(String, String)\n # A response a client read: its body comes from *reader*, and *url* is\n # where it came from.\n def initialize(status : Int32, reason : String, headers : Hash(String, String), apart : Hash(String, Array(String)) | ::Nil, reader : BodyReader, url : String, decoded : Bool)\n def initialize(status : Int32, reason : String, headers : Hash(String, String), body : String, apart : Hash(String, Array(String)) | ::Nil = nil)\n # A response to write: the reason is the status's own, the headers the\n # caller's. `Response.new(200, \"hello\")` is the common shape.\n def initialize(status : Int32, body : String = \"\", headers : Hash(String, String) = {} of String => String)\n # A response whose body is *source*, copied out by the server and\n # closed after.\n def initialize(status : Int32, source : IyiIO, headers : Hash(String, String) = {} of String => String)\n # The body read whole into the response, which is answered; why not,\n # when it cannot be.\n def load : Response | Cancelled | SocketError | HTTPError\n def reason : String\n def source : IyiIO | ::Nil\n def status : Int32\n # A response whose body the block writes as it goes, chunked.\n def self.stream(status : Int32, headers : Hash(String, String) = {} of String => String, &writer : (BodyWriter -> Nil)) : Response\n def success? : Bool\n def takeover : Proc(IyiSocket | TlsStream, String, Nil) | ::Nil\n protected def takeover=(takeover : Proc(IyiSocket | TlsStream, String, Nil)) : Proc(IyiSocket | TlsStream, String, Nil)\n def tls_resumed=(resumed : Bool) : Bool\n # Whether the `https://` connection the answer came over resumed a TLS\n # session by a ticket an `HTTPClient` kept, rather than a full\n # handshake; false in the clear and for one a program made.\n def tls_resumed? : Bool\n # A 101 Switching Protocols with *headers* (`Upgrade` and the\n # protocol's own), after which the connection is the block's: it is\n # handed the connection - the socket, or under `serve(tls:)` the\n # `TlsStream` over it - and the bytes the client sent after its request,\n # and the connection closes when it returns.\n def self.upgrade(headers : Hash(String, String), &takeover : (IyiSocket | TlsStream, String -> Nil)) : Response\n # The URL the answer came from, after any redirects; nil for one a\n # program made.\n def url : String | ::Nil\n def writer : Proc(BodyWriter, Nil) | ::Nil\n protected def writer=(writer : Proc(BodyWriter, Nil)) : Proc(BodyWriter, Nil)\nend\n\npub struct Server\n # The headers a coded body goes with: the coding, and `Vary` so that a\n # cache keeps the answers apart. A `Content-Length` the block gave is\n # the plain body's, and goes.\n protected def self.coded(response : Response, coding : String) : Response\n # The coding *response* goes in to the client of *req*, or nil: one the\n # client takes (RFC 9110 §12.5.3, a `q` of 0 refusing it), for an\n # answer with a body and a type worth compressing, and no coding of its\n # own. A whole body goes `zstd` when the client's `q` for it is at least\n # its `q` for gzip, and `gzip` otherwise. The tie goes to zstd for the\n # server's time: measured on this machine, release builds, on\n # src/std/http.iyi (199,134 bytes) `Gzip.compress` wrote 54,484 bytes in\n # 5 ms and `Zstd.compress` 61,753 in 1 ms, and on CHANGELOG.md (1.5 MB)\n # 552,446 in 58 ms against 589,785 in 13 ms - a quarter of the time, for\n # an eighth more bytes at most. A stream - an `IyiIO` or a\n # `Response.stream` - goes gzip or as it is: std/compress's `Deflater`\n # writes each piece as it comes, where std/zstd's compressor takes its\n # input whole, and a frame a write would code each piece alone.\n def self.coding_for(req : Request, response : Response) : String | ::Nil\n # A string body compressed whole in *coding*, `zstd` or `gzip`, with\n # the headers that say so. `Zstd.compress`'s frame needs a window of\n # 1 MiB at most, inside the 8 MB RFC 9659 §3 holds an encoder to.\n protected def self.compressed(response : Response, coding : String) : Response\n # A response's head. `Content-Length` is *length*'s - the caller's own\n # line is checked against it, and kept only where no body follows it: a\n # HEAD, or a 304 that names the length a 200 would have (RFC 9110 §8.6) -\n # or `Transfer-Encoding: chunked` when *chunked*, or neither for a body\n # the connection's close ends. `Connection` says what the connection will\n # do next, except on an *upgrade*, whose own line says it. A 1xx, 204 or\n # 304 has no content (RFC 9110 §15), and writes neither a body nor a\n # length of its own (§8.6): the body was read by the client as the start\n # of the next answer, and `Content-Length: 0` on a 304 told a cache that\n # the resource had become empty. A field added on lines of its own\n # (`add_header`) is written a line each.\n def self.format_head(response : Response, head : Bool, close : Bool, length : Int64 | ::Nil, chunked : Bool, upgrade : Bool = false) : String\n # The response as it goes on the wire, whole: `format_head` for its\n # string body, and the body.\n def self.format_response(response : Response, head : Bool, close : Bool) : String\n # One connection: requests until the client is done. A request that is\n # not HTTP is answered with why and the connection closed, since the\n # bytes after it cannot be trusted to be a request either. Requests that\n # came in one read are read where they lie, one after another. A\n # connection that opens with HTTP/2's preface is served as HTTP/2. Under\n # *tls* the handshake comes first, and the connection ends with\n # close_notify. A connection this side wrote last on - an answer, a\n # refusal, a GOAWAY - closes by lingering (`Conn#close`), so the reset\n # a close with the client's bytes unread is cannot take what was\n # written from the client before it reads it. One that timed out or was\n # cancelled, with nothing of this side's waiting to be read, closes at\n # once, and so does one whose client said `close` and sent nothing past\n # its request (`serve_h1`).\n def self.handle(client : IyiSocket, handler : Proc(Request, Response), listener : IyiSocket | ServeStop | Nil = nil, idle : Hash(UInt64, Conn) | ::Nil = nil, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 0, tls : TlsServerConfig | TlsFrozenServerConfig | Nil = nil, request_timeout_ms : Int32 = 0) : Nil\n def initialize\n # A request's head - the text before its blank line - read and checked,\n # or why it is not one.\n def self.parse_head(text : String) : RequestHead | HTTPError\n # A request at *from* in `text`, if a whole one is there. No request and\n # no error means \"not yet\". `consumed` counts from the start of `text`.\n def self.parse_request(text : String, from : Int32 = 0) : ParsedRequest\n # Serve `listener` until it is closed, on *workers* threads: each\n # connection is a task on the pool `spawn_parallel` starts (SPEC.md\n # III.4.13), started by whichever worker is free and served there to\n # its end, while the thread that called this accepts. The block runs on\n # the connection's worker, once per request, on several workers at\n # once; what it answers is written back.\n # The close ends a connection waiting on its client too, for its next\n # request or for the rest of one, and one that is answering ends when its\n # answer is written: a keep-alive client that sat idle kept `serve` from\n # returning for as long as it stayed, and so did one that had sent\n # what has arrived of a request so far - one byte of it was enough.\n #\n # *stream_bodies*: the block reads each request's body from `body_io`\n # rather than being handed it whole. *compress*: an answer whose type\n # std/mime calls compressible goes zstd or gzip to a client that\n # accepts one (`coding_for`).\n #\n # *timeout_ms*: a request's head must have arrived within it, counted\n # from when the server starts waiting for one - a kept-alive connection's\n # idle time included - and no read of a body nor write of an answer may\n # wait longer; a connection that does is closed. Without it, a client\n # that sends a byte of its head every few seconds (Slowloris) holds a\n # connection and its fiber for as long as it likes, and so does one that\n # reads none of its answer. A minute by default, nginx's\n # `client_header_timeout`; 0 for none. A connection handed over by\n # `Response.upgrade` waits as its protocol likes.\n #\n # *request_timeout_ms*: the whole request - head and body - must have\n # arrived within it, counted from the same moment as *timeout_ms*, as\n # Go's `ReadTimeout` beside its `ReadHeaderTimeout`; the head is held to\n # the sooner of the two. Without it a body sent a byte every few seconds\n # was bounded per read alone, and held its connection for as long as it\n # dripped. A block that reads the body itself (*stream_bodies*) reads\n # under it until the block returns, and what it leaves unread is read\n # past under it too. Over HTTP/2 the wait for each request and its body\n # is held to it on the connection the same way, the head to the sooner\n # of the two. Five minutes by default, which a 64 MiB body (`MAX_BODY`)\n # meets at 224 KB/s; a server taking large uploads over slow links\n # raises it. 0 for none. A connection past it is closed, unanswered, as\n # one past *timeout_ms* is.\n #\n # *max_connections*: at most this many connections are served at once;\n # 1024 by default, 0 for no limit. Past it the server stops accepting\n # until one ends, so the next connection waits in the kernel's listen\n # queue - queued, not refused, as Go's `netutil.LimitListener` does - and\n # is served in its turn; a client that gives up waiting goes unserved.\n # Each served connection holds its place until it closes, a kept-alive\n # one between requests too, which *timeout_ms* bounds. The process's\n # own limit on descriptors may come first.\n #\n # *tls*: each connection is TLS (std/tls's `TlsServer`, 1.3 or 1.2) under this\n # configuration, its handshake held to *timeout_ms* too and run on the\n # worker that serves it; one whose handshake fails is closed, the\n # client told why by an alert. Inside it a connection that opens with\n # HTTP/2's preface - as a client whose ALPN chose `h2` does, when\n # `tls.alpn` offers it - is served HTTP/2, and any other HTTP/1.1. A\n # `Response.upgrade` block over TLS is handed the `TlsStream`, so a\n # WebSocket is `wss://`. On more than one worker the config is a\n # `TlsFrozenServerConfig` - `config.frozen` - since every worker reads\n # it at once: its ticket keys are one set behind a lock, so a ticket a\n # connection on one worker was sent resumes on any other, and an\n # identity source given to `frozen` is a block the compiler checked. A\n # `TlsServerConfig` itself is refused at the call (\"Server.serve hands\n # `tls : TlsServerConfig` to other threads, several at once, and it is\n # not Share: ... Pass `config.frozen` ...\"), not frozen here: `frozen`\n # is a copy, so a property assigned after the server started would be\n # served by no worker and nothing would say so, and a config with an\n # `identity_source` cannot be frozen without its source given again as\n # a block the compiler checks. Under `workers: 1` either is served.\n #\n # *workers*: the pool's size, `IyiWorkers.count` by default - `IYI_WORKERS`\n # from the environment, else the cores this process may run on. The\n # pool is the process's one, and it does not grow or shrink once it has\n # started, so a count other than the one it started with panics. Every\n # worker runs the block at once, so the compiler checks it where it is\n # written as it checks a parallel task's (`@[Share]`, SPEC.md III.4.4):\n # every value it captures must be `Share` - a table behind a `Mutex`\n # (std/mutex), a count in an `Atomic`, a `SharedRouter` (std/router) -\n # every captured local assigned once, and the constants and class\n # variables it names `Share`. The refusal names the variable, why its\n # type is not `Share`, and the two ways out: the lock, or `workers: 1`.\n # `workers: 1`, written as the literal, serves on the calling thread\n # alone - each connection a task beside the accept loop, as `spawn`\n # makes one - and its block is not checked: a block that calls a\n # `Router`, appends to an `Array` or keeps a handle that is not `Share`\n # is served there. A count in a variable is checked whatever it holds,\n # since the compiler cannot know it is 1. A count of 1 from the default\n # (`IYI_WORKERS=1`, one core) serves on the calling thread too.\n #\n # A connection stays on the worker that started it (III.4.13, part 1):\n # connections spread over the workers free as they arrive, and one kept\n # alive is not moved later. A block that computes without parking holds\n # its worker meanwhile, as any task does. The listener is the calling\n # thread's, and a close of it is made there: a block that stops the\n # server sends on a `Channel` a task beside the server receives from,\n # and that task closes it. At the stop each connection waiting on its\n # client is closed on its own worker, sent close_notify there first\n # under *tls*.\n def self.serve(listener : IyiSocket, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count, &handler : (Request -> Response)) : Nil forall C\n # `serve` with a value for its handler rather than a block: a\n # `SharedProc`, a `SharedRouter` (std/router), or one of the program's\n # own, whose `call(request : Request) : Response` answers each request,\n # as Go's `http.Handler` does. On more than one worker it must be\n # `Share` (SPEC.md III.4.4), since every connection on every worker\n # calls the one value at once: the compiler refuses one that is not at\n # the argument, naming the field that failed and the two ways out - a\n # count in an `Atomic`, a pool or a cache behind a `Mutex`, a\n # `SharedRouter` for a `Router`, or `workers: 1`, under which any value\n # with that `call` is served on the calling thread, a `Router`\n # included. What the check does not see is the edge `spawn_parallel`\n # has too: a method the handler calls that writes a module's state. A\n # struct handler is copied into each connection's task, and an `Atomic`\n # in it with it: what the connections share is a class, or held by\n # one.\n #\n # The rest is the block form's: *stream_bodies*, *compress*,\n # *timeout_ms*, *tls* (`config.frozen` on more than one worker),\n # *request_timeout_ms*, *max_connections* and *workers*, HTTP/2 by its\n # preface, `Response.upgrade`, and a close of *listener* - on the\n # thread that called this - ending every connection waiting on its\n # client and returning once the others have answered.\n def self.serve(listener : IyiSocket, handler : H, stream_bodies : Bool = false, compress : Bool = false, timeout_ms : Int32 = 60000, tls : C = nil, request_timeout_ms : Int32 = REQUEST_TIMEOUT, max_connections : Int32 = MAX_CONNECTIONS, workers : Int32 = IyiWorkers.count) : Nil forall H, C\n protected def self.writer_for(socket : IyiSocket | TlsStream, chunked : Bool, gzip : Bool) : BodyWriter\nend\n\n# A TLS connection used as a socket is: read into a buffer, written to,\n# closed, its timeouts set - by a `Conn`, a `BodyWriter`, and a connection\n# handed over by an upgrade (`wss://` at either end). What TLS refuses is a\n# `SocketError` carrying TLS's own sentence (`layer_reason`), so answers\n# keep the types they have over a plain socket.\npub class TlsStream\n # The socket closed at once, nothing sent or read first.\n def abort : Nil\n # close_notify, the sending half shut, and what the peer still sends\n # read past for a bounded time before the socket closes\n # (`TlsConnection#close`).\n def close : Nil\n # close_notify and the sending half shut; reads go on until the peer's\n # own (`TlsConnection#close_write`).\n def close_write : SocketError | ::Nil\n def closed? : Bool\n def initialize(tls : TlsConnection, host : String)\n # The peer's certificate chain, leaf first, as the handshake checked it:\n # the server's, at a client; the client's, at a server that asked.\n def peer_certificates : Array(Certificate)\n # Up to *count* bytes into *buffer*: 0 once the peer closed with\n # close_notify.\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n # Up to *max_bytes* as a string, what is held first: \"\" once the peer\n # closed with close_notify. `IyiSocket#read`'s form, for a block an\n # upgrade hands either.\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # How long a read or a write of the connection under it may wait.\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n # Whether the handshake resumed a session (`TlsConnection#resumed?`).\n def resumed? : Bool\n def write(text : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\n# What `HTTP.upgrade` answers: the 101, and the connection, which is the\n# caller's now - the socket, and the bytes the server sent after its head.\npub class Upgraded\n def initialize(response : Response, socket : IyiSocket | TlsStream, leftover : String)\n def leftover : String\n def response : Response\n # The connection: the socket, or for `https://` the `TlsStream` over it.\n def socket : IyiSocket | TlsStream\nend\n\nimpl Error for HTTPError\n def message : String\nend\n" }, { "name": "std/http_proxy", @@ -538,7 +538,7 @@ { "name": "std/socket", "path": "src/std/socket.iyi", - "sourceSha256": "fe78564802a8a020e4fff120d2f676e30b86a1f5b31611d71c23f2f5b1ffed2f", + "sourceSha256": "eff1d8cd1e03a482ece90494c957df9f6d962808444e75f56a8b4843b2d5c894", "surface": "module std/socket\n\n# What a resolver answers and is built from, under the names a program\n# that connects by name already imports from here.\npub alias DnsError = Std::Dns::DnsError\n\n# An encrypted way to a name server, asked by a resolver `Resolver#encrypted`\n# made instead of its plaintext servers: std/secure_dns's `DnsOverTls`\n# (RFC 7858) and `DnsOverHttps` (RFC 8484). The trait is here and they are\n# there because they speak std/tls and std/http, which import this module,\n# and R-1 refuses the cycle - as std/http's `CookieStore` is std/cookie's\n# to implement.\npub trait DnsUpstream\n # Its connections closed; the next exchange opens new ones.\n abstract def close : Nil\n # The answer to each of *queries*, in their order, each one whole\n # message as it came; or why there are none. *timeout_ms* bounds each\n # wait on the server. An answer is matched to its query by the\n # transport - by ID over one connection, by request over HTTP - and the\n # resolver then reads it as it reads a datagram.\n abstract def exchange(queries : Array(Bytes), timeout_ms : Int32) : Array(Bytes) | DnsError | Cancelled\n # How a message names it: `dns.test (127.0.0.1:853) over TLS`.\n abstract def label : String\n # Whether its queries carry the ID 0: DNS over HTTPS's, which RFC 8484\n # 4.1 asks for so that an HTTP cache sees one request for one question.\n # The answer must carry the ID the query did all the same.\n abstract def zero_id? : Bool\nend\n\n# Either family's address: what a resolver answers for a name.\npub alias IPAddress = IPv4Address | IPv6Address\n\npub struct IPv4Address\n def b0 : UInt8\n def b1 : UInt8\n def b2 : UInt8\n def b3 : UInt8\n def initialize(b0 : UInt8, b1 : UInt8, b2 : UInt8, b3 : UInt8)\n def to_s : String\nend\n\n# Sixteen bytes in network order, held as two words: `high` is bytes 0 to\n# 7. Written the RFC 5952 way - lowercase, no leading zeros, the longest\n# run of two or more zero groups as `::`, the first of two equal runs -\n# and an IPv4-mapped address with its dotted tail, `::ffff:192.0.2.1`.\npub struct IPv6Address\n # Byte *index*, 0 to 15.\n def [](index : Int32) : UInt8\n # The 16-bit group *index*, 0 to 7. Not `group`: that name is the task\n # group's in iyi (SPEC.md III.4.9).\n def hextet(index : Int32) : Int32\n def high : UInt64\n def initialize(high : UInt64, low : UInt64)\n def low : UInt64\n # The sixteen bytes at *bytes*, network order.\n def self.new(bytes : Pointer(UInt8)) : IPv6Address\n def to_s : String\nend\n\npub class IyiSocket\n # Room for any family's `sockaddr`, zeroed, so a unix path the kernel\n # did not end with a NUL still ends.\n def self.__address_room : Pointer(UInt8)\n # A `sockaddr` the kernel filled, as text: dotted IPv4, IPv6 written as\n # RFC 5952 has it, or a unix socket's path.\n def self.__address_text(addr : Pointer(UInt8), family : Int32) : String\n # What every connected and accepted socket starts with: TCP_NODELAY, on\n # TCP; a unix socket has no Nagle.\n def self.__send_at_once(fd : Int32, family : Int32) : Nil\n def self.__sys3(number : Int64, a : Int64, b : Int64, c : Int64) : Int64\n def self.__sys5(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64) : Int64\n def self.__sys6(number : Int64, a : Int64, b : Int64, c : Int64, d : Int64, e : Int64, f : Int64) : Int64\n def self.__sys_accept(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32), family : Int32) : Int32\n def self.__sys_addr_size(family : Int32) : Int64\n def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n # Every close of a socket in this file goes through here. `__iyi_close` is\n # the descriptor close, and on Windows a socket is not a descriptor:\n # `CloseHandle` on a SOCKET releases the kernel handle and leaves Winsock's\n # half of it standing, and the graceful close that makes a peer's read\n # answer EOF rather than a reset is `closesocket`'s.\n def self.__sys_close(fd : Int32) : Nil\n # A raw syscall's negative errno, as the number.\n def self.__sys_code(result : Int64) : Int32\n # *ms* is `connect_to`'s wait's: the call answers EINPROGRESS at once.\n def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), family : Int32, ms : Int32) : Int32\n def self.__sys_connects_blocking?(family : Int32) : ::Bool\n def self.__sys_dgram_connect(fd : Int32, addr : Pointer(UInt8), family : Int32) : Int32\n def self.__sys_dgram_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n # The resolver's datagram socket: see the darwin arm. The raw call\n # answers the negative errno, which `__sys_code` reads.\n def self.__sys_dgram_socket(family : Int32) : Int32\n def self.__sys_dgram_wait(fd : Int32, ms : Int32) : ::Bool | Cancelled\n def self.__sys_getpeername(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32\n def self.__sys_in_progress?(result : Int32) : ::Bool\n def self.__sys_listen(fd : Int32, backlog : Int32) : Int32\n def self.__sys_no_sigpipe(fd : Int32) : Nil\n def self.__sys_nodelay?(fd : Int32) : ::Bool\n # `ppoll` on one descriptor for *ms* milliseconds - a timespec, or none\n # for -1 - and no signal mask. 0 when the time ran out.\n def self.__sys_poll(fd : Int32, events : Int32, ms : Int32) : Int32\n def self.__sys_recv(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64\n # MSG_NOSIGNAL (0x4000): a write to a connection the peer closed answers\n # EPIPE instead of killing the process with SIGPIPE.\n def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64, ms : Int32) : Int64\n # TCP_NODELAY (1) at IPPROTO_TCP (6): Nagle's algorithm off when *on*.\n def self.__sys_set_nodelay(fd : Int32, on : ::Bool) : Int32\n # F_GETFL (3), then F_SETFL (4) with O_NONBLOCK (0x800) added.\n def self.__sys_set_nonblock(fd : Int32) : Int32\n def self.__sys_set_reuseaddr(fd : Int32) : Int32\n # IPV6_V6ONLY (26) at IPPROTO_IPV6 (41).\n def self.__sys_set_v6only(fd : Int32) : Int32\n def self.__sys_shutdown_write(fd : Int32) : Int64\n # SO_ERROR (4) at SOL_SOCKET (1): what a connect that was in progress\n # ended with, 0 when it went through.\n def self.__sys_sock_error(fd : Int32) : Int32\n def self.__sys_socket(family : Int32) : Int32\n # A raw syscall answers the negative errno: EAGAIN is 11, EINPROGRESS 115.\n def self.__sys_would_block?(result : Int64) : ::Bool\n def accept : IyiSocket | Cancelled | SocketError\n # The order `connect` tries a name's addresses in (RFC 8305 section 4):\n # one family's first address, then the other's, alternating, each family\n # in the resolver's order, and the longer family's rest at the end.\n # IPv6 goes first when this machine has a route to the first IPv6\n # address - RFC 6724's first rule, asked the way glibc's getaddrinfo\n # asks it, by connecting a UDP socket, which sends nothing - and IPv4\n # first when it has none, so a machine without IPv6 does not start every\n # connect with an attempt that cannot leave it. A name of one family is\n # left in the resolver's order, and asks nothing.\n def self.attempt_order(addresses : Array(IPAddress)) : Array(IPAddress)\n def self.await_readable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n def self.await_writable(fd : Int32, ms : Int32 = -1) : ::Bool | Cancelled\n # A timeout as the waits take it: milliseconds, -1 for none.\n def self.bound(ms : Int32 | ::Nil) : Int32\n protected def bound_path(path : String) : Nil\n # *path* with the NUL a system call reads to.\n def self.c_path(path : String) : Pointer(UInt8)\n def close : Nil\n # The sending half ended, the receiving half kept: the peer reads the\n # end of the stream after what was written, and this end goes on reading\n # what the peer still sends until its own end. `shutdown(SHUT_WR)` on\n # POSIX, `shutdown(SD_SEND)` on Windows. A socket that is not connected\n # answers the platform's refusal as a `SocketError`; `close` is still\n # what gives the socket back.\n def close_write : SocketError | ::Nil\n def closed? : ::Bool\n # A host name is resolved by the machine's resolver (`Resolver.system`);\n # a name that does not resolve is a `SocketError` whose `unresolved?` is\n # true and whose sentence names the host. It panicked, \"cannot resolve\n # address: \\\"example.com\\\"\", for every name but `localhost`, so\n # `HTTP.get(\"http://example.com/\")` could not run.\n #\n # A name with more than one address is raced, Happy Eyeballs v2 (RFC\n # 8305): the first address is tried, the next `ATTEMPT_DELAY_MS` later\n # without waiting for the first to fail, or at once when an attempt\n # fails, and so on down the list (`attempt_order`'s: IPv6 first when\n # this machine has a route to it, the families alternating). The first\n # handshake to finish wins, and every other attempt is cancelled and its\n # socket closed before `connect` answers. The addresses were tried one\n # after another, IPv4 first, each with the whole timeout: a name whose\n # first address dropped SYNs cost all of *timeout_ms* - or the kernel's\n # two minutes of retries, unbounded - before the second was tried. Now\n # it costs the delay: 250 ms to a name whose first address is a full\n # listen queue and whose second listens (`bench/std_dns_exercise.sh`,\n # under a 5,000 ms bound).\n #\n # `localhost` is raced the same way, 127.0.0.1 and then ::1, without\n # asking the routing table: a listener here binds `localhost` at\n # 127.0.0.1, so it goes first, and Windows, which names ::1 first, has\n # dev servers that bind ::1 alone. This replaces the rule that tried\n # ::1 only after 127.0.0.1 refused: Windows takes 2 s to refuse a\n # connect, so a ::1-only listener was reached in 2017 ms and a port\n # nobody holds refused in 4059 ms, where now ::1 starts 250 ms after\n # 127.0.0.1 whatever 127.0.0.1 is still doing.\n #\n # *timeout_ms* bounds the whole connect, every attempt together (-1: as\n # long as the kernel waits, which on Linux is about two minutes of SYN\n # retries for a host that drops them). It bounded each address's\n # handshake, which a race has no use for: an attempt started 500 ms in\n # would have run 500 ms past the caller's bound. A connect that runs out\n # is a `SocketError` whose `timed_out?` is true. The name's lookup is\n # outside it, held by the resolver's own `timeout_ms` and `attempts`.\n #\n # A name none of whose addresses connects is one `SocketError` that\n # names each address in the order tried and how its attempt ended,\n # `tried`: `cannot connect to example.com:443: [2001:db8::1]:443 timed\n # out; 192.0.2.1:443 connection refused`. It answered the first\n # address's failure alone. Its `code` is the first attempt's, or a\n # timeout's when the time ran out first. A name with one address is\n # connected to as an address is, and fails with that address's error.\n def self.connect(host : String, port : Int32, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The same, with a host name resolved by *resolver*: name servers, a\n # hosts file and search list of the program's choosing, rather than the\n # machine's. An address and `localhost` are what they are either way.\n def self.connect(host : String, port : Int32, resolver : Resolver, timeout_ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # A connect to *addr*, waiting at most *ms* milliseconds for the\n # handshake (-1: as long as the kernel does). The resolver's TCP query\n # and `connect` to an address are its callers with a bound; a race's\n # attempts wait as the kernel does, and the race keeps the bound. The\n # bound reaches `__sys_connect` as well as the wait after it, because\n # on Windows that call is the wait: it parks in `ConnectEx` until the\n # handshake is over.\n def self.connect_to(addr : Pointer(UInt8), family : Int32, peer : String, ms : Int32 = -1) : IyiSocket | Cancelled | SocketError\n # The stream socket listening at *path*.\n def self.connect_unix(path : String) : IyiSocket | Cancelled | SocketError\n # *host* and *port* as a message names them: an IPv6 literal in its\n # brackets, as a URL writes it. Bare, `::1:57456` was an address with\n # no port, and `ff02::1:80` another address altogether.\n def self.endpoint(host : String, port : Int32) : String\n # The address family, the platform's number for it: an accepted\n # connection is its listener's, and on Windows the socket `AcceptEx`\n # fills in has to be made in it. With `fd`, what makes the socket again\n # on another thread (std/http's `Server.serve`, on its pool).\n def family : Int32\n def fd : Int32\n def initialize(fd : Int32, family : Int32 = AF_INET)\n def self.listen(host : String, port : Int32, backlog : Int32 = 128) : IyiSocket\n def self.listen(port : Int32, backlog : Int32 = 128) : IyiSocket\n # A stream socket at *path*. The path must not exist: a socket file an\n # earlier run left behind is refused rather than taken over, since\n # which program owns it is not something a bind can know. `close`\n # removes the file.\n def self.listen_unix(path : String, backlog : Int32 = 128) : IyiSocket\n def local_address : String\n # The port sits at the same two bytes of a `sockaddr_in` and a\n # `sockaddr_in6`.\n def local_port : Int32\n # `localhost` in any case, and with the one trailing dot of a name\n # written fully qualified: a host name's case is not part of it (RFC\n # 4343), and getaddrinfo, Python and curl all answer 127.0.0.1 for\n # `LOCALHOST` and `localhost.`. Matched exactly, these were \"cannot\n # resolve address: \\\"LOCALHOST\\\"\", and so were `Localhost` and\n # `localhost.`.\n def self.localhost?(host : String) : ::Bool\n # What `localhost` stands for, in the order `connect` tries it.\n def self.loopback : Array(IPAddress)\n def self.make_sockaddr_in(ip : IPv4Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_in6`: family, port, a zero flow label, the address, a zero\n # scope. The family's number and where it sits are the platform's.\n def self.make_sockaddr_in6(ip : IPv6Address, port : Int32) : Pointer(UInt8)\n # A `sockaddr_un`: the family, then the path and its NUL. The room for\n # the path is the platform's - 104 bytes on darwin, 108 elsewhere - and a\n # longer one is refused rather than cut, since a cut path is another\n # path.\n def self.make_sockaddr_un(path : String) : Pointer(UInt8)\n # Whether a small write goes at once (TCP_NODELAY) rather than waiting\n # for an earlier one's acknowledgement, as Nagle's algorithm (RFC 896)\n # has it wait. A connected or accepted TCP socket here sends at once, as\n # Go's net package sets its own; `nodelay = false` turns Nagle back on\n # for a program that writes in pieces it would rather have joined. With\n # Nagle on, a write behind an unacknowledged one waited out the peer's\n # delayed ACK, 40 ms on Linux: measured on loopback, --release, medians\n # of 200, a GET on a new https:// connection took 82.89 ms between\n # std/http's ends and 42.00 to OpenSSL's s_server, and takes 1.07 and\n # 1.10 now; wrk against a server answering a streamed body went from\n # 1,211 to 10,702 requests a second. A unix socket has no Nagle to turn\n # off: it answers false and is left alone.\n def nodelay=(on : ::Bool) : ::Bool\n # Whether TCP_NODELAY is on, as the socket's option reads.\n def nodelay? : ::Bool\n # Dotted-decimal IPv4 only: four octets of 0 to 255, no leading zero\n # (`\"010.1.1.1\"` is refused; glibc's `inet_aton` reads `010` as octal 8,\n # CVE-2021-29921, so it must not quietly be 10 here), plus `localhost`.\n # The refusal inspects the host so a stray space shows.\n def self.parse_ip(host : String) : IPv4Address\n # Eight groups of up to four hex digits, `::` once for a run of zero\n # groups, and a dotted IPv4 tail for the last two (`::ffff:1.2.3.4`). A\n # zone (`fe80::1%eth0`) is refused by name: it names an interface, and\n # this parser has no way to ask the platform which.\n def self.parse_ipv6(host : String) : IPv6Address\n def read(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def read(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The dotted-decimal reading alone, with the refusal's sentence as the\n # answer instead of a panic: the resolver reads the addresses a hosts\n # file and a resolv.conf write, and a line it cannot read is skipped\n # there, not the end of the program.\n def self.read_ipv4(host : String) : IPv4Address | String\n # The reading alone, the refusal's sentence as the answer: see\n # `read_ipv4`.\n def self.read_ipv6(host : String) : IPv6Address | String\n # The bounds on a read's and a write's wait, in milliseconds; nil waits\n # as long as it takes.\n def read_timeout_ms : Int32 | ::Nil\n def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\n def recv(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def recv(max_bytes : Int32 = 4096) : String | Cancelled | SocketError\n # The other end's address and port, as `local_address` and\n # `local_port` give this end's: what a server logs a client by. A unix\n # socket's peer has a path at most, often none (\"\"), and no port. Nil\n # when the other end is gone - the connection was reset or never made -\n # which is the peer's doing and no fault of the program's.\n def remote_address : String | ::Nil\n def remote_port : Int32 | ::Nil\n # Every address *host* stands for, by the machine's resolver, in the\n # resolver's order (see `Resolver#resolve`); `connect` tries them in\n # `attempt_order`'s.\n def self.resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def send(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def send(contents : String) : Int32 | Cancelled | SocketError\n # A host and port as the address a call takes, and its family: an IPv6\n # literal bare or in brackets, anything else IPv4.\n def self.sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), Int32)\n # This socket as a stream (`SocketIO`), the same one every time, so\n # what one read took ahead the next reads. It was a stream over the\n # descriptor that read a socket with nothing yet to read as failed.\n def to_io : Std::Socket::SocketIO\n def to_unsafe : Int32\n # Whether this is a unix socket, which has a path where TCP has a port:\n # `remote_port` and `local_port` refuse it. std/tls asks before it keys\n # a session's tickets by the port they came from.\n def unix? : ::Bool\n def write(buffer : Pointer(UInt8), count : Int32) : Int32 | Cancelled | SocketError\n def write(contents : String) : Int32 | Cancelled | SocketError\n def write_timeout_ms : Int32 | ::Nil\n def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil\nend\n\npub alias ResolvConf = Std::Dns::ResolvConf\n\n# A stub resolver: a host name's addresses, from a hosts file and then\n# from name servers, as the C library's resolver finds them - but iyi's,\n# so asking costs no library (see the header of this file).\n#\n# `Resolver.system` is the machine's: `/etc/resolv.conf` and `/etc/hosts`\n# on Linux and darwin, the registry and\n# `%SystemRoot%\\System32\\drivers\\etc\\hosts` on Windows (see\n# `__sys_dns_config` for what the registry can and cannot say). A\n# resolver built with `new` or `from_config` asks exactly what it was\n# given, which is how a test asks a server it runs itself.\n#\n# `resolve` answers every address for a name, IPv4 first and then IPv6,\n# each family in the order the hosts file or the server gave it;\n# `connect` races them in `IyiSocket.attempt_order`'s order:\n#\n# 1. An address, or `localhost` (127.0.0.1 and ::1: RFC 6761 6.3 keeps it\n# off the network), is itself. Anything that is not a host name is\n# `invalid_name?`.\n# 2. The hosts file: every line naming the host, in any case. A name\n# found there is not asked about.\n# 3. The names the search list and `ndots` make of it (`DnsName.candidates`),\n# in order. Each is asked for A and then AAAA records; NXDOMAIN or no\n# records moves on to the next, and any other failure ends the lookup\n# with that failure - a server that timed out on `db.corp.example` must\n# not turn the answer into `db.example`'s (musl's rule; glibc moves on).\n#\n# A and AAAA are asked one after the other, and `connect` starts its race\n# once both have answered. RFC 8305 section 3 asks both at once and\n# starts connecting on the A answer when AAAA is 50 ms behind it; that\n# makes a lookup answer in pieces, and saves the AAAA query's round trip\n# at most - 5 to 65 ms uncached for five names through systemd-resolved\n# on the machine this was written on, 0 to 1 ms cached (dig's query\n# times) - where the race saves what an address that drops SYNs costs,\n# the whole timeout. Asked in turn, an NXDOMAIN for A spares the AAAA\n# query, as it does for most of a search list's candidates.\n#\n# A query is one UDP datagram from a fresh socket (so a fresh source\n# port), with an ID from `Random::Secure`, to each server in turn, each\n# waited for `timeout_ms`, and the round repeated `attempts` times. A\n# datagram that does not carry the ID and the question is ignored and the\n# wait goes on; one from any other address never arrives, because the\n# socket is connected to the server. An answer with TC set is asked again\n# over TCP. SERVFAIL, REFUSED and a server that does not answer move on to\n# the next server; NXDOMAIN is the answer. An answer that cannot be read\n# is ignored like a forgery, and is the failure reported if nothing\n# better arrives before the timeout.\n#\n# Encrypted:\n# `encrypted(upstreams)` is the same resolver - hosts file, search list,\n# `ndots`, timeout and attempts - asking `DnsUpstream`s instead of its\n# plaintext servers: DNS over TLS or over HTTPS, std/secure_dns's\n# `DnsOverTls` and `DnsOverHttps`. Each upstream is asked in turn, the\n# round repeated `attempts` times, as the servers are; A and AAAA go in\n# one exchange. An answer over one is held to more than a datagram is:\n# the ID and the question as ever, and no TC and no byte past the message,\n# since a stream has room for any answer and its frame is the message.\n# The plaintext servers are not asked at all, not even when every\n# upstream is down, unless the caller passes *plaintext_fallback*: RFC\n# 8310 5's strict profile, where a lookup that cannot be private fails,\n# is the default, because falling back hands the very question the\n# program meant to keep private to anyone on the path, and a program\n# that would rather have an answer says so. With *plaintext_fallback*\n# (the opportunistic profile) the plaintext servers are asked only when\n# no upstream could be: one that answered, SERVFAIL included, has spoken.\n# One task at a time per resolver with upstreams: an upstream holds its\n# connection, as an `HTTPClient` does.\npub class Resolver\n protected def ask_upstreams_instead(upstreams : Array(DnsUpstream), plaintext_fallback : Bool) : Nil\n def attempts : Int32\n # *records* by ascending priority, those of one priority shuffled.\n def self.by_priority(records : Array(SvcbRecord)) : Array(SvcbRecord)\n # Every upstream's connections closed.\n def close : Nil\n # This resolver asking *upstreams* instead of its plaintext servers,\n # which are asked only with *plaintext_fallback*, and then only when no\n # upstream could be (see the class).\n def encrypted(upstreams : Array(DnsUpstream), plaintext_fallback : Bool = false) : Resolver\n def encrypted(upstream : DnsUpstream, plaintext_fallback : Bool = false) : Resolver\n # The machine's resolver - its hosts file and search list - asking\n # *upstreams*; its name servers only with *plaintext_fallback*.\n def self.encrypted(upstreams : Array(DnsUpstream), plaintext_fallback : Bool = false) : Resolver\n def self.encrypted(upstream : DnsUpstream, plaintext_fallback : Bool = false) : Resolver\n # A resolver from a parsed resolv.conf and a hosts file's text. A\n # `nameserver` line this resolver cannot use - an IPv6 address with a\n # zone, a typo - is skipped as glibc skips one; with none left, the\n # servers a machine without a configuration is assumed to run.\n def self.from_config(config : ResolvConf, hosts : String = \"\") : Resolver\n # A resolver from a resolv.conf and a hosts file at these paths. A file\n # that is not there reads as empty, as it does to the C library.\n def self.from_files(resolv_conf : String, hosts : String) : Resolver\n # The hosts file's text, read before any server is asked.\n def hosts : String\n # The HTTPS records (RFC 9460 section 9) of the origin `https://host:port`,\n # by SVCB resolution (section 3): the question is *host* for port 443\n # and `_port._https.host` for any other (section 9.1); an AliasMode\n # answer is followed to its target's HTTPS records, at most\n # `SvcbAnswer::MAX_ALIASES` of them, and CNAMEs on the way as `txt`\n # follows them; and the ServiceMode records found are answered by\n # priority, those of one priority in a random order (section 2.4.1).\n # The name is asked as written, as for `txt`, over the upstreams when\n # the resolver has them.\n #\n # Of an AliasMode set one record is followed, at random where there are\n # several (section 2.4.2), and any ServiceMode record beside it is\n # passed over (section 2.4.1). A target that has no HTTPS records ends\n # resolution there, with none: the answer's `name` is the target, which\n # section 3 still has a client connect to. An answer with a record\n # `SvcbRecord.parse` refuses is the whole set refused, `malformed?`, as\n # section 2.2 has it; so is a chain longer than the bound. NXDOMAIN and\n # NODATA for the origin's own name are `not_found?` and `no_address?`,\n # as they are for an address.\n def https_records(host : String, port : Int32 = 443) : SvcbAnswer | DnsError | Cancelled\n # *nameservers* are addresses, each with an optional port:\n # `\"192.0.2.1\"`, `\"192.0.2.1:5353\"`, `\"2001:db8::1\"`,\n # `\"[2001:db8::1]:5353\"`. One that is not is refused here, by name: it is\n # the program's own mistake. None at all is a resolver that reads only\n # *hosts*.\n def initialize(nameservers : Array(String), search : Array(String) = [] of String, ndots : Int32 = 1, timeout_ms : Int32 = 5000, attempts : Int32 = 2, hosts : String = \"\")\n # What NODATA for *type* says the name lacks: its records, by name, for\n # the types asked other than as addresses.\n def self.missing(type : Int32) : String\n def nameservers : Array(String)\n def ndots : Int32\n # The monotonic clock in milliseconds: the runtime's, which its sleep\n # queue keeps deadlines by. A target without the runtime has no socket\n # to wait on either (`__sys_dgram_socket` refuses there), so nothing\n # there reads a time.\n def self.now_ms : Int64\n # The answer to one question over plaintext: each server in turn, the\n # round repeated `attempts` times, until one answers NOERROR or\n # NXDOMAIN. The failure answered otherwise is the last server's.\n protected def plaintext(name : String, type : Int32) : Tuple(DnsMessage, String) | DnsError | Cancelled\n # Whether `nameservers` are asked when no upstream could be (RFC 8310's\n # opportunistic profile); false, the strict one, by default.\n def plaintext_fallback : Bool\n def self.rcode_name(rcode : Int32) : String\n # What an answer's RCODE makes of it: the message for NOERROR, and the\n # failure it says otherwise.\n def self.read_rcode(message : DnsMessage, name : String, label : String) : Tuple(DnsMessage, String) | DnsError\n # An answer that came over a stream, read: see `ask_upstream`.\n def self.read_streamed(bytes : Bytes, id : UInt16, name : String, type : Int32, label : String) : Tuple(DnsMessage, String) | DnsError\n # Every address *host* stands for: see the class's comment for the\n # order things are asked in.\n def resolve(host : String) : Array(IPAddress) | DnsError | Cancelled\n def search : Array(String)\n # A name server written as an address with an optional port, or the\n # sentence saying why it is not one.\n def self.server(text : String) : NameServer | String\n # The machine's resolver, read afresh each time: a configuration the\n # network manager rewrote is the one the next lookup uses.\n def self.system : Resolver\n def timeout_ms : Int32\n # The TXT records of *name*, each one's character-strings joined\n # (`DnsRecord.character_strings`), in the order the answer gave them,\n # CNAMEs followed as `resolve` follows them. The name is asked as\n # written - no hosts file, no search list - since a TXT name\n # (`_acme-challenge.example.com`, a domain's SPF) is written in full.\n def txt(name : String) : Array(String) | DnsError | Cancelled\n # The encrypted upstreams asked instead of `nameservers`; none for a\n # plaintext resolver.\n def upstreams : Array(DnsUpstream)\nend\n\n# A connection's own failure: which operation, and the platform's error\n# number for why - or, for a host name that could not be turned into an\n# address, the resolver's `DnsError`, and no number (`code` 0); or, for a\n# protocol layered on the connection (TLS), that protocol's own sentence,\n# and no number either; or, for a name whose addresses were all tried and\n# none connected, every one of them with how its attempt ended.\npub struct SocketError\n def code : Int32\n # The peer is gone: it reset the connection, or closed it and the write\n # found the pipe broken, or the connection was aborted.\n def disconnected? : ::Bool\n # Why the host's name did not resolve; nil for every other failure.\n def dns_error : DnsError | ::Nil\n # A connect to a name none of whose addresses took the connection:\n # *code* is what `refused?` and `timed_out?` read, and *tried* the\n # sentence naming every address.\n def self.every_address(operation : String, code : Int32, tried : String) : SocketError\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil, tried : String | ::Nil)\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil, layer_reason : String | ::Nil)\n def initialize(operation : String, code : Int32, dns_error : DnsError | ::Nil)\n def initialize(operation : String, code : Int32)\n # What the protocol over the connection said went wrong; nil otherwise.\n def layer_reason : String | ::Nil\n # A failure of the protocol over the connection, in its words: `cannot\n # read from example.com: tls: a record that does not decrypt`.\n def self.layered(operation : String, reason : String) : SocketError\n def operation : String\n def reason : String\n def refused? : ::Bool\n # A wait for the peer that ran past its socket's timeout, carrying the\n # platform's own number for it so `reason` and `timed_out?` read it as\n # they read the kernel's.\n def self.timed_out(operation : String) : SocketError\n # The peer did not answer in time: a connect the network timed out, or a\n # read or write past its socket's timeout.\n def timed_out? : ::Bool\n # For a connect to a name that stood for more than one address: each\n # address in the order it was tried and how its attempt ended -\n # `[2001:db8::1]:443 timed out; 192.0.2.1:443 connection refused` - which\n # `reason` answers. Nil for every other failure.\n def tried : String | ::Nil\n def unreachable? : ::Bool\n # The host was a name, and no address came of it: `dns_error` says why\n # (`not_found?` for NXDOMAIN, `server_failure?` for SERVFAIL, ...).\n def unresolved? : ::Bool\nend\n\n# A socket as a stream, for what is written against streams: `read_line`,\n# `puts`, `IyiIO.copy` between it and a file or a pipe. Its reads and\n# writes are the socket's own, which park the task and keep its\n# timeouts. A stream has no error to answer, so a read the socket refuses\n# ends the stream, `read_failed?` true, and a write it refuses panics,\n# naming the reason; a program that answers those reads the socket.\n# Writes are buffered: `flush`, or `IyiIO.copy`, which flushes, sends\n# them. Closing it closes the socket.\npub class SocketIO < ::IyiIO\n def initialize(socket : IyiSocket, buffer_size : Int32 = 4096)\n def socket : IyiSocket\nend\n\nimpl Error for SocketError\n def message : String\nend\n" }, { @@ -728,7 +728,7 @@ }, { "path": "src/iyi/concurrency.iyi", - "sha256": "0a2057fcd85011a1bdbd098176792092b1e5a5a4e0e9ddee6d23670e4fbf5214" + "sha256": "197f8f27f21e424be85d1f8b5d607659771202d51712f5304f7012125d44abd8" }, { "path": "src/iyi/enum.iyi", @@ -764,7 +764,7 @@ }, { "path": "src/iyi/prelude.iyi", - "sha256": "92596c2c55ebd108cc41a422f58d9ebd17be67f28639c8ad67aeae7482a99d14" + "sha256": "2c724ca28f93ee1260c649727ed33f201960b04ca64108a0e3fb07d7a52f051e" }, { "path": "src/iyi/primitives.iyi", diff --git a/records/wasm/arrays.wasm b/records/wasm/arrays.wasm index 43494561405d4e6227393419c1a7677d06657476..d529bc619209c4b754a3300398e1aa627e1b64b8 100755 GIT binary patch delta 1793 zcmbVMT})g>6rM9@=k~H~1C{_vi`|44kg~hnO(jsnPeO=@TKmvOh`&+PkVvA<_QAyD zYKf*)ZAfXD2Tf_!#MrW3*vd|`@xc%^#gZ;dfd(wWHrVt*UfMLq2dQW7?e4{-FM5;N z^PQhL=bLk8cHxe>aK{||vJ}pp|Eg3bs~fEDS}XFJ^#=1kAn3opBDImapk)PXcMo=F zQpLIOmjYGmT)SsJ2WxP8rWvgB1y{mAqGdL%MZtWt7W=M-If)Mz9PNk2{rjO;7KLFu z9F&ifXnL=*Z3|;~kG?}x+PBL1aDNG7dSSoo%n)uS=Z00JUp8zIQN1vxj)$QDUT)p# zB)MC`*yeR{2T9Qj+ub}^`EP%)fHA$W+x0(0{sXk%A<>l2m|pnXkOkXdO_o{k4z$W+ zTC0+0Ef|G_d@%wI5K%)B=
dP4J_re7gc>*gDhSiQXw2UnY-3ejh8o%%z5&^9B zi#%f=zfP}QODUYD>Kgo7+RBJPjuj`;l24U9gSX3C-_v=}44YU3A`dqNL_Jpf4L+Zu xZJ`y?8?TL!t!i!I4qBY|sg5I9z1{iG{=gBXvglJ>dA4O7g#!fC?r$)&{9o7W$r%6u delta 1606 zcmZ`(eMp^E7(dT*Jjd(nnAY7@DYaXE%-nX>o4amUDjOSm*&-_2v~B%SO0KmC?x>dV zZpr@03hbYb!$3Aa!i##PVrSj{2#c7)OpDUxOeh?|AYws5U(|EnSMPg+b>Z+l&+qx2 z=Y7uear-Z4_g~JQ+G)XPPsGv@3m&b_3WsYuo~f(td|JKT06_?=_ZuLYmqL#QE5g;` z$c~zt9o1p=S|bcVwJL6cMyS@unjlA#Jh>k}L6V^sNC3&zDEv%PoxOrv^bZH%+T+ln zKE43=z;Simh!@qL7oZT%s)CDTjp$t$As3)twG5Hjq`HhaqFx_@!s2U{e+3xhZxu7v z${5S|iQ!-=FlG+_O@B57d!e|#!XdrT!B{)tM1sK;Vw=N*s&oXVAg;a{f!$E4b4DSW z0SVO}rxX(Ur8qnfS(96x2&cQ%^aSMBubWHHGIq=TDjOWgVayyp=W=g;%GgQQ34;~n zFo(~m#&ODiPCqpcuK-M{ZzrG);`)I}SO)0Vo2TGS32*3uOE7($YIO8oT+Li==*-CA zU#mU$;k$vNnXL#3etB0;Xjnj~vn5H_f=WE_%<}l(hf8hyTw;#Xf_Jo<;+_Z^%wVQ4_ zsDGnrW{de%vk+9E!;*hM>(B~(j@H5G$&`kBsS5 fw-K;&o;;xsk;IPPE zl3WveJMdW(k@Ph_s}fNRz*YTu6i@hILWdv2QiL&ms1 &0ZwM%wV1k$V+kv4p~;0XR+)Y5D6pc%HX0z@X(1w<{D`z8Nzinc}a v^Je3X5pq K-DUD7n6_=8eRq?L-58j2K38cD%El$$mo z50_}uY7A6d=t%#zf*RDs#Dwm^Qm~{iNwFrjSqsIoL1Li~p{=D41zUUOUU%<4xG+2C zeCN#hnfcCruw*@0vL^EuT%BIBD!+)dCAyjt$u|;jrXF61L31j0F 1?0VM=KMZID!d3OEZHH7qGtMqQS$etXW(mFr;aZ9f;J zFH>T(aKSfrQLbmnXssaqv-DsUV`gDahb)NU23_OTH0dzbR|t|{$Cz2D^)4Ge1o%>a zR|dTtQhMiV80n!q`u;umCUSRscPJzz{;fXlgEXwDef>}c_r0zDpPLNAo!n*sA!LF- zq#vG`XCTn#HPK!=HtF(4z*FiYb8K9X_QOeN%*-*6IG1sGl6F1E;=Yyuqr^Bb1ngTy z70#&F2Vh @+=gdvJII#SPoEv%$s0dogaW|_*c&kKrZCYEO_-`72iU|9v61_ zHHqgYT;2wv4)Y5vX0-5WPZaiE(H`FGA!EWWnjO(zwD&yL-cz&>gYS^_&bxkmM+}4S zA>_CzV@ 2+Vr}>*iHlVHKA_Wh zm8zHc!-N|bFGw8oG_pFLC|(ZV#>G9Piii)0+j5)4A1Pf7$4%oT@)ml6Vw|j~D$$&h z9Qy7c%y77-F1!yz&s;NQfgR3Dyh$-$1e`MQNOo{a?-_zq-2IA(z{^V>?AuCe$U~!r z9{0Brv}hPZ!465hLVP(0n470yBxj2f-%Vu_OiVF8)q&4!Ko>J6IPdc>96uz==&~lr zdn@2A6OrU)PvTGbWr5+*5t<)d)b0t`XuF1*R12?u%BSRU@Hy$RbyFZa9#cK^)c}nb zQMzG6W#bUI&K9w;qQpCc jBk=+?|LKC0I|gqf$7!1F$h_FE}98rqdM zJqep{iHfpbQNbId!jj$i3;A!QTRl&8O1zVCv!bFzbI7S+(`(Pd*P)Ix=4y%6Hm?O? z@#iW;Gv5@In4hHip`oHX_LR6M8hH6Jl7r{;hR@-8yZ;@oP=DQn!AG(l{uSP(&$Olw H^nmq02ji?> delta 1538 zcmZuxU1(fI6rM9@XYSpb+D6wTl-713p>5J;vsq%AQrd=!G$~q?T5QnbgKbI*3c`)1 z wq!6@E`YKgaoH}?Sitz)d>MzkXNpNJ y3xtlt9~xX z<|(mWIO7{nQm)6zXxv8nXW4-o#`MB@H)22>w-oBUnx<^V`bt4=ZDLF>EVu_Pco$&A z{iXtXIh=Fb*TZlR&1n7&_#%39e|IDzB>ty9>Vqt-sUv5h27dFlhCAOfFuS }l{Fir#t0k8g`K z_#Q%D)@5ukyM&N$$ )QCUv8mhvVQfvF}5b6`@}|bN#ZgUe+1|> zj#Bj!Zx)<{c#`6H8d+^m6wjE8xO|9I5%mGFDECSHk)cax^um7GC>^Psu^>Iq9)=TPWKeQ$6(g5KROq zO;}S|JA|$?0X7jxyemA;+nx@U@EG47qUXbI^?Szq_3Dez@2NHP{F9#cnGC!VIjD3` z!s0t)qM}z+@urwCWH+vn|1O&9r&OoJ)hkX>RF!EqIhAbg#u2z0=}ha<600q~1BAhM zSBVzBEhh1oDSAH=FVKuVCE?s>Vq@f8S|QC_9eKrXy@hB+nwU<9MGkeRZE_MsJj*cM*ffb@#4 zO|cIa)mcNly4WoF60rNa`%jzw5hw{5S6k_jMm4r*(}tU^vgf=n_r7;VcYE%6zJJfh zId98MLd#6T=_5My4!*0GbmXlyb`%-DmyMmw`2n%|Jo$=z#RY~@P_(-3kCWl7n0u{f zWt{2j&)dOh+y2D{Fb0G4Psik`QFZinKg;R3)7x3SZP$uZDU9JA`YogM3A3m$gWr%+ z5!Nqkzmdt9B5ZK%dnnK~%XskZY{nGfVF?IJp~qZ?kOtdIAGWYc?`14G4(mk{6k&y( ztLiTM@-)U2;SSrro$Q;VSZh)lQ-s&$2QJ8ge`L1{&cJ|tNx<_^D$fd1u9Oi0UEnjD zHK=rfDMxhDn&ylSy8uFROA;(y7qYWoqA}dCv$J46Wmbd{+jymku{UfZ1kciZ6yccc zrvwm@<4Ld^j?1!SkR)A8CXf;NCmriX?D(Al#y*P1m!~tPD4Kk3KVvPnkq6i0P+vrN zURI@8$=^(&bP@CY6!;9F&Ge KZ3@@`*b;I83118^Y|T&*7ujWQ6Z^M^9_XgGH|zzYG7$SD=p zW8Q9p!%!F+W}xA4D8PMmn#0U%O9m*x!+A7enHt(T*}oU=&-;PEc#>tI^n|vF7Kqc1 zmQ-q|I4N)Kg j1qTzQ0=fNJ;II+B#HWAAc7TIx Ws$mzsfz5!CQR;#CaE#-nG;>nZ?N>gzj?a)&n)+7YsQsqg z-mT&u+I$~*uL2xb8Ht~^IG${m1?p|A&QLX&Q=dXDKu~UIhphZ-)YK|M?4)68cr_FV zi_AF5FA`#sxxXC}VUcQrr$_txejnw$AUlr2;=lz}P`cIL87gKIr5&*vQPOhjQhqzi zsQE#X*OQebYLg`QF~xi4tEE!a$L#KLJ+;taoRGNyedBO=ekg m^fdwL`(fr)iY6(r)B+w0TN;5Xd(Xx#iY`K~}@Aq-{-HdK~?tT9Mo Ts;@UA?qD$LlSueX+Q3*G^{tfLMKzydG~+UO_>g$9wqC&%+r}_Zrvz zF{b`6+Q4&oN7s7r^abgkj;q}x>In5b#p$@&+flW>c1?E@V|bH(t0;ZkbyS$abEH&+ zuPkk!OJ__G?zZjwD9~}8@!%Voj48rq2?#5p(^!R&0^3XOHL*6_&RAj$*7GDN!bh!K zmABYer!b}npR?>c$i5|lRh-P2BD^F&a6lF;$Q}oL1>N!m0nb2*JR?Zyl4AmnLypm` zL4^ZC^1M#kkTI*ns{p-nYXYn+>9w+7q%q7{*;z1`GAqJi%Q(@%*lU&%f@f$xif~Y# zq69E3FC@TT_(YZ^f+XpuL;@L*H|QuCu;Od|jC~l1FHdDmQ6%|%6Jr6($b-dM)E5z+ zm6b_m^4&?4ZrFG~2|fcjXn0fL%R1eYzjx2h{JQ;hOn$c?mdoF#VVPV}4{yg`eq@)! zAvBzn$LnDdX5^<0kO{w ?;2p^C9b%y2h0}iSr86|d+?G@TN^o%=Nm! %heyq_Fq7v}%`HNAKC59@$j=`J@c>SYGMAE^ zhfG%J6=I{a!qoH#J! f&Jph(N*o^mE`F$b)mWR+zs4|kB)k0`*YA 9)bAnbv#cu+N#$!~yhL=Nrw MDv72W{l*E!7#2i!|Qlgm#PPJXIR5= z3Vs5xTO}gihuy}Ww;(;)ueMA+JUi9eZVOoJH5Y%M&HK!yo7%0m)*aPSmFiX~2itWp zs_ThzyxI=u4?SrMb{Jn{YY*A?th%0NyjZX5NnOn%+w%_$8C4&_6#7rl<>o0dm+<8W zDbm!YMHZ%>KAu;vfCjWp!#;WgTL6bgs0ZT1QI2n*nUk7szw*hpeFp8))VBi9S#Qee z-X?CN%^T#s7H~*qB!1fDc%oGnsJBs=rfM*zTA>C&%JprKkvl_8ttG@GG)xVrPy53n zJx210xTs__wLv^AQ%&&HNMDy4DQCZIKLX4B{i>i;v%PPqn9Y=Sz-&ZGtIbRK!w941 zzb1JdSxKTcNpc=lylb&qDph^VZXMU-OAW>jnfKE-4!>U<3ZZ;UDCHD$) 6T& z ospKBoBxYlWv}6XSLIN =cqZ2Aq>IsXOsize0p diff --git a/records/wasm/dispatch.wasm b/records/wasm/dispatch.wasm index 9102983baf6a3accbe599ff823f8dc2bfb3be352..e94a8434f29e0203b5aab0f0aad2a3254046dfac 100755 GIT binary patch delta 1877 zcmbtUT})h65I%E;b6J*6Al)uMUEFR9QlMpFk)R~>Cz4u9)Tp%D+BPOyN%o;3(#6=P z8*NAgH6n|gl&TA G z?{c94I^=C74I6oRkPT2TOY=!=kVh3cD?|BE;JaCtlg${O%4O_0V=Qfh@n|V9rVjoi zujIo`Uww&*d3y(APm-E25M3p(I#?nHcEA`+$j7`;3suIeUT9478AYbc*z=6NV1 x^0G|1R?0;O*t{j+FY0b}ammVKL#vEQvAELxJqm^!#Dqr0fa z1v$Nos#%t+yP(at)5a*yol9ri7C%y9qiWU*8R1LBDaP5b6N>WnYo0jTce4m~o>B zt^!1iH;Z93lo6~r-}+2N=t3y6Ih8M)yI?5e#{ GsrVWBXb*VHzNO2!%d}8 k7%re;Vuljg8!NZLbpw1<-R zPOt!R)8A#q4~tHq%vyROM}Ld13k%fnr9~FtIf-J`$l;)sEiZTfEHd_se8&w9mnhkX zK?n^;C|eD`9@PWlu!cjXMAop$ibotP3L6#~mMm7Z{ThBlxA}2c)k!R`fxA=U#6@n3 z6i&IrD3#zv7?+3pp#83Kl@>T1aSg9ikOu)LR6ydxIAQ$S2c9flb(Ju@aGTZ5i(s|W zY;>{Bhs`FN+9zynAgQHn)f6az4V_KuhLRjVX}k0L9 +RQ63rv4=l;YN8u+kNwsl% z>EXX5Xd_({Gp|w7J)i`?J!k5JVV3Y8d^*y}XWYuFi~CgRoc8T>2C0ot#^8N^e^j+! zP!?{H*(N-^!Y$IZX8fL>b{{45F11v{)tDX^o)jrepoGo1G70BV|NntvdZO3Xmty6$ zt?YSLqG``{*pHx^X1`me>JN#IcQ9u38 Z2p z{=S(rb7tmtU3PU{c1^T+VR-V8w{hGZjn `FTjS-d*0$ndp&iv048P9zb#t}EgCmxth_ybyq`@VcB< z J0%hMr`e(_;BF5Cgb^E4b#_roeSh6CQF?DcG zCQGQs8F{6Is+p6^CD2j4?r}q7>|5IdGs4OCZ3G>aA8dm;IBy&;g+_q$axD7*?Y*(AEaAn!jNpgh1z43$=O6&T zTBPd!cMM!dxrYH{ArpL!e)!Hj1A!i1=IhY#p9!7U0Cvi=%5BIPI0xNOGd{~e!|$hb z9wnNZWno*Y1to+y-w4>?qvB2ZLLUUHZqa4jYgs5f-S(89jLVLeR4q!iB>VfI6#g*G zKBxlST(I)N0N+8$b_$Q{n1&-Moi~7}#H9rmQj)9JVuhz!9B?&Tnla%?o8#g@+TQk1 zdt2Jx12(|4{JnPkxaa}Oti2!d^-FYJT%d-3FR(Dr-zZj%9475-d9`m ~;|`-zLJ(m@?jMA%M@Cdy;BlE6E>e(Z07q3o;^R1K+#LjeuCBUD7+$)`?q(<0 z?X((Q?(lJ|$(HtIM;lISDO)uK3gAFz(z>xU$B#Ph{PAZU!Exi;9c`~OS87#pJzrIm z8pV1C`5SHR%qaYX`WH0JJTGJDuF?LYrS)y}O|2~6R8vn6bVS2Rn)$ f>kwXJu5PB!ztcpnd3eL)=6!_t$d?^J#TS=;o z+d&V1i=fSP&A4@qk{$ykgq%539}KI6|KQV|O+F)bR=vDfmCkA3THT~JzM6m${)ANR z7nFrh uknd&ErzS~v^y!GkEx{^u1x8s@MlPI0&Uogi<58$_5UAeO5fr)&zJ~JqzgFUKn+GY>00Flji`9&Qs_i1>SP`w%s v(DPbC*kYA> zDzRG#OO^Rg81K4boBd-^>#&4W 6rM9@=W=&j%7)*XV!IV$tL5)*8Z5DBq)~C>Uz#*!izYyj3Qc{e+gSBs z4VaXl7}0$gO^rxWW1>(Bb@!UBNn?G1KCqyGq=IYAHt|7>ZEX}xi)Zd-_pY&<%+C3r z@0>Yj?#-0@X3BlO$qVN$1ic^r>aH`|YYcyz@lxcUzkRSbQo0&1&d6T$?e^FBYby<- zvSx4Ox79OXM0(dA1>?fx+9gg~@9k>ZB44dP2S!wf1Tp3!hr&Z}T&^639dJVCHqrLF ztZaf(=#v4(jhbCe-~~7!-wYDlD9@;^U0w{5&ChaCZFl6YAdGCD*!6opV|cNgF|T~9 z849;=NIt)hv3D3_t`&wu*Gd^v3u7{Q1olHrmNvtwv~v%c+RiW`qs_2?=SGZuWsDV4 zjGPsuf0h--%zptMbP>S%7Q(Vy9%+GB!7ry<;Dd)_Hl)~Vj9s?@u%Q!qjH!k5wz8s+ zu~%#*46ScrOf8&|!>yF)y!lNl_;9;fZsnHqC1a=3w*8caT4 w3}0`%H|AEjF0p5 z2{Cm=m#@$X;L*Ez3bP_csla%ZAo0yv(r=YC RFV+jSFIQ?Vlki?Jh9z@O7C1`_^|Al>9 z#TYlQE>nZrT06X5pE~LojGB;y0dM&7j19Jg@1cVC2#?;Z;qX|P*MWEdlQYbxIDOC} zg(n~y^?)TA5uS`XBpNgN&b#V6Gx~n84dRx6*p43({a{-NZB)U?(}O~2-P%zNKceG9 z0yXTOWmUX5U8l!Fl*MN@dR|#@O}OjDJ^BwC&Jp-$fxPh?q1W)cv2c}mT*DEIBW=hc z#q;_C&OJ%2aN7d0pw(;mJss!6QI$B6yp~Fzi4zwkSzIXV40HAm@Z-JX3J>*9T*GN{ zej0E>1tL0x6Q=J^7~o-bx7?dfLroygsp}>;y6!LNx(@-)Q(G%Ko}u^wLPBC{0%cR? z*i1T2tVR|S>E;oes2pQtTmd+%0ur6tYLPtGZe&mc^J%fdU^e^(J3Q9F5EfpRZx2cj z*pp(-(ESb_vgXH<9(JTv87UP}4VW@;OeZt4kqpIyjyvD?jKer?e3v5~bh1^C4ypm) zD7l*QTF3GRy+4(lA9pnjDI4M8dkVy+lcI=M7l<6K1DDCZf~tC!aB8>|3&%xK78fF; zw9Z`8_|7fxzPwY&ON=B<;UJBPdV7$VDvMN9>Mm3Whs`I7c*Q|__dGB2Qu*#t{sw(x Oh;C8enqysD*Z&2(Tj!Yo delta 1748 zcmZuyeQZ-z6u;-5-nZAnShG(I6^dI#VePtx3<%7R85JA9LPBU6CL6+VBbuls68)pd zOn@!Mnf`HEFfJyJWX;x+% 7p^tI1!Z@oaer`_SHy}+k3w6 z@0@$ieYdjCTUqCYup7<~N8G(XI)lNjhYxMr+V*n%@4vhdjC<~+EAz6KyblEf!3JM_ zy)O`q|9s~h1mdTc9)-a0*wQ6V+v&S?w8cMPegOg_I!qE{WH^-Xg~M`TA8ddQSsbSA zb?FO(2i}z-#q}E TNl1l@JIp R#+4Y`s~Ib& z7)1+6|EwU4QF;J2I|yK73t{of{Vnh&Y?hZ>;Ox$%1!?px#uhCAtnZBy#?- JQvn6JCj)EYwsABA z>+qQYO5i^szMz&sQ(BV4p?U&`tW5Q=hF2-i5J68)D(J-NA5(`+-hdBb$Y`B|gHZLq zu P{XAu=I52UIz1kxEcz_;+*dX)oQ>i>eMZAM0{=XaH=ZZ-8qTI-e(@y5F*(vk zO;Wt5PvhLv#0sY+5Yt+thBxRqFZQd%iR2Ab`h1+Us4C#11!ovneuW_3KcMhX|D-jX zCg*1X6DknVQA`-#-=Ukw)ZKD-E)6w-_^rBba--`Wrt97dI6-Z#=yaaqPY@CkQxhng zYTIVaZelgE_;s#%#40L>LB_R!<0>H0*|jFgi>*cmH8A~^3WL$~Cv0$;14CGNb*VKd zonTFhIYSTHbkv+5Q@YQV`twpMq8cz|V4IHRWrKN&M{IY#{W+Vl-S{S3+GA&{9vxHz zzEW~E &irt)`s(BUrSZ}h;V9{9gU?$MFAHj zqnyr|)A+_U&?WyV=T-GT&=mG)*sI VA|#txhH?t`@ sAZ4YuD0T3I33Q& 7tT4) z`+45`p68v&cqcO6*|&9^pYPYto%D4W2U?8Q*Nr#L+A|@vnY+%U14Z2{p>S(UYkQMn zG_|yuW#gw{n3LnXVa)$A9t0iwxm)VEGtsP$@Z{O&(Qp3ncMt>S8trkje7z3G&4%?L zrssRsD}XMuZzG6q^DylP%u^e6xNd$;`# }4?KSz|bTl`*E4 z2%9h5o5fsGq64kT-L$VJ VX=RrS^ zvi52elCt_WjNpq=FS0Q0WiLLB`i$m718+1(<5Bbd@fz#HgSg@Q?a8-19;u70b#MSD z6Kj_k^j_{~fJaE-XOiEePu%_={oPXirt?P`28!j|D+e|4*KC3}LwuZJA+48Jl_btd zDtnrI`7Wj96mB0sgm>|DYKcJ;OQ{5JrPE$wk%DAE2_ep%EaYFII(qSYx( yqMA2S1CB|*-lvJNnFQ~E zd{pGJETklT*n!gDBfIq;M>8e;MRQDc7wyse+M`AL2^1XCPJCG*e@vbLRn`(mAn_Sp z7n4F0N0(WI2R4gW{1IOks^v|Uze``ItkHkg#0nMrETq)Ld8$?uKh7i~a*rma9Eq$k z2g-f=W#t=_zJe?-Yn__-iEaytY4x83c{?61$&;4VB~q;94*O;twE`C`?Fc@2=z=PX zULviDOO)gph&h#za7@fulShbPD~cQBvR@Nl5i?$dSS4mCoGv2XMXye3dUfSf@A@pd zPe0T^eaIA;rtg xT2k_qu``#vgID!|v~@p%?$v ztNN>kquuqqP4oZfC%7T17t}yil+3| u%V^7lb8F=juJ c#H~Frti~%CEgW_BuUBx*1H=C2I&NzJ0)a!dVE_OC delta 1481 zcmZ`&UuauZ7(c&ra&B(2mKAGLO6wBn+-kd~X(*kes}p1}tCNX}t5s0u1`+$vE(OC} zvspx}K9pY5Ynd`!o6wf6DFk~*@WB_cmDP5F?O3aGkfIdFP*6l$=67yvZXV`^bI$Mk zd%pXf- 8y7lC!Cme1$`TG8rQzwk-O96z9gO{?tg6@?-T{skOZES9C z426x7u?q+p6Jv)E%KS3s2OWCyt~&loHL0U+V*ELrG#38x;|=2u?L9{6W)038^_zYq zGaZ{{K%3FE<;Ua38QNbqE^O7{hH;hlKQrHNy@p+Fb?v2$i9{7+FM%=F9fpf*j4`!D zz0 zM&s+>GU%P$%K(>@hJPYQfW+)-Rxt)dl%T!0R-_UjN>hJKJ zC`!L`C)vI;kM*JA!8IzAmuyGXE6a)*>q9O6us-dBF5=lGyJk_z8>j&T(yMoAVlo}) z2Ou92D@!b(Bz?$+(%T`A=pDA^vh)_rQF)|bkKETDDcA>*cgWiDCHeeOIS8sO6oW55 zPS-`H(8S+MEXaM^#Vh`Zi}`AKWBI!Dw96`eMH6dO>@$#36Dg`z6F1ZGpggRJaa$s5 z)Q0kyzN~zs(vz3vvevGNRk|%8lIlMR@>V=tlqV}Ii==4L9o9w+)dG{Ib{6kGG^xs> zm&j^jnUXvWF{Kg`j*2OB;w%yDn&Jkz9M?pKnDGL{0x?73Yyt5n=+#M0udaM*9iKVp z>HF)c4`1h*rXQC(r~?)`?}M0E2?^(H#7py>fX!v|%YL|U&HAbzU8QlwGb)DHK9J|v z0P+O0skg_$QJZ$Q_C-e i_@% diff --git a/records/wasm/functions.wasm b/records/wasm/functions.wasm index 2f99c47902be59bbe299367fd6e1562b44cd2c59..a7b4ab3f3bcc299557000f2120c0e71a194fd111 100755 GIT binary patch delta 1613 zcmZ`(U2GIp6u#%4y))Y_mBw~ih&8*W6 u2jfB|Jr42iA;}cI*BQ3SX#8lgw6`yJoq7O#fdhSejXC82pJM*3Y zbI<+mbWExplWJ;Cg=?epYUTOO4O&Z`7JpfLm3a>k46UwsUA(?p)2i#XnNNI?S(kS| z607u?u>W(=%-;UpppDM #r_~i>6G%Ga^rK1P0ng`*S`TM_7Xf@TP=&lo+ zI*S;?6?)pJUSNUo-sT8na^Y#$xL+`~n~drL(wp@Hbte}NjkYWm1MD%|mZQ*Q_AEzt zw74M`9%AfJE??DJ#^l1e%}%j_VxO^s63K z)38BAD>08yB>j{p<~z_yZtw6^`BfMf7j+AaS0dd5n9{cb2@OfF1Yr7E0-%k~u& z`T$J(nF2VS3~9B;TVJV7o=7sQdj{59f40Kqf?szX4FrUObJoS KngL=oh; ib=u6*8XM;ak9qkY< z%~G3uO)g5F8qQi*+o2qm?C;y*4Q||=aC*iF-$Da8B|^c&3ib^fya7ZN&Q7qHl!Dz3 z6rp{hDY(zk42n?Rtc#|+J^4U;GH*Wvu0zI&H{ATXI0LSSP=;h1Yl8_PlrH5p1usx} zU7&(pcUTQym)pz^VgX)pyXVy YG|exPW)M%^noIc(I3y$TLG5?OTz ziUYyBIQ2A%qQF&%yGo;iKTx?C4#|xZ ?Wh-C{;*58 zQ{eK=Vd6#)>kbPY?Mt4vCa @Z1$5TypC|zx^xaA+H8uh2;Ccb*^z`A2TGKE0MfC~ fzIP6`?{$CMVQbqAbgp9iyBWv^K(pz8en9;X6e7c4 delta 1613 zcmZ`(Z%kWN6u;-5-uFrwjtE`1h-nSD4K_-th+7z&(JanQ=ghK5Gxup5TBC{E9>l+U z1)_-<6SQGZ<|aD+m=d5(n(W2K?}848d;l{Wm85LOWD~K?OYbJ=)g~^CkDa-}!&; zxxf2jb82i(P5q?8XnIlIez7hbZaRJHp{Dke%z1#IXMKf3;pY0r#`;h={=k*Yrh@y4 z!1f{&4t)fn_~6h!2&I1<@&kR=hL`1MdGsj`AtSX2mFc62_)$0=zxA&lUWltJ{=H|m zb(JxOYxHzbJ=bl<2its%$%S3E@vvZQ9~sr#NN+X>)SX;7l0LOk4$u XKrMnnAmd{takukaOyE?1baCwojh7!qzf5n$qf2CYMtW`lE{?A$p#`?!$H`Mjt zr1y*w0H}f~qZPmpGv*0^QWqqRs32|9d|SXvaNnd|{Qk|1RTlNTrHV1RaMGw$;RE>4 z{7i*U0p2wqFM)TWo>0TNvnLy(uSS{Cmxay7A7|l{k{f&5T`r;E&&K6*a9LmahJic6 zJ+yHb3iL3+<;?Jx|Iz Y2G+OzI>0= zatb#;*We8}G*c!EdkXJWSu@Gz;2NBS;~@xs`?dyEjmZ%vV$nJnn9u!=m(EbT&9= z#5%!SnWZ-QhJ2JfHC!+folp&bnlqj7BG)ssR?q0;J7@qs!s9-!U?QpW-5_dlVU`7? z Qcf@xyPF~AvOio}@$;*U~sQWFcLPqpJS z fgsQaT z5@p*-GrvT;P%w8@&j@dkq>)qJW A!!6g!K;)hIF-=;2snO)a>t&qC*T40Xq?X6{is2)FoPGxTRJ;}s z;}x}6lh;&dE%I{>6We(f6xQ?b?pj%Bn+SL9EybXwPBnIr5 2H5jr#*@jy9Q)_iz-@Ar-J&t^3HsDdRZEire4@tYJ33uA)lAih$^ZvQbtv;Vz zD#q2OGdPVKD(ftAaMK|r|6F7+n|Xi%KEWivOP_daj6sS3FABCB;`Ts-mqEO(8g*Eo z-Fg;>QI;BIFvMso!7GT`qb%Y|QD`B|`9i|N y*WQ+~cfxf@JMjrOza=~2dPt*B!&qiEOKEf%FB#$kI^L4P5FM9T zgs)tvT@Q*l?xN?V!JnnST0Uf6F~kIcZv;h)lZ4(7-}EOUa*H8)9f_=#!^)@4%VOe5 z5~bf&$je5xA-<*K!lF+XPAspa4rKDAWzHhWT6Bhe;2d%V2G#qY;f?%34GRG?ZHSAM zV>`r<&PTi@hU`tBBeX1`n@m2uFxzeJA-LUj`o6~FEvM&>_E}FG$!H0z9t!Q?xn9WV zdNUkv_QLsN4W8hI@wJ||%NsMjVB#KLU6Y>7O3(8q?OOgMW(7{EyH^oPoT3ru!G%Ws zzM7&Z^wiUh>@>s)n)w|NlVb#$_;iNjF}iC~)3j@!LeJ-dhviR;vW>YrWbTwpsNwzO zydL6`PDuRxDu?A+?k+&@LN+}^V!}T09maTFx4wB9;Ol~NNxKa3vY>pxXca$FRqF}C zF}h7dOr;WO8CoP+ {Czk}^w_s=p{d?|oI~pQYJTt@Z~s-z^ChA- Jz1kgF{{z!0%z6L- delta 1574 zcmZuxUu;uV7(c&rdai9@K~~C;{jtdwz`?q%;FQ2v-GYw7rb{F;VJ{*xoAJTWB|aGH zSd4MTIMRnBqAoKIv&q`&5-zEbg+#*^nVTeT6B=c(ImP&Zf-xF`-?^9d?tv!F_xt{y z-*>+6+(fS@(d!v%^Wx<2%ie-%PdL2g(2I|4X*rnao(&+J_+mD_I%{hVYzT+Km1Pwb zWub7Q |7dYQt*Jr5x|!1AT*kysKVy3tW3E3K z? #fC8RAnqJ|=}B z{+(n&zIw5Dy(pr`X3xuf^U_l#A97C{B17aGK-uCXu{Xr!R6HnmQXNYpZ_I-78TXXP zJVmPX*akUeR2kw59TyONx^aTMk|vO?la_f)wAhj}%p>QKFVL?(_!@5(_G?=3y3>Z3 zq8fW326R2bF)?6n{|5dQaXn;;;l;W3a64g7*P8n}2gj_LTh`|sYcOjivU(|WfD<~J zwVlc$-sGh7$Lk%%N#pAsYqzsz`hkgie05KHGb^3QKk2O%{(xD5L3MWq{`erRIQK0M z>c(2izNojJZe*V!hH2;bLX1xkX~O9&;jam@Y_oZjA+bj{O#(-$E{ZhQvkl^B*z6Ym~lu>E#=Ja#_3d^Kzejz-STk)YT?p z@FCr%A?8lU)6&00i&4;`Po2%66#eEx23L4Mw@Xh>_jZY>M5#v7>Vh=)$0Dh&{frY7 zedH>FtI(weZy~Q@fqdPDI8LMuK}yhq8qM7Y#l&&*_ANBl*&k=WI#9(s?qTz C4gp diff --git a/records/wasm/generic-types.wasm b/records/wasm/generic-types.wasm index 21b0886c445ef0aa34167430c6be983854a6a396..84e766fd1ecef897e710f3d4a67cd8338de908c5 100755 GIT binary patch delta 1834 zcmcIkYfM~46rM9@=W^KvsVoa9v|U9DR#> g9}=m{qO-2@$%zSWc+%(>!EFJ<-4zD4F96vb&RpB zWu$-B`_Wa5>4nd(9-j{|rWd~G#@lJ5XPNQdokfi4h5O{Wd{_?&1%6lq+Zt}Px!%iX zEH{H|I~jUmqmxVhD_*pQF}<+GiGND*tt(uWd5r0W-^!;7pcMX8T?McX;IQlu5cR11 zT#KaqDF8o1gPboUOojZt5QZt%Uj+3KmglvYlk1BqHZAWgrVVrIv10IfK*^n@ U-;TZfLHrPmazlpiM^88-yuQ+zfML|B9LK42;|0M zS4wh-CrgNBgpSGcGRpa0&&-(i`nA{NVB1tj9JX*cA*bJff@t4Rl-Gl}2^XeW5XmxQ zwko_$qS0)!Jp;m<4o5^|I^Mc!yfq!~1P74%$No-m0K!O^&Cw2#XEqCA>@l7+@DR~Q z1RA*KGz;; 4CSRvwHpr@`!=`G<{nE=cWikdMd5)1|lan&7H=SxtLS# z@Ljrv`Y@%lA!QRj>c9|zD*;dIgrqOp8qam60Q6AyhyvO*t1cXbM{}ck(5V()mhTLB z2RIXM&;ABikJxi>dmnbap|sZwrd1buD(xFcYrNUr&TqfZHQa5y-1V-^H5I;p7u9{G zUfo{(I_q5IMO3QrD4atWQ@ ^h*lU(|B+LvkI zXLOTIGz7^hpPm9T&9^bBK7Jog^34Y{j$V @BV?=E ly|{`N!9F#qU~V&gOYh*kRDJ^dC9o9fmpvqJ&EhY3{|5720}B8E delta 1834 zcmcIkTTGlq6rM9@=jUe^q_Vp#3T@Y-r6QMQMJmCn(U`QPULI_eSo>nZV$x{i5{-RW zDyfMS$?C=oP*WZ%O@Qt$4a(qZqc2v7 |7v(MZZdmr z-< 5P1FF1%b#4;V)4@_NyN)f&yw-m>F%fZw3K(;${7AdzdIOX zIaiSW*}!MFFs2v2w08WWpE14gfD>=0jjk(<59|vtrWYQPmkMDc3@Y%#I@r^Am&G+y z$XI?BR|^?>;lp+=^{;qf9b ?dNX}PhOVw3WLV%jjRb{B)k1u40&l)Nc* zxD@sS<+;8d-h*EC)&^M7BnN^P$-6-UB~>yA4IGZBA2&g&4MMWF8|HJ*J=p4U2@n1t zUwsFbU|GJ>0|8jEM8&lk24*vNGXNJd!Sy|0$1DSZZhoh?-M~Lbqr3*NQ=Zb>2Gvv# zybU$UDFz09Galt(lBp?HZCkaVjUeZ%2}g9}((+IYN-E}wjOSSqN>6MP)JeSHc*&)k zi;MD149egawGxBdc~laa5N>{tS9scmpVxRrj?s$C#9m449}uB|OXJbB@Ml>O0y(kR z&5~^5nG#|dqGR%cta85BGn3F>ulBlZ?0(f5hn*bKa_)WbMTb&R-T>lmT%2P;B+HCh zs&F@n?PinZIVs$ka71j+#M{=4w`Jm8U<1 *HiN|dD0?~Wks?$lutql!jKI0!{PNqx-4*; zX#*E1$44!^GA?p4IHEC+XXm1OAyl28I3 `GwBT9 z^?RugU+HW}*@TBWpa@(EIHePkzGP`U-<|@{L)kBUwCk+8dK8|^kLp3ET6kHZJ>Z>S zPq;Pvn;ku3&AsJ)#_?8Xyk;$i+*OV#eB jHM9u!sTl>|?x1gJBj=^^3*c`+NRj@Bi{yPd{B`%=wI2dh diff --git a/records/wasm/hashes.wasm b/records/wasm/hashes.wasm index c1c1da5ba674bc33a4b6fd7ddbf655b18f919a11..22835daae8f9715a65a4fcece838d220d2d6300f 100755 GIT binary patch delta 1868 zcmb_dYiv|S6rMA)b9;A-Lb|jSsB~+Q7NL*bG}TJ6Mk17kSJbMFu}ZsuO8^zNniBOI z`Jt3Q8%Z-54Qmr03AXfM+%a1dBQb^=LW|OBP%J`)KvIpw5>1tQ&b{v5O|1WHGM#h2 z^O*D8JJ{hH?C|w%%z kdySzS+F*>5%mA9dxXOi*w*?h+6hy&`GEl>zO zI<^Jo0jySCEy&iZ&*%!PA6uXR&Z`@A-B62KAqE4gyA@>v>SQZ|33bKcykP_rYFQXM zQTAyVYVrEh26e$!IKt8Q;C8eqRo`xhy%19CpMt|H6HajEZpMCff@AUKxs1^fqmJ+; zi!qc_ rtrP z;SE;o-P2kTX^*I102Zi+#^H1Cg?qMnJkk^=b rbxWu>D(W+R?gr6>jrd$4Be}ihRBtyRl#T%*PDT)obw@kjupQ2n!Hr z#%xmh8f3lMU?bhqmlj85eOexwRUS#pJHSy$+V*oC|54ciSghD@kY~Mx&qbv$#4D#+ zInPVg< <~N2ogTZbDDGhN9qcy}=y;ixr-w@q4htjA`%FX5& z=|-j3;pLdI$`D`UYXQ+ic_PWH;r0waNtvI)MKj*e4O0*jxAs#!;4_nkIEB{hK@5;5 zq@!X$zwtX9 lW(Q@qJF=XY&z8P|-LxM 0+|CEH{|1SU2|fS- delta 1868 zcmb_dYfKzf6u#%)y~FHKZNnC@EtW-Up$}dgLak6~G%6*1MMWhl+VZGNYPELIv{AQ) zA4d9T6GLu^rW=z&!UlISO*&>VF(yVKDT0kcL#tH_B_*k*Mu-s=&zW&|HWB|xa=GVx z=W)+-XQ;~<>M{oRW {sU)|PFRZOz?pgC7FjQAp&ZuxI?mf$~6ANkv6T zc|e^2co)i54u@JOw;t#4BFFo457gqVv<%osa0WyG*##Z0B0J%QC1U-r^3~Q{CKNX` zwH7U{W=t&MUjbt* EHZyPV{}CO^76VI#^{KnuDk;cJvSMTOyx30N4%+CIsp05 zYlRQMDu8PB{y}7Ss!!>9TK#Yk^5ByCi>?K=t{K7*RRhf^i>eFF2*%V6hx4`|7*m^C zpc`eMv_K7Bf7qav*9vDi`W`xj7JhZ<5FCTeYWMSSx;o|rrypVLmJ=L{G_7Qeju>@> zZ-f{_IYkbU>zFbfF|6`~_~fWn8w9_AxO(m|dXHPLABML8PFwN_d?WC_d=%CSIAtxE zP}A-$FFW>fb6N0sQ2h+BMm=^FKJ!l9*XHp^U3_g7d=G;Vn)#A}-pCCG;1N<7OmZr| znEMa>Ryv+kJ~_q!ZTSXYhbI0U4Dm7$ony?eHS+8<#}8AUy{OA;2<2#Q?f(&8gR D(W(b2j#2X}cxiE+DtBA0K$ZgfgRe@YYm@etn*a+8=G zXMW=JuuV#%Ue@XLHZmZMl-QJYDS2>Nc`zmK0!JZX+plo^o3abASm9qGCv+B{Go{eP z@8hhL=OpX$PCMonaiZlVz8UG=A=l`Wnz(|&?gc3|(TCA$Vk#agl@Ds-tj(d+v`M*N zpCDaRdL3R)Xgf4<8DH~@Udj_mUIllj`ANv!G|o(W!>XT$fVi`t;sHZXXkr4bcY}zM zC!|dgwR(PoQ#?ey t#nI$@rqWA;YbcC_#q*LhX47)=~ zJ1f3O4vWmha#*Om6~qWRM0)xTo8-4S(+ad^#p@{sYiJSjjSwvW#=;A;oWbt|XY%cx z*yz%xy$?3}go~D@P^=;?3@Ny(!zpYaMe#=0oZqv@Wn42} t<8 diff --git a/records/wasm/hello.wasm b/records/wasm/hello.wasm index 774e2f3630f77674cab25fbe160d5fa24a1edcc6..54cd5804395645eca81e7ce8183a1eb3feaf8928 100755 GIT binary patch delta 1565 zcmZ`(Z%iCT6rVTmW^OrA8sTV&6fS|ZRLaSo1Z~pR7&IcaYSL6oYZGHoO9&Npt2G)o zp&zWpw8T}mM#DiRsR>a2rEGIe)0R)5rbqRtk+h*T_{B(!O;o^IeY0D3??c^WW`6Iz z-+S|B-n>m;2&XTEhyN6Ca_leBeJp3A-c+q8UeaH-y1QdgW1Z_x75ch8v5G`>qP9xc ztEy|Pqsh-fw<>!c0e!56W?K4t(`tG5%V*T`O`o|PTCJ^%rSOK;Li0OT$6_&jXnjR^ zaBOmM6+oT!<5DTqTT3)ISw+jmCD-o%B$qLqxQDSFjIq!H ShObxtcZC^eHqt>s> z&z00wxWtYk#&(g7h>*NO9%^8P_2S?CkQmFo*$^sm%3bCM`x!gng@ojp`xsLLol^$1 z!j|!MF6Yx~#tMiNUO<{za*-%%;B7lw!`Sa$43^BvV@wU4u}e7=!?eALLn+MIFK~Dh zdhBTqzk%hPN7&m8y1l*?&W3;5@=7R#-`WeU@V>clk%898BMcyf0wYZDf9b;;|D#{Z z)~}Ecr5TVfFVEjE@sDAXKLFT~W--~wqgjDpqXXd}arq{t<>c=CvJc*at>aS+Bu fhDM{;ep@=kydac3L z42XztHblK|Z@#0w*|#4CkC1ZxO)tJ74ui)Ka!{p_r!@*8+vTegzo6|6ffCypG!T?_}+!3pwOs)rvZC01uiM%Erqj{$Gtnh})`RVF3zA~`<=II02>HgME= z@Bl>fOjT>*@UnZo8Xp9&$Zq-92H0>*?`oe9v~gccS*o|82my7{*A4iHHwNzf;cbBs zxbby?wljEbs$KDRzUoZsfz}4h8|XL%hu}Jzr_`ffmGu_8sEN_l=C2e?^(|&pr_crM zl-N&wvIB62`b6QBk9Zy3IjN}!6sIiUOa`fmw<%iM>pLyJQ`|+BA0qY!z)LD3;dvMF zyS=nPwf0f^rD4W7{t@f}NOm28!pf_3rVX@VAKj(IE8}KLM6)DAgnh|*^ay+gPkyX+ zrPmwb6(GX=p{S_gYx5<}4O5>`^QgvGLfnN4&EEkT*z1&ag8dl%md<3L%Ug<_eKT+~ L1iI5Q3r+Gr|GmF_ delta 1565 zcmZ`(Z%iCT6rVTmW^OrA8sTV&6fS|Zw3O>{5~}GRHEL|BwkAz$(Ka!$ts dC?TJ+-6Uq9T z+S-~#(ma;>01{?(-vf}yG}BDWiI1{sdF_*JYWc3;*be*5E%W8@qS;LIL9=7N6y7pF zB|MNBnO_31&iwI4IXr0Ipn11hvQS!fWz~E6jN#XJF}8y-7Mf!`RSAr#f!EFL3*9he z{<^TUY+aR0>?mREX|fR!Qq$z22FA>1|2hGyGWj BFo4 zqraA`UnIYoWk9~Xvha|^zlROJ2Cyy5VzQA(a{|9a2f_j3^7Tr~$=$ws0A7bJr$-q` z{Nb#@leDHsS=^IqK?yO=-6+KGi% n?tcy*)z3I00Cg0u;9>H<_4KKbf+QDN8c}k^Gpfw61kI3gG_S5#dK#2?E zEY6D;&!iq5ro8NVP2sG_+aOA`Nr}^x{8k`uJWkn5oJt#U@hHV{C9>)+6i;dsIQ O&nfvw^!pf@QUn~e|dm)xAd;|sX!a|wUniL8;TH6M||B$AMwV(o!|FF zAOvoFMWF2rUYlxHyp^vylX{@_0rPiE+ePod&uE-dk9tYYTkN7HMps+7STNPMctLdv zUC=Ix8S0ZAfMe7r3OhdH57V8KntDKSDgw?(keYavqJ87_ofh9E?x4!s#9j?Jsv;7e zaS^}MOAAzMAERFyUa&uY7xn;HT}Pp~dX~<#nl}89?o#5+S;G<097z{pU$P%M3O%s7 zUF}M*H^Qqxg!$U2sN>5ECH^u@eIl_(HNFz!E>vjdHb}=FyP^}UN9eb7?h wnRucyvB6vDIoiRJ&XkvzGgdv}>5R(4LRDA@`yL-w5LliY$SVuHCHpsk zA7)6{2rVfw^fiA*UT$7~Rv?g-J41GEgwG%#S8sxO5Ky5_kOL%pHbbA5-*1H ;@elmRoPY9|XQ}(*i%dsX=w{ zCj0_~uDAu`)0%8*WgjpGR0%4JQFAwyG5zqc`r;O(15~T_+pzu}>coOF%qFq64BHZZ ze`1Bp#k EBqFoFNlk6(WPekUG}I&be_KznhLcb&oiYA`Ve z_^uA-H%ieL7r1!=kqeP8&e15+cC}zVZh@R|8v}#4g-y&OsoGe!Z3RFZencw@`G2&V zLL8Tu-^X#&&Jz_J5f{ID4=oNjDEZfFs80qq;6(UGE#80?XdXXhvE^fMI;CDIJnp3i zKU8mG0f?!*?-cWE%Uxxu!c#1Y+{G4BCp@v`peTy1ue$I0s@VEYupL^g{iYp1D0YJF zA&iiwG0I&kgi&FvH2CLqd{A(MS9G#$Oo=96bbyli%4UyQ-iu<`Trt-Do58z?{PRG& z`B7qT@YD5XwkR}son_H#&{D+4Tx@IuEEdHaef}siIa-?)Ph)?7BMEy z3dWtGQn%nZ4vlh_iQ67))U?3kZZY^tit!xqCLNFTAa9cAO+2usOHT>8lo aJ=s|1VEOd#3X2(#flO9Oz;JCKNu)3IrrOtMowa77? zZ8*h2tDNj!{*$sB;;YN4Z$-Z2d7Uejwg)2|nm?_k44YL-HExC{YjlRfgVViY_&VXk z9Ir?)R`4F$ok=zQl2{x3LfC8(zBnsLPSG`W<`bMp4OQO#xZ{xo>^*?(N%_suP8qzN z+Eh$z!d8dLav#*XA4kPq JR0UA~BH+UF{&GrzkB`NRJ<6g>kR|6)Dd=%Ms{eTT< z{=aNlx@?%4Y!z +8U>;4OF2#$G6juv9z+vS* zj=2u4%%N^lSBX}!O>1^Mt=0$j^B!7L*T&PzjM7@B;V7+wAzCH((wePlo&E`vMXXwn zjMl)G`_j_ywTGhhUON`yv-@Ce@J86XmHrccbkbg#ktXuzuIzg@qIw2^J8S{t~J k7UYMN>sM?pu|Hi>W%p$MDt*q-XN)0i%%>Jj=j&7c15NI*8UO$Q delta 2406 zcmb_eYiv|i5I!?!&+W2MXu50(p_U~;DW%Vq6pA1qRBI!~N03(3AV?7s2*w3@dKWhM zfDj5bo;Dh4q7b*TyG;ySv-n6vLrbXTVG6;vbYq*Q255i^QR8FIz1;@}5`NI+W@f%K zGv}Mf?cEdp)D!;HCTE%Ba2rcI@ALT>tDW$8j=!wTC@X^l4-fNrrmuW$#`NkHvU?M_ z!6RWaL^9&&%kCUck*6fTxH!MaBhPMzkDy4d-2!u=$OvwMLLk|>6)r3J&35=s$?dv@ z2YqlE$=>USxg=$AHXm(7cR|h)SScNyFdo*(TqWL+6`ha?JLD>59WvhSgfxIhWL+1T z^W;}byd*DnL1xy)+>UXK@z3`&wwN(yyUMU(1~8@`e#=O|45=#8-A(!2a!xliL8H9Z z4W*DPbD~h1)jGvYa6G`+Ix7J*42)z
cnNw@}J zHiV4l2A~Zcb(0hw+mEwAM*Cr`TzCWif;u_xrWv^GCS4zr+i$|J1im_Gf;$EkXdE1b zlR#+oEf}BGXi;1KHe*1YptfiY|70?z9v(72y#?6-dyG@JVdEN_#Qf!0Kw|xJY)QHF zXtmA8yNr1&aA&&S`x66u6*?%5jSKEz0{^8y{@Xq9>&bY`+1JK^VsVmlgU0`E(y >r2Mt}=&&P!lK&?Px*&@;;Y9e|SiA{$q8>S8vgKoN8kO!B4*OD# z2f{j*f|$xLoMCQd*=tNyI4VTBy~0F}2uJ+bE6U^NtM7WgI)1(zEQg30uUq-OVmDYG zLJKMyqwG~eXgjr48vl&0_X@7@KhLlN%!n0VUQfj|SnM(1*(-+45o7HaG~Pkvp9YHN zhl#z$FNO62QAT-8i%z|!DxR^o^NuIT6vHe)v}0*FynC3mo={#@i{!Q@|TlKGMCsQC`xqe%)!cCFD}6@h@pd76Wgk z9Z`BDuHgc@oM4*2@|l?MX-+JGdYAc0Yv=5hVmNicAm=>bEh-`DJ-kImedvPI#%&)i zP1V(c(cL&P-C8U^Ses?;Ty{eDn)_y=OA=^79Hl;~g;Wj+*XB5OB(7mqA{=KfN*IYS zoRUCm64gC_ovIt+tJ _DtTo 1gLNNs@3izzgha7 m&6}o9{xjyW9e?Ph3{b7H!*nT|U-_QG8 zTPU$Dl-O030!JIGQ&e>dbS_9J_O34Q7OwQJkrQgb4Rd5p4Me8KxGUT_g$0F0d0uZ` z!5sN}4eST6Y^jB(!K fS>5MI9j5)>_ zhWe8k(;vT X+xRwDGMqyca{Na`6frBy?wblnh?0x5S4 zYpxm?hOL_PMYU9}iNah0A4kCtBl3?Zw8Cz+YXp|86W;ud8&=88Qp}}MO)14f0t-v= z$As_a*E<{{f$x$ coyY$E38Dt>13umNmGWc&@ zLCgpK-XL=ub(m% V zPX$iT{feSvVw}rF8a5xbxfDgupI?<5D=-WGQnCWCVDRiEGeMq?Gb#H<;c_lB_`%K~ z7K3<~Bv VZYh&^zuoNPuebDkB5KUld2%|%% zk?gD!!e}sFGWZ7+J|MWk8-|z%r;Zoz&!%DyTiIh?>JK7ep2%=sF?f`cuL82>M=5)Q z4|fJVqQc
(6XLQl&){EBI5+Rmg%gTJG=i9)h)9p40&y|a zv1f2Phi*Bz2)8}ht {`^lBE_ znzy)8OIF}_Jb0Y89Vf*t;oDhcX>wD?=QfZ_tf+n#)F8jXkI 49ltY z#HQ`nI$5?(pPib%D}2y;WKNG?Or! c-Z%u>$mel@cyse+djuuR#+zz$iw|urP_TJ5+p)Aa^ zed@)~l~u;q>HLU?Wq1$ngyYh;7poyE_w2>bV3%C657WI@yXZ{O?b6qk6X7vYjg8(1 j3-E*L&^~NkM7MqJb!@djrn-3@PdezQ_Q)@I_mO`9ifKqc delta 2032 zcmbtVe@xV67=NGVz4yy;;t&xJlGYV24COdZ!@wT}${>c=T*5J7l_t%cHS;WUt1HOd zXiap?7qq&qg^f!%HaEWRH2uRgAWnpxQ0AI#7@MW8tk%!j^M1c_-}}S Bv^eTs^>q&x+E5R#Rrp&0V?=F?DR zgRqRAA!As-b_TWq^h)1ZD%~r0orUa`QM2@4EsVWjmWE&nl~o@fFpb}5GWIYTldg06 z&w{NatB*(Zv9olf(4pTt2d{GIklThRbx6KDL +0|J@hB0^ltp%%#<1hT%v?@;yJ3y6w`RUWGv@=pAIB@zMSO71nrokhx8%5R~9T zj8~XLQ4#4b$9Z{UR2@?iQl`+b`DIH>Rn+|Xgxpb%Iq-`v%W(n&kqZVu?!<+JzC}3f z8#LY>4q!2ehxo(==2DWq&0vM2N>tjb49!vDh@1VQGH%~;SNoQ@y%kJ{h!Gz!^ZP|B zm>xpwQ7}^N^+IUP+H)HJh|2o~*Z3b7m>cI#H}A`(W`ZVq%ul~6l2?c<`-H|X6ZjgS zXnusyYy46;;1=bS$B@YCH(2q6eVkveAdv>DY(k7{D>VK!m2>ef)i|+OL?ejji3n$c z zH3t8n#rut+8`?Tc>yB#)tlA3YV7U&&bw}eI)?4BD$Qnzq!Z6p;wpn}jdNUpM9lWY1 zwVFki=TAJWuWQ6_d7y{39cLvh;oCVBX=u}@=N8Q))v&r2bU;3hAEQgL9(a^4h2kS| z4p-69lbW_)`Q%tW0~R#(uJF&zOJ;Zb#0**hoxB$TA5 +& zmoYP-0!tDD2mUp%Kag 2nD>TsBMDInntDhhf7AtQp?$ AhZI%y zDcp9E@$JuG3>mfZ6C%A8e7Kr_0D1>>scO+d*r0`k!;q%>>mWskBBAz0SSgzYJ_f60 zhbC{yKLz?=lN@T0)Tr_e90o93mbgirD=RfwCM`GlT$Q^uxhubL JDP~Q0oxDEEIt^}+Eq9utqiH{6B zQ8q4RY* QGHx5WceIVfTKjKqYIX{?V_e7y_wy9l>%(+el#b~TRtwT` zArv3Zv11IK32XzUqH3@ zSCqVXypqB=s;PeWR$eVKsdex&d~mS=m_gs_+-Qv3jYD--@|3bg>l+ ziA>vySK@vxe#PYyLwTRd+=lO_LXZ5i4pZQ!O>+CsGce20!vI{I3A{&td}A8}!96%S z@lAvGHCb2ye2cuRk2R{~L%0bF!mSJp-Ws+jk-=3f%XcI{Xdw%6Fks#Rs`8{fe;6}z zf1+ggh;5 nob)E2- z|R9Z&5sg#gMSya z^2JjIZ?Gk@25eS5Z(ib^b4V0%jzU~AW*GberOV=tx^QB#h$aw;(;`x0q(Dp#mEV9F z9GYcm5Z8}r*08{1wix_8#dsEYNXH{Sz(eXr5R<%?9y0l0_M^^l*MT!#d+uYSe88T$ zt$iV?&5vjatX>N35OqBp(KSRkEQ=n;Y0pK4=rJ4@)mBH>Y>feo58%}`>CG&Pdfwzp zeR~3b;c#3AzrrNzIITD)4i4&8BMqukZ#{Knk-?A9&MyRhv5i0z-xA?)Huaj+wC&m_ zJL+>bibcO`yv?~o<{~kK1|B5m3BWsaK;pYoZ5HF5y8!ww_~^moovQp4wnLVNM^{8+ z`I?TFQr57Yl_YZy56fpx<1J`bGtbavgjyNw!{qT-sOAaed4PIt@XKMVMI^;Y0pX5F zYDg=-#~
0BarQR%%mrq zH>n@4;U7SqK6M>e;sfWR7W80+`7nL$?Zfxoba3q*Oto}trv0}`Mq5KW^qbUuadxK~ zGY9KmyqxME7#SAQW!5{jAa ah@L hAk+0lrUNzK4m6ou4TmImD;-FXjcp5t7ukrQDVF53QT5od5s; delta 2405 zcmbVOYiv|S6rOWt=kD!dS!(HfsSD*%3w^La0S#>l8n7zJ4 &P=H7O9e_&9X+@9xr=Q)$T zdMEkno#cH-YN7eS(b_#_5IXScw)C4xKHu1RuZ$l%zfLY`fDD(f#8+B4dURolPewk1 zXnM?C ;Ll%~~^)I_&5!XZoE3~o;D+jl~STJb5| zc98My9WacHTDgfxPYoZaX6%BVUR|b|-3+U>kP?I}b!QJ`Xebhz=D 41n_noP!s+MJAw0m9OB6HZv>yU|nDKB6U;G5)6$*b>H=;~Ik|?k0`? zj@ibny^M_`qw^Z4|Ey_vCS&^J?P`S^y&7`o0Q{Wd&Pc%x(5Nn^U@Z_WPsK5OaM(t9 zua2<+2`IBXjOmYuQ|Ft~B3l}TXPKe?$X8Q6hIAsg<(IN9UDY+0I(*$`GUvvC%b zZO^wb`d0v3O`)h7Y}!ANG5zsgWBC@wh9u jA|KtmVn(MM#$ zdi*izeA#@5L-gl&Rqh6SHxn+&ulHaET(L;O^^*+DDs(Xb2WJBB(;r`cih R;@JJggvZc|W1utSBVBs#RGAd67F{j`( z1;a-y3&B&gCz{FF71xq#s#cwqsUggRTXK8|_XnaKRysTbM^dgUh0ClqcrX&6brX;C zvmMN Gi7h^^N~Q2 zc$#o5iL8E$6)%`)c-K@CMUt%$XN*Y(|AE4J`EFe}u~ zRz`5uprB3*TxQhZX9(jt;317iyq|~E)d;4!1A55hgL#kI!(9pXbgj7$kMn+O=9c!w zxV9*!rDXL|XotA#v6!wU#$i?bI8JyzF2s-Fh`6>szGh1eV7xb9U6bC-(zxdh9#QRw z@MjLKGV%?k1zKsvF|}_{H(O~?C-l}+M`j!R0PXxN;O(a 3&ooTU{Y~KaYcfmss9zUU~4`T;-b$WC~bS|gq zXeC7rTTw|exACxi_6XjDpsF}Zml33l+`{ycm#F4Z |KlT^Pp ze4qcHgWsP(w^91A{HRWJCtUP3^bYf&%KbK;?&SZF?!iFcsO@)SIlb2ecmF#)?xKZ0 zei1)}R#o#87MjyP>$F>U17GeNuYQ_&pT5fA7s3I2iW;?}QxB3PYANxtl&yJG(38#g zs&kj|H=s@*zJiPKp>t6)uVIb(fBM>U3qNqu!DV+bGoV{D;onU%-WuAWU!?Afy*u@o z+gJbG1yq0U$k0cZS?|=$#GTU1Nekx1Vfu_pyNAo*nY9{-o t+2HW$GyQmeL7IH2i5IlR{l zFUuFYp#ToaW11Y1pLIhX9G4fhG_J08Lpngc{IiF|Mw#6U1rN3t|CY@df3lddX2zIj zjbZftG{*GeD`mE^wKU1N4H(ml|Dz7}!m~*EX8Wj0iTu3}T3|{x9EC81 KOEc$Q`V|wu=HPR1$p05gR zOmm+wMtL+~h%V$YrWaRgegJL)oR;6d1Md=P9i;M8@~uHa7*nSP;VD7l+Atg@(mnzY z5g8rP5Y^WsaFD}@3Q0KqVrsDJwL@)HT}Qg)Uj GAMC)J4R6czW$J z18)nO48X$$H<`d&^yAn6Lw_?_zwUf&mI1|LVdm=wzj4mOD&Qw%od E=4eAU&GzwSC;mU|5h zz~AbyftyTgdd|kfeOOML>lLQA)!?HO7KTBT^5r?^*OGV0W`)@#8of=nW=xm~b3`;I z>|J-XcO~ot;5fwXc+1Hj5d+|O2xDBsNb|M`Ve}Z!8~j5m9}(Q(J@c#*v)4C2+(Dc9 z)IpCGnO8+>oyhfmZ}3F|zXvFqpC$AL|8Byn6uS&QW=mv^*sOTMdxbAPLZV1@6yl0e zXYd6o=jY>k qosakk zpHekhcmm G48G$75umu4xN_;`ZDdcs9&8V&6e zaD6Vh^Pz7ZpLGV#yI
v}1n8%uE5;)dh)$6Ub;!(vxE 9yDwz+3~O^3_tzF8_(_Tj> hjjtEaVD04NWpDcq6UjH4ZckIhv>pn r0b8iFS*>2Z0~g_=YW#7$doMkZ7e?` dv#)1>zo delta 1818 zcmb7FT})g>6rOWt=k9WC1);DI5pKGa-}1LXC|24gO|d0fO_j8W`a(fzjBRzT8Xs=y zhN4s=0n>qK#A@3JDbQ%bB%8*+q!D5fXcI7%hZ;dsBM+LiiGQhQ?k#)wp+30D?D;?E zn>lCZUb}8yyKbIuO@*`3wp7)Y3I%JD!r`jc{WVo>E%IazSP+&f2Vxmu4hpkBJmOQXY{81Ba&WBs1RxL$vP}3_V@J=JN z%I7*D7Y@ibG&w9k?tpCQkQcRdMlE+h3czN0^B9TSWJVNnH$PSIQwC%F@dCyg8DqW` zhC_qNjOoR{D|U>{MG3|lU`#K5Qyq%JGf4SnI;n~!f9-@O7?JhIp%#ke(c>@xV=}9Y zYK_U-F38?8?bf>SDq}CWwO}Zn>gdHMUE>C-bw3&XE1dq>(6&s*^y1U1zY8p$GoI&Q zn*E3|%A*0p(3Nb)^y1@ct{eUU7?9t*1#c5+?xFG{^7S4 LsVb(!yyhQR9M0X2a+nvUwW~ny#2Ly`FAel)A=Lf1^?A8Z9X6WOI<|FfhU*0 zWe{jWlL7d+;3gAzi+=o<|Iq)Is9$wHI>UfsF)!_9ga32h#&Y0MS?9wbb*#~cZ$kOl z6a$0LP1qPFIzGk9T&WVYU?DmHC}HgeI)j*8Yv8@bm#G-8Nq|;QE#`}!mi%?s`GVYI zU {)p%X*FzYi8b)%UNeJVZ@vOm5Q~8MC2LE%Gm0`x}=KES{ zGea(VEKOS$Np&JCuwd{-0>2w5nx7@~2ERIPmxe$C1byaai$KV4g2NM50J?6=L3~ zGx+yZ&f;hE#)-vhYC$|tOav38NJ0%Ya}R!nFf9AlVds6r8Wxy=n8D{M$HTx!bUxxE zd_+~H;|YXO`9%=Z?NOQsOk3TQznwO9NskCc?=$$PG$4(@r)Mad_*k67?SzZeG#c6` z #o9gZUBjlidLLgJS)9TwNRg9Cb|3+YPoOX~Sd%r$L2i-ZM3 zsqPF$!5u (YD z2!0!{K6!nCsy)x&>9yD!!Yl)%JY0ksCCg;rNT>K7;WGHrgdG#<2~tG( L3LPS~pe-S=WsF<~6mEl*x;xm8OlJq6^UTYi~3)IhMtoaT}gcwh6D8(}b^=1TCD zAPlQr+i?nrrtQEK2qt88YxVx16RQX6u{dd0w=O_8&cs3xeym9sRhXY<@SiDvufYeY rfFqPStX41Gfs61#HTo#ty@wvi3#ag;OS09$4{^8tDt&eu*QfjoNb&YH diff --git a/records/wasm/iyi-derive.wasm b/records/wasm/iyi-derive.wasm index d3ee91b23e8bcf26448e855fec9085131d50c749..68d67e2455425c1d3baa5cee73f9c39c0b86c2a4 100755 GIT binary patch delta 1552 zcmZ`(U1(fI6rM9@XLk3lZbO&NQk&X^R2$P|le>gy&{#1PW6 ^JgHUQ9w$zVl+SvV $MFstugbIK*)fJ?$U7aQK^wFIdWej)38QaGg3#>6dRJDmQv+!=Mo2*NeB_9LE z%)-C*&q4Sd9&M{}N$V|)9Uz=4z|b8kWfs<`-@=fAY1Lst3#8S17JLdbYFmU9%&67~ z$y-tfj9E}`8*oXh2qY1-x*8=^>!m211sK+cD&Po*yuP*>da}Z9c=P1z>O?Qpf~~T> zkR~(O3&Y`Go;?-_2#J5HJ?}ulS-Zg?l;IWw2%tcV3H}c~c>jO!^>TcJ{J}f}+RJyu zx~cG_!y5q4&$Fb=@OW9_+YxHGKwSQeK{;*LC*Fl#XqcH}AaQQS;c2pZj-|ZF1`{MX zcdHQJicyJKRn-SuYkwpu{Gqb?2|6QO@loQNW5Z>2xDTq~FMXyDJ`6as3$9vB@LkmU zX<>y9N*tMRcngRpaCw0xO%%G|y29!Z?V%1A85341ofGY)eD;C)Y$<;ZJVMdk?|Aid z;v9GkAqz&vrcg!*c}jLk9HiIh1WG)0lcjj&M)PKZ@JmlUuaDgm;a0IF^n=7j62BK{ zH;$8fiQi2)DX~Z5m>W?x=epvR&@EhifkF}X0C7vUO8lN)m&Af;oLt^aM^LI$6p6AZ zSN0CQ|9zO{Fr^;phcnxzj4ZH1MTs}4#!G g7sKkUf@a{<;XjRyV40BpA$Gn%A@S8w)4@-%oe=}zn8zMpd^)kXLF zXsU$L7dE482tM_C2^%YUJmYWYPrd91{x-hTM=$u@Uh5&<|LHXcVeV72&%BRXFDp16 zcz($ABrLuwCL-M;&KqJPD39S!#IK{TI!Zbv-kEWVB3>5dh_sQ@9}U8D0r*(W4@1IU zo}}wScgS3Mq#)(G##?!Sm%|aA7=f-A>6hfJ!Y7`o(&=B}ApNKf7S~^_K{og=JPoh( delta 1552 zcmZ`(VQ5=b6u#%4+?$sd&B{U=<~kSXbhT^KG?dO#>xMF%We$IAuG!p=IoA@wAJtSD zRA05uv98LX;N7&)iR~b ?D6ObP2$d@WT?0BLkok};? zH#XL%(nB?c4@R%vB}5?A>Wi;5=ZIvIe`-YT3|_DxzW6yARKYs^ZMwj2(^hV#*)NkGJg TR{^T5-=hlQi`iimx`*ABZ mu=zzVb*#(n)o;a+fTJm+bh5(Dfw!C0T3mo*Sy{^e=FX{-`w;vp>2G+2FreuCIIm diff --git a/records/wasm/iyi-enums.wasm b/records/wasm/iyi-enums.wasm index 01d18444899e2ed12f65a6b5bb4c0a4aebc6f07a..a0b018ec6dc5f7272583a905f3841de522d59fa2 100755 GIT binary patch delta 1917 zcmb_dZA@EL7(VYgJ(sqykJxo!fUR-Mh6sgLM47Z~L$ktQ;fF+OoF>bh zp7Va3^PJPAe%n&N?R0k`oI3Mn;lMdtjc0$Q$Ggw-T)6y-3#!6 1r(h>)=?Dks~u95UaPPm;;B?bB2p(>;gI&;HrNO+ zX=75nq9wLL5ggZUNNZH&Jql|9>b2rpGy|GXiv8N&TEtw_I#JmED*ePk2%FB+`Wm57 z#B1R*xuC<3kP_`3KLjDF{o;oj@M^9)I0-TBi#p5_)3D1T|H4*%dFMVt4iiEw%LF2q zR}&&XHfIE%E+gb24CX8|{7)j2`Gm-iEr