patx/relay-lang
modularize runtime and expand production docs
Commit 19b858e · patx · 2026-09-07T14:12:56-04:00
Comments
No comments yet.
Diff
diff --git a/Cargo.lock b/Cargo.lock
index b38d38d..e56e6ef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -15,35 +15,11 @@ dependencies = [
"zerocopy",
]
-[[package]]
-name = "allocator-api2"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
-
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "anyhow"
-version = "1.0.101"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
-
-[[package]]
-name = "ar_archive_writer"
-version = "0.5.1"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
-dependencies = [
- "object",
-]
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "argon2"
@@ -53,7 +29,7 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"password-hash",
]
@@ -65,18 +41,18 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
]
[[package]]
name = "async-trait"
-version = "0.1.89"
+version = "0.1.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
@@ -87,9 +63,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "axum"
@@ -118,7 +94,7 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
- "sha1",
+ "sha1 0.10.7",
"sync_wrapper",
"tokio",
"tokio-tungstenite",
@@ -151,21 +127,15 @@ dependencies = [
[[package]]
name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
-[[package]]
-name = "base64"
-version = "0.21.7"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
-version = "0.22.1"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "base64ct"
@@ -175,21 +145,15 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.10.0"
+version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bitvec"
-version = "1.0.1"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837"
dependencies = [
"funty",
"radium",
@@ -203,7 +167,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -215,6 +179,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "bson"
version = "2.15.0"
@@ -230,7 +203,7 @@ dependencies = [
"indexmap",
"js-sys",
"once_cell",
- "rand 0.9.2",
+ "rand 0.9.5",
"serde",
"serde_bytes",
"serde_json",
@@ -240,9 +213,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.19.1"
+version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "byteorder"
@@ -252,15 +225,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.11.1"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cc"
-version = "1.2.55"
+version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -274,36 +247,71 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
-name = "chrono"
-version = "0.4.43"
+name = "chacha20"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
dependencies = [
- "iana-time-zone",
- "num-traits",
- "windows-link",
+ "cfg-if",
+ "cpufeatures 0.3.1",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
+name = "combine"
+version = "4.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
]
[[package]]
-name = "chumsky"
-version = "0.9.3"
+name = "const-random-macro"
+version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
- "hashbrown 0.14.5",
- "stacker",
+ "getrandom 0.2.17",
+ "once_cell",
+ "tiny-keccak",
]
[[package]]
name = "convert_case"
-version = "0.4.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
[[package]]
name = "core-foundation"
@@ -315,12 +323,28 @@ dependencies = [
"libc",
]
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "core_detect"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -330,6 +354,51 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -340,11 +409,29 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
[[package]]
name = "darling"
-version = "0.13.4"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core",
"darling_macro",
@@ -352,66 +439,83 @@ dependencies = [
[[package]]
name = "darling_core"
-version = "0.13.4"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [
- "fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
- "syn 1.0.109",
+ "syn 2.0.119",
]
[[package]]
name = "darling_macro"
-version = "0.13.4"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core",
"quote",
- "syn 1.0.109",
+ "syn 2.0.119",
]
[[package]]
name = "data-encoding"
-version = "2.10.0"
+version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
+checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
[[package]]
name = "deranged"
-version = "0.5.5"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+
+[[package]]
+name = "derive-syn-parse"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762"
dependencies = [
- "powerfmt",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
]
[[package]]
-name = "derivative"
-version = "2.2.0"
+name = "derive-where"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.119",
]
[[package]]
name = "derive_more"
-version = "0.99.20"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
- "rustc_version 0.4.1",
- "syn 2.0.114",
+ "rustc_version",
+ "syn 2.0.119",
+ "unicode-xid",
]
[[package]]
@@ -420,29 +524,47 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
"subtle",
]
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid",
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
[[package]]
name = "displaydoc"
-version = "0.2.5"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+
[[package]]
name = "email-encoding"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
+checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd"
dependencies = [
- "base64 0.22.1",
+ "base64 0.23.1",
"memchr",
]
@@ -454,23 +576,17 @@ checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
[[package]]
name = "encoding_rs"
-version = "0.8.35"
+version = "0.8.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e"
dependencies = [
"cfg-if",
-]
-
-[[package]]
-name = "enum-as-inner"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
-dependencies = [
- "heck 0.4.1",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "core_detect",
+ "multiversion",
+ "multiversion_no_op",
+ "rustversion",
+ "scopeguard",
+ "simdutf8",
]
[[package]]
@@ -491,15 +607,15 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.3.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "find-msvc-tools"
-version = "0.1.9"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
[[package]]
name = "fnv"
@@ -507,12 +623,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-[[package]]
-name = "foldhash"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
-
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -545,9 +655,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
dependencies = [
"futures-channel",
"futures-core",
@@ -560,9 +670,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [
"futures-core",
"futures-sink",
@@ -570,15 +680,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-executor"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [
"futures-core",
"futures-task",
@@ -587,38 +697,38 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-macro"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
name = "futures-sink"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-channel",
"futures-core",
@@ -628,7 +738,6 @@ dependencies = [
"futures-task",
"memchr",
"pin-project-lite",
- "pin-utils",
"slab",
]
@@ -664,29 +773,30 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
- "r-efi",
+ "r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
-version = "0.4.1"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
- "r-efi",
- "wasip2",
- "wasip3",
+ "r-efi 6.0.0",
+ "rand_core 0.10.1",
+ "wasm-bindgen",
]
[[package]]
name = "h2"
-version = "0.4.13"
+version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
+checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
dependencies = [
"atomic-waker",
"bytes",
@@ -703,70 +813,106 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-dependencies = [
- "ahash",
- "allocator-api2",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-dependencies = [
- "foldhash",
-]
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
-name = "hashbrown"
-version = "0.16.1"
+name = "hex"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
-name = "heck"
-version = "0.4.1"
+name = "hickory-net"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "hickory-proto",
+ "idna",
+ "ipnet",
+ "jni",
+ "rand 0.10.2",
+ "thiserror 2.0.20",
+ "tinyvec",
+ "tokio",
+ "tracing",
+ "url",
+]
[[package]]
-name = "heck"
-version = "0.5.0"
+name = "hickory-proto"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d"
+dependencies = [
+ "data-encoding",
+ "idna",
+ "ipnet",
+ "jni",
+ "once_cell",
+ "prefix-trie",
+ "rand 0.10.2",
+ "ring",
+ "thiserror 2.0.20",
+ "tinyvec",
+ "tracing",
+ "url",
+]
[[package]]
-name = "hex"
-version = "0.4.3"
+name = "hickory-resolver"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-net",
+ "hickory-proto",
+ "ipconfig",
+ "ipnet",
+ "jni",
+ "moka",
+ "ndk-context",
+ "once_cell",
+ "parking_lot",
+ "rand 0.10.2",
+ "resolv-conf",
+ "smallvec",
+ "system-configuration",
+ "thiserror 2.0.20",
+ "tokio",
+ "tracing",
+]
[[package]]
name = "hmac"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [
- "digest",
+ "digest 0.11.3",
]
[[package]]
name = "html-escape"
-version = "0.2.13"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
-dependencies = [
- "utf8-width",
-]
+checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5"
[[package]]
name = "http"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
@@ -774,9 +920,9 @@ dependencies = [
[[package]]
name = "http-body"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
dependencies = [
"bytes",
"http",
@@ -784,9 +930,9 @@ dependencies = [
[[package]]
name = "http-body-util"
-version = "0.1.3"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
dependencies = [
"bytes",
"futures-core",
@@ -807,11 +953,20 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "hyper"
-version = "1.8.1"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
dependencies = [
"atomic-waker",
"bytes",
@@ -824,7 +979,6 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
- "pin-utils",
"smallvec",
"tokio",
"want",
@@ -832,19 +986,18 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.27.7"
+version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
- "rustls 0.23.36",
- "rustls-pki-types",
+ "rustls",
"tokio",
- "tokio-rustls 0.26.4",
+ "tokio-rustls",
"tower-service",
- "webpki-roots 1.0.6",
+ "webpki-roots",
]
[[package]]
@@ -880,7 +1033,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2 0.6.2",
+ "socket2",
"system-configuration",
"tokio",
"tower-service",
@@ -889,47 +1042,24 @@ dependencies = [
]
[[package]]
-name = "iana-time-zone"
-version = "0.1.65"
+name = "icu_collections"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "icu_collections"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
-dependencies = [
- "displaydoc",
- "potential_utf",
- "yoke",
- "zerofrom",
- "zerovec",
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
]
[[package]]
name = "icu_locale_core"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
@@ -940,9 +1070,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -954,16 +1084,17 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_properties"
-version = "2.1.2"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [
+ "displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
@@ -974,15 +1105,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.1.2"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]]
name = "icu_provider"
-version = "2.1.1"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -993,29 +1124,12 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "id-arena"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
-[[package]]
-name = "idna"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
-dependencies = [
- "matches",
- "unicode-bidi",
- "unicode-normalization",
-]
-
[[package]]
name = "idna"
version = "1.1.0"
@@ -1029,9 +1143,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1039,123 +1153,146 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.13.0"
+version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
- "hashbrown 0.16.1",
- "serde",
- "serde_core",
+ "hashbrown",
]
[[package]]
name = "ipconfig"
-version = "0.3.2"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
+checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
dependencies = [
- "socket2 0.5.10",
+ "socket2",
"widestring",
- "windows-sys 0.48.0",
- "winreg",
+ "windows-registry",
+ "windows-result",
+ "windows-sys 0.61.2",
]
[[package]]
name = "ipnet"
-version = "2.11.0"
+version = "2.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "iri-string"
-version = "0.7.10"
+name = "jni"
+version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
- "memchr",
- "serde",
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.20",
+ "walkdir",
+ "windows-link",
]
[[package]]
-name = "itoa"
-version = "1.0.17"
+name = "jni-macros"
+version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn 2.0.119",
+]
[[package]]
-name = "js-sys"
-version = "0.3.85"
+name = "jni-sys"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
- "once_cell",
- "wasm-bindgen",
+ "jni-sys-macros",
]
[[package]]
-name = "lazy_static"
-version = "1.5.0"
+name = "jni-sys-macros"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
[[package]]
-name = "leb128fmt"
-version = "0.1.0"
+name = "js-sys"
+version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
[[package]]
name = "lettre"
-version = "0.11.19"
+version = "0.11.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
+checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae"
dependencies = [
"async-trait",
- "base64 0.22.1",
- "chumsky",
+ "base64 0.23.1",
"email-encoding",
"email_address",
"fastrand",
"futures-io",
"futures-util",
"httpdate",
- "idna 1.1.0",
+ "idna",
"mime",
"nom",
"percent-encoding",
"quoted_printable",
- "rustls 0.23.36",
- "socket2 0.6.2",
+ "rustls",
+ "socket2",
"tokio",
- "tokio-rustls 0.26.4",
+ "tokio-rustls",
"url",
- "webpki-roots 1.0.6",
+ "webpki-roots",
]
[[package]]
name = "libc"
-version = "0.2.181"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
-
-[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]]
name = "lock_api"
@@ -1168,30 +1305,63 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
-name = "lru-cache"
+name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "macro_magic"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d"
dependencies = [
- "linked-hash-map",
+ "macro_magic_core",
+ "macro_magic_macros",
+ "quote",
+ "syn 2.0.119",
]
[[package]]
-name = "lru-slab"
-version = "0.1.2"
+name = "macro_magic_core"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150"
+dependencies = [
+ "const-random",
+ "derive-syn-parse",
+ "macro_magic_core_macros",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "macro_magic_core_macros"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
[[package]]
-name = "matches"
-version = "0.1.10"
+name = "macro_magic_macros"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869"
+dependencies = [
+ "macro_magic_core",
+ "quote",
+ "syn 2.0.119",
+]
[[package]]
name = "matchit"
@@ -1201,19 +1371,25 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "md-5"
-version = "0.10.6"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
dependencies = [
"cfg-if",
- "digest",
+ "digest 0.11.3",
]
[[package]]
name = "memchr"
-version = "2.8.0"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "memo-map"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+checksum = "5449c8c750f1a07ea702bbd212bd999fceece9b3d1508b17023b3e174583124b"
[[package]]
name = "mime"
@@ -1223,69 +1399,116 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minijinja"
-version = "2.15.1"
+version = "2.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b479616bb6f0779fb0f3964246beda02d4b01144e1b0d5519616e012ccc2a245"
+checksum = "86886cf6dbf4e614b19c9a1eec9775f021869d7eadde0fc73921a81b90c9b4c9"
dependencies = [
+ "memo-map",
"serde",
]
[[package]]
name = "mio"
-version = "1.1.1"
+version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
+[[package]]
+name = "moka"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "equivalent",
+ "parking_lot",
+ "portable-atomic",
+ "smallvec",
+ "tagptr",
+ "uuid",
+]
+
+[[package]]
+name = "mongocrypt"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8426a875ded61430d4a811dbfda7633b6b8af0225c547fc6c28b8b0aa7d79a13"
+dependencies = [
+ "bson",
+ "mongocrypt-sys",
+ "once_cell",
+ "serde",
+]
+
+[[package]]
+name = "mongocrypt-sys"
+version = "0.1.6+1.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "851fac73f7fe22f6a3ab87f720ce509cae7c9fd08e7dd27866cc232dee07ccf4"
+
[[package]]
name = "mongodb"
-version = "2.8.2"
+version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef206acb1b72389b49bc9985efe7eb1f8a9bb18e5680d262fac26c07f44025f1"
+checksum = "fd64784a1fdbcf2a334717446edc583061159067a2e7cc326ee567ecf035f07b"
dependencies = [
- "async-trait",
- "base64 0.13.1",
- "bitflags 1.3.2",
+ "base64 0.22.1",
+ "bitflags",
"bson",
- "chrono",
- "derivative",
+ "derive-where",
"derive_more",
"futures-core",
- "futures-executor",
"futures-io",
"futures-util",
"hex",
+ "hickory-net",
+ "hickory-proto",
+ "hickory-resolver",
"hmac",
- "lazy_static",
+ "macro_magic",
"md-5",
+ "mongocrypt",
+ "mongodb-internal-macros",
"pbkdf2",
"percent-encoding",
- "rand 0.8.5",
+ "rand 0.9.5",
"rustc_version_runtime",
- "rustls 0.21.12",
- "rustls-pemfile",
+ "rustls",
"serde",
"serde_bytes",
"serde_with",
- "sha-1",
+ "sha1 0.11.0",
"sha2",
- "socket2 0.4.10",
+ "socket2",
"stringprep",
"strsim",
"take_mut",
- "thiserror 1.0.69",
+ "thiserror 2.0.20",
"tokio",
- "tokio-rustls 0.24.1",
+ "tokio-rustls",
"tokio-util",
- "trust-dns-proto",
- "trust-dns-resolver",
"typed-builder",
"uuid",
- "webpki-roots 0.25.4",
+ "webpki-roots",
+]
+
+[[package]]
+name = "mongodb-internal-macros"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f243f9039382cd06378f13980b79700236468b6fb22f24abfa99783e9569f1a3"
+dependencies = [
+ "macro_magic",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
]
[[package]]
@@ -1305,11 +1528,39 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "multiversion"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201"
+dependencies = [
+ "multiversion-macros",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "target-features",
+]
+
+[[package]]
+name = "multiversion_no_op"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
+
[[package]]
name = "native-tls"
-version = "0.2.14"
+version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
@@ -1322,6 +1573,12 @@ dependencies = [
"tempfile",
]
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
[[package]]
name = "nom"
version = "8.0.0"
@@ -1333,9 +1590,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.2.0"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-traits"
@@ -1347,31 +1604,25 @@ dependencies = [
]
[[package]]
-name = "object"
-version = "0.37.3"
+name = "once_cell"
+version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
dependencies = [
- "memchr",
+ "critical-section",
+ "portable-atomic",
]
-[[package]]
-name = "once_cell"
-version = "1.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
-
[[package]]
name = "openssl"
-version = "0.10.75"
+version = "0.10.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
+checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags",
"cfg-if",
"foreign-types",
"libc",
- "once_cell",
"openssl-macros",
"openssl-sys",
]
@@ -1384,20 +1635,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
]
[[package]]
name = "openssl-probe"
-version = "0.1.6"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
-version = "0.9.111"
+version = "0.9.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
+checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
dependencies = [
"cc",
"libc",
@@ -1441,11 +1692,11 @@ dependencies = [
[[package]]
name = "pbkdf2"
-version = "0.11.0"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
dependencies = [
- "digest",
+ "digest 0.11.3",
]
[[package]]
@@ -1456,27 +1707,27 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
-name = "pin-utils"
-version = "0.1.0"
+name = "pkg-config"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
-name = "pkg-config"
-version = "0.3.32"
+name = "portable-atomic"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "potential_utf"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [
"zerovec",
]
@@ -1497,39 +1748,30 @@ dependencies = [
]
[[package]]
-name = "prettyplease"
-version = "0.2.37"
+name = "prefix-trie"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7"
dependencies = [
- "proc-macro2",
- "syn 2.0.114",
+ "either",
+ "ipnet",
+ "num-traits",
]
[[package]]
name = "proc-macro2"
-version = "1.0.106"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
-[[package]]
-name = "psm"
-version = "0.1.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
-dependencies = [
- "ar_archive_writer",
- "cc",
-]
-
[[package]]
name = "quinn"
-version = "0.11.9"
+version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
dependencies = [
"bytes",
"cfg_aliases",
@@ -1537,9 +1779,9 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
- "rustls 0.23.36",
- "socket2 0.6.2",
- "thiserror 2.0.18",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.20",
"tokio",
"tracing",
"web-time",
@@ -1547,20 +1789,21 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.11.13"
+version = "0.11.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
dependencies = [
"bytes",
- "getrandom 0.3.4",
+ "getrandom 0.4.3",
"lru-slab",
- "rand 0.9.2",
+ "rand 0.10.2",
+ "rand_pcg",
"ring",
"rustc-hash",
- "rustls 0.23.36",
+ "rustls",
"rustls-pki-types",
"slab",
- "thiserror 2.0.18",
+ "thiserror 2.0.20",
"tinyvec",
"tracing",
"web-time",
@@ -1568,32 +1811,32 @@ dependencies = [
[[package]]
name = "quinn-udp"
-version = "0.5.14"
+version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
- "socket2 0.6.2",
+ "socket2",
"tracing",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "quote"
-version = "1.0.44"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "quoted_printable"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
+checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
[[package]]
name = "r-efi"
@@ -1601,6 +1844,12 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "radium"
version = "0.7.0"
@@ -1609,9 +1858,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
-version = "0.8.5"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
dependencies = [
"libc",
"rand_chacha 0.3.1",
@@ -1620,14 +1869,25 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.9.2"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "rand_chacha"
version = "0.3.1"
@@ -1666,18 +1926,33 @@ dependencies = [
"getrandom 0.3.4",
]
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags",
]
[[package]]
name = "relay"
-version = "0.1.0"
+version = "0.2.0-rc.1"
dependencies = [
"anyhow",
"argon2",
@@ -1725,7 +2000,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
- "rustls 0.23.36",
+ "rustls",
"rustls-pki-types",
"serde",
"serde_json",
@@ -1733,15 +2008,15 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-native-tls",
- "tokio-rustls 0.26.4",
+ "tokio-rustls",
"tower 0.5.3",
- "tower-http 0.6.8",
+ "tower-http 0.6.11",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "webpki-roots 1.0.6",
+ "webpki-roots",
]
[[package]]
@@ -1766,18 +2041,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "2.1.1"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
-
-[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver 0.9.0",
-]
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustc_version"
@@ -1785,26 +2051,26 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
- "semver 1.0.27",
+ "semver",
]
[[package]]
name = "rustc_version_runtime"
-version = "0.2.1"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f"
+checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d"
dependencies = [
- "rustc_version 0.2.3",
- "semver 0.9.0",
+ "rustc_version",
+ "semver",
]
[[package]]
name = "rustix"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags",
"errno",
"libc",
"linux-raw-sys",
@@ -1813,45 +2079,24 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.21.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
-dependencies = [
- "log",
- "ring",
- "rustls-webpki 0.101.7",
- "sct",
-]
-
-[[package]]
-name = "rustls"
-version = "0.23.36"
+version = "0.23.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
+checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
- "rustls-webpki 0.103.9",
+ "rustls-webpki",
"subtle",
"zeroize",
]
-[[package]]
-name = "rustls-pemfile"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
-dependencies = [
- "base64 0.21.7",
-]
-
[[package]]
name = "rustls-pki-types"
-version = "1.14.0"
+version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"web-time",
"zeroize",
@@ -1859,19 +2104,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.101.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
-dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.103.9"
+version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"ring",
"rustls-pki-types",
@@ -1880,9 +2115,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.22"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1891,10 +2126,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
-name = "schannel"
-version = "0.1.28"
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
@@ -1905,24 +2149,14 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "sct"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
-dependencies = [
- "ring",
- "untrusted",
-]
-
[[package]]
name = "security-framework"
-version = "2.11.1"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
- "bitflags 2.10.0",
- "core-foundation",
+ "bitflags",
+ "core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@@ -1930,9 +2164,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.15.0"
+version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1940,30 +2174,15 @@ dependencies = [
[[package]]
name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
-[[package]]
-name = "semver"
-version = "1.0.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
-
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
@@ -1981,29 +2200,29 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
@@ -2038,64 +2257,64 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "1.14.0"
+version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
dependencies = [
- "serde",
+ "serde_core",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
-version = "1.5.2"
+version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.119",
]
[[package]]
-name = "sha-1"
-version = "0.10.1"
+name = "sha1"
+version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
+checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
]
[[package]]
name = "sha1"
-version = "0.10.6"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
dependencies = [
"cfg-if",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.3.1",
+ "digest 0.11.3",
]
[[package]]
name = "sha2"
-version = "0.10.9"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.3.1",
+ "digest 0.11.3",
]
[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signal-hook-registry"
@@ -2108,52 +2327,48 @@ dependencies = [
]
[[package]]
-name = "slab"
-version = "0.4.12"
+name = "simd_cesu8"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
[[package]]
-name = "smallvec"
-version = "1.15.1"
+name = "simdutf8"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
-name = "socket2"
-version = "0.4.10"
+name = "slab"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
-dependencies = [
- "libc",
- "winapi",
-]
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
-name = "socket2"
-version = "0.5.10"
+name = "smallvec"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
-dependencies = [
- "libc",
- "windows-sys 0.52.0",
-]
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
[[package]]
name = "socket2"
-version = "0.6.2"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "spin"
-version = "0.9.8"
+version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
[[package]]
name = "stable_deref_trait"
@@ -2161,19 +2376,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-[[package]]
-name = "stacker"
-version = "0.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
-dependencies = [
- "cc",
- "cfg-if",
- "libc",
- "psm",
- "windows-sys 0.59.0",
-]
-
[[package]]
name = "stringprep"
version = "0.1.5"
@@ -2187,9 +2389,9 @@ dependencies = [
[[package]]
name = "strsim"
-version = "0.10.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
@@ -2199,9 +2401,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "1.0.109"
+version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -2210,9 +2412,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.114"
+version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
@@ -2236,7 +2438,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
]
[[package]]
@@ -2245,8 +2447,8 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
- "bitflags 2.10.0",
- "core-foundation",
+ "bitflags",
+ "core-foundation 0.9.4",
"system-configuration-sys",
]
@@ -2260,6 +2462,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "tagptr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
+
[[package]]
name = "take_mut"
version = "0.2.2"
@@ -2272,14 +2480,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+[[package]]
+name = "target-features"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5"
+
[[package]]
name = "tempfile"
-version = "3.25.0"
+version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
- "getrandom 0.4.1",
+ "getrandom 0.4.3",
"once_cell",
"rustix",
"windows-sys 0.61.2",
@@ -2296,11 +2510,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.18"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
- "thiserror-impl 2.0.18",
+ "thiserror-impl 2.0.20",
]
[[package]]
@@ -2311,28 +2525,27 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.18"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
name = "time"
-version = "0.3.47"
+version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [
"deranged",
- "itoa",
"num-conv",
"powerfmt",
"serde_core",
@@ -2342,25 +2555,34 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
-version = "0.2.27"
+version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
[[package]]
name = "tinystr"
-version = "0.8.2"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"zerovec",
@@ -2368,9 +2590,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.10.0"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
+checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b"
dependencies = [
"tinyvec_macros",
]
@@ -2383,9 +2605,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.49.0"
+version = "1.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [
"bytes",
"libc",
@@ -2393,20 +2615,20 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2 0.6.2",
+ "socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.6.0"
+version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
@@ -2421,21 +2643,11 @@ dependencies = [
[[package]]
name = "tokio-rustls"
-version = "0.24.1"
+version = "0.26.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
dependencies = [
- "rustls 0.21.12",
- "tokio",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.26.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
-dependencies = [
- "rustls 0.23.36",
+ "rustls",
"tokio",
]
@@ -2453,14 +2665,16 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.18"
+version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
+ "futures-util",
+ "libc",
"pin-project-lite",
"tokio",
]
@@ -2498,7 +2712,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags",
"bytes",
"http",
"http-body",
@@ -2510,20 +2724,20 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.8"
+version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags",
"bytes",
"futures-util",
"http",
"http-body",
- "iri-string",
"pin-project-lite",
"tower 0.5.3",
"tower-layer",
"tower-service",
+ "url",
]
[[package]]
@@ -2558,51 +2772,6 @@ dependencies = [
"once_cell",
]
-[[package]]
-name = "trust-dns-proto"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d"
-dependencies = [
- "async-trait",
- "cfg-if",
- "data-encoding",
- "enum-as-inner",
- "futures-channel",
- "futures-io",
- "futures-util",
- "idna 0.2.3",
- "ipnet",
- "lazy_static",
- "log",
- "rand 0.8.5",
- "smallvec",
- "thiserror 1.0.69",
- "tinyvec",
- "tokio",
- "url",
-]
-
-[[package]]
-name = "trust-dns-resolver"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558"
-dependencies = [
- "cfg-if",
- "futures-util",
- "ipconfig",
- "lazy_static",
- "log",
- "lru-cache",
- "parking_lot",
- "resolv-conf",
- "smallvec",
- "thiserror 1.0.69",
- "tokio",
- "trust-dns-proto",
-]
-
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -2621,28 +2790,37 @@ dependencies = [
"http",
"httparse",
"log",
- "rand 0.8.5",
- "sha1",
+ "rand 0.8.8",
+ "sha1 0.10.7",
"thiserror 1.0.69",
"utf-8",
]
[[package]]
name = "typed-builder"
-version = "0.10.0"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
+checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a"
+dependencies = [
+ "typed-builder-macro",
+]
+
+[[package]]
+name = "typed-builder-macro"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.119",
]
[[package]]
name = "typenum"
-version = "1.19.0"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-bidi"
@@ -2652,9 +2830,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
-version = "1.0.23"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-normalization"
@@ -2671,6 +2849,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
[[package]]
name = "unicode-xid"
version = "0.2.6"
@@ -2690,7 +2874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
- "idna 1.1.0",
+ "idna",
"percent-encoding",
"serde",
]
@@ -2701,12 +2885,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-[[package]]
-name = "utf8-width"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
-
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -2715,11 +2893,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.20.0"
+version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
+checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
dependencies = [
- "getrandom 0.3.4",
+ "getrandom 0.4.3",
"js-sys",
"serde_core",
"wasm-bindgen",
@@ -2737,6 +2915,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -2754,27 +2942,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
-version = "1.0.2+wasi-0.2.9"
+version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
-dependencies = [
- "wit-bindgen",
-]
-
-[[package]]
-name = "wasip3"
-version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
@@ -2785,23 +2964,19 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.58"
+version = "0.4.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
+checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
dependencies = [
- "cfg-if",
- "futures-util",
"js-sys",
- "once_cell",
"wasm-bindgen",
- "web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2809,65 +2984,31 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
-[[package]]
-name = "wasm-encoder"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
-dependencies = [
- "leb128fmt",
- "wasmparser",
-]
-
-[[package]]
-name = "wasm-metadata"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
-dependencies = [
- "anyhow",
- "indexmap",
- "wasm-encoder",
- "wasmparser",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
-dependencies = [
- "bitflags 2.10.0",
- "hashbrown 0.15.5",
- "indexmap",
- "semver 1.0.27",
-]
-
[[package]]
name = "web-sys"
-version = "0.3.85"
+version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
+checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -2885,15 +3026,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.25.4"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
-
-[[package]]
-name = "webpki-roots"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
dependencies = [
"rustls-pki-types",
]
@@ -2905,60 +3040,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-core"
-version = "0.62.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.114",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.3"
+name = "winapi-util"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.114",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2996,40 +3083,13 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
-dependencies = [
- "windows-targets 0.53.5",
+ "windows-targets",
]
[[package]]
@@ -3041,295 +3101,81 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.53.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
-dependencies = [
- "windows-link",
- "windows_aarch64_gnullvm 0.53.1",
- "windows_aarch64_msvc 0.53.1",
- "windows_i686_gnu 0.53.1",
- "windows_i686_gnullvm 0.53.1",
- "windows_i686_msvc 0.53.1",
- "windows_x86_64_gnu 0.53.1",
- "windows_x86_64_gnullvm 0.53.1",
- "windows_x86_64_msvc 0.53.1",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-[[package]]
-name = "windows_i686_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
-
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-[[package]]
-name = "windows_i686_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
-
-[[package]]
-name = "winreg"
-version = "0.50.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
-dependencies = [
- "cfg-if",
- "windows-sys 0.48.0",
-]
-
[[package]]
name = "wit-bindgen"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
-dependencies = [
- "wit-bindgen-rust-macro",
-]
-
-[[package]]
-name = "wit-bindgen-core"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
-dependencies = [
- "anyhow",
- "heck 0.5.0",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-bindgen-rust"
-version = "0.51.0"
+version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
-dependencies = [
- "anyhow",
- "heck 0.5.0",
- "indexmap",
- "prettyplease",
- "syn 2.0.114",
- "wasm-metadata",
- "wit-bindgen-core",
- "wit-component",
-]
-
-[[package]]
-name = "wit-bindgen-rust-macro"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
-dependencies = [
- "anyhow",
- "prettyplease",
- "proc-macro2",
- "quote",
- "syn 2.0.114",
- "wit-bindgen-core",
- "wit-bindgen-rust",
-]
-
-[[package]]
-name = "wit-component"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
-dependencies = [
- "anyhow",
- "bitflags 2.10.0",
- "indexmap",
- "log",
- "serde",
- "serde_derive",
- "serde_json",
- "wasm-encoder",
- "wasm-metadata",
- "wasmparser",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-parser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
-dependencies = [
- "anyhow",
- "id-arena",
- "indexmap",
- "log",
- "semver 1.0.27",
- "serde",
- "serde_derive",
- "serde_json",
- "unicode-xid",
- "wasmparser",
-]
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "writeable"
-version = "0.6.2"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "wyz"
@@ -3342,9 +3188,9 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -3353,68 +3199,68 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.39"
+version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.39"
+version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
]
[[package]]
name = "zerofrom"
-version = "0.1.6"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 2.0.119",
"synstructure",
]
[[package]]
name = "zeroize"
-version = "1.8.2"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerotrie"
-version = "0.2.3"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"yoke",
@@ -3423,9 +3269,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.5"
+version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
dependencies = [
"yoke",
"zerofrom",
@@ -3434,17 +3280,17 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.2"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.114",
+ "syn 3.0.5",
]
[[package]]
name = "zmij"
-version = "1.0.20"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/Cargo.toml b/Cargo.toml
index 95edd9b..dd3e386 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,11 @@
[package]
name = "relay"
-version = "0.1.0"
+version = "0.2.0-rc.1"
edition = "2021"
+rust-version = "1.93"
+license = "MIT"
+description = "An implicitly asynchronous language for web services"
+repository = "https://github.com/patx/relay-lang"
[dependencies]
tokio = { version = "1.35", features = ["full"] }
@@ -16,10 +20,15 @@ tower-http = "0.5"
html-escape = "0.2"
indexmap = "2"
async-recursion = "1"
-mongodb = { version = "2.8", features = ["tokio-runtime"] }
+mongodb = "3.8"
futures = "0.3"
minijinja = "2"
url = "2"
argon2 = "0.5"
lettre = { version = "0.11", default-features = false, features = ["builder", "smtp-transport", "tokio1-rustls-tls"] }
multer = "3"
+
+[profile.release]
+lto = "thin"
+codegen-units = 1
+strip = "symbols"
diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md
new file mode 100644
index 0000000..5d9e87b
--- /dev/null
+++ b/IMPLEMENTATION.md
@@ -0,0 +1,37 @@
+# Production release implementation
+
+Target: trusted Linux web services, MongoDB, implicit async, documented breaking changes.
+
+This checklist records verified work, not a production certification.
+
+## Latest validation checkpoint
+
+- The two-process Twitter integration scenario passed before the dependency refresh.
+- Cancellation during task draining now aborts the drained children; a regression
+ test reproduces the formerly orphaned-task case.
+- The compatible dependency refresh passed 61 tests (one live MongoDB test ignored).
+- Migrated MongoDB from driver 2.8 to the 3.x action API, following the
+ [official migration guide](https://github.com/mongodb/mongo-rust-driver/blob/main/migration-3.0.md).
+- After that migration, all 61 ordinary tests pass, and the isolated live MongoDB
+ session/CRUD test passes. RustSec reports no advisories or yanked-version
+ warnings for the 328 locked dependencies checked on September 7, 2026.
+- Release remains gated on protocol hardening, release/CI tooling, fuzzing and
+ the full 60-minute soak. Earlier binary integration results do not certify the
+ updated dependency build.
+
+## Release gates
+
+- [ ] Execution-owned environments and application-owned routing
+- [ ] Shared eager async results, supervision, cancellation and deadlines
+- [ ] Lexical scopes, namespaced imports, structured diagnostics and cleanup
+- [ ] Practical collections, environment access and explicit templates
+- [ ] CLI checking, formatting and test discovery
+- [ ] HTTP limits, error sanitization, consistent JSON and request isolation
+- [ ] Secure expiring persistent sessions and CSRF
+- [ ] Bounded MongoDB queries and atomic example updates
+- [ ] Graceful shutdown, structured logs and metrics
+- [ ] Linux release/container tooling and CI
+- [ ] Migrated examples and language/deployment documentation
+- [ ] Integration, fuzz, concurrency and 60-minute soak release gates
+
+Existing user changes: two additional tests in src/main.rs and examples/twitter_clone/.
diff --git a/README.md b/README.md
index f8d1608..bfa5ed4 100644
--- a/README.md
+++ b/README.md
@@ -1,2355 +1,135 @@
-# Relay Programming Language
+# Relay
-**Version:** 0.1
-**License:** MIT
+Relay is an implicitly asynchronous, indentation-based language for trusted web services. The interpreter is written in Rust and uses Tokio, Axum, MongoDB, Reqwest and MiniJinja.
-> A networking-first, async-by-default programming language with Python-like syntax and Node.js-style non-blocking semantics.
+**Current development release: 0.2.0-rc.1.** This is a breaking release candidate. See [implementation status](IMPLEMENTATION.md), [migration notes](docs/migration.md), and [deployment guidance](docs/deployment.md). Passing unit tests alone is not a production certification.
-Relay is designed for building high-performance web services, APIs, and network applications with minimal boilerplate. Everything is asynchronous by default, but there's **no `await` keyword**—async operations start immediately and only block when their values are actually needed.
+## Build and run
+Rust 1.93 or newer is required.
-
-## Table of Contents
-
-- [Features](#features)
-- [Quick Start](#quick-start)
-- [Example Prereqs](#example-prereqs)
-- [Installation](#installation)
-- [Learning Path](#learning-path)
-- [Language Guide](#language-guide)
- - [Syntax Fundamentals](#syntax-fundamentals)
- - [Data Types](#data-types)
- - [Control Flow](#control-flow)
- - [Functions](#functions)
- - [Async Model](#async-model)
-- [API Reference](#api-reference)
- - [Core Functions](#core-functions)
- - [Async & Concurrency](#async--concurrency)
- - [File System](#file-system)
- - [HTTP Client](#http-client)
- - [Email (SMTP)](#email-smtp)
- - [Web Server](#web-server)
- - [Database (MongoDB)](#database-mongodb)
-- [Examples](#examples)
-- [How It Works](#how-it-works)
-- [Best Practices](#best-practices)
-- [Troubleshooting](#troubleshooting)
-- [Contributing](#contributing)
-
-
-
-## Features
-
-- **Implicit Async**: No `await` keyword needed—async operations resolve automatically when values are accessed
-- **Python-like Syntax**: Clean, indentation-based syntax that's easy to read and write
-- **Built-in Web Server**: Flask/FastAPI-style decorators with automatic routing
-- **Route Groups + OpenAPI**: Prefix route groups and expose generated `/openapi.json`
-- **Static File Serving**: Mount directories directly in your web app (`app.static("/assets", "./public")`)
-- **Middleware Hooks**: Run request middleware before handlers (`app.use(auth_middleware)`)
-- **HTTP Client**: Async HTTP client with `get/post/put/patch/delete` and request/response headers
-- **Email (SMTP)**: Async SMTP mail sending with secure-by-default TLS and template helpers
-- **Multipart Uploads**: Parse `multipart/form-data` bodies with file metadata/bytes
-- **Upload Guards**: Configure max request/file size limits and MIME allowlists via `app.uploads(...)`
-- **MongoDB Integration**: Native async MongoDB support
-- **Session Management**: Built-in session handling with HttpOnly cookies
-- **Pluggable Session Backends**: Use in-memory sessions or custom load/save callbacks
-- **Authentication Helpers**: Password hashing/verification and pluggable auth stores
-- **Structured API Errors**: `HTTPError(...)` helper with consistent JSON error envelope
-- **Request IDs**: Per-request `request_id` in handler context and `x-request-id` response header
-- **Template Rendering**: MiniJinja-powered templates available in any string expression (`{{ variable }}`)
-- **Type Hints**: Optional runtime type checking for function parameters
-- **List Comprehensions**: Python-style inline list transforms with optional filtering
-- **Destructuring Assignment**: Unpack lists/strings/dicts into multiple variables
-- **Error Handling**: `try/except` blocks for graceful runtime error recovery
-- **Concurrency Primitives**: `spawn`, `all`, `race`, `timeout`, and `cancel` for parallel execution
-
-
-
-## Quick Start
-
-### Installation
-
-```bash
-# Clone the repository
-git clone https://github.com/patx/relay-lang.git
-cd relay-lang
-
-# Install the Relay CLI
-cargo install --path .
-
-# Run a Relay program
-relay path/to/app.ry
-```
-
-### Run Included Examples
-
-```bash
-# Small language/runtime example
-relay examples/type_hint_validation.ry
-
-# Full web app examples
-relay examples/param_types.ry
-relay examples/pastebin.ry # requires local MongoDB
-relay examples/url_shortener.ry # requires local MongoDB
-```
-
-### Example Prereqs
-
-| Example | Needs |
-|---|---|
-| `examples/type_hint_validation.ry` | None |
-| `examples/param_types.ry` | None |
-| `examples/pastebin.ry` | Local MongoDB at `mongodb://localhost:27017` |
-| `examples/url_shortener.ry` | Local MongoDB at `mongodb://localhost:27017` |
-| `examples/upload_email_attachment.ry` | SMTP credentials + template files in `examples/templates/` |
-| `examples/email_smtp.ry` | SMTP credentials |
-
-### Hello World
-
-```python
-print("Hello, Relay!")
-```
-
-### Async Hello World
-
-```python
-sleep(2000, print("world"))
-print("hello")
-```
-
-**Output:**
-```
-hello
-world
+```sh
+cargo build --release --locked
+./target/release/relay --help
+./target/release/relay check examples/param_types.ry
+./target/release/relay run examples/param_types.ry
```
-Notice how "hello" prints immediately while "world" waits 2 seconds—all without blocking the main thread.
-
-### Simple Web Server
-
```python
app = WebApp()
-server = WebServer()
-
-fn auth()
- if (request.path == "/private" && session["user"] == None)
- return app.redirect("/login")
-app.use(auth)
-app.static("/assets", "./public")
-
[email protected]("/")
-fn index()
- return "Hello, Relay!"
[email protected]("/hello/<name>")
+fn hello(name: str)
+ return {"hello": name, "ok": True}
+server = WebServer()
server.run(app)
```
-Visit `http://127.0.0.1:8080/` to see your response.
-
-
-
-## Installation
-
-### Prerequisites
-
-- Rust 1.70 or later
-- Cargo (included with Rust)
-
-### From Source
-
-```bash
-# Clone the repository
-git clone https://github.com/patx/relay-lang.git
-cd relay-lang
-
-# Build and install
-cargo install --path .
-
-# Run a Relay program
-relay path/to/app.ry
-```
-
-### Development Mode
-
-If you're developing the Relay compiler itself:
-
-```bash
-cargo run -- path/to/file.ry
-```
-
-### Environment Variables
-
-- `RELAY_BIND`: Override the default bind address for web servers (default: `127.0.0.1:8080`)
-
-Example:
-```bash
-RELAY_BIND=0.0.0.0:8080 relay server.ry
-```
-
-
-
-## Learning Path
-
-If your goal is to learn Relay end-to-end, use this order:
-
-1. Start with [Quick Start](#quick-start) to run your first script and web app.
-2. Work through [Language Guide](#language-guide) for syntax, types, control flow, modules, and async behavior.
-3. Use [API Reference](#api-reference) as the complete built-in/runtime reference.
-4. Run scripts in [`examples/`](examples/) to see complete services and integrations.
-5. Revisit [Best Practices](#best-practices) and [Troubleshooting](#troubleshooting) as you build real apps.
-
-
-## Language Guide
-
-### Syntax Fundamentals
-
-Relay uses **indentation-based syntax** with 4 spaces per indentation level. Tabs are not allowed.
-
-```python
-fn example()
- x = 10
- if (x > 5)
- print("x is greater than 5")
- else
- print("x is 5 or less")
-```
-
-**Key Rules:**
-- Indentation must be exactly 4 spaces per level
-- No tabs allowed
-- Function bodies, control flow blocks, and loops all require indentation
-- Comments start with `//` and continue to the end of the line
-
-```python
-// This is a comment
-x = 42 // This is also a comment
-```
-
-### Modules and Imports
-
-Relay supports loading code from multiple `.ry` files with `import`:
-
-```python
-import utils
-import web.routes
-import shared/helpers.ry
-```
-
-**How imports resolve:**
-- `import utils` loads `utils.ry`
-- `import web.routes` loads `web/routes.ry`
-- Relative paths are resolved from the importing file's directory
-- A module is loaded only once per run (duplicate imports are ignored)
-
-The module filenames above are illustrative examples of resolution behavior.
-
-Imported modules execute in the same global scope, so functions and variables they define become directly available.
-
-### Data Types
-
-#### Primitives
-
-```python
-// Integers
-age = 25
-count = -10
-
-// Floats
-pi = 3.14159
-temperature = -273.15
-
-// Strings
-name = "Relay"
-message = "Hello, world!"
-
-// Booleans
-is_active = True
-is_complete = False
-
-// None
-result = None
-```
-
-#### Collections
-
-**Lists:**
-```python
-numbers = [1, 2, 3, 4, 5]
-mixed = [1, "two", 3.0, True]
-nested = [[1, 2], [3, 4]]
-
-// List comprehensions
-squares = [n * n for n in numbers]
-evens = [n for n in numbers if n % 2 == 0]
-
-// Access elements
-first = numbers[0] // 1
-last = numbers[4] // 5
-
-// Lists are mutable
-numbers[0] = 10
-```
-
-**Dictionaries:**
-```python
-user = {"name": "Ada", "age": 30, "active": True}
-
-// Access values
-name = user["name"] // "Ada"
-age = user["age"] // 30
-
-// Keys are always strings
-// Values can be any type
-```
-
-**Important:** Dictionary keys are automatically stringified. `{1: "value"}` becomes `{"1": "value"}`.
-
-#### Destructuring Assignment
-
-Relay supports destructuring assignment for iterables such as lists, strings, and dictionaries (dictionary keys are unpacked):
-
-```python
-a, b, c = [10, 20, 30]
-x, y = "hi" // x = "h", y = "i"
-k1, k2 = {"a": 1, "b": 2}
-```
-
-The number of variables must match the number of unpacked values.
-
-### Control Flow
-
-#### If-Else
-
-```python
-if (condition)
- // then block
- print("condition is true")
-```
-
-```python
-if (x > 0)
- print("positive")
-else
- print("zero or negative")
-```
+The default bind address is `127.0.0.1:8080`. Use `RELAY_BIND` to change it. Use four spaces for indentation, `fn` for functions, and `//` or `/* ... */` for comments. Blocks do not use colons.
-#### While Loops
+## Language
-```python
-i = 0
-while (i < 5)
- print(i)
- i = i + 1
-```
+Values include integers, floats, booleans, strings, lists, dictionaries, JSON, bytes, functions, modules and asynchronous results. Strings are Unicode; dictionary keys are strings. Type hints are optional runtime checks, not static types.
-Augmented assignment operators are supported:
```python
-i = 0
-while (i < 5)
- print(i)
- i =+ 1 // equivalent to i = i + 1
-```
+fn greet(name: str, prefix = "Hello")
+ return prefix + ", " + name
-#### For Loops
+items = [1, 2, 3]
+items.append(4)
+assert_equal(items[::-1], [4, 3, 2, 1])
+assert(2 in items)
+names = [str(item) for item in items if item > 1]
-```python
-// Iterate over lists
-for (item in [1, 2, 3, 4, 5])
+for (item in names)
print(item)
-// Iterate over dictionary keys
-user = {"name": "Ada", "age": 30}
-for (key in user)
- print(key + ": " + str(user[key]))
-```
-
-**Note:** For loops iterate over collection elements or dictionary keys.
-
-#### `break` and `continue`
-
-```python
-i = 0
-while (i < 6)
- i =+ 1
- if (i == 2)
- continue
- if (i == 5)
- break
- print(i)
-```
-
-#### Error Handling (`try/except`)
-
-Use `try/except` to catch runtime errors and continue execution:
-
-```python
-try
- value = int("not-a-number")
- print(value)
-except
- print("Could not parse integer")
-```
-
-You can also bind the error message:
-
-```python
try
- result = missing_name + 1
-except(err)
- print("Error:", err)
-```
-
-### Functions
-
-#### Basic Functions
-
-```python
-fn greet(name)
- return "Hello, " + name
-
-message = greet("World")
-print(message) // Hello, World
-```
-
-#### Default Parameters
-
-```python
-fn greet(name: str = "World")
- return "Hello, " + name
-
-print(greet()) // Hello, World
-print(greet("Relay")) // Hello, Relay
-```
-
-#### Type Hints
-
-Relay supports runtime type checking for parameters:
-
-```python
-fn add(a: int, b: int)
- return a + b
-
-result = add(5, 10) // OK
-result = add("5", "10") // Type error!
-```
-
-Type hints on regular functions are strict validation (no implicit coercion). Unknown type hints raise a runtime type error.
-
-**Supported Types:**
-- `str`: String
-- `int`: Integer
-- `float`: Float
-- `bool`: Boolean
-- `json` or `Json`: JSON object
-
-```python
-fn process_data(data: json)
- return data["key"]
-```
-
-#### Return Values
-
-Functions can return any value:
-
-```python
-fn get_user()
- return {"name": "Ada", "id": 1}
-
-fn calculate()
- return 42
-
-fn do_work()
- // No explicit return = returns None
- print("Working...")
-```
-
-### Async Model
-
-Relay's async model is unique: **there is no `await` keyword**. Instead, async operations return `Deferred` values that automatically resolve when you try to use them.
-
-#### Expression Statements Don't Block
-
-When you call an async function without using its return value, it runs in the background:
-
-```python
-sleep(1000, print("delayed"))
-print("immediate")
-```
-
-**Output:**
-```
-immediate
-delayed
-```
-
-#### Deferred Values
-
-When you assign the result of an async operation, you get a `Deferred` value. The operation starts immediately but doesn't block:
-
-```python
-x = sleep(1000, 10) // Returns immediately with Deferred<10>
-y = sleep(1000, 20) // Returns immediately with Deferred<20>
-print(x + y) // Waits for both, then prints 30
-```
-
-Both sleeps start at the same time, so this takes ~1 second, not 2.
-
-#### How Deferred Resolution Works
-
-A `Deferred` value automatically resolves (waits for the async operation to complete) when:
-
-1. **Used in an expression:**
-```python
-result = deferred_value + 10
-```
-
-2. **Passed to a function:**
-```python
-print(deferred_value)
-```
-
-3. **Returned from a function:**
-```python
-fn get_data()
- return http.get("https://api.example.com")
-```
-
-4. **Used in a comparison:**
-```python
-if (deferred_value > 10)
- print("greater than 10")
-```
-
-#### Concurrency Primitives
-
-**`spawn(expr)`** - Run an expression in parallel:
-```python
-fn work(n)
- sleep(500, n * 2)
-
-task1 = spawn(work(5))
-task2 = spawn(work(10))
-result1 = task1.join() // Wait for completion
-result2 = task2.join()
-```
-
-**`all(tasks)`** - Wait for all tasks to complete:
-```python
-tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
-results = all(tasks) // [2, 4, 6]
-```
-
-**`race(tasks)`** - Wait for the first task to complete:
-```python
-tasks = [spawn(sleep(1000, "slow")), spawn(sleep(100, "fast"))]
-winner = race(tasks) // "fast"
-```
-
-**`timeout(expr, ms)`** - Add a timeout to any operation:
-```python
-result = timeout(http.get("https://slow-api.com"), 5000)
-```
-
-**`cancel(task)`** - Cancel a running task:
-```python
-task = spawn(long_operation())
-cancel(task)
-```
-
-
-
-## API Reference
-
-### Core Functions
-
-#### `print(value, ...)`
-Print values to stdout. Multiple arguments are printed space-separated.
-
-```python
-print("Hello") // Hello
-print("x =", 42) // x = 42
-print("a", "b", "c") // a b c
-```
-
-#### `str(value)`
-Convert any value to a string.
-
-```python
-str(123) // "123"
-str(3.14) // "3.14"
-str(True) // "True"
-str([1, 2, 3]) // "[1, 2, 3]"
-```
-
-#### `int(value)`
-Convert a value to an integer.
-
-```python
-int("42") // 42
-int(3.9) // 3
-int(True) // 1
-int("invalid") // Runtime error
-```
-
-#### `float(value)`
-Convert a value to a float.
-
-```python
-float("3.14") // 3.14
-float(42) // 42.0
-float("2.5e3") // 2500.0
-```
-
-### Async & Concurrency
-
-#### `sleep(milliseconds, value=None)`
-**Returns:** `Deferred<value>`
-
-Sleep for the specified duration, then resolve to `value`.
-
-```python
-sleep(1000, print("done")) // Print after 1 second
-result = sleep(2000, 42) // Wait 2s, result = 42
+ raise "example failure"
+except(error)
+ print(error.code, error.message)
+finally
+ print("finished")
```
-#### `spawn(expression)`
-**Returns:** `Task`
+Functions have lexical scopes and may return closures. Captured bindings are read-only. Assignments inside a function are local; explicit sessions and database operations provide shared state across requests. Optional parameters use `=`; missing required arguments and unsupported keyword arguments are errors. Use `return`, or a final expression for an implicit function result.
-Execute an expression in parallel. Returns a `Task` object.
+Integer arithmetic is checked. Division by zero and unsupported mixed arithmetic raise errors. Equality is structural, including nested collections. String interpolation is explicit:
```python
-task = spawn(expensive_computation())
-// Do other work...
-result = task.join()
+message = format("Hello {{ name }}", {"name": "Ada"})
```
-**Task Methods:**
-- `task.join()` - Wait for the task to complete and return its value
+Collection helpers include `len`, `range`, membership, slicing, `+=` and `-=`. Legacy `=+` and `=-` are accepted for migration. Strings support `split`, `join`, `strip`, `replace`, `lower`, `upper`, `startswith`, and `endswith`; lists support `append`, `extend`, and `pop`; dictionaries support `get`, `keys`, `values`, `items`, and `pop`. Explicit dictionary fields take precedence over method names.
-#### `all(tasks)`
-**Returns:** List of results
-
-Wait for all tasks to complete. Returns results in order.
+## Modules
```python
-tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
-results = all(tasks) // Wait for all, returns [result1, result2, result3]
+import lib.helpers as helpers
+from "../shared/validation.ry" import validate_name
```
-#### `race(tasks)`
-**Returns:** First completed result
-
-Wait for the first task to complete, return its result.
-
-```python
-tasks = [
- spawn(http.get("https://api1.com")),
- spawn(http.get("https://api2.com"))
-]
-fastest = race(tasks) // Returns whichever completes first
-```
+Imports resolve relative to the source file. Each module has its own namespace and is cached by canonical path. Names beginning with `_` are private; cycles are rejected. A route module exports a `register(app)` function and defines its decorated handlers within that function. See [the multi-file Twitter example](examples/twitter_clone/README.md).
-#### `timeout(expression, milliseconds)`
-**Returns:** `Deferred<value>` or timeout error
+## Async
-Add a timeout to any async operation.
+There is no `await` keyword. I/O and timer calls start immediately; consuming their values waits for completion. Results and failures can be consumed repeatedly.
```python
-result = timeout(http.get("https://slow.com"), 5000) // 5 second timeout
+http = Http()
+first = http.get("https://example.com")
+second = http.get("https://example.org")
+responses = all([first, second])
+print(responses[0].status)
```
-If the timeout is exceeded, a runtime error is raised.
+- `spawn(expression)` schedules an expression in an isolated execution environment.
+- `all(items)` waits concurrently and preserves input order.
+- `race(items)` returns the first completion, including errors; the input must be nonempty.
+- `timeout(expression, milliseconds)` bounds execution of the expression.
+- `cancel(task)` requests cancellation; repeated cancellation is harmless.
+- `sleep(milliseconds, expression)` evaluates the optional expression after the delay.
+- `app.background(function, ...args)` runs supervised application work without request context.
-#### `cancel(task)`
-Cancel a running task.
+Scripts and HTTP requests join their child work before exiting or sending their response. Unobserved failures propagate. Background tasks are process-local, not durable jobs. There are bounds on syntax nesting, function recursion and active tasks.
-```python
-task = spawn(long_running_operation())
-cancel(task)
-```
+## Web services
-### File System
+Routes support GET, POST, PUT, PATCH, DELETE and WebSockets; GET routes support HEAD through Axum. Middleware uses `fn middleware(ctx, next)`; `next()` may be called only once. Route groups, static mounts, request validation and OpenAPI generation remain available.
-#### `read_file(path)`
-**Returns:** `Deferred<string>`
+Arguments bind with **path > body > query** precedence. Handler type hints coerce incoming values. `request` exposes method, actual path, route pattern, query, headers, cookies, form, JSON and request ID. `get_query()`, `get_body()`, and `get_json()` are convenience helpers.
-Read a file's contents as a UTF-8 string.
+Dictionaries and lists produce JSON preserving nested types. Strings produce plain text. Use `Response(body, status=..., content_type=...)`, `app.json(value)`, `app.redirect(url)`, or `app.render_template(path, data)` for explicit responses. HTML templates automatically escape interpolated values.
```python
-content = read_file("data.txt")
-print(content)
-```
-
-#### `save_file(content, path)`
-**Returns:** `Deferred<None>`
+mongo = Mongo(env.require("RELAY_MONGO_URI"))
+db = mongo.db("example")
+app.sessions(db.collection("relay_sessions"))
-Write a string to a file.
-
-```python
-save_file("Hello, World!", "output.txt")
[email protected]("/me")
+fn me()
+ return {"user": session["user"], "csrf_token": session.csrf_token()}
```
-#### `read_json(path)`
-**Returns:** `Deferred<dict>`
+Rotate sessions after login with `session.regenerate()`, destroy them at logout with `session.destroy()`, and send the CSRF token for unsafe cookie-authenticated requests. MongoDB sessions support expiry and versioned writes across processes. Production mode requires them.
-Read and parse a JSON file.
+MongoDB provides CRUD, `find(filter, limit=100, skip=0, sort={...})`, `count(filter)`, `find_one_and_update(filter, update, upsert=True)`, and `create_index(keys, unique=True)`. Find results are capped at 1,000. Use atomic updates for counters and indexes for uniqueness.
-```python
-data = read_json("config.json")
-print(data["api_key"])
-```
+The standard library also includes file/JSON I/O, `json_parse`/`json_stringify`, environment access, SMTP with attachments, password hashing/verification, multipart uploads and HTTP clients. The [archived reference](docs/v0.1-reference.md) documents existing APIs; consult the migration guide for changed behavior.
-#### `save_json(data, path)`
-**Returns:** `Deferred<None>`
+## Tooling and validation
-Serialize data to JSON and write to a file.
-
-```python
-config = {"host": "localhost", "port": 8080}
-save_json(config, "config.json")
+```sh
+relay check app.ry # syntax, signatures, control flow and import checks; no app execution
+relay fmt app.ry # conservative whitespace formatting; preserves comments and strings
+relay fmt --check examples
+relay test tests # test_*.ry files, test_* functions, isolated runtimes
+cargo test --locked # Rust unit/router tests
```
-### HTTP Client
-
-#### `Http()`
-Create an HTTP client instance.
-
-```python
-http = Http()
-```
-
-#### `http.get(url, headers=None)`
-**Returns:** `Deferred<Response>`
-
-Send a GET request.
-
-```python
-http = Http()
-resp = http.get(
- "https://api.example.com/users",
- headers={"authorization": "Bearer token"}
-)
-print(resp.status) // 200
-print(resp.text) // Response body as string
-```
-
-#### `http.post(url, data=None, headers=None)`
-#### `http.put(url, data=None, headers=None)`
-#### `http.patch(url, data=None, headers=None)`
-#### `http.delete(url, data=None, headers=None)`
-**Returns:** `Deferred<Response>`
-
-Send requests with optional payloads and headers.
-
-```python
-http = Http()
-payload = {"name": "Ada", "email": "[email protected]"}
-resp = http.post(
- "https://api.example.com/users",
- payload,
- headers={"content-type": "application/json"}
-)
-```
-
-#### Response Object
-
-HTTP responses have the following properties:
-
-- `resp.status` - HTTP status code (int)
-- `resp.text` - Response body as string
-- `resp.json()` - Parse response body as JSON
-- `resp.headers` - Response headers (dict)
-
-```python
-http = Http()
-resp = http.get("https://api.github.com/users/octocat")
-
-print(resp.status) // 200
-data = resp.json()
-print(data["login"]) // octocat
-```
-
-### Email (SMTP)
-
-#### `Email(host, port=587, username=None, password=None, from=None, tls="starttls")`
-Create an SMTP email client instance.
-
-```python
-email = Email(
- "smtp.example.com",
- 587,
- "smtp-user",
- "smtp-pass",
- "Relay <[email protected]>",
- tls="starttls"
-)
-```
-
-TLS modes:
-- `"starttls"` (default) - requires STARTTLS upgrade
-- `"wrapper"` - implicit TLS/SMTPS (commonly port 465)
-- `"insecure"` - plain SMTP (only for local/dev)
-
-#### `email.send(to, subject, text=None, html=None, cc=None, bcc=None, reply_to=None, from=None, headers=None, attachments=None)`
-**Returns:** `Deferred<dict>`
-
-Send an email asynchronously over SMTP.
-
-- `to`, `cc`, `bcc` accept `str` or `list[str]`
-- at least one of `text` or `html` is required
-- `from` can be passed per-call or configured in `Email(...)`
-- `attachments` accepts a single attachment dict or list of attachment dicts
-
-```python
-email = Email(
- host="smtp.example.com",
- username="smtp-user",
- password="smtp-pass",
- from="Relay <[email protected]>"
-)
-
-result = email.send(
- to=["[email protected]"],
- subject="Welcome",
- text="Welcome to Relay!"
-)
-
-print(result["ok"]) // True
-print(result["smtp_code"]) // e.g. 250
-```
-
-Attachment dict format:
-- `filename` (required) - attachment file name
-- `bytes` or `content` or `data` (required) - file payload (`bytes`, `str`, or `list[int]`)
-- `content_type` (optional) - MIME type (default: `application/octet-stream`)
-
-```python
-result = email.send(
- to="[email protected]",
- subject="Monthly report",
- text="See attached report.",
- attachments=[
- {
- "filename": "report.txt",
- "content_type": "text/plain",
- "bytes": "Report body"
- }
- ]
-)
-```
-
-Send result fields:
-- `ok` - always `True` on success
-- `transport` - `"smtp"`
-- `host` / `port` - resolved transport target
-- `message_id` - message id when available, else `None`
-- `envelope_to` - recipient list used for SMTP envelope
-- `smtp_code` / `smtp_message` - provider response details when available
-
-#### `email.render(template, data=None)`
-**Returns:** `string`
-
-Render an email template string with MiniJinja variables.
-
-```python
-body = email.render("Hi {{ name }}, welcome!", {"name": "Ada"})
-```
-
-#### `email.render_file(path, data=None)`
-**Returns:** `Deferred<string>`
-
-Load and render a template file asynchronously.
-
-```python
-html = email.render_file("templates/welcome.html", {"name": "Ada"})
-email.send(to="[email protected]", subject="Welcome", html=html)
-```
-
-### Web Server
-
-#### `WebApp()`
-Create a web application instance.
-
-```python
-app = WebApp()
-```
-
-#### Route Decorators
-
-Define HTTP endpoints using decorators:
-
-- `@app.get(path)`
-- `@app.post(path)`
-- `@app.put(path)`
-- `@app.patch(path)`
-- `@app.delete(path)`
-- `@app.ws(path)`
-
-```python
-app = WebApp()
-
[email protected]("/")
-fn index()
- return {"message": "Welcome to Relay"}
-
[email protected]("/users")
-fn create_user(name: str, email: str)
- return {"id": 123, "name": name, "email": email}
-```
-
-Decorator schema options:
-- `validate=...` - Validate/coerce body payload (JSON first, then form)
-- `query=...` - Validate/coerce query parameters
-- `body=...` - Validate/coerce form body
-- `json=...` - Validate/coerce JSON body
-
-```python
[email protected]("/search", query={"limit": "int", "q?": "str"})
-fn search(limit, q = None)
- return {"limit": limit, "q": q}
-
[email protected]("/users", json={"name": "str", "age?": "int"})
-fn create_user(name, age = None)
- return {"name": name, "age": age}
-```
-
-#### Route Groups and OpenAPI
-
-Use grouped route prefixes to organize larger APIs:
-
-```python
-app = WebApp()
-api = app.group("/api")
-v1 = api.group("/v1")
-
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- return {"id": user_id}
-```
-
-Enable generated OpenAPI docs:
-
-```python
-app.openapi(title="Relay API", version="1.0.0")
-// Exposes GET /openapi.json
-```
-
-#### WebSocket Routes
-
-Use `@app.ws(path)` for WebSocket upgrade endpoints.
-WebSocket handlers receive a `socket` object with:
-- `socket.recv()` → returns text (`str`), binary (`bytes`), or `None` on close
-- `socket.send(value)` → sends text (or binary when `bytes`)
-- `socket.close()` → closes the connection
-
-```python
[email protected]("/chat")
-fn chat_room()
- while True
- msg = socket.recv()
- if (msg == None)
- break
- socket.send("echo: " + str(msg))
-```
-
-#### Path Parameters
-
-Use `<name>` syntax to capture path segments:
-
-```python
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- return {"id": user_id, "name": "Ada"}
-
[email protected]("/posts/<post_id>/comments/<comment_id>")
-fn get_comment(post_id, comment_id)
- return {"post": post_id, "comment": comment_id}
-```
-
-#### Handler Parameters
-
-Handler parameters are automatically bound from:
-1. **Path parameters** (highest priority)
-2. **Request body form fields** (`application/x-www-form-urlencoded`)
-3. **Query parameters** (lowest priority)
-
-```python
-// GET /search?q=relay&limit=10
[email protected]("/search")
-fn search(q: str, limit: int = 20)
- return {"query": q, "limit": limit}
-
-// POST /users with form body: name=Ada&[email protected]
[email protected]("/users")
-fn create_user(name: str, email: str)
- return {"name": name, "email": email}
-
-// GET /users/123
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- return {"id": user_id}
-```
-
-JSON bodies are available as the `data` parameter (default name) or by typing a handler param as `Json`.
-
-```python
-// POST /events with JSON body {"type":"signup","user":"ada"}
[email protected]("/events")
-fn create_event(data: Json)
- return {"event_type": data["type"], "user": data["user"]}
-```
-
-For zero-arg handlers (or explicit access), use request helpers: `get_query()`, `get_body()`, and `get_json()`.
-
-#### Middleware
-
-Register middleware with `app.use(fn)`.
-
-Middleware functions can be:
-- `fn middleware()` (legacy style)
-- `fn middleware(ctx)` where `ctx` contains request fields
-- `fn middleware(ctx, next)` with full chain semantics
-
-If middleware returns a non-`None` value, Relay short-circuits and sends that response.
-
-`next()` runs the remainder of the middleware chain and then the handler.
-
-```python
-fn audit(ctx, next)
- print("before:", ctx["path"])
- result = next()
- print("after:", ctx["path"])
- return result
-```
-
-#### Type Hints in Handlers
-
-Use type hints to enforce parameter types and enable automatic coercion:
-
-```python
[email protected]("/calculate")
-fn calculate(a: int, b: int)
- return {"result": a + b}
-
-// POST /calculate with form body a=5&b=10
-// Automatically converts strings to ints: {"result": 15}
-```
-
-**Supported types:**
-- `str` - String
-- `int` - Integer
-- `float` - Float
-- `bool` - Boolean
-- `json` or `Json` - Full JSON body (for POST/PUT/PATCH)
-
-#### Request Object
-
-Every handler has access to a `request` dictionary:
-
-```python
[email protected]("/debug")
-fn debug_request()
- print(request["method"]) // GET
- print(request["path"]) // /debug
- print(request["request_id"])// rid_...
- print(request["query"]) // Query parameters dict
- print(request["form"]) // Form fields dict (if present)
- print(request["json"]) // JSON body (if present)
- print(request["headers"]) // Headers dict
- print(request["cookies"]) // Cookies dict
- return "OK"
-```
-
-**Request fields:**
-- `method` - HTTP method (string)
-- `path` - Request path (string)
-- `request_id` - Request identifier string
-- `query` - Query parameters (dict)
-- `form` - Parsed form body fields (dict, when present)
- - URL-encoded fields become strings
- - Multipart text fields become strings
- - Multipart file fields become dicts:
- - `filename` (`str` or `None`)
- - `content_type` (`str` or `None`)
- - `size` (`int`)
- - `bytes` (`bytes`)
-- `headers` - Request headers (dict)
-- `cookies` - Cookies (dict)
-- `json` - Parsed JSON body (if present)
-
-#### Request Helpers
-
-Use helpers when you want payload access without binding handler parameters:
-
-- `get_query()` - Query parameters as a dict (empty dict when unavailable)
-- `get_body()` - Parsed form fields as a dict (empty dict when unavailable)
-- `get_json()` - Parsed JSON body (or `None` when unavailable)
-
-```python
[email protected]("/search")
-fn search()
- query = get_query()
- return {"q": query["q"]}
-
[email protected]("/submit")
-fn submit()
- form = get_body()
- payload = get_json()
- return {"form": form, "json": payload}
-```
-
-#### Validation Helpers
-
-Use schema helpers for explicit request validation:
-
-- `validate(data, schema)` - Validate/coerce an object against schema
-- `require_query(schema)` - Validate query params in handlers
-- `require_body(schema)` - Validate form body in handlers
-- `require_json(schema)` - Validate JSON body in handlers
-
-Schema format:
-- `"field": "type"` for required fields
-- `"field?": "type"` for optional fields
-- `"field": {"type": "int", "required": True, "default": 10}` for explicit rules
-
-```python
[email protected]("/search")
-fn search()
- params = require_query({"limit": "int", "q?": "str"})
- return {"limit": params["limit"], "q": params["q"]}
-```
-
-#### Cookies
-
-Access cookies via the `cookies` dict:
-
-```python
[email protected]("/")
-fn index()
- user_id = cookies["user_id"]
- return "User ID: " + user_id
-```
-
-#### Sessions
-
-Relay provides built-in session management with HttpOnly cookies:
-
-```python
[email protected]("/login")
-fn login(username: str)
- session["user"] = username
- session["logged_in"] = True
- return "Logged in"
-
[email protected]("/profile")
-fn profile()
- if (session["logged_in"] == True)
- return "Welcome, " + session["user"]
- else
- return app.redirect("/login")
-```
-
-**Session features:**
-- Automatically persisted across requests
-- Stored server-side (not in cookies)
-- Uses `relay_sid` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` automatically on HTTPS)
-- Session data is a dictionary that persists modifications
-
-Customize cookie policy:
-
-```python
-app.session(secure=True, http_only=True, same_site="Lax")
-```
-
-Use a custom session backend (for any database/service):
-
-```python
-session_db = {}
-
-fn load_session(sid)
- return session_db[sid]
-
-fn save_session(sid, data)
- session_db[sid] = data
-
-app.session_backend(load_session, save_session)
-```
-
-#### Upload Limits and MIME Allowlist
-
-Configure upload safety controls per app:
-
-```python
-app.uploads(
- max_body_bytes=10 * 1024 * 1024, // default 10 MiB
- max_file_bytes=5 * 1024 * 1024, // default 5 MiB per file field
- allowed_mime_types=["image/png", "application/pdf"] // optional
-)
-```
-
-Parameters:
-- `max_body_bytes` - Maximum raw request body size accepted
-- `max_file_bytes` - Maximum size for each multipart file field
-- `allowed_mime_types` - Optional MIME allowlist for multipart file fields (`None` disables allowlist)
-
-When limits are exceeded or a MIME type is disallowed, Relay returns `400 bad_request`.
-
-#### Response Types
-
-Handlers can return various types:
-
-**JSON (automatic):**
-```python
[email protected]("/api/user")
-fn get_user()
- return {"name": "Ada", "id": 123} // Auto-serialized to JSON
-```
-
-**Plain text:**
-```python
[email protected]("/")
-fn index()
- return "Hello, World!" // Content-Type: text/plain
-```
-
-**HTML:**
-```python
[email protected]("/")
-fn index()
- return "<h1>Welcome</h1>" // Content-Type: text/html
-```
-
-**Custom Response:**
-```python
[email protected]("/custom")
-fn custom()
- return Response(
- {"error": "Not found"},
- status=404,
- content_type="application/json"
- )
-```
-
-**Redirect:**
-```python
[email protected]("/old-path")
-fn old_endpoint()
- return app.redirect("/new-path")
-```
-
-#### `Response(body, status=200, content_type=None)`
-Create a custom HTTP response.
-
-```python
[email protected]("/xml")
-fn get_xml()
- xml = "<root><item>data</item></root>"
- return Response(xml, status=200, content_type="application/xml")
-```
-
-**Parameters:**
-- `body` - Response body (string, dict, list, or bytes)
-- `status` - HTTP status code (default: 200)
-- `content_type` - Content-Type header (auto-detected if not specified)
-
-#### `HTTPError(status, code, message, details=None)`
-Create a structured API error response.
-When called inside a handler, Relay also includes `request_id` in the error payload.
-
-```python
[email protected]("/users")
-fn create_user(name)
- if (name == None)
- return HTTPError(400, "validation_error", "Missing name", {"field": "name"})
- return {"ok": True}
-```
-
-#### Authentication Helpers
-
-#### `auth_hash_password(password)`
-**Returns:** password hash string
-
-#### `auth_verify_password(password, hash)`
-**Returns:** `bool`
-
-```python
-hash = auth_hash_password("super-secret")
-is_valid = auth_verify_password("super-secret", hash) // True
-```
-
-#### `AuthStore(load_fn=None, save_fn=None)`
-Create an authentication store.
-
-- `AuthStore()` uses in-memory storage.
-- `AuthStore(load_fn, save_fn)` uses custom callbacks for pluggable backends.
-
-Available methods:
-- `store.register(username, password)` - hashes and stores password
-- `store.verify(username, password)` - verifies against stored hash
-- `store.get_hash(username)` - returns stored hash or `None`
-- `store.set_hash(username, hash)` - stores precomputed hash
-
-```python
-store = AuthStore()
-store.register("ada", "pw")
-print(store.verify("ada", "pw")) // True
-
-auth_db = {}
-fn load_user(name)
- return auth_db[name]
-
-fn save_user(name, hash)
- auth_db[name] = hash
-
-custom = AuthStore(load_user, save_user)
-custom.register("bob", "pw2")
-print(custom.verify("bob", "pw2")) // True
-```
-
-#### `app.redirect(url)`
-**Returns:** `Response` with 302 status
-
-Create a redirect response.
-
-```python
[email protected]("/submit")
-fn submit(data)
- // Process data...
- return app.redirect("/success")
-```
-
-#### Template Rendering (MiniJinja)
-
-Relay uses **MiniJinja** (Rust implementation of Jinja2) for template interpolation in strings.
-
-Template strings are evaluated anywhere in the interpreter (not only in web handlers) when a string contains both `{{` and `}}`.
-
-```python
-name = "Relay"
-version = "0.1"
-
-title = "{{ name }} v{{ version }}"
-print(title) // Relay v0.1
-
-items = ["a", "b", "c"]
-print("Count: {{ items | length }}")
-```
-
-Web handlers use the same engine:
-
-```python
[email protected]("/")
-fn index()
- return "<h1>{{ name }} v{{ version }}</h1>"
-```
-
-Templates can reference values in the current scope and support MiniJinja expressions/filters.
-
-Use `app.render_template(path, ...kwargs)` when you want explicit template rendering from a file without relying on implicit `{{ ... }}` string evaluation:
-
-```python
-app = WebApp()
-html = app.render_template("templates/welcome.html", name="Ada", plan="Pro")
-```
-
-#### `WebServer()`
-Create a web server instance.
-
-```python
-server = WebServer()
-```
-
-#### `server.run(app)`
-Start the web server.
-
-```python
-app = WebApp()
-server = WebServer()
-
[email protected]("/")
-fn index()
- return "Hello, World!"
-
-server.run(app) // Starts server on 127.0.0.1:8080
-```
-
-**Configuration:**
-- Default bind address: `127.0.0.1:8080`
-- Override with `RELAY_BIND` environment variable:
- ```bash
- RELAY_BIND=0.0.0.0:8080 relay server.ry
- ```
-
-### Database (MongoDB)
-
-#### `Mongo(connection_string)`
-Create a MongoDB client.
-
-```python
-mongo = Mongo("mongodb://localhost:27017")
-```
-
-**Connection string format:**
-```
-mongodb://[username:password@]host[:port][/database]
-```
-
-Examples:
-```python
-// Local MongoDB
-mongo = Mongo("mongodb://localhost:27017")
-
-// MongoDB Atlas
-mongo = Mongo("mongodb+srv://user:[email protected]/")
-
-// With authentication
-mongo = Mongo("mongodb://admin:password@localhost:27017")
-```
-
-#### `mongo.db(database_name)`
-**Returns:** Database instance
-
-Access a database.
-
-```python
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("my_app")
-```
-
-#### `db.collection(collection_name)`
-**Returns:** Collection instance
-
-Access a collection.
-
-```python
-users = db.collection("users")
-posts = db.collection("posts")
-```
-
-#### `collection.insert_one(document)`
-**Returns:** `Deferred<dict>` with `inserted_id`
-
-Insert a single document.
-
-```python
-users = db.collection("users")
-result = users.insert_one({"name": "Ada", "email": "[email protected]"})
-print(result["inserted_id"]) // ObjectId as string
-```
-
-#### `collection.insert_many(documents)`
-**Returns:** `Deferred<dict>` with `inserted_ids`
-
-Insert multiple documents.
-
-```python
-users = db.collection("users")
-docs = [
- {"name": "Ada", "email": "[email protected]"},
- {"name": "Grace", "email": "[email protected]"}
-]
-result = users.insert_many(docs)
-print(result["inserted_ids"]) // Dict of index -> ObjectId string
-```
-
-#### `collection.find_one(filter)`
-**Returns:** `Deferred<dict>` or `None`
-
-Find a single document matching the filter.
-
-```python
-users = db.collection("users")
-user = users.find_one({"email": "[email protected]"})
-if (user != None)
- print(user["name"])
-```
-
-**Filter examples:**
-```python
-// Exact match
-user = users.find_one({"name": "Ada"})
-
-// Multiple conditions (implicit AND)
-user = users.find_one({"name": "Ada", "active": True})
-
-// By ObjectId
-user = users.find_one({"_id": "507f1f77bcf86cd799439011"})
-```
-
-#### `collection.find(filter)`
-**Returns:** `Deferred<list>` of documents
-
-Find all documents matching the filter.
-
-```python
-users = db.collection("users")
-active_users = users.find({"active": True})
-for (user in active_users)
- print(user["name"])
-```
-
-**Find all documents:**
-```python
-all_users = users.find({})
-```
-
-#### `collection.update_one(filter, update)`
-**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
-
-Update a single document.
-
-```python
-users = db.collection("users")
-result = users.update_one(
- {"email": "[email protected]"},
- {"$set": {"active": True}}
-)
-print(result["modified_count"]) // 1
-```
-
-**Update operators:**
-```python
-// Set fields
-users.update_one({"_id": id}, {"$set": {"status": "active"}})
-
-// Increment
-users.update_one({"_id": id}, {"$inc": {"login_count": 1}})
-
-// Unset fields
-users.update_one({"_id": id}, {"$unset": {"temp_field": ""}})
-```
-
-#### `collection.update_many(filter, update)`
-**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
-
-Update multiple documents.
-
-```python
-users = db.collection("users")
-result = users.update_many(
- {"active": False},
- {"$set": {"status": "inactive"}}
-)
-print(result["modified_count"])
-```
-
-#### `collection.delete_one(filter)`
-**Returns:** `Deferred<dict>` with `deleted_count`
-
-Delete a single document.
-
-```python
-users = db.collection("users")
-result = users.delete_one({"email": "[email protected]"})
-print(result["deleted_count"]) // 1 or 0
-```
-
-#### `collection.delete_many(filter)`
-**Returns:** `Deferred<dict>` with `deleted_count`
-
-Delete multiple documents.
-
-```python
-users = db.collection("users")
-result = users.delete_many({"active": False})
-print(result["deleted_count"]) // Number of deleted documents
-```
-
-
-
-## Examples
-
-### 1. Hello World (Async)
-
-```python
-sleep(2000, print("world"))
-print("hello")
-```
-
-**Output:**
-```
-hello
-world
-```
-
-### 2. Simple Web API
-
-```python
-app = WebApp()
-server = WebServer()
-
[email protected]("/health")
-fn health()
- return {"status": "ok", "service": "relay-api"}
-
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- return {"id": user_id, "name": "Ada Lovelace"}
-
-server.run(app)
-```
-
-### 3. Pastebin Service
-
-```python
-app = WebApp()
-server = WebServer()
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("pastebin")
-pastes = db.collection("pastes")
-
-fn find_paste(paste_id)
- return pastes.find_one({"_id": paste_id})
-
[email protected]("/")
-fn index()
- return read_file("examples/static/index.html")
-
[email protected]("/")
-fn create_paste(content = None)
- if (content == None)
- return Response("Missing content", status=400)
- result = pastes.insert_one({"content": content})
- paste_id = str(result["inserted_id"])
- return app.redirect("/" + paste_id)
-
[email protected]("/<paste_id>")
-fn view_paste(paste_id)
- paste = find_paste(paste_id)
- if (paste == None)
- return Response("Not found", status=404)
- return read_file("examples/static/paste.html")
-
[email protected]("/api/paste/<paste_id>")
-fn get_paste(paste_id)
- paste = find_paste(paste_id)
- if (paste == None)
- return Response("Not found", status=404)
- return {"id": paste_id, "content": "{{ paste[\"content\"] }}"}
-
-server.run(app)
-```
-
-Template files used by this example:
-- `examples/static/index.html` for paste creation form
-- `examples/static/paste.html` for `<pre><code>` viewer UI and syntax highlighting
-
-### 4. Concurrent HTTP Requests
-
-```python
-http = Http()
-
-fn fetch_user(user_id)
- resp = http.get("https://api.example.com/users/" + str(user_id))
- return resp.json()
-
-// Fetch 5 users concurrently
-tasks = []
-i = 1
-while (i <= 5)
- tasks =+ [spawn(fetch_user(i))]
- i =+ 1
-
-users = all(tasks)
-for (user in users)
- print(user["name"])
-```
-
-### 5. File Processing Pipeline
-
-```python
-fn process_file(filename)
- content = read_file(filename)
- lines = len(content.split("\n"))
- return {"file": filename, "lines": lines}
-
-files = ["data1.txt", "data2.txt", "data3.txt"]
-tasks = []
-for (f in files)
- tasks =+ [spawn(process_file(f))]
-
-results = all(tasks)
-save_json(results, "report.json")
-print("Processing complete!")
-```
-
-### 6. Session-based Authentication
-
-```python
-app = WebApp()
-server = WebServer()
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("auth_demo")
-users = db.collection("users")
-
[email protected]("/")
-fn index()
- if (session["authenticated"] == True)
- return "Welcome, " + session["username"]
- return app.redirect("/login")
-
[email protected]("/login")
-fn login(username: str, password: str)
- user = users.find_one({"username": username})
- if (user == None)
- return Response("Invalid credentials", status=401)
-
- // In production, use proper password hashing!
- if (user["password"] == password)
- session["authenticated"] = True
- session["username"] = username
- return app.redirect("/")
-
- return Response("Invalid credentials", status=401)
-
[email protected]("/logout")
-fn logout()
- session["authenticated"] = False
- session["username"] = None
- return app.redirect("/login")
-
-server.run(app)
-```
-
-### 7. REST API with MongoDB
-
-```python
-app = WebApp()
-server = WebServer()
-mongo = Mongo("mongodb://localhost:27017")
-db = mongo.db("blog")
-posts = db.collection("posts")
-
[email protected]("/posts")
-fn list_posts()
- all_posts = posts.find({})
- return all_posts
-
[email protected]("/posts")
-fn create_post(title: str, content: str, author: str)
- result = posts.insert_one({
- "title": title,
- "content": content,
- "author": author
- })
- return {"id": str(result["inserted_id"])}
-
[email protected]("/posts/<post_id>")
-fn get_post(post_id)
- post = posts.find_one({"_id": post_id})
- if (post == None)
- return Response("Post not found", status=404)
- return post
-
[email protected]("/posts/<post_id>")
-fn update_post(post_id, title: str, content: str)
- result = posts.update_one(
- {"_id": post_id},
- {"$set": {"title": title, "content": content}}
- )
- if (result["matched_count"] == 0)
- return Response("Post not found", status=404)
- return {"updated": True}
-
[email protected]("/posts/<post_id>")
-fn delete_post(post_id)
- result = posts.delete_one({"_id": post_id})
- if (result["deleted_count"] == 0)
- return Response("Post not found", status=404)
- return {"deleted": True}
-
-server.run(app)
-```
-
-### 8. Timeout and Error Handling
-
-```python
-http = Http()
-
-fn fetch_with_timeout(url)
- return timeout(http.get(url), 5000)
-
-// Try to fetch with 5 second timeout
-result = fetch_with_timeout("https://slow-api.com/data")
-print(result.text)
-```
-
-### 9. Race Condition Example
-
-```python
-http = Http()
-
-// Fetch from multiple mirrors, use whichever responds first
-mirrors = [
- "https://mirror1.example.com/data",
- "https://mirror2.example.com/data",
- "https://mirror3.example.com/data"
-]
-
-tasks = []
-for (url in mirrors)
- tasks =+ [spawn(http.get(url))]
-
-fastest = race(tasks)
-print("Fastest mirror returned:", fastest.text)
-```
-
-### 10. Background Task Processing
-
-```python
-fn process_item(item)
- sleep(1000, print("Processed: " + str(item)))
-
-items = [1, 2, 3, 4, 5]
-
-// Spawn all tasks without waiting
-for (item in items)
- spawn(process_item(item))
-
-print("All tasks started, continuing...")
-// Tasks run in background
-```
-
-### 11. SMTP Welcome Email with Templates
-
-```python
-email = Email(
- host="smtp.example.com",
- username="smtp-user",
- password="smtp-pass",
- from="Relay <[email protected]>"
-)
-
-fn send_welcome(name, to)
- html = email.render(
- "<h1>Welcome {{ name }}</h1><p>Thanks for joining Relay.</p>",
- {"name": name}
- )
- return email.send(
- to=to,
- subject="Welcome to Relay",
- text="Welcome " + name + "!",
- html=html
- )
-
-result = send_welcome("Ada", "[email protected]")
-print("Email queued:", result["ok"])
-```
-
-
-
-## How It Works
-
-### Architecture Overview
-
-Relay is built on:
-- **Rust**: The interpreter is written in Rust for performance and safety
-- **Tokio**: Async runtime for non-blocking I/O
-- **Axum**: High-performance web framework for the built-in server
-- **Lettre**: Async SMTP transport for built-in email delivery
-- **MongoDB driver**: Native async MongoDB support
-
-### Compilation Pipeline
-
-1. **Lexer**: Tokenizes source code with indentation-aware parsing
-2. **Parser**: Builds an Abstract Syntax Tree (AST)
-3. **Evaluator**: Interprets the AST with async/await translation
-
-### The Async Model in Detail
-
-#### Deferred Values
-
-When you call an async function, Relay immediately starts the operation and returns a `Deferred` value:
-
-```python
-// This starts the HTTP request immediately
-response = http.get("https://api.example.com")
-// response is Deferred<Response>
-
-// The request is already in-flight here
-print("Request started")
-
-// Only when we access response.status does it wait
-print(response.status) // <-- Blocks here if not complete
-```
-
-#### Auto-Resolution
-
-`Deferred` values automatically resolve when:
-
-1. **Used in operations:**
-```python
-x = sleep(1000, 10)
-y = x + 5 // Waits for x to resolve
-```
-
-2. **Passed to functions:**
-```python
-result = sleep(1000, 42)
-print(result) // Waits before printing
-```
-
-3. **Used in control flow:**
-```python
-data = http.get("https://api.example.com")
-if (data.status == 200) // Waits before comparison
- print("Success")
-```
-
-4. **Indexed:**
-```python
-resp = http.get("https://api.example.com")
-json_data = resp.json()
-print(json_data["key"]) // Waits for json() before indexing
-```
-
-#### Expression Statements
-
-Expression statements (expressions not assigned to variables) run without blocking:
-
-```python
-// This starts the sleep but doesn't wait
-sleep(1000, print("delayed"))
-
-// This prints immediately
-print("immediate")
-
-// Output:
-// immediate
-// delayed (after 1 second)
-```
-
-### Concurrency Model
-
-Relay uses Tokio's work-stealing scheduler to run tasks concurrently:
-
-```python
-// Start 3 HTTP requests concurrently
-task1 = spawn(http.get("https://api1.com"))
-task2 = spawn(http.get("https://api2.com"))
-task3 = spawn(http.get("https://api3.com"))
-
-// Wait for all to complete
-results = all([task1, task2, task3])
-```
-
-All three requests run in parallel, completing in the time of the slowest request (not 3× the time).
-
-### Web Server Architecture
-
-The web server uses Axum's routing system:
-
-1. **Route Registration**: Decorators like `@app.get("/path")` register handlers
-2. **Request Handling**: Incoming requests are matched against registered routes
-3. **Parameter Binding**: Path/query/body parameters are extracted and bound to handler parameters
-4. **Type Coercion**: Type hints trigger automatic type conversion
-5. **Response Generation**: Return values are automatically serialized to appropriate content types
-
-### Session Storage
-
-Sessions are stored server-side in an in-memory hash map:
-- Session ID is generated by Relay at runtime
-- `relay_sid` cookie stores the session ID (`HttpOnly`, `SameSite=Lax`, `Secure` on HTTPS)
-- Session data persists across requests for the same session ID
-- Sessions are stored in memory (cleared on server restart)
-
-Every web request is also assigned a `request_id` and echoed as the `x-request-id` response header.
-
-**Note:** In production, you'd want to persist sessions to a database.
-
-### MongoDB Integration
-
-MongoDB operations return `Deferred` values that resolve when the database operation completes:
-
-```python
-// This starts the query immediately
-users = collection.find({"active": True})
-
-// The query is running in the background here
-print("Query started")
-
-// Only when we iterate do we wait for results
-for (user in users) // <-- Blocks here
- print(user["name"])
-```
-
-
-
-## Best Practices
-
-### 1. Leverage Concurrent Execution
-
-Instead of:
-```python
-// Sequential (slow)
-result1 = http.get("https://api1.com")
-result2 = http.get("https://api2.com")
-result3 = http.get("https://api3.com")
-```
-
-Do:
-```python
-// Concurrent (fast)
-tasks = [
- spawn(http.get("https://api1.com")),
- spawn(http.get("https://api2.com")),
- spawn(http.get("https://api3.com"))
-]
-results = all(tasks)
-```
-
-### 2. Use Type Hints for API Handlers
-
-Type hints provide automatic validation and coercion:
-
-```python
[email protected]("/calculate")
-fn calculate(a: int, b: int, operation: str = "add")
- if (operation == "add")
- return {"result": a + b}
- else
- return {"result": a - b}
-```
-
-### 3. Handle Missing Data Gracefully
-
-Always check for `None` when querying databases or processing optional parameters:
-
-```python
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- user = users.find_one({"_id": user_id})
- if (user == None)
- return Response("User not found", status=404)
- return user
-```
-
-### 4. Use Sessions for State Management
-
-Don't try to maintain state in global variables. Use sessions:
-
-```python
-// Bad
-current_user = None
-
[email protected]("/login")
-fn login(username)
- current_user = username // Won't work across requests
-
-// Good
[email protected]("/login")
-fn login(username)
- session["user"] = username
-```
-
-### 5. Implement Timeouts for External Calls
-
-Always add timeouts to external HTTP requests:
-
-```python
-fn fetch_data(url)
- return timeout(http.get(url), 10000) // 10 second timeout
-```
-
-### 6. Structure Large Applications
-
-Split handlers into logical groups:
-
-```python
-app = WebApp()
-server = WebServer()
-
-// Auth routes
[email protected]("/auth/login")
-fn login(username, password)
- // ...
-
[email protected]("/auth/logout")
-fn logout()
- // ...
-
-// User routes
[email protected]("/users/<user_id>")
-fn get_user(user_id)
- // ...
-
[email protected]("/users")
-fn create_user(name, email)
- // ...
-
-// Post routes
[email protected]("/posts")
-fn list_posts()
- // ...
-
-server.run(app)
-```
-
-### 7. Use Augmented Assignment
-
-For cleaner counter increments:
-
-```python
-// Instead of
-i = i + 1
-
-// Use
-i =+ 1
-```
-
-### 8. Return Early for Error Cases
-
-Structure handlers with early returns for error cases:
-
-```python
[email protected]("/posts/<post_id>")
-fn get_post(post_id)
- post = posts.find_one({"_id": post_id})
- if (post == None)
- return Response("Not found", status=404)
-
- if (post["published"] == False)
- return Response("Not published", status=403)
-
- return post
-```
-
-
-
-## Troubleshooting
-
-### Common Errors
-
-#### "Indentation must be 4 spaces per level"
-
-**Cause:** Relay requires exactly 4 spaces per indentation level.
-
-**Fix:** Ensure all indentation uses 4 spaces (not tabs, not 2 spaces).
-
-```python
-// Wrong
-fn example()
- print("hello") // 2 spaces
-
-// Right
-fn example()
- print("hello") // 4 spaces
-```
-
-#### "Tabs are not allowed (spaces only)"
-
-**Cause:** Relay does not support tabs for indentation.
-
-**Fix:** Configure your editor to use spaces instead of tabs.
-
-#### "Type error: Cannot add int and str"
-
-**Cause:** Attempting to use incompatible types in an operation.
-
-**Fix:** Use explicit type conversion:
-
-```python
-// Wrong
-x = 10 + "5"
-
-// Right
-x = 10 + int("5")
-```
-
-#### "Name error: Undefined variable 'x'"
-
-**Cause:** Using a variable before it's defined.
-
-**Fix:** Ensure variables are assigned before use:
-
-```python
-// Wrong
-print(x)
-x = 10
-
-// Right
-x = 10
-print(x)
-```
-
-#### "Runtime error: Index out of bounds"
-
-**Cause:** Accessing a list index that doesn't exist.
-
-**Fix:** Check list length before accessing:
-
-```python
-items = [1, 2, 3]
-if (len(items) > 5)
- print(items[5])
-```
-
-### Debugging Tips
-
-1. **Use print statements:** Relay's simplest debugging tool
-```python
-fn process_data(data)
- print("Processing:", data) // Debug output
- result = transform(data)
- print("Result:", result) // Debug output
- return result
-```
-
-2. **Check async resolution:** If something seems to hang, check if you're waiting for a `Deferred` value
-```python
-// This might hang if the HTTP request never completes
-result = http.get("https://unreachable.com")
-print(result.status)
-
-// Add a timeout:
-result = timeout(http.get("https://unreachable.com"), 5000)
-```
-
-3. **Verify MongoDB connection:** Test your connection string in the MongoDB shell first
-
-4. **Check file paths:** File operations use paths relative to where you run the `relay` command
-
-5. **Inspect request objects:** Log the request object to debug handler issues
-```python
[email protected]("/debug")
-fn debug()
- print(request)
- return "OK"
-```
-
-### Performance Tips
-
-1. **Batch database operations:** Use `insert_many` instead of multiple `insert_one` calls
-```python
-// Slow
-for (item in items)
- collection.insert_one(item)
-
-// Fast
-collection.insert_many(items)
-```
-
-2. **Use `spawn` for I/O-heavy tasks:** Parallelize independent operations
-```python
-// Serial: 5 seconds total
-sleep(1000, "a")
-sleep(1000, "b")
-sleep(1000, "c")
-sleep(1000, "d")
-sleep(1000, "e")
-
-// Parallel: 1 second total
-all([
- spawn(sleep(1000, "a")),
- spawn(sleep(1000, "b")),
- spawn(sleep(1000, "c")),
- spawn(sleep(1000, "d")),
- spawn(sleep(1000, "e"))
-])
-```
-
-3. **Minimize synchronous operations:** Keep handlers fast to avoid blocking the event loop
-
-## Contributing
-
-Contributions are welcome! Here's how to get started:
-
-### Development Setup
-
-```bash
-# Clone the repo
-git clone https://github.com/patx/relay-lang.git
-cd relay-lang
-
-# Build in debug mode
-cargo build
-
-# Run tests
-cargo test
-
-# Run the sample app
-cargo run -- examples/type_hint_validation.ry
-```
-
-### Adding Features
-
-1. **Lexer changes:** Modify the `Lexer` struct and `tokenize()` method
-2. **Parser changes:** Update the `Parser` struct and AST types
-3. **Runtime changes:** Modify the `Evaluator` and `install_stdlib()` function
-4. **Testing:** Add runnable `.ry` scripts and corresponding docs snippets
-
-### Coding Standards
-
-- Follow Rust conventions and `rustfmt` formatting
-- Add comments for complex logic
-- Core interpreter remains in a single Rust file (v0.1) while Relay scripts support multi-file imports
-- Update this README for any user-facing changes
-
-### Reporting Issues
-
-Found a bug? [Open an issue](https://github.com/patx/relay-lang/issues) with:
-- Relay version
-- Operating system
-- Minimal reproducible example
-- Expected vs. actual behavior
-
-
-## Roadmap
-
-**v0.2 (Planned):**
-- [x] Multiple file support and imports
-- [x] List comprehensions
-- [x] Destructuring assignment
-- [x] Error handling with try/except
-- [x] WebSocket support
-- [X] Static file serving
-- [X] Middleware support
-
-**v0.3 (Future):**
-- [ ] Package manager
-- [ ] Standard library expansion
-- [ ] SQL database support (PostgreSQL, SQLite)
-- [ ] Redis integration
-- [ ] Interactive REPL with syntax highlighting
-- [ ] Web framework improvements (routing groups, validation)
-- [ ] Worker processes for CPU-heavy tasks
-
-**Known Gaps (Identified in v0.1):**
-- [ ] JSON request key binding to scalar handler args (e.g. bind `{"name":"Ada"}` directly to `fn create(name)`).
-- [x] HTTP client parity for `put`, `patch`, `delete`, request headers, and response header access.
-- [ ] First-class CLI flags (`--help`, `--version`) for better install verification and discoverability.
-- [ ] Built-in HTML escaping helper for safely rendering user content directly in server-side templates.
-
-
-## License
-
-MIT License
-
-Copyright (c) 2026 Harrison Erd
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
+Use `assert(value, message)` and `assert_equal(actual, expected)` in Relay tests. No external package registry or static type checker is included.
-## Support
+The source is grouped into frontend, runtime, standard library, web, session, networking and CLI files under `src/`. The binary entry point delegates to the library. Unit tests, live integration scripts and the hour-long soak are separate checks; see [deployment verification](docs/deployment.md#verification).
-- **Documentation:** This README
-- **Examples:** `examples/` (start with `examples/type_hint_validation.ry` and `examples/pastebin.ry`)
-- **Issues:** [GitHub Issues](https://github.com/patx/relay-lang/issues)
-- **Discussions:** [GitHub Discussions](https://github.com/patx/relay-lang/discussions)
+Licensed under [MIT](LICENSE).
diff --git a/docs/deployment.md b/docs/deployment.md
new file mode 100644
index 0000000..a3609e3
--- /dev/null
+++ b/docs/deployment.md
@@ -0,0 +1,49 @@
+# Linux deployment
+
+Use a TLS reverse proxy and a dedicated MongoDB database/user. Application source is trusted code with filesystem and network access. Relay is not an untrusted-code sandbox.
+
+```sh
+cargo build --release --locked
+RELAY_ENV=production \
+RELAY_BIND=127.0.0.1:8080 \
+RELAY_PUBLIC_ORIGIN=https://example.com \
+RELAY_TRUSTED_PROXIES=127.0.0.1 \
+RELAY_MANAGEMENT_BIND=127.0.0.1:9090 \
+RELAY_MONGO_URI='mongodb://localhost:27017' \
+./target/release/relay run examples/twitter_clone/app.ry
+```
+
+Provide database credentials and SMTP credentials using your deployment's secret mechanism. Do not commit them. `RELAY_MONGO_URI` and `RELAY_MONGO_DATABASE` are conventions used by the examples; application code reads them through `env.get`/`env.require`.
+
+| Setting | Default | Meaning |
+|---|---|---|
+| `RELAY_ENV` | `development` | `production` requires MongoDB sessions, HTTPS public origin and Secure cookies |
+| `RELAY_BIND` | `127.0.0.1:8080` | Service socket |
+| `RELAY_PUBLIC_ORIGIN` | unset | Exact browser origin, without trailing slash |
+| `RELAY_TRUSTED_PROXIES` | none | Comma-separated individual proxy IP addresses; no CIDR expansion |
+| `RELAY_REQUEST_TIMEOUT_MS` | 30000 | HTTP handler deadline |
+| `RELAY_MAX_REQUESTS` | 256 | Concurrent HTTP admission cap; excess receives 503 |
+| `RELAY_SHUTDOWN_GRACE_MS` | 30000 | Graceful drain budget |
+| `RELAY_MANAGEMENT_BIND` | disabled | Separate health/readiness/metrics listener; bind privately |
+
+Uploads default to a 10 MiB request limit and 5 MiB file limit. Override with `app.uploads(...)`. MIME allowlists validate declared MIME types, not file contents. HTTP client connect/total timeouts are 10/30 seconds; SMTP has a 30-second transport timeout. MongoDB queries are bounded and have a 30-second server execution limit where supported.
+
+SIGTERM/SIGINT stops readiness and new connections, drains work within the grace period, and cancels remaining work. Configure the process supervisor/container stop timeout slightly above Relay's grace period. In-process background work does not survive restarts; use an external durable queue when that guarantee is needed.
+
+Management endpoints are `/healthz`, `/readyz`, and `/metrics`. Readiness indicates that this process has completed startup and is accepting requests; it does not continuously probe MongoDB. Metrics expose request totals, active requests, server errors, cumulative duration and background task count. Logs are JSON lines on stderr. Keep the management listener off public networks.
+
+Cross-origin unsafe browser requests are denied unless they match the configured public origin. This release does not enable arbitrary cross-origin credentialed APIs. For development, the current HTTP host is accepted. Forwarded headers are trusted only when the TCP peer matches an explicitly configured proxy IP. Direct production exposure without a TLS proxy is unsupported.
+
+Scale by running multiple processes behind the proxy against the same session collection. Keep deployments on the same session schema. Session conflicts return 409 rather than silently dropping updates. Database schema/index changes belong to the application and should be planned separately from binary rollout.
+
+## Verification
+
+```sh
+cargo test --locked
+cargo build --release --locked
+node tools/local-validation.mjs mongo-tests
+node tools/local-validation.mjs integration
+node tools/local-validation.mjs soak 3600
+```
+
+Local validation requires `mongod` and Node.js. Each invocation creates an isolated temporary database directory, stops only its own processes, and retains test data/logs for inspection. Soak reports include the binary SHA-256, endpoint timing histograms, memory samples, error counts and shutdown result. A short smoke run is not the release soak. Do not claim a release has passed until its exact binary completes the full run and the remaining release checks pass.
diff --git a/docs/migration.md b/docs/migration.md
new file mode 100644
index 0000000..891c8fc
--- /dev/null
+++ b/docs/migration.md
@@ -0,0 +1,41 @@
+# Migrating from Relay 0.1
+
+This release changes language semantics. Test an application against the candidate before deployment. The archived [0.1 reference](v0.1-reference.md) describes the old behavior.
+
+## Imports and functions
+
+Imports no longer merge globals. Use `import helpers as helpers` and `helpers.function()`, or `from helpers import function`. Quoted relative paths such as `from "../lib/common.ry" import helper` are supported. Names beginning with `_` are private; cycles are errors.
+
+Route modules define `fn register(app)` and register their decorators within it; the entry script imports the module and calls `module.register(app)`. The Twitter example demonstrates this pattern.
+
+Functions resolve names lexically. Rebinding a name creates or updates a local binding, not a caller's variable. Captured/module collections cannot be mutated from a function; copy them into a local variable first. Sessions and database handles provide explicit shared persistence. Blocks use their surrounding function scope. Closures capture read-only lexical values.
+
+Missing, extra, duplicate, and unknown arguments are errors. Function type hints validate without coercion; HTTP inputs are coerced before invocation. Integer overflow, division by zero, and unsupported mixed arithmetic raise language errors. Use `str(value)` to concatenate a number with text. Equality compares values structurally instead of comparing their printed representation.
+
+## Async and errors
+
+I/O and timer operations start immediately, including assignments. Reading an async result waits for it; repeated readers receive the same value or error. `spawn(expression)` schedules the expression itself. `timeout(expression, milliseconds)` covers expression execution. `all` waits concurrently in input order; `race` returns the first completion and rejects an empty input. Cancel explicitly when other consumers no longer need a task.
+
+Scripts and requests join their child operations before exit/response. Unobserved failures now fail the scope instead of being discarded. `app.background(function, ...arguments)` creates supervised, process-local background work and returns `None`; it must not capture request or session context. It is not a durable queue.
+
+Caught errors expose `code` and `message`; `raise` and `finally` are supported. Diagnostics include source locations and function frames. `RELAY_TRACE=1` emits statement-line trace events for local debugging.
+
+## Strings and HTTP
+
+String literals no longer evaluate templates. Use `format("Hello {{ name }}", {"name": name})` for text formatting. `app.render_template(path, data)` renders HTML with automatic escaping and returns an HTML response. `Response(existing_response, status=404)` preserves its body and content type. MiniJinja's explicit `safe` filter should be used only for trusted HTML.
+
+Plain strings are returned as plain text, even if they contain HTML. Use `Response(html, content_type="text/html; charset=utf-8")` for explicit HTML. Dictionaries and lists now preserve JSON types recursively. Schema failures return 422. Unexpected handler failures return a generic 500 response with a request ID; details are not returned to clients.
+
+`request.path` is the actual path; `request.route_pattern` is the registered pattern. Request IDs are present on responses. Untrusted forwarded headers are removed; configure trusted proxy IP addresses explicitly.
+
+## Sessions and databases
+
+Configure shared sessions with `app.sessions(db.collection("relay_sessions"))` before starting the server. This creates the expiry index. Production mode requires MongoDB-backed sessions and an HTTPS public origin. Callback session backends remain for development compatibility and do not provide versioned distributed guarantees.
+
+Sessions expire after 30 minutes idle or seven days absolute. Use `session.regenerate()` after authentication, `session.destroy()` at logout, and `session.csrf_token()` to obtain a token. Existing-session POST/PUT/PATCH/DELETE requests require that token in `x-csrf-token` or the `csrf_token` form field. Refresh the token after login because rotation changes it. Concurrent conflicting changes return 409; clients may retry after refreshing their session. Revoked sessions cannot be recreated by stale writes.
+
+MongoDB `find(filter, limit=100, skip=0, sort={...})` is bounded; the maximum limit is 1,000. Use `count(filter)` for totals and paginate larger result sets. Use `find_one_and_update(filter, update, upsert=True)` for atomic counters. `create_index(keys, unique=True)` establishes constraints. The Twitter example uses these APIs.
+
+## Tools
+
+The CLI provides `run`, `check`, `fmt`, and `test`. `relay file.ry` remains an alias for `run`. `fmt` conservatively normalizes trailing whitespace while preserving comments and literal content. `check` validates syntax, function signatures, control flow and imports without executing the app; it is not a static type checker.
diff --git a/docs/v0.1-reference.md b/docs/v0.1-reference.md
new file mode 100644
index 0000000..67e0968
--- /dev/null
+++ b/docs/v0.1-reference.md
@@ -0,0 +1,2355 @@
+# Relay Programming Language
+
+**Version:** 0.1
+**License:** MIT
+
+> A networking-first, async-by-default programming language with Python-like syntax and Node.js-style non-blocking semantics.
+
+Relay is designed for building high-performance web services, APIs, and network applications with minimal boilerplate. Everything is asynchronous by default, but there's **no `await` keyword**—async operations start immediately and only block when their values are actually needed.
+
+
+
+## Table of Contents
+
+- [Features](#features)
+- [Quick Start](#quick-start)
+- [Example Prereqs](#example-prereqs)
+- [Installation](#installation)
+- [Learning Path](#learning-path)
+- [Language Guide](#language-guide)
+ - [Syntax Fundamentals](#syntax-fundamentals)
+ - [Data Types](#data-types)
+ - [Control Flow](#control-flow)
+ - [Functions](#functions)
+ - [Async Model](#async-model)
+- [API Reference](#api-reference)
+ - [Core Functions](#core-functions)
+ - [Async & Concurrency](#async--concurrency)
+ - [File System](#file-system)
+ - [HTTP Client](#http-client)
+ - [Email (SMTP)](#email-smtp)
+ - [Web Server](#web-server)
+ - [Database (MongoDB)](#database-mongodb)
+- [Examples](#examples)
+- [How It Works](#how-it-works)
+- [Best Practices](#best-practices)
+- [Troubleshooting](#troubleshooting)
+- [Contributing](#contributing)
+
+
+
+## Features
+
+- **Implicit Async**: No `await` keyword needed—async operations resolve automatically when values are accessed
+- **Python-like Syntax**: Clean, indentation-based syntax that's easy to read and write
+- **Built-in Web Server**: Flask/FastAPI-style decorators with automatic routing
+- **Route Groups + OpenAPI**: Prefix route groups and expose generated `/openapi.json`
+- **Static File Serving**: Mount directories directly in your web app (`app.static("/assets", "./public")`)
+- **Middleware Hooks**: Run request middleware before handlers (`app.use(auth_middleware)`)
+- **HTTP Client**: Async HTTP client with `get/post/put/patch/delete` and request/response headers
+- **Email (SMTP)**: Async SMTP mail sending with secure-by-default TLS and template helpers
+- **Multipart Uploads**: Parse `multipart/form-data` bodies with file metadata/bytes
+- **Upload Guards**: Configure max request/file size limits and MIME allowlists via `app.uploads(...)`
+- **MongoDB Integration**: Native async MongoDB support
+- **Session Management**: Built-in session handling with HttpOnly cookies
+- **Pluggable Session Backends**: Use in-memory sessions or custom load/save callbacks
+- **Authentication Helpers**: Password hashing/verification and pluggable auth stores
+- **Structured API Errors**: `HTTPError(...)` helper with consistent JSON error envelope
+- **Request IDs**: Per-request `request_id` in handler context and `x-request-id` response header
+- **Template Rendering**: MiniJinja-powered templates available in any string expression (`{{ variable }}`)
+- **Type Hints**: Optional runtime type checking for function parameters
+- **List Comprehensions**: Python-style inline list transforms with optional filtering
+- **Destructuring Assignment**: Unpack lists/strings/dicts into multiple variables
+- **Error Handling**: `try/except` blocks for graceful runtime error recovery
+- **Concurrency Primitives**: `spawn`, `all`, `race`, `timeout`, and `cancel` for parallel execution
+
+
+
+## Quick Start
+
+### Installation
+
+```bash
+# Clone the repository
+git clone https://github.com/patx/relay-lang.git
+cd relay-lang
+
+# Install the Relay CLI
+cargo install --path .
+
+# Run a Relay program
+relay path/to/app.ry
+```
+
+### Run Included Examples
+
+```bash
+# Small language/runtime example
+relay examples/type_hint_validation.ry
+
+# Full web app examples
+relay examples/param_types.ry
+relay examples/pastebin.ry # requires local MongoDB
+relay examples/url_shortener.ry # requires local MongoDB
+```
+
+### Example Prereqs
+
+| Example | Needs |
+|---|---|
+| `examples/type_hint_validation.ry` | None |
+| `examples/param_types.ry` | None |
+| `examples/pastebin.ry` | Local MongoDB at `mongodb://localhost:27017` |
+| `examples/url_shortener.ry` | Local MongoDB at `mongodb://localhost:27017` |
+| `examples/upload_email_attachment.ry` | SMTP credentials + template files in `examples/templates/` |
+| `examples/email_smtp.ry` | SMTP credentials |
+
+### Hello World
+
+```python
+print("Hello, Relay!")
+```
+
+### Async Hello World
+
+```python
+sleep(2000, print("world"))
+print("hello")
+```
+
+**Output:**
+```
+hello
+world
+```
+
+Notice how "hello" prints immediately while "world" waits 2 seconds—all without blocking the main thread.
+
+### Simple Web Server
+
+```python
+app = WebApp()
+server = WebServer()
+
+fn auth()
+ if (request.path == "/private" && session["user"] == None)
+ return app.redirect("/login")
+
+app.use(auth)
+app.static("/assets", "./public")
+
[email protected]("/")
+fn index()
+ return "Hello, Relay!"
+
+server.run(app)
+```
+
+Visit `http://127.0.0.1:8080/` to see your response.
+
+
+
+## Installation
+
+### Prerequisites
+
+- Rust 1.70 or later
+- Cargo (included with Rust)
+
+### From Source
+
+```bash
+# Clone the repository
+git clone https://github.com/patx/relay-lang.git
+cd relay-lang
+
+# Build and install
+cargo install --path .
+
+# Run a Relay program
+relay path/to/app.ry
+```
+
+### Development Mode
+
+If you're developing the Relay compiler itself:
+
+```bash
+cargo run -- path/to/file.ry
+```
+
+### Environment Variables
+
+- `RELAY_BIND`: Override the default bind address for web servers (default: `127.0.0.1:8080`)
+
+Example:
+```bash
+RELAY_BIND=0.0.0.0:8080 relay server.ry
+```
+
+
+
+## Learning Path
+
+If your goal is to learn Relay end-to-end, use this order:
+
+1. Start with [Quick Start](#quick-start) to run your first script and web app.
+2. Work through [Language Guide](#language-guide) for syntax, types, control flow, modules, and async behavior.
+3. Use [API Reference](#api-reference) as the complete built-in/runtime reference.
+4. Run scripts in [`examples/`](examples/) to see complete services and integrations.
+5. Revisit [Best Practices](#best-practices) and [Troubleshooting](#troubleshooting) as you build real apps.
+
+
+## Language Guide
+
+### Syntax Fundamentals
+
+Relay uses **indentation-based syntax** with 4 spaces per indentation level. Tabs are not allowed.
+
+```python
+fn example()
+ x = 10
+ if (x > 5)
+ print("x is greater than 5")
+ else
+ print("x is 5 or less")
+```
+
+**Key Rules:**
+- Indentation must be exactly 4 spaces per level
+- No tabs allowed
+- Function bodies, control flow blocks, and loops all require indentation
+- Comments start with `//` and continue to the end of the line
+
+```python
+// This is a comment
+x = 42 // This is also a comment
+```
+
+### Modules and Imports
+
+Relay supports loading code from multiple `.ry` files with `import`:
+
+```python
+import utils
+import web.routes
+import shared/helpers.ry
+```
+
+**How imports resolve:**
+- `import utils` loads `utils.ry`
+- `import web.routes` loads `web/routes.ry`
+- Relative paths are resolved from the importing file's directory
+- A module is loaded only once per run (duplicate imports are ignored)
+
+The module filenames above are illustrative examples of resolution behavior.
+
+Imported modules execute in the same global scope, so functions and variables they define become directly available.
+
+### Data Types
+
+#### Primitives
+
+```python
+// Integers
+age = 25
+count = -10
+
+// Floats
+pi = 3.14159
+temperature = -273.15
+
+// Strings
+name = "Relay"
+message = "Hello, world!"
+
+// Booleans
+is_active = True
+is_complete = False
+
+// None
+result = None
+```
+
+#### Collections
+
+**Lists:**
+```python
+numbers = [1, 2, 3, 4, 5]
+mixed = [1, "two", 3.0, True]
+nested = [[1, 2], [3, 4]]
+
+// List comprehensions
+squares = [n * n for n in numbers]
+evens = [n for n in numbers if n % 2 == 0]
+
+// Access elements
+first = numbers[0] // 1
+last = numbers[4] // 5
+
+// Lists are mutable
+numbers[0] = 10
+```
+
+**Dictionaries:**
+```python
+user = {"name": "Ada", "age": 30, "active": True}
+
+// Access values
+name = user["name"] // "Ada"
+age = user["age"] // 30
+
+// Keys are always strings
+// Values can be any type
+```
+
+**Important:** Dictionary keys are automatically stringified. `{1: "value"}` becomes `{"1": "value"}`.
+
+#### Destructuring Assignment
+
+Relay supports destructuring assignment for iterables such as lists, strings, and dictionaries (dictionary keys are unpacked):
+
+```python
+a, b, c = [10, 20, 30]
+x, y = "hi" // x = "h", y = "i"
+k1, k2 = {"a": 1, "b": 2}
+```
+
+The number of variables must match the number of unpacked values.
+
+### Control Flow
+
+#### If-Else
+
+```python
+if (condition)
+ // then block
+ print("condition is true")
+```
+
+```python
+if (x > 0)
+ print("positive")
+else
+ print("zero or negative")
+```
+
+#### While Loops
+
+```python
+i = 0
+while (i < 5)
+ print(i)
+ i = i + 1
+```
+
+Augmented assignment operators are supported:
+```python
+i = 0
+while (i < 5)
+ print(i)
+ i =+ 1 // equivalent to i = i + 1
+```
+
+#### For Loops
+
+```python
+// Iterate over lists
+for (item in [1, 2, 3, 4, 5])
+ print(item)
+
+// Iterate over dictionary keys
+user = {"name": "Ada", "age": 30}
+for (key in user)
+ print(key + ": " + str(user[key]))
+```
+
+**Note:** For loops iterate over collection elements or dictionary keys.
+
+#### `break` and `continue`
+
+```python
+i = 0
+while (i < 6)
+ i =+ 1
+ if (i == 2)
+ continue
+ if (i == 5)
+ break
+ print(i)
+```
+
+#### Error Handling (`try/except`)
+
+Use `try/except` to catch runtime errors and continue execution:
+
+```python
+try
+ value = int("not-a-number")
+ print(value)
+except
+ print("Could not parse integer")
+```
+
+You can also bind the error message:
+
+```python
+try
+ result = missing_name + 1
+except(err)
+ print("Error:", err)
+```
+
+### Functions
+
+#### Basic Functions
+
+```python
+fn greet(name)
+ return "Hello, " + name
+
+message = greet("World")
+print(message) // Hello, World
+```
+
+#### Default Parameters
+
+```python
+fn greet(name: str = "World")
+ return "Hello, " + name
+
+print(greet()) // Hello, World
+print(greet("Relay")) // Hello, Relay
+```
+
+#### Type Hints
+
+Relay supports runtime type checking for parameters:
+
+```python
+fn add(a: int, b: int)
+ return a + b
+
+result = add(5, 10) // OK
+result = add("5", "10") // Type error!
+```
+
+Type hints on regular functions are strict validation (no implicit coercion). Unknown type hints raise a runtime type error.
+
+**Supported Types:**
+- `str`: String
+- `int`: Integer
+- `float`: Float
+- `bool`: Boolean
+- `json` or `Json`: JSON object
+
+```python
+fn process_data(data: json)
+ return data["key"]
+```
+
+#### Return Values
+
+Functions can return any value:
+
+```python
+fn get_user()
+ return {"name": "Ada", "id": 1}
+
+fn calculate()
+ return 42
+
+fn do_work()
+ // No explicit return = returns None
+ print("Working...")
+```
+
+### Async Model
+
+Relay's async model is unique: **there is no `await` keyword**. Instead, async operations return `Deferred` values that automatically resolve when you try to use them.
+
+#### Expression Statements Don't Block
+
+When you call an async function without using its return value, it runs in the background:
+
+```python
+sleep(1000, print("delayed"))
+print("immediate")
+```
+
+**Output:**
+```
+immediate
+delayed
+```
+
+#### Deferred Values
+
+When you assign the result of an async operation, you get a `Deferred` value. The operation starts immediately but doesn't block:
+
+```python
+x = sleep(1000, 10) // Returns immediately with Deferred<10>
+y = sleep(1000, 20) // Returns immediately with Deferred<20>
+print(x + y) // Waits for both, then prints 30
+```
+
+Both sleeps start at the same time, so this takes ~1 second, not 2.
+
+#### How Deferred Resolution Works
+
+A `Deferred` value automatically resolves (waits for the async operation to complete) when:
+
+1. **Used in an expression:**
+```python
+result = deferred_value + 10
+```
+
+2. **Passed to a function:**
+```python
+print(deferred_value)
+```
+
+3. **Returned from a function:**
+```python
+fn get_data()
+ return http.get("https://api.example.com")
+```
+
+4. **Used in a comparison:**
+```python
+if (deferred_value > 10)
+ print("greater than 10")
+```
+
+#### Concurrency Primitives
+
+**`spawn(expr)`** - Run an expression in parallel:
+```python
+fn work(n)
+ sleep(500, n * 2)
+
+task1 = spawn(work(5))
+task2 = spawn(work(10))
+result1 = task1.join() // Wait for completion
+result2 = task2.join()
+```
+
+**`all(tasks)`** - Wait for all tasks to complete:
+```python
+tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
+results = all(tasks) // [2, 4, 6]
+```
+
+**`race(tasks)`** - Wait for the first task to complete:
+```python
+tasks = [spawn(sleep(1000, "slow")), spawn(sleep(100, "fast"))]
+winner = race(tasks) // "fast"
+```
+
+**`timeout(expr, ms)`** - Add a timeout to any operation:
+```python
+result = timeout(http.get("https://slow-api.com"), 5000)
+```
+
+**`cancel(task)`** - Cancel a running task:
+```python
+task = spawn(long_operation())
+cancel(task)
+```
+
+
+
+## API Reference
+
+### Core Functions
+
+#### `print(value, ...)`
+Print values to stdout. Multiple arguments are printed space-separated.
+
+```python
+print("Hello") // Hello
+print("x =", 42) // x = 42
+print("a", "b", "c") // a b c
+```
+
+#### `str(value)`
+Convert any value to a string.
+
+```python
+str(123) // "123"
+str(3.14) // "3.14"
+str(True) // "True"
+str([1, 2, 3]) // "[1, 2, 3]"
+```
+
+#### `int(value)`
+Convert a value to an integer.
+
+```python
+int("42") // 42
+int(3.9) // 3
+int(True) // 1
+int("invalid") // Runtime error
+```
+
+#### `float(value)`
+Convert a value to a float.
+
+```python
+float("3.14") // 3.14
+float(42) // 42.0
+float("2.5e3") // 2500.0
+```
+
+### Async & Concurrency
+
+#### `sleep(milliseconds, value=None)`
+**Returns:** `Deferred<value>`
+
+Sleep for the specified duration, then resolve to `value`.
+
+```python
+sleep(1000, print("done")) // Print after 1 second
+result = sleep(2000, 42) // Wait 2s, result = 42
+```
+
+#### `spawn(expression)`
+**Returns:** `Task`
+
+Execute an expression in parallel. Returns a `Task` object.
+
+```python
+task = spawn(expensive_computation())
+// Do other work...
+result = task.join()
+```
+
+**Task Methods:**
+- `task.join()` - Wait for the task to complete and return its value
+
+#### `all(tasks)`
+**Returns:** List of results
+
+Wait for all tasks to complete. Returns results in order.
+
+```python
+tasks = [spawn(work(1)), spawn(work(2)), spawn(work(3))]
+results = all(tasks) // Wait for all, returns [result1, result2, result3]
+```
+
+#### `race(tasks)`
+**Returns:** First completed result
+
+Wait for the first task to complete, return its result.
+
+```python
+tasks = [
+ spawn(http.get("https://api1.com")),
+ spawn(http.get("https://api2.com"))
+]
+fastest = race(tasks) // Returns whichever completes first
+```
+
+#### `timeout(expression, milliseconds)`
+**Returns:** `Deferred<value>` or timeout error
+
+Add a timeout to any async operation.
+
+```python
+result = timeout(http.get("https://slow.com"), 5000) // 5 second timeout
+```
+
+If the timeout is exceeded, a runtime error is raised.
+
+#### `cancel(task)`
+Cancel a running task.
+
+```python
+task = spawn(long_running_operation())
+cancel(task)
+```
+
+### File System
+
+#### `read_file(path)`
+**Returns:** `Deferred<string>`
+
+Read a file's contents as a UTF-8 string.
+
+```python
+content = read_file("data.txt")
+print(content)
+```
+
+#### `save_file(content, path)`
+**Returns:** `Deferred<None>`
+
+Write a string to a file.
+
+```python
+save_file("Hello, World!", "output.txt")
+```
+
+#### `read_json(path)`
+**Returns:** `Deferred<dict>`
+
+Read and parse a JSON file.
+
+```python
+data = read_json("config.json")
+print(data["api_key"])
+```
+
+#### `save_json(data, path)`
+**Returns:** `Deferred<None>`
+
+Serialize data to JSON and write to a file.
+
+```python
+config = {"host": "localhost", "port": 8080}
+save_json(config, "config.json")
+```
+
+### HTTP Client
+
+#### `Http()`
+Create an HTTP client instance.
+
+```python
+http = Http()
+```
+
+#### `http.get(url, headers=None)`
+**Returns:** `Deferred<Response>`
+
+Send a GET request.
+
+```python
+http = Http()
+resp = http.get(
+ "https://api.example.com/users",
+ headers={"authorization": "Bearer token"}
+)
+print(resp.status) // 200
+print(resp.text) // Response body as string
+```
+
+#### `http.post(url, data=None, headers=None)`
+#### `http.put(url, data=None, headers=None)`
+#### `http.patch(url, data=None, headers=None)`
+#### `http.delete(url, data=None, headers=None)`
+**Returns:** `Deferred<Response>`
+
+Send requests with optional payloads and headers.
+
+```python
+http = Http()
+payload = {"name": "Ada", "email": "[email protected]"}
+resp = http.post(
+ "https://api.example.com/users",
+ payload,
+ headers={"content-type": "application/json"}
+)
+```
+
+#### Response Object
+
+HTTP responses have the following properties:
+
+- `resp.status` - HTTP status code (int)
+- `resp.text` - Response body as string
+- `resp.json()` - Parse response body as JSON
+- `resp.headers` - Response headers (dict)
+
+```python
+http = Http()
+resp = http.get("https://api.github.com/users/octocat")
+
+print(resp.status) // 200
+data = resp.json()
+print(data["login"]) // octocat
+```
+
+### Email (SMTP)
+
+#### `Email(host, port=587, username=None, password=None, from=None, tls="starttls")`
+Create an SMTP email client instance.
+
+```python
+email = Email(
+ "smtp.example.com",
+ 587,
+ "smtp-user",
+ "smtp-pass",
+ "Relay <[email protected]>",
+ tls="starttls"
+)
+```
+
+TLS modes:
+- `"starttls"` (default) - requires STARTTLS upgrade
+- `"wrapper"` - implicit TLS/SMTPS (commonly port 465)
+- `"insecure"` - plain SMTP (only for local/dev)
+
+#### `email.send(to, subject, text=None, html=None, cc=None, bcc=None, reply_to=None, from=None, headers=None, attachments=None)`
+**Returns:** `Deferred<dict>`
+
+Send an email asynchronously over SMTP.
+
+- `to`, `cc`, `bcc` accept `str` or `list[str]`
+- at least one of `text` or `html` is required
+- `from` can be passed per-call or configured in `Email(...)`
+- `attachments` accepts a single attachment dict or list of attachment dicts
+
+```python
+email = Email(
+ host="smtp.example.com",
+ username="smtp-user",
+ password="smtp-pass",
+ from="Relay <[email protected]>"
+)
+
+result = email.send(
+ to=["[email protected]"],
+ subject="Welcome",
+ text="Welcome to Relay!"
+)
+
+print(result["ok"]) // True
+print(result["smtp_code"]) // e.g. 250
+```
+
+Attachment dict format:
+- `filename` (required) - attachment file name
+- `bytes` or `content` or `data` (required) - file payload (`bytes`, `str`, or `list[int]`)
+- `content_type` (optional) - MIME type (default: `application/octet-stream`)
+
+```python
+result = email.send(
+ to="[email protected]",
+ subject="Monthly report",
+ text="See attached report.",
+ attachments=[
+ {
+ "filename": "report.txt",
+ "content_type": "text/plain",
+ "bytes": "Report body"
+ }
+ ]
+)
+```
+
+Send result fields:
+- `ok` - always `True` on success
+- `transport` - `"smtp"`
+- `host` / `port` - resolved transport target
+- `message_id` - message id when available, else `None`
+- `envelope_to` - recipient list used for SMTP envelope
+- `smtp_code` / `smtp_message` - provider response details when available
+
+#### `email.render(template, data=None)`
+**Returns:** `string`
+
+Render an email template string with MiniJinja variables.
+
+```python
+body = email.render("Hi {{ name }}, welcome!", {"name": "Ada"})
+```
+
+#### `email.render_file(path, data=None)`
+**Returns:** `Deferred<string>`
+
+Load and render a template file asynchronously.
+
+```python
+html = email.render_file("templates/welcome.html", {"name": "Ada"})
+email.send(to="[email protected]", subject="Welcome", html=html)
+```
+
+### Web Server
+
+#### `WebApp()`
+Create a web application instance.
+
+```python
+app = WebApp()
+```
+
+#### Route Decorators
+
+Define HTTP endpoints using decorators:
+
+- `@app.get(path)`
+- `@app.post(path)`
+- `@app.put(path)`
+- `@app.patch(path)`
+- `@app.delete(path)`
+- `@app.ws(path)`
+
+```python
+app = WebApp()
+
[email protected]("/")
+fn index()
+ return {"message": "Welcome to Relay"}
+
[email protected]("/users")
+fn create_user(name: str, email: str)
+ return {"id": 123, "name": name, "email": email}
+```
+
+Decorator schema options:
+- `validate=...` - Validate/coerce body payload (JSON first, then form)
+- `query=...` - Validate/coerce query parameters
+- `body=...` - Validate/coerce form body
+- `json=...` - Validate/coerce JSON body
+
+```python
[email protected]("/search", query={"limit": "int", "q?": "str"})
+fn search(limit, q = None)
+ return {"limit": limit, "q": q}
+
[email protected]("/users", json={"name": "str", "age?": "int"})
+fn create_user(name, age = None)
+ return {"name": name, "age": age}
+```
+
+#### Route Groups and OpenAPI
+
+Use grouped route prefixes to organize larger APIs:
+
+```python
+app = WebApp()
+api = app.group("/api")
+v1 = api.group("/v1")
+
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id}
+```
+
+Enable generated OpenAPI docs:
+
+```python
+app.openapi(title="Relay API", version="1.0.0")
+// Exposes GET /openapi.json
+```
+
+#### WebSocket Routes
+
+Use `@app.ws(path)` for WebSocket upgrade endpoints.
+WebSocket handlers receive a `socket` object with:
+- `socket.recv()` → returns text (`str`), binary (`bytes`), or `None` on close
+- `socket.send(value)` → sends text (or binary when `bytes`)
+- `socket.close()` → closes the connection
+
+```python
[email protected]("/chat")
+fn chat_room()
+ while True
+ msg = socket.recv()
+ if (msg == None)
+ break
+ socket.send("echo: " + str(msg))
+```
+
+#### Path Parameters
+
+Use `<name>` syntax to capture path segments:
+
+```python
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id, "name": "Ada"}
+
[email protected]("/posts/<post_id>/comments/<comment_id>")
+fn get_comment(post_id, comment_id)
+ return {"post": post_id, "comment": comment_id}
+```
+
+#### Handler Parameters
+
+Handler parameters are automatically bound from:
+1. **Path parameters** (highest priority)
+2. **Request body form fields** (`application/x-www-form-urlencoded`)
+3. **Query parameters** (lowest priority)
+
+```python
+// GET /search?q=relay&limit=10
[email protected]("/search")
+fn search(q: str, limit: int = 20)
+ return {"query": q, "limit": limit}
+
+// POST /users with form body: name=Ada&[email protected]
[email protected]("/users")
+fn create_user(name: str, email: str)
+ return {"name": name, "email": email}
+
+// GET /users/123
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id}
+```
+
+JSON bodies are available as the `data` parameter (default name) or by typing a handler param as `Json`.
+
+```python
+// POST /events with JSON body {"type":"signup","user":"ada"}
[email protected]("/events")
+fn create_event(data: Json)
+ return {"event_type": data["type"], "user": data["user"]}
+```
+
+For zero-arg handlers (or explicit access), use request helpers: `get_query()`, `get_body()`, and `get_json()`.
+
+#### Middleware
+
+Register middleware with `app.use(fn)`.
+
+Middleware functions can be:
+- `fn middleware()` (legacy style)
+- `fn middleware(ctx)` where `ctx` contains request fields
+- `fn middleware(ctx, next)` with full chain semantics
+
+If middleware returns a non-`None` value, Relay short-circuits and sends that response.
+
+`next()` runs the remainder of the middleware chain and then the handler.
+
+```python
+fn audit(ctx, next)
+ print("before:", ctx["path"])
+ result = next()
+ print("after:", ctx["path"])
+ return result
+```
+
+#### Type Hints in Handlers
+
+Use type hints to enforce parameter types and enable automatic coercion:
+
+```python
[email protected]("/calculate")
+fn calculate(a: int, b: int)
+ return {"result": a + b}
+
+// POST /calculate with form body a=5&b=10
+// Automatically converts strings to ints: {"result": 15}
+```
+
+**Supported types:**
+- `str` - String
+- `int` - Integer
+- `float` - Float
+- `bool` - Boolean
+- `json` or `Json` - Full JSON body (for POST/PUT/PATCH)
+
+#### Request Object
+
+Every handler has access to a `request` dictionary:
+
+```python
[email protected]("/debug")
+fn debug_request()
+ print(request["method"]) // GET
+ print(request["path"]) // /debug
+ print(request["request_id"])// rid_...
+ print(request["query"]) // Query parameters dict
+ print(request["form"]) // Form fields dict (if present)
+ print(request["json"]) // JSON body (if present)
+ print(request["headers"]) // Headers dict
+ print(request["cookies"]) // Cookies dict
+ return "OK"
+```
+
+**Request fields:**
+- `method` - HTTP method (string)
+- `path` - Request path (string)
+- `request_id` - Request identifier string
+- `query` - Query parameters (dict)
+- `form` - Parsed form body fields (dict, when present)
+ - URL-encoded fields become strings
+ - Multipart text fields become strings
+ - Multipart file fields become dicts:
+ - `filename` (`str` or `None`)
+ - `content_type` (`str` or `None`)
+ - `size` (`int`)
+ - `bytes` (`bytes`)
+- `headers` - Request headers (dict)
+- `cookies` - Cookies (dict)
+- `json` - Parsed JSON body (if present)
+
+#### Request Helpers
+
+Use helpers when you want payload access without binding handler parameters:
+
+- `get_query()` - Query parameters as a dict (empty dict when unavailable)
+- `get_body()` - Parsed form fields as a dict (empty dict when unavailable)
+- `get_json()` - Parsed JSON body (or `None` when unavailable)
+
+```python
[email protected]("/search")
+fn search()
+ query = get_query()
+ return {"q": query["q"]}
+
[email protected]("/submit")
+fn submit()
+ form = get_body()
+ payload = get_json()
+ return {"form": form, "json": payload}
+```
+
+#### Validation Helpers
+
+Use schema helpers for explicit request validation:
+
+- `validate(data, schema)` - Validate/coerce an object against schema
+- `require_query(schema)` - Validate query params in handlers
+- `require_body(schema)` - Validate form body in handlers
+- `require_json(schema)` - Validate JSON body in handlers
+
+Schema format:
+- `"field": "type"` for required fields
+- `"field?": "type"` for optional fields
+- `"field": {"type": "int", "required": True, "default": 10}` for explicit rules
+
+```python
[email protected]("/search")
+fn search()
+ params = require_query({"limit": "int", "q?": "str"})
+ return {"limit": params["limit"], "q": params["q"]}
+```
+
+#### Cookies
+
+Access cookies via the `cookies` dict:
+
+```python
[email protected]("/")
+fn index()
+ user_id = cookies["user_id"]
+ return "User ID: " + user_id
+```
+
+#### Sessions
+
+Relay provides built-in session management with HttpOnly cookies:
+
+```python
[email protected]("/login")
+fn login(username: str)
+ session["user"] = username
+ session["logged_in"] = True
+ return "Logged in"
+
[email protected]("/profile")
+fn profile()
+ if (session["logged_in"] == True)
+ return "Welcome, " + session["user"]
+ else
+ return app.redirect("/login")
+```
+
+**Session features:**
+- Automatically persisted across requests
+- Stored server-side (not in cookies)
+- Uses `relay_sid` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` automatically on HTTPS)
+- Session data is a dictionary that persists modifications
+
+Customize cookie policy:
+
+```python
+app.session(secure=True, http_only=True, same_site="Lax")
+```
+
+Use a custom session backend (for any database/service):
+
+```python
+session_db = {}
+
+fn load_session(sid)
+ return session_db[sid]
+
+fn save_session(sid, data)
+ session_db[sid] = data
+
+app.session_backend(load_session, save_session)
+```
+
+#### Upload Limits and MIME Allowlist
+
+Configure upload safety controls per app:
+
+```python
+app.uploads(
+ max_body_bytes=10 * 1024 * 1024, // default 10 MiB
+ max_file_bytes=5 * 1024 * 1024, // default 5 MiB per file field
+ allowed_mime_types=["image/png", "application/pdf"] // optional
+)
+```
+
+Parameters:
+- `max_body_bytes` - Maximum raw request body size accepted
+- `max_file_bytes` - Maximum size for each multipart file field
+- `allowed_mime_types` - Optional MIME allowlist for multipart file fields (`None` disables allowlist)
+
+When limits are exceeded or a MIME type is disallowed, Relay returns `400 bad_request`.
+
+#### Response Types
+
+Handlers can return various types:
+
+**JSON (automatic):**
+```python
[email protected]("/api/user")
+fn get_user()
+ return {"name": "Ada", "id": 123} // Auto-serialized to JSON
+```
+
+**Plain text:**
+```python
[email protected]("/")
+fn index()
+ return "Hello, World!" // Content-Type: text/plain
+```
+
+**HTML:**
+```python
[email protected]("/")
+fn index()
+ return "<h1>Welcome</h1>" // Content-Type: text/html
+```
+
+**Custom Response:**
+```python
[email protected]("/custom")
+fn custom()
+ return Response(
+ {"error": "Not found"},
+ status=404,
+ content_type="application/json"
+ )
+```
+
+**Redirect:**
+```python
[email protected]("/old-path")
+fn old_endpoint()
+ return app.redirect("/new-path")
+```
+
+#### `Response(body, status=200, content_type=None)`
+Create a custom HTTP response.
+
+```python
[email protected]("/xml")
+fn get_xml()
+ xml = "<root><item>data</item></root>"
+ return Response(xml, status=200, content_type="application/xml")
+```
+
+**Parameters:**
+- `body` - Response body (string, dict, list, or bytes)
+- `status` - HTTP status code (default: 200)
+- `content_type` - Content-Type header (auto-detected if not specified)
+
+#### `HTTPError(status, code, message, details=None)`
+Create a structured API error response.
+When called inside a handler, Relay also includes `request_id` in the error payload.
+
+```python
[email protected]("/users")
+fn create_user(name)
+ if (name == None)
+ return HTTPError(400, "validation_error", "Missing name", {"field": "name"})
+ return {"ok": True}
+```
+
+#### Authentication Helpers
+
+#### `auth_hash_password(password)`
+**Returns:** password hash string
+
+#### `auth_verify_password(password, hash)`
+**Returns:** `bool`
+
+```python
+hash = auth_hash_password("super-secret")
+is_valid = auth_verify_password("super-secret", hash) // True
+```
+
+#### `AuthStore(load_fn=None, save_fn=None)`
+Create an authentication store.
+
+- `AuthStore()` uses in-memory storage.
+- `AuthStore(load_fn, save_fn)` uses custom callbacks for pluggable backends.
+
+Available methods:
+- `store.register(username, password)` - hashes and stores password
+- `store.verify(username, password)` - verifies against stored hash
+- `store.get_hash(username)` - returns stored hash or `None`
+- `store.set_hash(username, hash)` - stores precomputed hash
+
+```python
+store = AuthStore()
+store.register("ada", "pw")
+print(store.verify("ada", "pw")) // True
+
+auth_db = {}
+fn load_user(name)
+ return auth_db[name]
+
+fn save_user(name, hash)
+ auth_db[name] = hash
+
+custom = AuthStore(load_user, save_user)
+custom.register("bob", "pw2")
+print(custom.verify("bob", "pw2")) // True
+```
+
+#### `app.redirect(url)`
+**Returns:** `Response` with 302 status
+
+Create a redirect response.
+
+```python
[email protected]("/submit")
+fn submit(data)
+ // Process data...
+ return app.redirect("/success")
+```
+
+#### Template Rendering (MiniJinja)
+
+Relay uses **MiniJinja** (Rust implementation of Jinja2) for template interpolation in strings.
+
+Template strings are evaluated anywhere in the interpreter (not only in web handlers) when a string contains both `{{` and `}}`.
+
+```python
+name = "Relay"
+version = "0.1"
+
+title = "{{ name }} v{{ version }}"
+print(title) // Relay v0.1
+
+items = ["a", "b", "c"]
+print("Count: {{ items | length }}")
+```
+
+Web handlers use the same engine:
+
+```python
[email protected]("/")
+fn index()
+ return "<h1>{{ name }} v{{ version }}</h1>"
+```
+
+Templates can reference values in the current scope and support MiniJinja expressions/filters.
+
+Use `app.render_template(path, ...kwargs)` when you want explicit template rendering from a file without relying on implicit `{{ ... }}` string evaluation:
+
+```python
+app = WebApp()
+html = app.render_template("templates/welcome.html", name="Ada", plan="Pro")
+```
+
+#### `WebServer()`
+Create a web server instance.
+
+```python
+server = WebServer()
+```
+
+#### `server.run(app)`
+Start the web server.
+
+```python
+app = WebApp()
+server = WebServer()
+
[email protected]("/")
+fn index()
+ return "Hello, World!"
+
+server.run(app) // Starts server on 127.0.0.1:8080
+```
+
+**Configuration:**
+- Default bind address: `127.0.0.1:8080`
+- Override with `RELAY_BIND` environment variable:
+ ```bash
+ RELAY_BIND=0.0.0.0:8080 relay server.ry
+ ```
+
+### Database (MongoDB)
+
+#### `Mongo(connection_string)`
+Create a MongoDB client.
+
+```python
+mongo = Mongo("mongodb://localhost:27017")
+```
+
+**Connection string format:**
+```
+mongodb://[username:password@]host[:port][/database]
+```
+
+Examples:
+```python
+// Local MongoDB
+mongo = Mongo("mongodb://localhost:27017")
+
+// MongoDB Atlas
+mongo = Mongo("mongodb+srv://user:[email protected]/")
+
+// With authentication
+mongo = Mongo("mongodb://admin:password@localhost:27017")
+```
+
+#### `mongo.db(database_name)`
+**Returns:** Database instance
+
+Access a database.
+
+```python
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("my_app")
+```
+
+#### `db.collection(collection_name)`
+**Returns:** Collection instance
+
+Access a collection.
+
+```python
+users = db.collection("users")
+posts = db.collection("posts")
+```
+
+#### `collection.insert_one(document)`
+**Returns:** `Deferred<dict>` with `inserted_id`
+
+Insert a single document.
+
+```python
+users = db.collection("users")
+result = users.insert_one({"name": "Ada", "email": "[email protected]"})
+print(result["inserted_id"]) // ObjectId as string
+```
+
+#### `collection.insert_many(documents)`
+**Returns:** `Deferred<dict>` with `inserted_ids`
+
+Insert multiple documents.
+
+```python
+users = db.collection("users")
+docs = [
+ {"name": "Ada", "email": "[email protected]"},
+ {"name": "Grace", "email": "[email protected]"}
+]
+result = users.insert_many(docs)
+print(result["inserted_ids"]) // Dict of index -> ObjectId string
+```
+
+#### `collection.find_one(filter)`
+**Returns:** `Deferred<dict>` or `None`
+
+Find a single document matching the filter.
+
+```python
+users = db.collection("users")
+user = users.find_one({"email": "[email protected]"})
+if (user != None)
+ print(user["name"])
+```
+
+**Filter examples:**
+```python
+// Exact match
+user = users.find_one({"name": "Ada"})
+
+// Multiple conditions (implicit AND)
+user = users.find_one({"name": "Ada", "active": True})
+
+// By ObjectId
+user = users.find_one({"_id": "507f1f77bcf86cd799439011"})
+```
+
+#### `collection.find(filter)`
+**Returns:** `Deferred<list>` of documents
+
+Find all documents matching the filter.
+
+```python
+users = db.collection("users")
+active_users = users.find({"active": True})
+for (user in active_users)
+ print(user["name"])
+```
+
+**Find all documents:**
+```python
+all_users = users.find({})
+```
+
+#### `collection.update_one(filter, update)`
+**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
+
+Update a single document.
+
+```python
+users = db.collection("users")
+result = users.update_one(
+ {"email": "[email protected]"},
+ {"$set": {"active": True}}
+)
+print(result["modified_count"]) // 1
+```
+
+**Update operators:**
+```python
+// Set fields
+users.update_one({"_id": id}, {"$set": {"status": "active"}})
+
+// Increment
+users.update_one({"_id": id}, {"$inc": {"login_count": 1}})
+
+// Unset fields
+users.update_one({"_id": id}, {"$unset": {"temp_field": ""}})
+```
+
+#### `collection.update_many(filter, update)`
+**Returns:** `Deferred<dict>` with `matched_count` and `modified_count`
+
+Update multiple documents.
+
+```python
+users = db.collection("users")
+result = users.update_many(
+ {"active": False},
+ {"$set": {"status": "inactive"}}
+)
+print(result["modified_count"])
+```
+
+#### `collection.delete_one(filter)`
+**Returns:** `Deferred<dict>` with `deleted_count`
+
+Delete a single document.
+
+```python
+users = db.collection("users")
+result = users.delete_one({"email": "[email protected]"})
+print(result["deleted_count"]) // 1 or 0
+```
+
+#### `collection.delete_many(filter)`
+**Returns:** `Deferred<dict>` with `deleted_count`
+
+Delete multiple documents.
+
+```python
+users = db.collection("users")
+result = users.delete_many({"active": False})
+print(result["deleted_count"]) // Number of deleted documents
+```
+
+
+
+## Examples
+
+### 1. Hello World (Async)
+
+```python
+sleep(2000, print("world"))
+print("hello")
+```
+
+**Output:**
+```
+hello
+world
+```
+
+### 2. Simple Web API
+
+```python
+app = WebApp()
+server = WebServer()
+
[email protected]("/health")
+fn health()
+ return {"status": "ok", "service": "relay-api"}
+
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ return {"id": user_id, "name": "Ada Lovelace"}
+
+server.run(app)
+```
+
+### 3. Pastebin Service
+
+```python
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("pastebin")
+pastes = db.collection("pastes")
+
+fn find_paste(paste_id)
+ return pastes.find_one({"_id": paste_id})
+
[email protected]("/")
+fn index()
+ return read_file("examples/static/index.html")
+
[email protected]("/")
+fn create_paste(content = None)
+ if (content == None)
+ return Response("Missing content", status=400)
+ result = pastes.insert_one({"content": content})
+ paste_id = str(result["inserted_id"])
+ return app.redirect("/" + paste_id)
+
[email protected]("/<paste_id>")
+fn view_paste(paste_id)
+ paste = find_paste(paste_id)
+ if (paste == None)
+ return Response("Not found", status=404)
+ return read_file("examples/static/paste.html")
+
[email protected]("/api/paste/<paste_id>")
+fn get_paste(paste_id)
+ paste = find_paste(paste_id)
+ if (paste == None)
+ return Response("Not found", status=404)
+ return {"id": paste_id, "content": "{{ paste[\"content\"] }}"}
+
+server.run(app)
+```
+
+Template files used by this example:
+- `examples/static/index.html` for paste creation form
+- `examples/static/paste.html` for `<pre><code>` viewer UI and syntax highlighting
+
+### 4. Concurrent HTTP Requests
+
+```python
+http = Http()
+
+fn fetch_user(user_id)
+ resp = http.get("https://api.example.com/users/" + str(user_id))
+ return resp.json()
+
+// Fetch 5 users concurrently
+tasks = []
+i = 1
+while (i <= 5)
+ tasks =+ [spawn(fetch_user(i))]
+ i =+ 1
+
+users = all(tasks)
+for (user in users)
+ print(user["name"])
+```
+
+### 5. File Processing Pipeline
+
+```python
+fn process_file(filename)
+ content = read_file(filename)
+ lines = len(content.split("\n"))
+ return {"file": filename, "lines": lines}
+
+files = ["data1.txt", "data2.txt", "data3.txt"]
+tasks = []
+for (f in files)
+ tasks =+ [spawn(process_file(f))]
+
+results = all(tasks)
+save_json(results, "report.json")
+print("Processing complete!")
+```
+
+### 6. Session-based Authentication
+
+```python
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("auth_demo")
+users = db.collection("users")
+
[email protected]("/")
+fn index()
+ if (session["authenticated"] == True)
+ return "Welcome, " + session["username"]
+ return app.redirect("/login")
+
[email protected]("/login")
+fn login(username: str, password: str)
+ user = users.find_one({"username": username})
+ if (user == None)
+ return Response("Invalid credentials", status=401)
+
+ // In production, use proper password hashing!
+ if (user["password"] == password)
+ session["authenticated"] = True
+ session["username"] = username
+ return app.redirect("/")
+
+ return Response("Invalid credentials", status=401)
+
[email protected]("/logout")
+fn logout()
+ session["authenticated"] = False
+ session["username"] = None
+ return app.redirect("/login")
+
+server.run(app)
+```
+
+### 7. REST API with MongoDB
+
+```python
+app = WebApp()
+server = WebServer()
+mongo = Mongo("mongodb://localhost:27017")
+db = mongo.db("blog")
+posts = db.collection("posts")
+
[email protected]("/posts")
+fn list_posts()
+ all_posts = posts.find({})
+ return all_posts
+
[email protected]("/posts")
+fn create_post(title: str, content: str, author: str)
+ result = posts.insert_one({
+ "title": title,
+ "content": content,
+ "author": author
+ })
+ return {"id": str(result["inserted_id"])}
+
[email protected]("/posts/<post_id>")
+fn get_post(post_id)
+ post = posts.find_one({"_id": post_id})
+ if (post == None)
+ return Response("Post not found", status=404)
+ return post
+
[email protected]("/posts/<post_id>")
+fn update_post(post_id, title: str, content: str)
+ result = posts.update_one(
+ {"_id": post_id},
+ {"$set": {"title": title, "content": content}}
+ )
+ if (result["matched_count"] == 0)
+ return Response("Post not found", status=404)
+ return {"updated": True}
+
[email protected]("/posts/<post_id>")
+fn delete_post(post_id)
+ result = posts.delete_one({"_id": post_id})
+ if (result["deleted_count"] == 0)
+ return Response("Post not found", status=404)
+ return {"deleted": True}
+
+server.run(app)
+```
+
+### 8. Timeout and Error Handling
+
+```python
+http = Http()
+
+fn fetch_with_timeout(url)
+ return timeout(http.get(url), 5000)
+
+// Try to fetch with 5 second timeout
+result = fetch_with_timeout("https://slow-api.com/data")
+print(result.text)
+```
+
+### 9. Race Condition Example
+
+```python
+http = Http()
+
+// Fetch from multiple mirrors, use whichever responds first
+mirrors = [
+ "https://mirror1.example.com/data",
+ "https://mirror2.example.com/data",
+ "https://mirror3.example.com/data"
+]
+
+tasks = []
+for (url in mirrors)
+ tasks =+ [spawn(http.get(url))]
+
+fastest = race(tasks)
+print("Fastest mirror returned:", fastest.text)
+```
+
+### 10. Background Task Processing
+
+```python
+fn process_item(item)
+ sleep(1000, print("Processed: " + str(item)))
+
+items = [1, 2, 3, 4, 5]
+
+// Spawn all tasks without waiting
+for (item in items)
+ spawn(process_item(item))
+
+print("All tasks started, continuing...")
+// Tasks run in background
+```
+
+### 11. SMTP Welcome Email with Templates
+
+```python
+email = Email(
+ host="smtp.example.com",
+ username="smtp-user",
+ password="smtp-pass",
+ from="Relay <[email protected]>"
+)
+
+fn send_welcome(name, to)
+ html = email.render(
+ "<h1>Welcome {{ name }}</h1><p>Thanks for joining Relay.</p>",
+ {"name": name}
+ )
+ return email.send(
+ to=to,
+ subject="Welcome to Relay",
+ text="Welcome " + name + "!",
+ html=html
+ )
+
+result = send_welcome("Ada", "[email protected]")
+print("Email queued:", result["ok"])
+```
+
+
+
+## How It Works
+
+### Architecture Overview
+
+Relay is built on:
+- **Rust**: The interpreter is written in Rust for performance and safety
+- **Tokio**: Async runtime for non-blocking I/O
+- **Axum**: High-performance web framework for the built-in server
+- **Lettre**: Async SMTP transport for built-in email delivery
+- **MongoDB driver**: Native async MongoDB support
+
+### Compilation Pipeline
+
+1. **Lexer**: Tokenizes source code with indentation-aware parsing
+2. **Parser**: Builds an Abstract Syntax Tree (AST)
+3. **Evaluator**: Interprets the AST with async/await translation
+
+### The Async Model in Detail
+
+#### Deferred Values
+
+When you call an async function, Relay immediately starts the operation and returns a `Deferred` value:
+
+```python
+// This starts the HTTP request immediately
+response = http.get("https://api.example.com")
+// response is Deferred<Response>
+
+// The request is already in-flight here
+print("Request started")
+
+// Only when we access response.status does it wait
+print(response.status) // <-- Blocks here if not complete
+```
+
+#### Auto-Resolution
+
+`Deferred` values automatically resolve when:
+
+1. **Used in operations:**
+```python
+x = sleep(1000, 10)
+y = x + 5 // Waits for x to resolve
+```
+
+2. **Passed to functions:**
+```python
+result = sleep(1000, 42)
+print(result) // Waits before printing
+```
+
+3. **Used in control flow:**
+```python
+data = http.get("https://api.example.com")
+if (data.status == 200) // Waits before comparison
+ print("Success")
+```
+
+4. **Indexed:**
+```python
+resp = http.get("https://api.example.com")
+json_data = resp.json()
+print(json_data["key"]) // Waits for json() before indexing
+```
+
+#### Expression Statements
+
+Expression statements (expressions not assigned to variables) run without blocking:
+
+```python
+// This starts the sleep but doesn't wait
+sleep(1000, print("delayed"))
+
+// This prints immediately
+print("immediate")
+
+// Output:
+// immediate
+// delayed (after 1 second)
+```
+
+### Concurrency Model
+
+Relay uses Tokio's work-stealing scheduler to run tasks concurrently:
+
+```python
+// Start 3 HTTP requests concurrently
+task1 = spawn(http.get("https://api1.com"))
+task2 = spawn(http.get("https://api2.com"))
+task3 = spawn(http.get("https://api3.com"))
+
+// Wait for all to complete
+results = all([task1, task2, task3])
+```
+
+All three requests run in parallel, completing in the time of the slowest request (not 3× the time).
+
+### Web Server Architecture
+
+The web server uses Axum's routing system:
+
+1. **Route Registration**: Decorators like `@app.get("/path")` register handlers
+2. **Request Handling**: Incoming requests are matched against registered routes
+3. **Parameter Binding**: Path/query/body parameters are extracted and bound to handler parameters
+4. **Type Coercion**: Type hints trigger automatic type conversion
+5. **Response Generation**: Return values are automatically serialized to appropriate content types
+
+### Session Storage
+
+Sessions are stored server-side in an in-memory hash map:
+- Session ID is generated by Relay at runtime
+- `relay_sid` cookie stores the session ID (`HttpOnly`, `SameSite=Lax`, `Secure` on HTTPS)
+- Session data persists across requests for the same session ID
+- Sessions are stored in memory (cleared on server restart)
+
+Every web request is also assigned a `request_id` and echoed as the `x-request-id` response header.
+
+**Note:** In production, you'd want to persist sessions to a database.
+
+### MongoDB Integration
+
+MongoDB operations return `Deferred` values that resolve when the database operation completes:
+
+```python
+// This starts the query immediately
+users = collection.find({"active": True})
+
+// The query is running in the background here
+print("Query started")
+
+// Only when we iterate do we wait for results
+for (user in users) // <-- Blocks here
+ print(user["name"])
+```
+
+
+
+## Best Practices
+
+### 1. Leverage Concurrent Execution
+
+Instead of:
+```python
+// Sequential (slow)
+result1 = http.get("https://api1.com")
+result2 = http.get("https://api2.com")
+result3 = http.get("https://api3.com")
+```
+
+Do:
+```python
+// Concurrent (fast)
+tasks = [
+ spawn(http.get("https://api1.com")),
+ spawn(http.get("https://api2.com")),
+ spawn(http.get("https://api3.com"))
+]
+results = all(tasks)
+```
+
+### 2. Use Type Hints for API Handlers
+
+Type hints provide automatic validation and coercion:
+
+```python
[email protected]("/calculate")
+fn calculate(a: int, b: int, operation: str = "add")
+ if (operation == "add")
+ return {"result": a + b}
+ else
+ return {"result": a - b}
+```
+
+### 3. Handle Missing Data Gracefully
+
+Always check for `None` when querying databases or processing optional parameters:
+
+```python
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ user = users.find_one({"_id": user_id})
+ if (user == None)
+ return Response("User not found", status=404)
+ return user
+```
+
+### 4. Use Sessions for State Management
+
+Don't try to maintain state in global variables. Use sessions:
+
+```python
+// Bad
+current_user = None
+
[email protected]("/login")
+fn login(username)
+ current_user = username // Won't work across requests
+
+// Good
[email protected]("/login")
+fn login(username)
+ session["user"] = username
+```
+
+### 5. Implement Timeouts for External Calls
+
+Always add timeouts to external HTTP requests:
+
+```python
+fn fetch_data(url)
+ return timeout(http.get(url), 10000) // 10 second timeout
+```
+
+### 6. Structure Large Applications
+
+Split handlers into logical groups:
+
+```python
+app = WebApp()
+server = WebServer()
+
+// Auth routes
[email protected]("/auth/login")
+fn login(username, password)
+ // ...
+
[email protected]("/auth/logout")
+fn logout()
+ // ...
+
+// User routes
[email protected]("/users/<user_id>")
+fn get_user(user_id)
+ // ...
+
[email protected]("/users")
+fn create_user(name, email)
+ // ...
+
+// Post routes
[email protected]("/posts")
+fn list_posts()
+ // ...
+
+server.run(app)
+```
+
+### 7. Use Augmented Assignment
+
+For cleaner counter increments:
+
+```python
+// Instead of
+i = i + 1
+
+// Use
+i =+ 1
+```
+
+### 8. Return Early for Error Cases
+
+Structure handlers with early returns for error cases:
+
+```python
[email protected]("/posts/<post_id>")
+fn get_post(post_id)
+ post = posts.find_one({"_id": post_id})
+ if (post == None)
+ return Response("Not found", status=404)
+
+ if (post["published"] == False)
+ return Response("Not published", status=403)
+
+ return post
+```
+
+
+
+## Troubleshooting
+
+### Common Errors
+
+#### "Indentation must be 4 spaces per level"
+
+**Cause:** Relay requires exactly 4 spaces per indentation level.
+
+**Fix:** Ensure all indentation uses 4 spaces (not tabs, not 2 spaces).
+
+```python
+// Wrong
+fn example()
+ print("hello") // 2 spaces
+
+// Right
+fn example()
+ print("hello") // 4 spaces
+```
+
+#### "Tabs are not allowed (spaces only)"
+
+**Cause:** Relay does not support tabs for indentation.
+
+**Fix:** Configure your editor to use spaces instead of tabs.
+
+#### "Type error: Cannot add int and str"
+
+**Cause:** Attempting to use incompatible types in an operation.
+
+**Fix:** Use explicit type conversion:
+
+```python
+// Wrong
+x = 10 + "5"
+
+// Right
+x = 10 + int("5")
+```
+
+#### "Name error: Undefined variable 'x'"
+
+**Cause:** Using a variable before it's defined.
+
+**Fix:** Ensure variables are assigned before use:
+
+```python
+// Wrong
+print(x)
+x = 10
+
+// Right
+x = 10
+print(x)
+```
+
+#### "Runtime error: Index out of bounds"
+
+**Cause:** Accessing a list index that doesn't exist.
+
+**Fix:** Check list length before accessing:
+
+```python
+items = [1, 2, 3]
+if (len(items) > 5)
+ print(items[5])
+```
+
+### Debugging Tips
+
+1. **Use print statements:** Relay's simplest debugging tool
+```python
+fn process_data(data)
+ print("Processing:", data) // Debug output
+ result = transform(data)
+ print("Result:", result) // Debug output
+ return result
+```
+
+2. **Check async resolution:** If something seems to hang, check if you're waiting for a `Deferred` value
+```python
+// This might hang if the HTTP request never completes
+result = http.get("https://unreachable.com")
+print(result.status)
+
+// Add a timeout:
+result = timeout(http.get("https://unreachable.com"), 5000)
+```
+
+3. **Verify MongoDB connection:** Test your connection string in the MongoDB shell first
+
+4. **Check file paths:** File operations use paths relative to where you run the `relay` command
+
+5. **Inspect request objects:** Log the request object to debug handler issues
+```python
[email protected]("/debug")
+fn debug()
+ print(request)
+ return "OK"
+```
+
+### Performance Tips
+
+1. **Batch database operations:** Use `insert_many` instead of multiple `insert_one` calls
+```python
+// Slow
+for (item in items)
+ collection.insert_one(item)
+
+// Fast
+collection.insert_many(items)
+```
+
+2. **Use `spawn` for I/O-heavy tasks:** Parallelize independent operations
+```python
+// Serial: 5 seconds total
+sleep(1000, "a")
+sleep(1000, "b")
+sleep(1000, "c")
+sleep(1000, "d")
+sleep(1000, "e")
+
+// Parallel: 1 second total
+all([
+ spawn(sleep(1000, "a")),
+ spawn(sleep(1000, "b")),
+ spawn(sleep(1000, "c")),
+ spawn(sleep(1000, "d")),
+ spawn(sleep(1000, "e"))
+])
+```
+
+3. **Minimize synchronous operations:** Keep handlers fast to avoid blocking the event loop
+
+## Contributing
+
+Contributions are welcome! Here's how to get started:
+
+### Development Setup
+
+```bash
+# Clone the repo
+git clone https://github.com/patx/relay-lang.git
+cd relay-lang
+
+# Build in debug mode
+cargo build
+
+# Run tests
+cargo test
+
+# Run the sample app
+cargo run -- examples/type_hint_validation.ry
+```
+
+### Adding Features
+
+1. **Lexer changes:** Modify the `Lexer` struct and `tokenize()` method
+2. **Parser changes:** Update the `Parser` struct and AST types
+3. **Runtime changes:** Modify the `Evaluator` and `install_stdlib()` function
+4. **Testing:** Add runnable `.ry` scripts and corresponding docs snippets
+
+### Coding Standards
+
+- Follow Rust conventions and `rustfmt` formatting
+- Add comments for complex logic
+- Core interpreter remains in a single Rust file (v0.1) while Relay scripts support multi-file imports
+- Update this README for any user-facing changes
+
+### Reporting Issues
+
+Found a bug? [Open an issue](https://github.com/patx/relay-lang/issues) with:
+- Relay version
+- Operating system
+- Minimal reproducible example
+- Expected vs. actual behavior
+
+
+## Roadmap
+
+**v0.2 (Planned):**
+- [x] Multiple file support and imports
+- [x] List comprehensions
+- [x] Destructuring assignment
+- [x] Error handling with try/except
+- [x] WebSocket support
+- [X] Static file serving
+- [X] Middleware support
+
+**v0.3 (Future):**
+- [ ] Package manager
+- [ ] Standard library expansion
+- [ ] SQL database support (PostgreSQL, SQLite)
+- [ ] Redis integration
+- [ ] Interactive REPL with syntax highlighting
+- [ ] Web framework improvements (routing groups, validation)
+- [ ] Worker processes for CPU-heavy tasks
+
+**Known Gaps (Identified in v0.1):**
+- [ ] JSON request key binding to scalar handler args (e.g. bind `{"name":"Ada"}` directly to `fn create(name)`).
+- [x] HTTP client parity for `put`, `patch`, `delete`, request headers, and response header access.
+- [ ] First-class CLI flags (`--help`, `--version`) for better install verification and discoverability.
+- [ ] Built-in HTML escaping helper for safely rendering user content directly in server-side templates.
+
+
+## License
+
+MIT License
+
+Copyright (c) 2026 Harrison Erd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+## Support
+
+- **Documentation:** This README
+- **Examples:** `examples/` (start with `examples/type_hint_validation.ry` and `examples/pastebin.ry`)
+- **Issues:** [GitHub Issues](https://github.com/patx/relay-lang/issues)
+- **Discussions:** [GitHub Discussions](https://github.com/patx/relay-lang/discussions)
diff --git a/examples/email_smtp.ry b/examples/email_smtp.ry
index 02304dc..4b4d5a8 100644
--- a/examples/email_smtp.ry
+++ b/examples/email_smtp.ry
@@ -1,8 +1,8 @@
email = Email(
- host="smtp.example.com",
- username="smtp-user",
- password="smtp-pass",
- from="Relay <[email protected]>"
+ host=env.require("SMTP_HOST"),
+ username=env.require("SMTP_USERNAME"),
+ password=env.require("SMTP_PASSWORD"),
+ from=env.require("SMTP_FROM")
)
fn send_welcome(name, to)
@@ -18,5 +18,5 @@ fn send_welcome(name, to)
html=html
)
-result = send_welcome("Ada", "[email protected]")
+result = send_welcome("Ada", env.require("SMTP_TO"))
print("SMTP send result:", result)
diff --git a/examples/param_types.ry b/examples/param_types.ry
index 2f3859e..bf6075e 100644
--- a/examples/param_types.ry
+++ b/examples/param_types.ry
@@ -3,7 +3,7 @@ server = WebServer()
@app.get("/")
fn index()
- return "<h1>Relay handler binding test</h1><p>POST /with-args or /with-helpers</p>"
+ return Response("<h1>Relay handler binding test</h1><p>POST /with-args or /with-helpers</p>", content_type="text/html; charset=utf-8")
// Args are bound from query/body/json keys (path params still take highest precedence).
@app.post("/with-args")
diff --git a/examples/pastebin.ry b/examples/pastebin.ry
index 0fd23bb..2576567 100644
--- a/examples/pastebin.ry
+++ b/examples/pastebin.ry
@@ -1,7 +1,8 @@
app = WebApp()
server = WebServer()
-mongo = Mongo("mongodb://localhost:27017")
+mongo = Mongo(env.get("RELAY_MONGO_URI", "mongodb://localhost:27017"))
db = mongo.db("pastebin")
+app.sessions(db.collection("relay_sessions"))
pastes = db.collection("pastes")
fn find_paste(paste_id)
@@ -9,7 +10,7 @@ fn find_paste(paste_id)
@app.get("/")
fn index()
- return read_file("examples/static/index.html")
+ return Response(read_file("examples/static/index.html"), content_type="text/html; charset=utf-8")
@app.post("/")
fn create_paste(content = None)
@@ -25,7 +26,7 @@ fn view_paste(paste_id)
paste = find_paste(paste_id)
if (paste == None)
return Response("Not found", status=404)
- return read_file("examples/static/paste.html")
+ return Response(read_file("examples/static/paste.html"), content_type="text/html; charset=utf-8")
@app.get("/api/paste/<paste_id>")
fn get_paste(paste_id)
@@ -35,6 +36,6 @@ fn get_paste(paste_id)
content = paste["content"]
if (content == None)
return Response("Not found", status=404)
- return {"id": paste_id, "content": "{{ content }}"}
+ return {"id": paste_id, "content": content}
server.run(app)
diff --git a/examples/production_probe.ry b/examples/production_probe.ry
new file mode 100644
index 0000000..0f5546d
--- /dev/null
+++ b/examples/production_probe.ry
@@ -0,0 +1,25 @@
+app = WebApp()
+mongo = Mongo(env.get("RELAY_MONGO_URI", "mongodb://127.0.0.1:27187"))
+db = mongo.db(env.get("RELAY_MONGO_DATABASE", "relay_probe"))
+app.sessions(db.collection("sessions"))
+rows = db.collection("rows")
+
[email protected]("/plain")
+fn plain()
+ return "ok"
+
[email protected]("/json")
+fn json_response()
+ return {"ok": True, "count": 42, "items": [1, None]}
+
[email protected]("/mongo")
+fn mongo_response()
+ return {"count": rows.count({})}
+
[email protected]("/isolation/<name>")
+fn isolation(name)
+ str(sleep(1))
+ return {"name": name, "request_id": request["request_id"]}
+
+server = WebServer()
+server.run(app)
diff --git a/examples/twitter_clone/README.md b/examples/twitter_clone/README.md
new file mode 100644
index 0000000..3bfc100
--- /dev/null
+++ b/examples/twitter_clone/README.md
@@ -0,0 +1,27 @@
+# Relay Chirp
+
+A small Twitter-style example for Relay.
+
+## Run
+
+```bash
+./target/debug/relay examples/twitter_clone/app.ry
+```
+
+The example expects a local MongoDB instance at `mongodb://localhost:27017` and uses the `twitter_clone` database.
+
+## Features
+
+- Signup and login with Relay sessions
+- Short text posts
+- Public profile pages
+- Follow and unfollow actions
+- Home feed plus discover feed
+- Multi-file Relay app with a static SPA frontend
+
+## Layout
+
+- `app.ry` boots the web app and imports the route modules
+- `lib/common.ry` holds shared helpers and Mongo collection access
+- `routes/` contains page, auth, feed, and profile APIs
+- `static/` contains the browser UI
diff --git a/examples/twitter_clone/app.ry b/examples/twitter_clone/app.ry
new file mode 100644
index 0000000..1c4ae00
--- /dev/null
+++ b/examples/twitter_clone/app.ry
@@ -0,0 +1,16 @@
+import lib.common as common
+import routes.pages as pages
+import routes.auth as auth
+import routes.feed as feed
+import routes.users as users
+
+app = WebApp()
+app.sessions(common.db.collection("relay_sessions"))
+common.posts.create_index({"author_username": 1, "seq": -1})
+common.follows.create_index({"follower_username": 1, "following_username": 1}, unique=True)
+pages.register(app)
+auth.register(app)
+feed.register(app)
+users.register(app)
+server = WebServer()
+server.run(app)
diff --git a/examples/twitter_clone/lib/common.ry b/examples/twitter_clone/lib/common.ry
new file mode 100644
index 0000000..0866b25
--- /dev/null
+++ b/examples/twitter_clone/lib/common.ry
@@ -0,0 +1,217 @@
+mongo = Mongo(env.get("RELAY_MONGO_URI", "mongodb://localhost:27017"))
+db = mongo.db(env.get("RELAY_MONGO_DATABASE", "twitter_clone"))
+users = db.collection("twitter_clone_users")
+posts = db.collection("twitter_clone_posts")
+follows = db.collection("twitter_clone_follows")
+counters = db.collection("twitter_clone_counters")
+
+fn count_items(items)
+ total = 0
+ for (item in items)
+ total += 1
+ return total
+
+fn text_or_empty(value)
+ if (value == None)
+ return ""
+ return str(value)
+
+fn number_or_zero(value)
+ if (value == None)
+ return 0
+
+ try
+ return int(str(value))
+ except
+ return 0
+
+fn api_error(status, message)
+ return Response(
+ message,
+ status=status,
+ content_type="text/plain; charset=utf-8"
+ )
+
+fn request_input(name)
+ form = request["form"]
+ if (form != None)
+ value = form[name]
+ if (value != None)
+ return value
+
+ query = request["query"]
+ if (query != None)
+ value = query[name]
+ if (value != None)
+ return value
+
+ json_data = request["json"]
+ if (json_data != None)
+ value = json_data[name]
+ if (value != None)
+ return value
+
+ return None
+
+fn username_key(username)
+ return "user:" + username
+
+fn follow_key(follower_username, following_username)
+ return "follow:" + follower_username + ":" + following_username
+
+fn counter_key(name)
+ return "counter:" + name
+
+fn post_key(seq)
+ return "post:" + str(seq)
+
+fn find_user(username)
+ return users.find_one({"_id": username_key(username)})
+
+fn current_user_doc()
+ username = session["username"]
+ if (username == None)
+ return None
+
+ user = find_user(str(username))
+ if (user == None)
+ session["username"] = None
+ return None
+
+ return user
+
+fn active_viewer_username()
+ viewer = current_user_doc()
+ if (viewer == None)
+ return None
+ return str(viewer["username"])
+
+fn next_sequence(name)
+ doc = counters.find_one_and_update({"_id": counter_key(name)}, {"$inc": {"value": 1}}, upsert=True)
+ return int(doc["value"])
+
+fn is_username_char(ch)
+ return (
+ ch == "_" ||
+ ch == "a" || ch == "b" || ch == "c" || ch == "d" || ch == "e" || ch == "f" ||
+ ch == "g" || ch == "h" || ch == "i" || ch == "j" || ch == "k" || ch == "l" ||
+ ch == "m" || ch == "n" || ch == "o" || ch == "p" || ch == "q" || ch == "r" ||
+ ch == "s" || ch == "t" || ch == "u" || ch == "v" || ch == "w" || ch == "x" ||
+ ch == "y" || ch == "z" ||
+ ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" ||
+ ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9"
+ )
+
+fn is_valid_username(username)
+ size = count_items(username)
+ if (size < 3 || size > 20)
+ return False
+
+ for (ch in username)
+ if (is_username_char(ch) == False)
+ return False
+
+ return True
+
+fn is_valid_password(password)
+ size = count_items(password)
+ return (size >= 6 && size <= 72)
+
+fn is_valid_bio(bio)
+ return count_items(bio) <= 160
+
+fn is_valid_post_body(body)
+ size = count_items(body)
+ return (size >= 1 && size <= 280)
+
+fn user_brief_from_doc(user_doc)
+ return {
+ "username": text_or_empty(user_doc["username"]),
+ "bio": text_or_empty(user_doc["bio"])
+ }
+
+fn public_user_from_doc(user_doc, viewer_username = None)
+ username = text_or_empty(user_doc["username"])
+ followers_count = follows.count({"following_username": username})
+ following_count = follows.count({"follower_username": username})
+ posts_count = posts.count({"author_username": username})
+
+ viewer_is_following = False
+ if (viewer_username != None && viewer_username != username)
+ existing_follow = follows.find_one({"_id": follow_key(viewer_username, username)})
+ viewer_is_following = (existing_follow != None)
+
+ return {
+ "username": username,
+ "bio": text_or_empty(user_doc["bio"]),
+ "joined_seq": number_or_zero(user_doc["joined_seq"]),
+ "followers_count": followers_count,
+ "following_count": following_count,
+ "posts_count": posts_count,
+ "viewer_is_following": viewer_is_following,
+ "is_self": (viewer_username == username)
+ }
+
+fn serialize_post_doc(post_doc)
+ author_username = text_or_empty(post_doc["author_username"])
+ author_doc = find_user(author_username)
+
+ author = {"username": author_username, "bio": ""}
+ if (author_doc != None)
+ author = user_brief_from_doc(author_doc)
+
+ return {
+ "id": text_or_empty(post_doc["_id"]),
+ "seq": number_or_zero(post_doc["seq"]),
+ "body": text_or_empty(post_doc["body"]),
+ "author": author
+ }
+
+fn insert_post_desc(sorted_posts, post)
+ out = []
+ inserted = False
+
+ for (existing in sorted_posts)
+ if (inserted == False && post["seq"] > existing["seq"])
+ out += [post]
+ inserted = True
+ out += [existing]
+
+ if (inserted == False)
+ out += [post]
+
+ return out
+
+fn sort_posts_desc(posts_list)
+ sorted_posts = []
+ for (post in posts_list)
+ sorted_posts = insert_post_desc(sorted_posts, post)
+ return sorted_posts
+
+fn collect_posts(filter)
+ raw_posts = posts.find(filter, sort={"seq": -1}, limit=100)
+ items = []
+ for (raw_post in raw_posts)
+ try
+ items += [serialize_post_doc(raw_post)]
+ except
+ continue
+ return sort_posts_desc(items)
+
+fn discover_posts()
+ return collect_posts({})
+
+fn user_posts(username)
+ return collect_posts({"author_username": username})
+
+fn home_feed_posts(username)
+ items = user_posts(username)
+ follow_docs = follows.find({"follower_username": username})
+
+ for (follow_doc in follow_docs)
+ followed_username = text_or_empty(follow_doc["following_username"])
+ followed_posts = user_posts(followed_username)
+ for (post in followed_posts)
+ items += [post]
+
+ return sort_posts_desc(items)
diff --git a/examples/twitter_clone/routes/auth.ry b/examples/twitter_clone/routes/auth.ry
new file mode 100644
index 0000000..84d5c82
--- /dev/null
+++ b/examples/twitter_clone/routes/auth.ry
@@ -0,0 +1,81 @@
+from "../lib/common.ry" import users, posts, follows, counters, count_items, text_or_empty, number_or_zero, api_error, request_input, username_key, follow_key, counter_key, post_key, find_user, current_user_doc, active_viewer_username, next_sequence, is_username_char, is_valid_username, is_valid_password, is_valid_bio, is_valid_post_body, user_brief_from_doc, public_user_from_doc, serialize_post_doc, insert_post_desc, sort_posts_desc, collect_posts, discover_posts, user_posts, home_feed_posts
+
+fn register(app)
+ @app.get("/api/me")
+ fn me()
+ user = current_user_doc()
+ if (user == None)
+ return app.json({"authenticated": False, "csrf_token": session.csrf_token()})
+
+ username = text_or_empty(user["username"])
+ return app.json({
+ "authenticated": True,
+ "csrf_token": session.csrf_token(),
+ "user": public_user_from_doc(user, username)
+ })
+
+ @app.post("/api/auth/signup")
+ fn signup()
+ username = text_or_empty(request_input("username"))
+ password = text_or_empty(request_input("password"))
+ bio = text_or_empty(request_input("bio"))
+
+ if (username == "" || password == "")
+ return api_error(400, "Username and password are required")
+
+ if (is_valid_username(username) == False)
+ return api_error(400, "Username must be 3-20 chars using lowercase letters, digits, or underscores")
+
+ if (is_valid_password(password) == False)
+ return api_error(400, "Password must be between 6 and 72 characters")
+
+ if (is_valid_bio(bio) == False)
+ return api_error(400, "Bio must be 160 characters or fewer")
+
+ if (find_user(username) != None)
+ return api_error(409, "That username is already taken")
+
+ joined_seq = next_sequence("users")
+ created = users.insert_one({
+ "_id": username_key(username),
+ "username": username,
+ "password_hash": auth_hash_password(password),
+ "bio": bio,
+ "joined_seq": joined_seq
+ })
+ str(created["inserted_id"])
+
+ session.regenerate()
+ session["username"] = username
+ return app.json({
+ "ok": True,
+ "user": public_user_from_doc(find_user(username), username)
+ })
+
+ @app.post("/api/auth/login")
+ fn login()
+ username = text_or_empty(request_input("username"))
+ password = text_or_empty(request_input("password"))
+
+ if (username == "" || password == "")
+ return api_error(400, "Username and password are required")
+
+ user = find_user(username)
+
+ if (user == None)
+ return api_error(401, "Invalid username or password")
+
+ if (auth_verify_password(password, text_or_empty(user["password_hash"])) == False)
+ return api_error(401, "Invalid username or password")
+
+ session.regenerate()
+ session["username"] = username
+ return app.json({
+ "ok": True,
+ "user": public_user_from_doc(user, username)
+ })
+
+ @app.post("/api/auth/logout")
+ fn logout()
+ session.destroy()
+ return app.json({"ok": True})
diff --git a/examples/twitter_clone/routes/feed.ry b/examples/twitter_clone/routes/feed.ry
new file mode 100644
index 0000000..4ddbc3e
--- /dev/null
+++ b/examples/twitter_clone/routes/feed.ry
@@ -0,0 +1,53 @@
+from "../lib/common.ry" import users, posts, follows, counters, count_items, text_or_empty, number_or_zero, api_error, request_input, username_key, follow_key, counter_key, post_key, find_user, current_user_doc, active_viewer_username, next_sequence, is_username_char, is_valid_username, is_valid_password, is_valid_bio, is_valid_post_body, user_brief_from_doc, public_user_from_doc, serialize_post_doc, insert_post_desc, sort_posts_desc, collect_posts, discover_posts, user_posts, home_feed_posts
+
+fn register(app)
+ @app.get("/api/feed/home")
+ fn home_feed()
+ user = current_user_doc()
+ if (user == None)
+ return api_error(401, "Log in to view your home feed")
+
+ username = text_or_empty(user["username"])
+ return app.json({
+ "feed": "home",
+ "items": home_feed_posts(username)
+ })
+
+ @app.get("/api/feed/discover")
+ fn discover_feed()
+ return app.json({
+ "feed": "discover",
+ "items": discover_posts()
+ })
+
+ @app.post("/api/posts")
+ fn create_post()
+ user = current_user_doc()
+ if (user == None)
+ return api_error(401, "Log in to post")
+
+ body = text_or_empty(request_input("body"))
+ if (is_valid_post_body(body) == False)
+ return api_error(400, "Post body must be between 1 and 280 characters")
+
+ username = text_or_empty(user["username"])
+ seq = next_sequence("posts")
+ post_id = post_key(seq)
+
+ created = posts.insert_one({
+ "_id": post_id,
+ "seq": seq,
+ "author_username": username,
+ "body": body
+ })
+ str(created["inserted_id"])
+
+ return app.json({
+ "ok": True,
+ "post": {
+ "id": post_id,
+ "seq": seq,
+ "body": body,
+ "author": user_brief_from_doc(user)
+ }
+ })
diff --git a/examples/twitter_clone/routes/pages.ry b/examples/twitter_clone/routes/pages.ry
new file mode 100644
index 0000000..54c4f4d
--- /dev/null
+++ b/examples/twitter_clone/routes/pages.ry
@@ -0,0 +1,40 @@
+fn register(app)
+ fn read_asset(primary_path, secondary_path)
+ try
+ return str(read_file(primary_path))
+ except
+ return str(read_file(secondary_path))
+
+ fn app_shell()
+ return read_asset(
+ "examples/twitter_clone/static/index.html",
+ "static/index.html"
+ )
+
+ @app.get("/")
+ fn index()
+ return Response(app_shell(), content_type="text/html; charset=utf-8")
+
+ @app.get("/u/<username>")
+ fn profile_page(username)
+ return Response(app_shell(), content_type="text/html; charset=utf-8")
+
+ @app.get("/static/app.js")
+ fn app_js()
+ return Response(
+ read_asset(
+ "examples/twitter_clone/static/app.js",
+ "static/app.js"
+ ),
+ content_type="application/javascript"
+ )
+
+ @app.get("/static/styles.css")
+ fn styles_css()
+ return Response(
+ read_asset(
+ "examples/twitter_clone/static/styles.css",
+ "static/styles.css"
+ ),
+ content_type="text/css"
+ )
diff --git a/examples/twitter_clone/routes/users.ry b/examples/twitter_clone/routes/users.ry
new file mode 100644
index 0000000..7cc0015
--- /dev/null
+++ b/examples/twitter_clone/routes/users.ry
@@ -0,0 +1,73 @@
+from "../lib/common.ry" import users, posts, follows, counters, count_items, text_or_empty, number_or_zero, api_error, request_input, username_key, follow_key, counter_key, post_key, find_user, current_user_doc, active_viewer_username, next_sequence, is_username_char, is_valid_username, is_valid_password, is_valid_bio, is_valid_post_body, user_brief_from_doc, public_user_from_doc, serialize_post_doc, insert_post_desc, sort_posts_desc, collect_posts, discover_posts, user_posts, home_feed_posts
+
+fn register(app)
+ @app.get("/api/users/<username>")
+ fn get_profile(username)
+ user = find_user(username)
+ if (user == None)
+ return api_error(404, "User not found")
+
+ viewer_username = active_viewer_username()
+ return app.json({
+ "profile": public_user_from_doc(user, viewer_username),
+ "posts": user_posts(username)
+ })
+
+ @app.post("/api/users/<username>/follow")
+ fn follow_user(username)
+ user = current_user_doc()
+ if (user == None)
+ return api_error(401, "Log in to follow users")
+
+ viewer_username = text_or_empty(user["username"])
+ if (viewer_username == username)
+ return api_error(400, "You cannot follow yourself")
+
+ target_user = find_user(username)
+ if (target_user == None)
+ return api_error(404, "User not found")
+
+ relation_id = follow_key(viewer_username, username)
+ existing = follows.find_one({"_id": relation_id})
+ if (existing != None)
+ return api_error(409, "You already follow that user")
+
+ created = follows.insert_one({
+ "_id": relation_id,
+ "follower_username": viewer_username,
+ "following_username": username,
+ "created_seq": next_sequence("follows")
+ })
+ str(created["inserted_id"])
+
+ return app.json({
+ "ok": True,
+ "profile": public_user_from_doc(target_user, viewer_username)
+ })
+
+ @app.post("/api/users/<username>/unfollow")
+ fn unfollow_user(username)
+ user = current_user_doc()
+ if (user == None)
+ return api_error(401, "Log in to unfollow users")
+
+ viewer_username = text_or_empty(user["username"])
+ if (viewer_username == username)
+ return api_error(400, "You cannot unfollow yourself")
+
+ target_user = find_user(username)
+ if (target_user == None)
+ return api_error(404, "User not found")
+
+ relation_id = follow_key(viewer_username, username)
+ existing = follows.find_one({"_id": relation_id})
+ if (existing == None)
+ return api_error(404, "You do not follow that user")
+
+ deleted = follows.delete_one({"_id": relation_id})
+ number_or_zero(deleted)
+
+ return app.json({
+ "ok": True,
+ "profile": public_user_from_doc(target_user, viewer_username)
+ })
diff --git a/examples/twitter_clone/static/app.js b/examples/twitter_clone/static/app.js
new file mode 100644
index 0000000..bb78fca
--- /dev/null
+++ b/examples/twitter_clone/static/app.js
@@ -0,0 +1,615 @@
+const state = {
+ csrfToken: null,
+ authMode: "signup",
+ currentFeed: "discover",
+ error: "",
+ feedItems: [],
+ loading: true,
+ me: null,
+ profile: null,
+ profilePosts: [],
+ route: parseRoute(window.location.pathname),
+ viewToken: 0,
+};
+
+const sidebarEl = document.getElementById("sidebar");
+const contentEl = document.getElementById("content");
+const sessionPillEl = document.getElementById("session-pill");
+
+function parseRoute(pathname) {
+ const match = pathname.match(/^\/u\/([^/]+)$/);
+ if (match) {
+ return { name: "profile", username: decodeURIComponent(match[1]) };
+ }
+ return { name: "home" };
+}
+
+function escapeHtml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function profilePath(username) {
+ return `/u/${encodeURIComponent(username)}`;
+}
+
+async function fetchJson(url, options = {}) {
+ const request = { ...options };
+ request.headers = { ...(request.headers || {}) };
+ if (state.csrfToken && !["GET", "HEAD"].includes((request.method || "GET").toUpperCase())) {
+ request.headers["x-csrf-token"] = state.csrfToken;
+ }
+
+ if (request.body && typeof request.body !== "string") {
+ request.headers["content-type"] = "application/json";
+ request.body = JSON.stringify(request.body);
+ }
+
+ const response = await fetch(url, request);
+ const text = await response.text();
+ let data = {};
+
+ if (text) {
+ try {
+ data = JSON.parse(text);
+ } catch (_error) {
+ data = { raw: text };
+ }
+ }
+
+ if (!response.ok) {
+ const error = data.error || {};
+ const message = error.message || data.raw || `Request failed with ${response.status}`;
+ const problem = new Error(message);
+ problem.code = error.code || "request_failed";
+ problem.status = response.status;
+ problem.details = error.details;
+ throw problem;
+ }
+
+ return data;
+}
+
+async function refreshSession() {
+ const data = await fetchJson("/api/me");
+ state.csrfToken = data.csrf_token;
+ state.me = data.authenticated ? data.user : null;
[Diff truncated. Use the raw view or local clone for the full content.]