@zio.dev/zio-blocks 0.0.33 → 0.0.55

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 (215) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,225 @@
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). Its definition begins:
7
+
8
+ ```scala
9
+ sealed trait PathCodec[A]
10
+ ```
11
+
12
+ ## Motivation
13
+
14
+ 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.
15
+
16
+ ## Structure
17
+
18
+ `PathCodec` is an ADT with four node types:
19
+
20
+ | Node | Meaning |
21
+ | ----------- | -------------------------------------------------------------- |
22
+ | `Segment` | A single path segment, described by a `SegmentCodec[A]` |
23
+ | `Concat` | Two path codecs composed sequentially with `/` or `++` |
24
+ | `Transform` | Bidirectional type mapping over an existing codec |
25
+ | `Fallback` | Two literal alternatives (applies only with `orElse`) |
26
+
27
+ ## Construction
28
+
29
+ We build a `PathCodec` from smart constructors that produce typed segment nodes, from a path string, or by wrapping a `SegmentCodec` directly.
30
+
31
+ ### Predefined segment constructors
32
+
33
+ The most common path building blocks are available as smart constructors on the companion:
34
+
35
+ ```scala
36
+ import zio.blocks.endpoint._
37
+ import zio.blocks.endpoint.RoutePattern._
38
+
39
+ val literalUsers: PathCodec[Unit] = PathCodec.literal("users")
40
+ val intId: PathCodec[Int] = PathCodec.int("id")
41
+ val longId: PathCodec[Long] = PathCodec.long("id")
42
+ val stringSlug: PathCodec[String] = PathCodec.string("slug")
43
+ val uuidId: PathCodec[java.util.UUID] = PathCodec.uuid("id")
44
+ val boolFlag: PathCodec[Boolean] = PathCodec.bool("enabled")
45
+ val rest: PathCodec[zio.http.Path] = PathCodec.trailing
46
+ ```
47
+
48
+ `PathCodec.literal` validates the value — it rejects empty strings and strings containing `/` or characters requiring URL encoding.
49
+
50
+ ### From a string
51
+
52
+ To build a codec from a slash-separated string of literal segments, use `PathCodec.apply`:
53
+
54
+ ```scala
55
+ import zio.blocks.endpoint._
56
+ import zio.blocks.endpoint.RoutePattern._
57
+
58
+ val path: PathCodec[Unit] = PathCodec("/api/v1/users")
59
+ ```
60
+
61
+ This is equivalent to concatenating `PathCodec.literal` for each segment.
62
+
63
+ ### From a `SegmentCodec`
64
+
65
+ To wrap a custom `SegmentCodec[A]` into a `PathCodec[A]`, use `PathCodec.apply(segment)`:
66
+
67
+ ```scala
68
+ import zio.blocks.endpoint._
69
+ import zio.blocks.endpoint.RoutePattern._
70
+
71
+ val combined = PathCodec(SegmentCodec.literal("v") ~ SegmentCodec.int("version"))
72
+ ```
73
+
74
+ 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.
75
+
76
+ ## Marking a capture as unused with `.unused`
77
+
78
+ Sometimes a route needs to capture a segment for matching or formatting, but a downstream handler intentionally does not consume that variable. `PathCodec` instances expose `.unused`, which converts the value type to `Unit` while keeping the same path shape:
79
+
80
+ ```scala
81
+ import zio.blocks.endpoint._
82
+ import zio.blocks.endpoint.RoutePattern._
83
+
84
+ val userId: PathCodec[Int] = PathCodec.int("id")
85
+ val ignoredUserId: PathCodec[Unit] = PathCodec.int("id").unused
86
+ ```
87
+
88
+ `.unused` converts the path codec's value type to `Unit`: the route still matches the same shape and renders `{name}` placeholders, but decoding yields `Unit` instead of the captured value, so no handler parameter is required. `format` on an unused path fails because there is no value to encode back into the ignored segment.
89
+
90
+ `PathCodec.unused` composes normally with `/` and is unaffected by `transform` / `transformOrFail` lifting.
91
+
92
+ ## Composition
93
+
94
+ Path codecs compose in two ways: sequential concatenation with `/` or `++`, and literal alternatives with `orElse`.
95
+
96
+ ### Sequential composition with `/` and `++`
97
+
98
+ `/` 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:
99
+
100
+ ```scala
101
+ import zio.blocks.endpoint._
102
+ import zio.blocks.endpoint.RoutePattern._
103
+
104
+ val route: PathCodec[Int] = PathCodec.literal("users") / PathCodec.int("id")
105
+ ```
106
+
107
+ In the context of a `RoutePattern`, the same operator works directly:
108
+
109
+ ```scala
110
+ import zio.blocks.endpoint._
111
+ import zio.blocks.endpoint.RoutePattern._
112
+ import zio.http.Method
113
+
114
+ val pattern = Method.GET / "users" / PathCodec.int("id") / "posts"
115
+ ```
116
+
117
+ ### Literal alternatives with `orElse`
118
+
119
+ To match either of two literal segments, use `orElse`:
120
+
121
+ ```scala
122
+ import zio.blocks.endpoint._
123
+ import zio.blocks.endpoint.RoutePattern._
124
+
125
+ val either: PathCodec[Unit] =
126
+ PathCodec.literal("users").orElse(PathCodec.literal("members"))
127
+ ```
128
+
129
+ `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.
130
+
131
+ ## Decoding and Formatting
132
+
133
+ `PathCodec` is bidirectional: `PathCodec#decode` turns a runtime `Path` into a typed value, and `PathCodec#format` turns a typed value back into a `Path`.
134
+
135
+ ### `PathCodec#decode`
136
+
137
+ To extract a typed value from a runtime `Path`:
138
+
139
+ ```scala
140
+ import zio.blocks.endpoint._
141
+ import zio.blocks.endpoint.RoutePattern._
142
+ import zio.http.Path
143
+
144
+ val codec = PathCodec.int("id")
145
+
146
+ val result: Either[String, Int] = codec.decode(Path("/42"))
147
+ ```
148
+
149
+ `PathCodec#decode` returns `Left(message)` when no segment matches or a segment cannot be parsed.
150
+
151
+ ### `PathCodec#format`
152
+
153
+ To turn a typed value into a `Path`:
154
+
155
+ ```scala
156
+ import zio.blocks.endpoint._
157
+ import zio.blocks.endpoint.RoutePattern._
158
+ import zio.http.Path
159
+
160
+ val codec = PathCodec.uuid("id")
161
+
162
+ val path: Either[String, Path] =
163
+ codec.format(java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))
164
+ ```
165
+
166
+ ### `PathCodec#matches`
167
+
168
+ To test whether a `Path` matches without extracting a value:
169
+
170
+ ```scala
171
+ import zio.blocks.endpoint._
172
+ import zio.blocks.endpoint.RoutePattern._
173
+ import zio.http.Path
174
+
175
+ val codec = PathCodec.literal("users")
176
+ val matched = codec.matches(Path("/users"))
177
+ ```
178
+
179
+ ## Type Transformations
180
+
181
+ Use these methods to map the typed value that `PathCodec` decodes or encodes without changing the underlying path structure.
182
+
183
+ ### `PathCodec#transform`
184
+
185
+ To map the decoded value to a different type without changing the path structure, use `PathCodec#transform`. Both directions must be total:
186
+
187
+ ```scala
188
+ import zio.blocks.endpoint._
189
+ import zio.blocks.endpoint.RoutePattern._
190
+ import zio.blocks.endpoint.PathCodec._
191
+
192
+ final case class UserId(value: Int)
193
+
194
+ val userIdCodec: PathCodec[UserId] =
195
+ PathCodec.int("id").transform(UserId(_), _.value)
196
+ ```
197
+
198
+ ### `PathCodec#transformOrFail`
199
+
200
+ When decoding or encoding can fail, use `PathCodec#transformOrFail`. A `Left` from the decode function causes the path not to match:
201
+
202
+ ```scala
203
+ import zio.blocks.endpoint._
204
+ import zio.blocks.endpoint.RoutePattern._
205
+
206
+ val nonNegativeInt: PathCodec[Int] =
207
+ PathCodec.int("count").transformOrFail(
208
+ n => if (n >= 0) Right(n) else Left(s"Expected non-negative, got $n"),
209
+ (n: Int) => Right(n)
210
+ )
211
+ ```
212
+
213
+ ## Rendering
214
+
215
+ `PathCodec#render` produces a human-readable path string. Dynamic segments appear as `{name}` by default:
216
+
217
+ ```scala
218
+ import zio.blocks.endpoint._
219
+ import zio.blocks.endpoint.RoutePattern._
220
+
221
+ val codec = PathCodec.literal("users") / PathCodec.int("id")
222
+ val rendered: String = codec.render
223
+ ```
224
+
225
+ 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,194 @@
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. 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
+ ## Motivation
17
+
18
+ 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.
19
+
20
+ 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.
21
+
22
+ ## Construction
23
+
24
+ Several construction forms exist. The method-first syntax is the most common and most readable; the others cover less typical use cases.
25
+
26
+ ### Method-first syntax (recommended)
27
+
28
+ The primary syntax uses the `Method` extension method `/` to produce a `RoutePattern` directly:
29
+
30
+ ```scala
31
+ import zio.blocks.endpoint._
32
+ import zio.blocks.endpoint.RoutePattern._
33
+ import zio.http.Method
34
+
35
+ val getUsers = Method.GET / "users"
36
+ val postUser = Method.POST / "users"
37
+ val deleteOrder = Method.DELETE / "orders" / PathCodec.uuid("orderId")
38
+ ```
39
+
40
+ This is the recommended construction style: it reads like the route itself and keeps the method close to the path.
41
+
42
+ ### Constant constructors
43
+
44
+ Pre-built method-only patterns are available as constants on the `RoutePattern` companion:
45
+
46
+ ```scala
47
+ import zio.blocks.endpoint._
48
+ import zio.blocks.endpoint.RoutePattern._
49
+
50
+ val get = RoutePattern.GET
51
+ val post = RoutePattern.POST
52
+ val put = RoutePattern.PUT
53
+ val delete = RoutePattern.DELETE
54
+ val patch = RoutePattern.PATCH
55
+ val head = RoutePattern.HEAD
56
+ val options = RoutePattern.OPTIONS
57
+ ```
58
+
59
+ 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.
60
+
61
+ ### From a `Path` value
62
+
63
+ To construct a pattern from a runtime `Path` (all literal segments), use `RoutePattern.apply(method, path)`:
64
+
65
+ ```scala
66
+ import zio.blocks.endpoint._
67
+ import zio.blocks.endpoint.RoutePattern._
68
+ import zio.http.{Method, Path}
69
+
70
+ val route = RoutePattern(Method.GET, Path("/users/active"))
71
+ ```
72
+
73
+ ### Catch-all trailing patterns
74
+
75
+ To match any path suffix, use `RoutePattern.any`:
76
+
77
+ ```scala
78
+ import zio.blocks.endpoint._
79
+ import zio.blocks.endpoint.RoutePattern._
80
+ import zio.http.{Method, Path}
81
+
82
+ val catchAll: RoutePattern[Path] = RoutePattern.any
83
+ val getAny: RoutePattern[Path] = RoutePattern.any(Method.GET)
84
+ ```
85
+
86
+ A trailing catch-all captures a `zio.http.Path` runtime value without declaring any named path variables.
87
+
88
+ ## Path Composition with `/`
89
+
90
+ To append additional `PathCodec` segments to a `RoutePattern`, use the `/` method:
91
+
92
+ ```scala
93
+ import zio.blocks.endpoint._
94
+ import zio.blocks.endpoint.RoutePattern._
95
+ import zio.http.Method
96
+
97
+ val route = Method.GET / "users" / PathCodec.int("id") / "posts"
98
+ ```
99
+
100
+ 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)`.
101
+
102
+ ## Decoding and Encoding
103
+
104
+ `RoutePattern` is bidirectional: `RoutePattern#decode` validates a live request against the pattern, and `RoutePattern#encode` rebuilds a request pair from a typed path value.
105
+
106
+ ### `RoutePattern#decode`
107
+
108
+ To check whether a method and path match, and extract the typed path value:
109
+
110
+ ```scala
111
+ import zio.blocks.endpoint._
112
+ import zio.blocks.endpoint.RoutePattern._
113
+ import zio.http.{Method, Path}
114
+
115
+ val route = Method.GET / "users" / PathCodec.int("id")
116
+
117
+ val result: Either[String, Int] = route.decode(Method.GET, Path("/users/42"))
118
+ ```
119
+
120
+ `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.
121
+
122
+ ### `RoutePattern#encode`
123
+
124
+ To turn a typed value back into a `(Method, Path)` pair:
125
+
126
+ ```scala
127
+ import zio.blocks.endpoint._
128
+ import zio.blocks.endpoint.RoutePattern._
129
+ import zio.http.{Method, Path}
130
+
131
+ val route = Method.POST / "orders" / PathCodec.uuid("orderId")
132
+
133
+ val result: Either[String, (Method, Path)] = route.encode(java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))
134
+ ```
135
+
136
+ ### `RoutePattern#matches`
137
+
138
+ To test membership without extracting the value:
139
+
140
+ ```scala
141
+ import zio.blocks.endpoint._
142
+ import zio.blocks.endpoint.RoutePattern._
143
+ import zio.http.{Method, Path}
144
+
145
+ val route = Method.GET / "users"
146
+ val matches = route.matches(Method.GET, Path("/users"))
147
+ ```
148
+
149
+ ## Structural Operations
150
+
151
+ Three structural operations transform an existing `RoutePattern` without building a new one from scratch: `RoutePattern#alternatives`, `RoutePattern#nest`, and `RoutePattern#render`.
152
+
153
+ ### Alternatives
154
+
155
+ `RoutePattern#alternatives` expands `Method.ANY` and `Method.Methods` into a flat list of single-method patterns. `RouteTree` calls this before inserting into the trie:
156
+
157
+ ```scala
158
+ import zio.blocks.endpoint._
159
+ import zio.blocks.endpoint.RoutePattern._
160
+ import zio.http.Method
161
+
162
+ val anyGet: RoutePattern[?] = RoutePattern.any(Method.GET)
163
+ val expanded = anyGet.alternatives
164
+ ```
165
+
166
+ ### Nesting
167
+
168
+ `RoutePattern#nest` prepends a literal path prefix without modifying the route's type or dynamic segments:
169
+
170
+ ```scala
171
+ import zio.blocks.endpoint._
172
+ import zio.blocks.endpoint.RoutePattern._
173
+ import zio.http.Method
174
+
175
+ val route = Method.GET / "users" / PathCodec.int("id")
176
+ val versioned = route.nest(PathCodec("/api/v1"))
177
+ ```
178
+
179
+ This is useful for adding a version prefix to a group of existing routes without rewriting each one.
180
+
181
+ ### Rendering
182
+
183
+ `RoutePattern#render` produces a human-readable string representation, useful for logging and OpenAPI path generation:
184
+
185
+ ```scala
186
+ import zio.blocks.endpoint._
187
+ import zio.blocks.endpoint.RoutePattern._
188
+ import zio.http.Method
189
+
190
+ val route = Method.GET / "users" / PathCodec.int("id")
191
+ val rendered: String = route.render
192
+ ```
193
+
194
+ 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.