@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,237 @@
1
+ ---
2
+ id: path-codec
3
+ title: "PathCodec"
4
+ ---
5
+
6
+ `PathCodec[A]` is a composable descriptor for URL path structures. It holds a tree of segment codecs connected by concatenation and fallback nodes, and provides bidirectional path conversion: `PathCodec#decode` extracts a typed value from a `Path`, returning `Either[String, A]` (the typed value or error), and `PathCodec#format` formats a typed value back to a `Path`, returning `Either[String, Path]` (the path or error). In addition to its runtime value type `A`, each codec also carries a phantom `PathVars` track that records the ordered list of declared path-variable markers contributed by its dynamic segments. Its definition begins:
7
+
8
+ ```scala
9
+ sealed trait PathCodec[A] {
10
+ type PathVars
11
+ }
12
+ ```
13
+
14
+ `PathVars` is purely type-level: it has zero runtime footprint and does not affect decoding or formatting. It exists so downstream tooling (for example, handler macros or static checks) can recover which named path variables a route declared, in order, and whether any of them were explicitly marked as ignored.
15
+
16
+ ## Motivation
17
+
18
+ URL paths need both matching and generation. A routing library that only matches paths requires a separate URL-builder for links and redirects, leading to duplication and drift. `PathCodec` is bidirectional: every codec that can decode `/users/42` into `42: Int` can also format `42` back into `/users/42`. This makes it safe to use the same path definition for routing, link generation, and OpenAPI path parameter documentation.
19
+
20
+ ## Structure
21
+
22
+ `PathCodec` is an ADT with four node types:
23
+
24
+ | Node | Meaning |
25
+ | ----------- | -------------------------------------------------------------- |
26
+ | `Segment` | A single path segment, described by a `SegmentCodec[A]` |
27
+ | `Concat` | Two path codecs composed sequentially with `/` or `++` |
28
+ | `Transform` | Bidirectional type mapping over an existing codec |
29
+ | `Fallback` | Two literal alternatives (applies only with `orElse`) |
30
+
31
+ ## Construction
32
+
33
+ We build a `PathCodec` from smart constructors that produce typed segment nodes, from a path string, or by wrapping a `SegmentCodec` directly.
34
+
35
+ ### Predefined segment constructors
36
+
37
+ The most common path building blocks are available as smart constructors on the companion:
38
+
39
+ ```scala
40
+ import zio.blocks.endpoint._
41
+ import zio.blocks.endpoint.RoutePattern._
42
+
43
+ val literalUsers: PathCodec[Unit] = PathCodec.literal("users")
44
+ val intId: PathCodec[Int] = PathCodec.int("id")
45
+ val longId: PathCodec[Long] = PathCodec.long("id")
46
+ val stringSlug: PathCodec[String] = PathCodec.string("slug")
47
+ val uuidId: PathCodec[java.util.UUID] = PathCodec.uuid("id")
48
+ val boolFlag: PathCodec[Boolean] = PathCodec.bool("enabled")
49
+ val rest: PathCodec[zio.http.Path] = PathCodec.trailing
50
+ ```
51
+
52
+ `PathCodec.literal` is a macro that validates the value at compile time — it rejects empty strings and strings containing `/` or characters requiring URL encoding.
53
+
54
+ For literal names like `PathCodec.int("id")`, the name is preserved as a singleton type inside `PathVars`, so the phantom track remembers not just that the codec captures an `Int`, but that it came from the path variable named `"id"`.
55
+
56
+ ### From a string
57
+
58
+ To build a codec from a slash-separated string of literal segments, use `PathCodec.apply`:
59
+
60
+ ```scala
61
+ import zio.blocks.endpoint._
62
+ import zio.blocks.endpoint.RoutePattern._
63
+
64
+ val path: PathCodec[Unit] = PathCodec("/api/v1/users")
65
+ ```
66
+
67
+ This is equivalent to concatenating `PathCodec.literal` for each segment.
68
+
69
+ ### From a `SegmentCodec`
70
+
71
+ To wrap a custom `SegmentCodec[A]` into a `PathCodec[A]`, use `PathCodec.apply(segment)`:
72
+
73
+ ```scala
74
+ import zio.blocks.endpoint._
75
+ import zio.blocks.endpoint.RoutePattern._
76
+
77
+ val combined = PathCodec(SegmentCodec.literal("v") ~ SegmentCodec.int("version"))
78
+ ```
79
+
80
+ There is also an implicit conversion from `SegmentCodec[A]` to `PathCodec[A]` and from `String` to `PathCodec[Unit]`, so both can appear directly in `/` expressions.
81
+
82
+ ## Phantom `PathVars` Track and `.unused`
83
+
84
+ Every dynamic path segment contributes one phantom marker to `PathCodec#PathVars`:
85
+
86
+ - `PathCodec.int("id")` contributes `PathVar["id", Int]`
87
+ - `PathCodec.uuid("orderId")` contributes `PathVar["orderId", UUID]`
88
+ - literal segments and `PathCodec.trailing` contribute no markers
89
+
90
+ Sequential composition with `/` or `++` concatenates those markers in declaration order, matching the left-to-right route shape.
91
+
92
+ Sometimes a route needs to capture a segment for matching or formatting, but a downstream handler intentionally does not consume that variable. For that case, single-variable codecs expose `.unused`, which keeps the runtime behavior identical while relabeling the phantom marker to `PathVar.Ignored[Name, Type]`:
93
+
94
+ ```scala
95
+ import zio.blocks.endpoint._
96
+ import zio.blocks.endpoint.RoutePattern._
97
+
98
+ val userId: PathCodec[Int] = PathCodec.int("id")
99
+ val ignoredUserId: PathCodec[Int] = PathCodec.int("id").unused
100
+ ```
101
+
102
+ `.unused` has zero runtime cost: decoding, formatting, rendering, and matching all behave exactly the same as the non-`.unused` codec. The only difference is the phantom `PathVars` marker, which tells tooling that this declared path variable was intentionally ignored.
103
+
104
+ ## Composition
105
+
106
+ Path codecs compose in two ways: sequential concatenation with `/` or `++`, and literal alternatives with `orElse`.
107
+
108
+ ### Sequential composition with `/` and `++`
109
+
110
+ `/` and `++` are equivalent: both concatenate two path codecs. The result type is flattened automatically (so `Unit / Int` gives `Int`, not `(Unit, Int)`), eliminating `Unit` components:
111
+
112
+ ```scala
113
+ import zio.blocks.endpoint._
114
+ import zio.blocks.endpoint.RoutePattern._
115
+
116
+ val route: PathCodec[Int] = PathCodec.literal("users") / PathCodec.int("id")
117
+ ```
118
+
119
+ In the context of a `RoutePattern`, the same operator works directly:
120
+
121
+ ```scala
122
+ import zio.blocks.endpoint._
123
+ import zio.blocks.endpoint.RoutePattern._
124
+ import zio.http.Method
125
+
126
+ val pattern = Method.GET / "users" / PathCodec.int("id") / "posts"
127
+ ```
128
+
129
+ ### Literal alternatives with `orElse`
130
+
131
+ To match either of two literal segments, use `orElse`:
132
+
133
+ ```scala
134
+ import zio.blocks.endpoint._
135
+ import zio.blocks.endpoint.RoutePattern._
136
+
137
+ val either: PathCodec[Unit] =
138
+ PathCodec.literal("users").orElse(PathCodec.literal("members"))
139
+ ```
140
+
141
+ `orElse` is for literal alternatives only. Both branches must be `PathCodec[Unit]` with no captured path variables, and `PathCodec#alternatives` still validates at runtime that the branches are genuinely literal-only. In practice, use `orElse` only with `PathCodec.literal(...)` or string-literal path codecs.
142
+
143
+ ## Decoding and Formatting
144
+
145
+ `PathCodec` is bidirectional: `PathCodec#decode` turns a runtime `Path` into a typed value, and `PathCodec#format` turns a typed value back into a `Path`.
146
+
147
+ ### `PathCodec#decode`
148
+
149
+ To extract a typed value from a runtime `Path`:
150
+
151
+ ```scala
152
+ import zio.blocks.endpoint._
153
+ import zio.blocks.endpoint.RoutePattern._
154
+ import zio.http.Path
155
+
156
+ val codec = PathCodec.int("id")
157
+
158
+ val result: Either[String, Int] = codec.decode(Path("/42"))
159
+ ```
160
+
161
+ `PathCodec#decode` returns `Left(message)` when no segment matches or a segment cannot be parsed.
162
+
163
+ ### `PathCodec#format`
164
+
165
+ To turn a typed value into a `Path`:
166
+
167
+ ```scala
168
+ import zio.blocks.endpoint._
169
+ import zio.blocks.endpoint.RoutePattern._
170
+ import zio.http.Path
171
+
172
+ val codec = PathCodec.uuid("id")
173
+
174
+ val path: Either[String, Path] =
175
+ codec.format(java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))
176
+ ```
177
+
178
+ ### `PathCodec#matches`
179
+
180
+ To test whether a `Path` matches without extracting a value:
181
+
182
+ ```scala
183
+ import zio.blocks.endpoint._
184
+ import zio.blocks.endpoint.RoutePattern._
185
+ import zio.http.Path
186
+
187
+ val codec = PathCodec.literal("users")
188
+ val matched = codec.matches(Path("/users"))
189
+ ```
190
+
191
+ ## Type Transformations
192
+
193
+ Use these methods to map the typed value that `PathCodec` decodes or encodes without changing the underlying path structure.
194
+
195
+ ### `PathCodec#transform`
196
+
197
+ To map the decoded value to a different type without changing the path structure, use `PathCodec#transform`. Both directions must be total:
198
+
199
+ ```scala
200
+ import zio.blocks.endpoint._
201
+ import zio.blocks.endpoint.RoutePattern._
202
+ import zio.blocks.endpoint.PathCodec._
203
+
204
+ final case class UserId(value: Int)
205
+
206
+ val userIdCodec: PathCodec[UserId] =
207
+ PathCodec.int("id").transform[UserId](UserId(_), _.value)
208
+ ```
209
+
210
+ ### `PathCodec#transformOrFail`
211
+
212
+ When decoding or encoding can fail, use `PathCodec#transformOrFail`. A `Left` from the decode function causes the path not to match:
213
+
214
+ ```scala
215
+ import zio.blocks.endpoint._
216
+ import zio.blocks.endpoint.RoutePattern._
217
+
218
+ val nonNegativeInt: PathCodec[Int] =
219
+ PathCodec.int("count").transformOrFail[Int](
220
+ n => if (n >= 0) Right(n) else Left(s"Expected non-negative, got $n"),
221
+ n => Right(n)
222
+ )
223
+ ```
224
+
225
+ ## Rendering
226
+
227
+ `PathCodec#render` produces a human-readable path string. Dynamic segments appear as `{name}` by default:
228
+
229
+ ```scala
230
+ import zio.blocks.endpoint._
231
+ import zio.blocks.endpoint.RoutePattern._
232
+
233
+ val codec = PathCodec.literal("users") / PathCodec.int("id")
234
+ val rendered: String = codec.render
235
+ ```
236
+
237
+ To use a different prefix/suffix for dynamic segments (e.g., `:id` for Express-style paths), call the lower-level `PathCodec.render(codec, prefix = ":", suffix = "")`.
@@ -0,0 +1,196 @@
1
+ ---
2
+ id: route-pattern
3
+ title: "RoutePattern"
4
+ ---
5
+
6
+ `RoutePattern[A]` pairs an HTTP method with a typed path pattern. It is the primary routing descriptor in `zio-blocks-endpoint`: every `Endpoint` carries a `RoutePattern` that determines which HTTP method and URL path it matches. Like `PathCodec`, it also carries a phantom `PathVars` track that mirrors the ordered path-variable declarations contributed by its path codec. Its shape is:
7
+
8
+ ```scala
9
+ final case class RoutePattern[A](
10
+ method: Method,
11
+ pathCodec: PathCodec[A],
12
+ doc: Doc = Doc.empty
13
+ )
14
+ ```
15
+
16
+ The `PathVars` track is not a constructor parameter because it is purely type-level: it has zero runtime footprint and is derived from `pathCodec`. A literal-only route contributes no path-variable markers; a route with dynamic segments preserves them in declaration order.
17
+
18
+ ## Motivation
19
+
20
+ HTTP routing requires matching both a method (GET, POST, …) and a path (`/users/42`). `RoutePattern` holds both in a single typed value. The type parameter `A` is the type of values extracted from the dynamic path segments — `Unit` for fully-literal paths, `Int` for a single integer segment, `(String, UUID)` for two dynamic segments, and so on.
21
+
22
+ Using a typed route pattern means route construction and path extraction are verified at compile time: the type of the extracted path value is always consistent with the path codec definition. The phantom `PathVars` track keeps the declared path-variable names and ignored-variable markers available to tooling without changing runtime behavior.
23
+
24
+ ## Construction
25
+
26
+ Several construction forms exist. The method-first syntax is the most common and most readable; the others cover less typical use cases.
27
+
28
+ ### Method-first syntax (recommended)
29
+
30
+ The primary syntax uses the `Method` extension method `/` to produce a `RoutePattern` directly:
31
+
32
+ ```scala
33
+ import zio.blocks.endpoint._
34
+ import zio.blocks.endpoint.RoutePattern._
35
+ import zio.http.Method
36
+
37
+ val getUsers = Method.GET / "users"
38
+ val postUser = Method.POST / "users"
39
+ val deleteOrder = Method.DELETE / "orders" / PathCodec.uuid("orderId")
40
+ ```
41
+
42
+ This is the recommended construction style: it reads like the route itself and keeps the method close to the path.
43
+
44
+ ### Constant constructors
45
+
46
+ Pre-built method-only patterns are available as constants on the `RoutePattern` companion:
47
+
48
+ ```scala
49
+ import zio.blocks.endpoint._
50
+ import zio.blocks.endpoint.RoutePattern._
51
+
52
+ val get = RoutePattern.GET
53
+ val post = RoutePattern.POST
54
+ val put = RoutePattern.PUT
55
+ val delete = RoutePattern.DELETE
56
+ val patch = RoutePattern.PATCH
57
+ val head = RoutePattern.HEAD
58
+ val options = RoutePattern.OPTIONS
59
+ ```
60
+
61
+ These are equivalent to `RoutePattern(Method.GET)` etc., with an empty path codec. Additional constants like `CONNECT` and `TRACE` are also available for the complete set of HTTP methods.
62
+
63
+ ### From a `Path` value
64
+
65
+ To construct a pattern from a runtime `Path` (all literal segments), use `RoutePattern.apply(method, path)`:
66
+
67
+ ```scala
68
+ import zio.blocks.endpoint._
69
+ import zio.blocks.endpoint.RoutePattern._
70
+ import zio.http.{Method, Path}
71
+
72
+ val route = RoutePattern(Method.GET, Path("/users/active"))
73
+ ```
74
+
75
+ ### Catch-all trailing patterns
76
+
77
+ To match any path suffix, use `RoutePattern.any`:
78
+
79
+ ```scala
80
+ import zio.blocks.endpoint._
81
+ import zio.blocks.endpoint.RoutePattern._
82
+ import zio.http.{Method, Path}
83
+
84
+ val catchAll: RoutePattern[Path] = RoutePattern.any
85
+ val getAny: RoutePattern[Path] = RoutePattern.any(Method.GET)
86
+ ```
87
+
88
+ These helpers preserve `PathVars = SegmentCodec.NoPathVars`: a trailing catch-all captures a `zio.http.Path` runtime value, but it does not declare any named path variables.
89
+
90
+ ## Path Composition with `/`
91
+
92
+ To append additional `PathCodec` segments to a `RoutePattern`, use the `/` method:
93
+
94
+ ```scala
95
+ import zio.blocks.endpoint._
96
+ import zio.blocks.endpoint.RoutePattern._
97
+ import zio.http.Method
98
+
99
+ val route = Method.GET / "users" / PathCodec.int("id") / "posts"
100
+ ```
101
+
102
+ Each `/` call produces a new `RoutePattern` with a widened type. The type is automatically flattened, eliminating `Unit` components: `Unit / Int / Unit` becomes `Int`, not `((Unit, Int), Unit)`. At the same time, the phantom `PathVars` track is concatenated left-to-right, so a route like `Method.GET / PathCodec.int("id") / PathCodec.string("slug")` preserves the declaration order of `"id"` then `"slug"` for downstream tooling.
103
+
104
+ ## Decoding and Encoding
105
+
106
+ `RoutePattern` is bidirectional: `RoutePattern#decode` validates a live request against the pattern, and `RoutePattern#encode` rebuilds a request pair from a typed path value.
107
+
108
+ ### `RoutePattern#decode`
109
+
110
+ To check whether a method and path match, and extract the typed path value:
111
+
112
+ ```scala
113
+ import zio.blocks.endpoint._
114
+ import zio.blocks.endpoint.RoutePattern._
115
+ import zio.http.{Method, Path}
116
+
117
+ val route = Method.GET / "users" / PathCodec.int("id")
118
+
119
+ val result: Either[String, Int] = route.decode(Method.GET, Path("/users/42"))
120
+ ```
121
+
122
+ `RoutePattern#decode` returns `Left` if the method does not match or any segment fails to parse, and `Right(value)` with the extracted path value otherwise. HEAD requests automatically fall back to GET for compatibility.
123
+
124
+ ### `RoutePattern#encode`
125
+
126
+ To turn a typed value back into a `(Method, Path)` pair:
127
+
128
+ ```scala
129
+ import zio.blocks.endpoint._
130
+ import zio.blocks.endpoint.RoutePattern._
131
+ import zio.http.{Method, Path}
132
+
133
+ val route = Method.POST / "orders" / PathCodec.uuid("orderId")
134
+
135
+ val result: Either[String, (Method, Path)] = route.encode(java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))
136
+ ```
137
+
138
+ ### `RoutePattern#matches`
139
+
140
+ To test membership without extracting the value:
141
+
142
+ ```scala
143
+ import zio.blocks.endpoint._
144
+ import zio.blocks.endpoint.RoutePattern._
145
+ import zio.http.{Method, Path}
146
+
147
+ val route = Method.GET / "users"
148
+ val matches = route.matches(Method.GET, Path("/users"))
149
+ ```
150
+
151
+ ## Structural Operations
152
+
153
+ Three structural operations transform an existing `RoutePattern` without building a new one from scratch: `RoutePattern#alternatives`, `RoutePattern#nest`, and `RoutePattern#render`.
154
+
155
+ ### Alternatives
156
+
157
+ `RoutePattern#alternatives` expands `Method.ANY` and `Method.Methods` into a flat list of single-method patterns. `RouteTree` calls this before inserting into the trie:
158
+
159
+ ```scala
160
+ import zio.blocks.endpoint._
161
+ import zio.blocks.endpoint.RoutePattern._
162
+ import zio.http.Method
163
+
164
+ val anyGet: RoutePattern[?] = RoutePattern.any(Method.GET)
165
+ val expanded = anyGet.alternatives
166
+ ```
167
+
168
+ ### Nesting
169
+
170
+ `RoutePattern#nest` prepends a literal path prefix without modifying the route's type or dynamic segments:
171
+
172
+ ```scala
173
+ import zio.blocks.endpoint._
174
+ import zio.blocks.endpoint.RoutePattern._
175
+ import zio.http.Method
176
+
177
+ val route = Method.GET / "users" / PathCodec.int("id")
178
+ val versioned = route.nest(PathCodec("/api/v1"))
179
+ ```
180
+
181
+ This is useful for adding a version prefix to a group of existing routes without rewriting each one.
182
+
183
+ ### Rendering
184
+
185
+ `RoutePattern#render` produces a human-readable string representation, useful for logging and OpenAPI path generation:
186
+
187
+ ```scala
188
+ import zio.blocks.endpoint._
189
+ import zio.blocks.endpoint.RoutePattern._
190
+ import zio.http.Method
191
+
192
+ val route = Method.GET / "users" / PathCodec.int("id")
193
+ val rendered: String = route.render
194
+ ```
195
+
196
+ Dynamic segments are rendered as `{name}` by default, matching OpenAPI path parameter convention.
@@ -0,0 +1,111 @@
1
+ ---
2
+ id: route-tree
3
+ title: "RouteTree"
4
+ ---
5
+
6
+ `RouteTree[A]` is a routing trie keyed by HTTP method and path. It maps `(Method, Path)` pairs to values of type `A`, performing prefix-tree lookup with segment-priority ordering. Server-side interpreters primarily use it to build efficient route dispatch tables from a collection of `RoutePattern` values. Its structure is:
7
+
8
+ ```scala
9
+ final case class RouteTree[A](
10
+ roots: Map[Method, SegmentSubtree[A]]
11
+ )
12
+ ```
13
+
14
+ `SegmentSubtree[A]` is a single level of the trie, holding literal-keyed branches and priority-ordered dynamic segment branches:
15
+
16
+ ```scala
17
+ final case class SegmentSubtree[A](
18
+ literals: Map[String, SegmentSubtree[A]],
19
+ others: ListMap[SegmentCodec.Key, (SegmentCodec[_], SegmentSubtree[A])],
20
+ value: Option[A]
21
+ )
22
+ ```
23
+
24
+ ## Motivation
25
+
26
+ Routing a `(Method, Path)` pair to a handler requires checking many patterns efficiently. A flat scan through all routes is O(n) and scales poorly. `RouteTree` uses a prefix trie keyed on path segments, so matching is O(depth) — proportional to the number of segments in the path, not the number of registered routes.
27
+
28
+ Within each trie level, literal segments are stored in a `Map[String, ...]` for O(1) lookup, while dynamic segments are stored in a `ListMap` ordered by match priority. The priority order (literal → int → long → uuid → bool → string → combined → trailing) ensures that more specific segments win ambiguous matches.
29
+
30
+ ## Building a `RouteTree`
31
+
32
+ Start from an empty tree and add patterns with `RouteTree#add`:
33
+
34
+ ```scala
35
+ import zio.blocks.endpoint._
36
+ import zio.blocks.endpoint.RoutePattern._
37
+ import zio.http.{Method, Path}
38
+
39
+ val tree = RouteTree.empty[String]
40
+ .add(Method.GET / "users", "list-users")
41
+ .add(Method.GET / "users" / PathCodec.int("id"), "get-user")
42
+ .add(Method.POST / "users", "create-user")
43
+ ```
44
+
45
+ `RouteTree#add` calls `RoutePattern#alternatives` internally to expand `Method.ANY` and `Method.Methods` into individual method entries before inserting into the trie.
46
+
47
+ ## Lookup
48
+
49
+ `RouteTree#get` looks up a `(Method, Path)` pair and returns the associated value if a match is found:
50
+
51
+ ```scala
52
+ import zio.blocks.endpoint._
53
+ import zio.blocks.endpoint.RoutePattern._
54
+ import zio.http.{Method, Path}
55
+
56
+ val tree = RouteTree.empty[String]
57
+ .add(Method.GET / "users" / PathCodec.int("id"), "get-user")
58
+
59
+ val result: Option[String] = tree.get(Method.GET, Path("/users/42"))
60
+ ```
61
+
62
+ HEAD requests automatically fall back to the GET subtree if no HEAD handler is registered, matching the HTTP specification.
63
+
64
+ ## Merging
65
+
66
+ `RouteTree#merge` combines two trees. On conflicts, the right-hand-side value takes precedence:
67
+
68
+ ```scala
69
+ import zio.blocks.endpoint._
70
+ import zio.blocks.endpoint.RoutePattern._
71
+ import zio.http.Method
72
+
73
+ val treeA = RouteTree.empty[String].add(Method.GET / "users", "users-v1")
74
+ val treeB = RouteTree.empty[String].add(Method.GET / "users", "users-v2")
75
+
76
+ val merged = treeA.merge(treeB)
77
+ // GET /users → "users-v2"
78
+ ```
79
+
80
+ ## Mapping
81
+
82
+ `RouteTree#map` transforms all values in the trie while preserving its structure:
83
+
84
+ ```scala
85
+ import zio.blocks.endpoint._
86
+ import zio.blocks.endpoint.RoutePattern._
87
+ import zio.http.Method
88
+
89
+ val tree: RouteTree[String] = RouteTree.empty[String]
90
+ .add(Method.GET / "users", "get-users")
91
+
92
+ val lengths: RouteTree[Int] = tree.map(_.length)
93
+ ```
94
+
95
+ ## Match Priority
96
+
97
+ Within a single trie level, `SegmentSubtree` tries literal branches first, then dynamic branches in priority order. Given a path like `/users/active`:
98
+
99
+ 1. `literals.get("active")` is tried first — if an exact literal route for `"active"` exists, it matches.
100
+ 2. Otherwise, dynamic segment codecs are tried in order: `Int` (fails, `"active"` is not numeric), `Long` (fails), `UUID` (fails), `Bool` (fails), `String` (succeeds).
101
+
102
+ This means `/users/42` matches an `Int` route even if a `String` route is also registered, because integers are higher priority than strings.
103
+
104
+ ## `SegmentSubtree`
105
+
106
+ `SegmentSubtree` is the internal per-level trie node. It is not typically constructed directly — `RouteTree#add` builds it internally. The two key fields are:
107
+
108
+ - **`literals`**: a `Map[String, SegmentSubtree[A]]` for O(1) exact-match lookups.
109
+ - **`others`**: a `ListMap[SegmentCodec.Key, (SegmentCodec[_], SegmentSubtree[A])]` ordered by priority for dynamic segment matching.
110
+
111
+ The `value: Option[A]` field holds the registered value when a complete path terminates at this node. Trailing segments have special handling: if a `Trailing` codec is registered and the current index is past the end of the path segments, the lookup returns its subtree value.