@zio.dev/zio-blocks 0.0.32 → 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.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +293 -51
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +3 -3
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +34 -192
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +5 -5
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +3 -3
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +13 -1
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +2922 -583
- package/sidebars.js +238 -43
- package/superpowers/plans/2026-03-19-docs-critique-subagent.md +407 -0
- package/superpowers/specs/2026-03-19-docs-critique-subagent-design.md +222 -0
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -18,7 +18,7 @@ Schema evolution is the process of changing data structures over time while keep
|
|
|
18
18
|
|
|
19
19
|
## `Into[A, B]` — One-Way Conversion
|
|
20
20
|
|
|
21
|
-
[`Into[A, B]`](
|
|
21
|
+
[`Into[A, B]`](into.md) converts a value of type `A` to `Either[SchemaError, B]`. It is the right choice whenever the migration is asymmetric — for example, when adding a field with a default value, removing a field, or transforming data in a way that cannot be reversed.
|
|
22
22
|
|
|
23
23
|
Typical use cases:
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ Typical use cases:
|
|
|
28
28
|
|
|
29
29
|
## `As[A, B]` — Bidirectional Round-Trip
|
|
30
30
|
|
|
31
|
-
[`As[A, B]`](
|
|
31
|
+
[`As[A, B]`](as.md) extends `Into[A, B]` with a `from(b: B): Either[SchemaError, A]` reverse direction. It guarantees that `A → B → A` restores the original value (within the constraints of numeric precision and optional fields). Use `As` when both sides of the conversion must remain in sync.
|
|
32
32
|
|
|
33
33
|
Typical use cases:
|
|
34
34
|
|
|
@@ -69,13 +69,13 @@ Compare this to a manual implementation:
|
|
|
69
69
|
`Into` is part of the `zio-blocks-schema` module:
|
|
70
70
|
|
|
71
71
|
```scala
|
|
72
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
72
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
For Scala.js:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -915,7 +915,7 @@ backToMap
|
|
|
915
915
|
|
|
916
916
|
## Related Type: `As[A, B]`
|
|
917
917
|
|
|
918
|
-
`As[A, B]` extends `Into[A, B]` with a reverse direction, enabling round-trip safe bidirectional conversions. Because `As` must guarantee that `A → B → A` restores the original value, it applies stricter derivation constraints than `Into`. See [As](
|
|
918
|
+
`As[A, B]` extends `Into[A, B]` with a reverse direction, enabling round-trip safe bidirectional conversions. Because `As` must guarantee that `A → B → A` restores the original value, it applies stricter derivation constraints than `Into`. See [As](as.md) for the full reference.
|
|
919
919
|
|
|
920
920
|
## Best Practices
|
|
921
921
|
|
|
@@ -3,23 +3,29 @@ id: schema-expr
|
|
|
3
3
|
title: "SchemaExpr"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`SchemaExpr[A,
|
|
6
|
+
`SchemaExpr[A, B]` is a **schema-aware expression** that computes a result of type `B` from an input value of type `A`. It is invariant in both type parameters. The input type `A` must be fully described by a [`Schema`](./schema.md), and the expression is built from [optics](./optics.md), literal values, and operators. The fundamental operations are `eval` and `evalDynamic`.
|
|
7
|
+
|
|
8
|
+
At runtime, `SchemaExpr` is a typed wrapper around `DynamicSchemaExpr`. The typed layer carries the input and output schemas, while the dynamic layer stores the serializable AST. This split is why you will sometimes see both `.dynamic` and `DynamicSchemaExpr` in advanced examples: `SchemaExpr` is the public typed API, and `DynamicSchemaExpr` is the untyped transport/runtime form underneath it.
|
|
7
9
|
|
|
8
10
|
`SchemaExpr`:
|
|
9
11
|
- represents expressions as a reified AST, enabling introspection and serialization
|
|
10
12
|
- supports relational (`===`, `>`, `<`, `>=`, `<=`, `!=`), logical (`&&`, `||`, `!`), arithmetic (`+`, `-`, `*`), and string (`concat`, `matches`, `length`) operations
|
|
11
13
|
- evaluates to `Either[OpticCheck, Seq[B]]`, handling failures and multi-valued results from traversals
|
|
12
|
-
-
|
|
14
|
+
- preserves both input and output schemas at the type level
|
|
13
15
|
|
|
14
16
|
```scala
|
|
15
|
-
|
|
17
|
+
final case class SchemaExpr[A, B](
|
|
18
|
+
dynamic: DynamicSchemaExpr,
|
|
19
|
+
inputSchema: Schema[A],
|
|
20
|
+
outputSchema: Schema[B]
|
|
21
|
+
) {
|
|
16
22
|
def eval(input: A): Either[OpticCheck, Seq[B]]
|
|
17
23
|
def evalDynamic(input: A): Either[OpticCheck, Seq[DynamicValue]]
|
|
18
24
|
}
|
|
19
25
|
```
|
|
20
26
|
|
|
21
27
|
:::tip
|
|
22
|
-
For practical walkthroughs of building with `SchemaExpr`, see [Query DSL Part 1: Expressions](
|
|
28
|
+
For practical walkthroughs of building with `SchemaExpr`, see [Query DSL Part 1: Expressions](../../guides/query-dsl-reified-optics.md), [Part 2: SQL Generation](../../guides/query-dsl-sql.md), and [Part 3: Extending the Expression Language](../../guides/query-dsl-extending.md).
|
|
23
29
|
:::
|
|
24
30
|
|
|
25
31
|
## Motivation
|
|
@@ -70,13 +76,13 @@ val result: Either[OpticCheck, Seq[Boolean]] = combined.eval(alice)
|
|
|
70
76
|
## Installation
|
|
71
77
|
|
|
72
78
|
```scala
|
|
73
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
79
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
|
|
74
80
|
```
|
|
75
81
|
|
|
76
82
|
For cross-platform (Scala.js):
|
|
77
83
|
|
|
78
84
|
```scala
|
|
79
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
85
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
|
|
80
86
|
```
|
|
81
87
|
|
|
82
88
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -85,6 +91,12 @@ Supported Scala versions: 2.13.x and 3.x.
|
|
|
85
91
|
|
|
86
92
|
`SchemaExpr` instances are typically created through **operator syntax on optics** rather than by constructing AST nodes directly. Each operator on `Optic[S, A]` returns a `SchemaExpr[S, B]`.
|
|
87
93
|
|
|
94
|
+
There are three common construction styles:
|
|
95
|
+
|
|
96
|
+
- optic/operator syntax such as `Person.age >= 18`
|
|
97
|
+
- literal expressions such as `SchemaExpr.literal[Person, Int](18)`
|
|
98
|
+
- advanced direct construction when you intentionally need to work with the underlying dynamic AST
|
|
99
|
+
|
|
88
100
|
### Via Relational Operators on Optics
|
|
89
101
|
|
|
90
102
|
The comparison operators `===`, `>`, `>=`, `<`, `<=`, and `!=` on `Optic[S, A]` create `SchemaExpr.Relational` nodes. Each operator has two overloads — one comparing against a literal value, and one comparing against another optic:
|
|
@@ -204,7 +216,7 @@ val adultOrAlice: SchemaExpr[Person, Boolean] = isAdult || isAlice
|
|
|
204
216
|
|
|
205
217
|
### Via Direct AST Construction
|
|
206
218
|
|
|
207
|
-
For advanced use cases,
|
|
219
|
+
For advanced use cases, you can construct `SchemaExpr` values directly. In most application code, prefer optic/operator syntax or `SchemaExpr.literal` because they preserve the typed surface and are easier to read.
|
|
208
220
|
|
|
209
221
|
```scala
|
|
210
222
|
import zio.blocks.schema._
|
|
@@ -217,16 +229,20 @@ object Item extends CompanionOptics[Item] {
|
|
|
217
229
|
val price: Lens[Item, Int] = $(_.price)
|
|
218
230
|
}
|
|
219
231
|
|
|
220
|
-
// Construct
|
|
221
|
-
val lit: SchemaExpr[Item, Int] =
|
|
222
|
-
val opticExpr: SchemaExpr[Item, Int] =
|
|
223
|
-
val comparison: SchemaExpr[Item, Boolean] =
|
|
232
|
+
// Construct via factory methods
|
|
233
|
+
val lit: SchemaExpr[Item, Int] = SchemaExpr.literal[Item, Int](42)
|
|
234
|
+
val opticExpr: SchemaExpr[Item, Int] = SchemaExpr.optic[Item, Int](Item.price.toDynamic, Item.schema)
|
|
235
|
+
val comparison: SchemaExpr[Item, Boolean] = SchemaExpr.relational(
|
|
224
236
|
opticExpr,
|
|
225
237
|
lit,
|
|
226
238
|
SchemaExpr.RelationalOperator.GreaterThan
|
|
227
239
|
)
|
|
228
240
|
```
|
|
229
241
|
|
|
242
|
+
`SchemaExpr.literal` is the normal way to inject constants into an expression tree. It produces a typed `SchemaExpr[S, A]`, while storing the value internally as a `DynamicSchemaExpr.Literal`. That is usually what you want in user code, including migration builders.
|
|
243
|
+
|
|
244
|
+
If you need the raw dynamic form for serialization, transport, or custom interpreters, use `.dynamic` on an existing `SchemaExpr` rather than constructing `DynamicSchemaExpr` directly unless you are working on expression internals.
|
|
245
|
+
|
|
230
246
|
## Core Operations
|
|
231
247
|
|
|
232
248
|
### Evaluation
|
|
@@ -236,7 +252,7 @@ val comparison: SchemaExpr[Item, Boolean] = new SchemaExpr.Relational(
|
|
|
236
252
|
Evaluates the expression against an input value, returning the typed result. The result is a `Seq[B]` because traversal-based expressions can produce multiple values.
|
|
237
253
|
|
|
238
254
|
```scala
|
|
239
|
-
trait
|
|
255
|
+
trait SchemaExprLike[A, B] {
|
|
240
256
|
def eval(input: A): Either[OpticCheck, Seq[B]]
|
|
241
257
|
}
|
|
242
258
|
```
|
|
@@ -272,7 +288,7 @@ When an expression wraps a `Traversal` optic, `eval` returns multiple values —
|
|
|
272
288
|
Like `eval`, but converts the result to [`DynamicValue`](./dynamic-value.md) instances. This is useful for serialization or when working with schema-agnostic code.
|
|
273
289
|
|
|
274
290
|
```scala
|
|
275
|
-
trait
|
|
291
|
+
trait SchemaExprLike[A, B] {
|
|
276
292
|
def evalDynamic(input: A): Either[OpticCheck, Seq[DynamicValue]]
|
|
277
293
|
}
|
|
278
294
|
```
|
|
@@ -290,7 +306,7 @@ object Person extends CompanionOptics[Person] {
|
|
|
290
306
|
val name: Lens[Person, String] = $(_.name)
|
|
291
307
|
}
|
|
292
308
|
|
|
293
|
-
val nameExpr =
|
|
309
|
+
val nameExpr = SchemaExpr.optic[Person, String](Person.name.toDynamic, Person.schema)
|
|
294
310
|
val result = nameExpr.evalDynamic(Person("Alice", 30))
|
|
295
311
|
// Right(List(DynamicValue.Primitive(PrimitiveValue.String("Alice"))))
|
|
296
312
|
```
|
|
@@ -301,9 +317,13 @@ val result = nameExpr.evalDynamic(Person("Alice", 30))
|
|
|
301
317
|
|
|
302
318
|
Combines two boolean-typed expressions with logical AND. Both operands must produce `Boolean` results.
|
|
303
319
|
|
|
320
|
+
`&&` and `||` are provided via `SchemaExpr.BooleanOps`, an implicit class in the `SchemaExpr` companion, rather than as direct methods on `SchemaExpr`. This lets downstream libraries supply their own `&&`/`||` overloads (e.g. returning a DDB- or SQL-specific expression type) by bringing in their own implicit class via an explicit import, which takes priority over the companion-scope implicit in Scala 2 and 3.
|
|
321
|
+
|
|
304
322
|
```scala
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
// In SchemaExpr companion:
|
|
324
|
+
implicit final class BooleanOps[A, B](val self: SchemaExpr[A, B]) extends AnyVal {
|
|
325
|
+
def and(that: SchemaExpr[A, Boolean])(implicit ev: B <:< Boolean): SchemaExpr[A, Boolean]
|
|
326
|
+
def &&(that: SchemaExpr[A, Boolean])(implicit ev: B <:< Boolean): SchemaExpr[A, Boolean]
|
|
307
327
|
}
|
|
308
328
|
```
|
|
309
329
|
|
|
@@ -331,8 +351,10 @@ val result = isAdultAlice.eval(Person("Alice", 30))
|
|
|
331
351
|
Combines two boolean-typed expressions with logical OR.
|
|
332
352
|
|
|
333
353
|
```scala
|
|
334
|
-
|
|
335
|
-
|
|
354
|
+
// In SchemaExpr companion:
|
|
355
|
+
implicit final class BooleanOps[A, B](val self: SchemaExpr[A, B]) extends AnyVal {
|
|
356
|
+
def or(that: SchemaExpr[A, Boolean])(implicit ev: B <:< Boolean): SchemaExpr[A, Boolean]
|
|
357
|
+
def ||(that: SchemaExpr[A, Boolean])(implicit ev: B <:< Boolean): SchemaExpr[A, Boolean]
|
|
336
358
|
}
|
|
337
359
|
```
|
|
338
360
|
|
|
@@ -355,50 +377,43 @@ val result = isAdultOrAlice.eval(Person("Alice", 12))
|
|
|
355
377
|
// Right(List(true)) — Alice, even though not adult
|
|
356
378
|
```
|
|
357
379
|
|
|
358
|
-
##
|
|
380
|
+
## Structure
|
|
359
381
|
|
|
360
|
-
`SchemaExpr` is a
|
|
382
|
+
`SchemaExpr` is a typed wrapper. The actual expression AST lives in `DynamicSchemaExpr`, which is a sealed trait of serializable expression nodes. `SchemaExpr` adds the input and output schemas needed to convert between typed values and [`DynamicValue`](./dynamic-value.md).
|
|
361
383
|
|
|
362
|
-
###
|
|
384
|
+
### `DynamicSchemaExpr` leaf nodes
|
|
363
385
|
|
|
364
|
-
#### `
|
|
386
|
+
#### `DynamicSchemaExpr.Literal`
|
|
365
387
|
|
|
366
|
-
A constant value
|
|
388
|
+
A constant value represented directly as a `DynamicValue`.
|
|
367
389
|
|
|
368
390
|
```scala
|
|
369
|
-
object
|
|
370
|
-
case class Literal
|
|
391
|
+
object DynamicSchemaExpr {
|
|
392
|
+
final case class Literal(value: DynamicValue, schema: Schema[_]) extends DynamicSchemaExpr
|
|
371
393
|
}
|
|
372
394
|
```
|
|
373
395
|
|
|
374
|
-
|
|
396
|
+
#### `DynamicSchemaExpr.Select`
|
|
375
397
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
Wraps an [`Optic[A, B]`](./optics.md) to extract values from the input. The behavior depends on the optic type:
|
|
398
|
+
Selects values from the input using a [`DynamicOptic`](./dynamic-optic.md).
|
|
379
399
|
|
|
380
400
|
```scala
|
|
381
|
-
object
|
|
382
|
-
case class
|
|
401
|
+
object DynamicSchemaExpr {
|
|
402
|
+
final case class Select(path: DynamicOptic) extends DynamicSchemaExpr
|
|
383
403
|
}
|
|
384
404
|
```
|
|
385
405
|
|
|
386
|
-
|
|
387
|
-
|-------------|---------------------------------------------------------------------------------------|
|
|
388
|
-
| `Lens` | Always succeeds with a single value |
|
|
389
|
-
| `Prism` | Succeeds if the input matches the expected case; otherwise returns `Left(OpticCheck)` |
|
|
390
|
-
| `Optional` | Succeeds if the value is present; otherwise returns `Left(OpticCheck)` |
|
|
391
|
-
| `Traversal` | Returns all elements; returns `Left(OpticCheck)` if the collection is empty |
|
|
406
|
+
`SchemaExpr.optic(...)` and optic operator syntax eventually produce `DynamicSchemaExpr.Select` nodes.
|
|
392
407
|
|
|
393
|
-
### Unary
|
|
408
|
+
### Unary operations
|
|
394
409
|
|
|
395
|
-
#### `
|
|
410
|
+
#### `DynamicSchemaExpr.Not`
|
|
396
411
|
|
|
397
412
|
Negates a boolean expression.
|
|
398
413
|
|
|
399
414
|
```scala
|
|
400
|
-
object
|
|
401
|
-
case class Not
|
|
415
|
+
object DynamicSchemaExpr {
|
|
416
|
+
final case class Not(expr: DynamicSchemaExpr) extends DynamicSchemaExpr
|
|
402
417
|
}
|
|
403
418
|
```
|
|
404
419
|
|
|
@@ -421,21 +436,21 @@ val result = inactive.eval(User(active = true))
|
|
|
421
436
|
// Right(List(false))
|
|
422
437
|
```
|
|
423
438
|
|
|
424
|
-
### Binary
|
|
439
|
+
### Binary operations
|
|
425
440
|
|
|
426
|
-
`Relational`, `Logical`, `Arithmetic`,
|
|
441
|
+
`Relational`, `Logical`, `Arithmetic`, `Bitwise`, and the string binary operations are all represented as `DynamicSchemaExpr` nodes holding child expressions.
|
|
427
442
|
|
|
428
|
-
#### `
|
|
443
|
+
#### `DynamicSchemaExpr.Relational`
|
|
429
444
|
|
|
430
445
|
Compares two expressions using a `RelationalOperator`. Returns a boolean result.
|
|
431
446
|
|
|
432
447
|
```scala
|
|
433
|
-
object
|
|
434
|
-
case class Relational
|
|
435
|
-
left:
|
|
436
|
-
right:
|
|
448
|
+
object DynamicSchemaExpr {
|
|
449
|
+
final case class Relational(
|
|
450
|
+
left: DynamicSchemaExpr,
|
|
451
|
+
right: DynamicSchemaExpr,
|
|
437
452
|
operator: RelationalOperator
|
|
438
|
-
) extends
|
|
453
|
+
) extends DynamicSchemaExpr
|
|
439
454
|
}
|
|
440
455
|
```
|
|
441
456
|
|
|
@@ -454,17 +469,17 @@ The available `RelationalOperator` values are:
|
|
|
454
469
|
Equality and inequality (`===`, `!=`) compare values directly. Ordering operators (`<`, `<=`, `>`, `>=`) compare via `DynamicValue` ordering internally.
|
|
455
470
|
:::
|
|
456
471
|
|
|
457
|
-
#### `
|
|
472
|
+
#### `DynamicSchemaExpr.Logical`
|
|
458
473
|
|
|
459
474
|
Combines two boolean expressions with a `LogicalOperator`.
|
|
460
475
|
|
|
461
476
|
```scala
|
|
462
|
-
object
|
|
463
|
-
case class Logical
|
|
464
|
-
left:
|
|
465
|
-
right:
|
|
477
|
+
object DynamicSchemaExpr {
|
|
478
|
+
final case class Logical(
|
|
479
|
+
left: DynamicSchemaExpr,
|
|
480
|
+
right: DynamicSchemaExpr,
|
|
466
481
|
operator: LogicalOperator
|
|
467
|
-
) extends
|
|
482
|
+
) extends DynamicSchemaExpr
|
|
468
483
|
}
|
|
469
484
|
```
|
|
470
485
|
|
|
@@ -475,18 +490,18 @@ The available `LogicalOperator` values are:
|
|
|
475
490
|
| `And` | `&&` | Logical conjunction |
|
|
476
491
|
| `Or` | `\|\|` | Logical disjunction |
|
|
477
492
|
|
|
478
|
-
#### `
|
|
493
|
+
#### `DynamicSchemaExpr.Arithmetic`
|
|
479
494
|
|
|
480
495
|
Performs arithmetic on two numeric expressions using an `ArithmeticOperator`. Requires an `IsNumeric[A]` type class instance.
|
|
481
496
|
|
|
482
497
|
```scala
|
|
483
|
-
object
|
|
484
|
-
case class Arithmetic
|
|
485
|
-
left:
|
|
486
|
-
right:
|
|
498
|
+
object DynamicSchemaExpr {
|
|
499
|
+
final case class Arithmetic(
|
|
500
|
+
left: DynamicSchemaExpr,
|
|
501
|
+
right: DynamicSchemaExpr,
|
|
487
502
|
operator: ArithmeticOperator,
|
|
488
|
-
|
|
489
|
-
) extends
|
|
503
|
+
numericType: NumericTypeTag
|
|
504
|
+
) extends DynamicSchemaExpr
|
|
490
505
|
}
|
|
491
506
|
```
|
|
492
507
|
|
|
@@ -498,112 +513,29 @@ The available `ArithmeticOperator` values are:
|
|
|
498
513
|
| `Subtract` | `-` | Subtraction |
|
|
499
514
|
| `Multiply` | `*` | Multiplication |
|
|
500
515
|
|
|
501
|
-
Supported numeric types
|
|
502
|
-
|
|
503
|
-
#### `SchemaExpr.StringConcat`
|
|
504
|
-
|
|
505
|
-
Concatenates two string expressions.
|
|
506
|
-
|
|
507
|
-
```scala
|
|
508
|
-
object SchemaExpr {
|
|
509
|
-
case class StringConcat[A](
|
|
510
|
-
left: SchemaExpr[A, String],
|
|
511
|
-
right: SchemaExpr[A, String]
|
|
512
|
-
) extends SchemaExpr[A, String]
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
Created via the `concat` method on string optics:
|
|
517
|
-
|
|
518
|
-
```scala
|
|
519
|
-
import zio.blocks.schema._
|
|
520
|
-
|
|
521
|
-
case class Greeting(prefix: String)
|
|
522
|
-
|
|
523
|
-
object Greeting extends CompanionOptics[Greeting] {
|
|
524
|
-
implicit val schema: Schema[Greeting] = Schema.derived
|
|
525
|
-
|
|
526
|
-
val prefix: Lens[Greeting, String] = $(_.prefix)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
val withName = Greeting.prefix.concat(", World!")
|
|
530
|
-
val result = withName.eval(Greeting("Hello"))
|
|
531
|
-
// Right(List("Hello, World!"))
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
### Other Operations
|
|
535
|
-
|
|
536
|
-
`StringRegexMatch` and `StringLength` extend `SchemaExpr` directly rather than through `UnaryOp` or `BinaryOp`.
|
|
537
|
-
|
|
538
|
-
#### `SchemaExpr.StringRegexMatch`
|
|
539
|
-
|
|
540
|
-
Tests whether a string matches a regular expression pattern. Despite having two operands (`regex` and `string`), it extends `SchemaExpr[A, Boolean]` directly.
|
|
541
|
-
|
|
542
|
-
```scala
|
|
543
|
-
object SchemaExpr {
|
|
544
|
-
case class StringRegexMatch[A](
|
|
545
|
-
regex: SchemaExpr[A, String],
|
|
546
|
-
string: SchemaExpr[A, String]
|
|
547
|
-
) extends SchemaExpr[A, Boolean]
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
Created via the `matches` method on string optics:
|
|
552
|
-
|
|
553
|
-
```scala
|
|
554
|
-
import zio.blocks.schema._
|
|
555
|
-
|
|
556
|
-
case class Email(address: String)
|
|
557
|
-
|
|
558
|
-
object Email extends CompanionOptics[Email] {
|
|
559
|
-
implicit val schema: Schema[Email] = Schema.derived
|
|
560
|
-
|
|
561
|
-
val address: Lens[Email, String] = $(_.address)
|
|
562
|
-
}
|
|
516
|
+
Supported numeric types include `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `BigInt`, and `BigDecimal`.
|
|
563
517
|
|
|
564
|
-
|
|
565
|
-
val result = isValid.eval(Email("alice@example.com"))
|
|
566
|
-
// Right(List(true))
|
|
567
|
-
```
|
|
518
|
+
#### Other string and bitwise operations
|
|
568
519
|
|
|
569
|
-
|
|
520
|
+
`DynamicSchemaExpr` also includes:
|
|
570
521
|
|
|
571
|
-
|
|
522
|
+
- `StringConcat`
|
|
523
|
+
- `StringRegexMatch`
|
|
524
|
+
- `StringLength`
|
|
525
|
+
- `StringSubstring`
|
|
526
|
+
- `StringTrim`
|
|
527
|
+
- `StringToUpperCase`
|
|
528
|
+
- `StringToLowerCase`
|
|
529
|
+
- `StringReplace`
|
|
530
|
+
- `StringStartsWith`
|
|
531
|
+
- `StringEndsWith`
|
|
532
|
+
- `StringContains`
|
|
533
|
+
- `StringIndexOf`
|
|
534
|
+
- `PrimitiveConversion`
|
|
535
|
+
- `Bitwise`
|
|
536
|
+
- `BitwiseNot`
|
|
572
537
|
|
|
573
|
-
|
|
574
|
-
object SchemaExpr {
|
|
575
|
-
case class StringLength[A](
|
|
576
|
-
string: SchemaExpr[A, String]
|
|
577
|
-
) extends SchemaExpr[A, Int]
|
|
578
|
-
}
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
Created via the `length` method on string optics:
|
|
582
|
-
|
|
583
|
-
```scala
|
|
584
|
-
import zio.blocks.schema._
|
|
585
|
-
|
|
586
|
-
case class Message(body: String)
|
|
587
|
-
|
|
588
|
-
object Message extends CompanionOptics[Message] {
|
|
589
|
-
implicit val schema: Schema[Message] = Schema.derived
|
|
590
|
-
|
|
591
|
-
val body: Lens[Message, String] = $(_.body)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
val bodyLength = Message.body.length
|
|
595
|
-
val result = bodyLength.eval(Message("Hello!"))
|
|
596
|
-
// Right(List(6))
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Abstract Intermediate Traits
|
|
600
|
-
|
|
601
|
-
Two sealed traits categorize some expressions by arity:
|
|
602
|
-
|
|
603
|
-
- **`UnaryOp[A, B]`** — has a single `expr: SchemaExpr[A, B]`. Extended by `Not`.
|
|
604
|
-
- **`BinaryOp[A, B, C]`** — has `left: SchemaExpr[A, B]` and `right: SchemaExpr[A, B]`. Extended by `Relational`, `Logical`, `Arithmetic`, and `StringConcat`.
|
|
605
|
-
|
|
606
|
-
Not all expression nodes use these traits — `StringRegexMatch` and `StringLength` extend `SchemaExpr` directly. These intermediate traits are useful for pattern matching when you need to generically process the expression tree.
|
|
538
|
+
These are the cases downstream interpreters should consider when translating `SchemaExpr.dynamic` into SQL, filters, or other query languages.
|
|
607
539
|
|
|
608
540
|
## Error Handling
|
|
609
541
|
|
|
@@ -631,19 +563,22 @@ result match {
|
|
|
631
563
|
|
|
632
564
|
## Advanced Usage: Building Query DSLs
|
|
633
565
|
|
|
634
|
-
Because `SchemaExpr` is a sealed, inspectable AST, third-party libraries can
|
|
566
|
+
Because `SchemaExpr.dynamic` is a sealed, inspectable AST, third-party libraries can translate expressions into other languages. The public entry point should still accept `SchemaExpr`; only the interpreter internals need to cross into `DynamicSchemaExpr`.
|
|
635
567
|
|
|
636
568
|
```scala
|
|
637
|
-
// Pseudocode —
|
|
638
|
-
def toSql[A](expr: SchemaExpr[A,
|
|
639
|
-
|
|
569
|
+
// Pseudocode — keeps SchemaExpr as the public API
|
|
570
|
+
def toSql[A, B](expr: SchemaExpr[A, B]): String =
|
|
571
|
+
toSqlDynamic(expr.dynamic)
|
|
572
|
+
|
|
573
|
+
def toSqlDynamic(expr: DynamicSchemaExpr): String = expr match {
|
|
574
|
+
case DynamicSchemaExpr.Relational(left, right, op) =>
|
|
640
575
|
s"${toSqlValue(left)} ${opToSql(op)} ${toSqlValue(right)}"
|
|
641
|
-
case
|
|
642
|
-
s"(${
|
|
643
|
-
case
|
|
644
|
-
s"(${
|
|
645
|
-
case
|
|
646
|
-
s"NOT (${
|
|
576
|
+
case DynamicSchemaExpr.Logical(left, right, DynamicSchemaExpr.LogicalOperator.And) =>
|
|
577
|
+
s"(${toSqlDynamic(left)}) AND (${toSqlDynamic(right)})"
|
|
578
|
+
case DynamicSchemaExpr.Logical(left, right, DynamicSchemaExpr.LogicalOperator.Or) =>
|
|
579
|
+
s"(${toSqlDynamic(left)}) OR (${toSqlDynamic(right)})"
|
|
580
|
+
case DynamicSchemaExpr.Not(inner) =>
|
|
581
|
+
s"NOT (${toSqlDynamic(inner)})"
|
|
647
582
|
// ...
|
|
648
583
|
}
|
|
649
584
|
```
|
|
@@ -658,7 +593,7 @@ This is the key advantage of reified expressions over plain functions — the sa
|
|
|
658
593
|
|
|
659
594
|
### Schema
|
|
660
595
|
|
|
661
|
-
[Schema](./schema.md) provides the type information needed
|
|
596
|
+
[Schema](./schema.md) provides the type information needed to construct typed `SchemaExpr` values and to convert typed results to and from `DynamicValue` during evaluation.
|
|
662
597
|
|
|
663
598
|
### DynamicValue
|
|
664
599
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: schema
|
|
3
|
+
slug: schema
|
|
3
4
|
title: "Schema"
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -186,6 +187,17 @@ import zio.blocks.schema.Schema
|
|
|
186
187
|
Schema[Option[A]] // Generic option for reference types
|
|
187
188
|
```
|
|
188
189
|
|
|
190
|
+
### Either Values
|
|
191
|
+
|
|
192
|
+
An `Either[A, B]` schema is available whenever both branch types have schemas. Primitive values and primitive-backed wrappers use specialized primitive register layouts, while other values use the layouts described by their schemas:
|
|
193
|
+
|
|
194
|
+
```scala
|
|
195
|
+
import zio.blocks.schema.Schema
|
|
196
|
+
|
|
197
|
+
Schema[Either[String, Int]]
|
|
198
|
+
Schema[Either[Int, Long]]
|
|
199
|
+
```
|
|
200
|
+
|
|
189
201
|
### Collection Types
|
|
190
202
|
|
|
191
203
|
ZIO Blocks also provides polymorphic schemas for standard Scala collections. You can summon schemas for collections of any element type `A` (and key/value types `K`/`V` for maps):
|
|
@@ -588,7 +600,7 @@ ZIO Blocks provides the `transform` method for creating schemas for wrapper type
|
|
|
588
600
|
|
|
589
601
|
```scala
|
|
590
602
|
final case class Schema[A](reflect: Reflect.Bound[A]) {
|
|
591
|
-
def transform[B](to: A => B, from: B => A): Schema[B] = ???
|
|
603
|
+
def transform[B](to: A => B, from: B => A)(implicit typeId: TypeId[B]): Schema[B] = ???
|
|
592
604
|
}
|
|
593
605
|
```
|
|
594
606
|
|
|
@@ -294,7 +294,7 @@ Structural types integrate seamlessly with ZIO Blocks' broader ecosystem:
|
|
|
294
294
|
|
|
295
295
|
### With Schema Evolution Macros
|
|
296
296
|
|
|
297
|
-
Structural schemas work with [Schema Evolution](
|
|
297
|
+
Structural schemas work with [Schema Evolution](schema-evolution/into.md) macros for cross-type conversion. When two types share the same structural shape, the conversion machinery can work across type boundaries:
|
|
298
298
|
|
|
299
299
|
```scala
|
|
300
300
|
import zio.blocks.schema.Schema
|
|
@@ -167,6 +167,68 @@ import zio.blocks.schema.json._
|
|
|
167
167
|
val jsonCodec = Person.schema.derive(JsonFormat)
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Customizing Derivation with Instance and Modifier Overrides
|
|
171
|
+
|
|
172
|
+
By default, `Deriver` automatically derives codecs for all types. But sometimes you need to customize how specific types are encoded or decoded—for example, encoding `LocalDate` as `"dd/MM/yyyy"` instead of ISO format.
|
|
173
|
+
|
|
174
|
+
ZIO Blocks provides three levels of customization, in progressive order:
|
|
175
|
+
|
|
176
|
+
1. **Type-level override** (`Deriver.withInstance`) — Override the codec for ALL occurrences of a type. Configure once, use everywhere:
|
|
177
|
+
|
|
178
|
+
```scala
|
|
179
|
+
import java.time.LocalDate
|
|
180
|
+
import java.time.format.DateTimeFormatter
|
|
181
|
+
|
|
182
|
+
// Create a custom JsonCodec for LocalDate
|
|
183
|
+
val customDateCodec: JsonCodec[LocalDate] = new JsonCodec[LocalDate] {
|
|
184
|
+
private val fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
|
185
|
+
def decodeValue(in: JsonReader): LocalDate = LocalDate.parse(in.readString(), fmt)
|
|
186
|
+
def encodeValue(x: LocalDate, out: JsonWriter): Unit = out.writeVal(fmt.format(x))
|
|
187
|
+
// ... AST overrides ...
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Configure the deriver once
|
|
191
|
+
val myDeriver = JsonCodecDeriver.withInstance[LocalDate](customDateCodec)
|
|
192
|
+
|
|
193
|
+
// Use everywhere — all LocalDate fields use the custom codec
|
|
194
|
+
val codec1 = Schema[Event].deriving(myDeriver).derive
|
|
195
|
+
val codec2 = Schema[Meeting].deriving(myDeriver).derive
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. **Field-level override** (`Deriver.withInstance` with typeId + termName) — Override a specific field only:
|
|
199
|
+
|
|
200
|
+
```scala
|
|
201
|
+
// Only Event.date uses custom format; other LocalDate fields are unchanged
|
|
202
|
+
val deriver = JsonCodecDeriver.withInstance[Event, LocalDate](
|
|
203
|
+
TypeId.of[Event], "date", customDateCodec
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
3. **Modifier override** (`Deriver.withModifier`) — Rename fields, add aliases:
|
|
208
|
+
|
|
209
|
+
```scala
|
|
210
|
+
val deriver = JsonCodecDeriver.withModifier(
|
|
211
|
+
TypeId.of[Person], "firstName", Modifier.rename("first_name")
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Chaining Overrides
|
|
216
|
+
|
|
217
|
+
Overrides compose, allowing you to build complex derivers incrementally:
|
|
218
|
+
|
|
219
|
+
```scala
|
|
220
|
+
val myDeriver = JsonCodecDeriver
|
|
221
|
+
.withInstance[LocalDate](customDateCodec)
|
|
222
|
+
.withModifier(TypeId.of[Event], "name", Modifier.rename("title"))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Important Notes
|
|
226
|
+
|
|
227
|
+
- `withInstance` and `withModifier` return a NEW deriver (they are immutable).
|
|
228
|
+
- When combined with `DerivationBuilder`, deriver-level instance overrides take precedence over builder-level instance overrides. Modifier override precedence is order-sensitive and should not be assumed to follow the same rule.
|
|
229
|
+
- Unknown `termName` values are silently ignored.
|
|
230
|
+
- The `B` type parameter in field-level `withInstance` is not statically checked against the actual field type.
|
|
231
|
+
|
|
170
232
|
## Example 1: Deriving a `Show` Type Class Instance
|
|
171
233
|
|
|
172
234
|
Let's say we want to derive a `Show` type class instance for any type of type `A`:
|
|
@@ -1455,7 +1517,7 @@ Now we can use the derived `Gen[Person]` instance to generate random `Person` va
|
|
|
1455
1517
|
|
|
1456
1518
|
```scala
|
|
1457
1519
|
val random = new Random(42) // Seeded for reproducible output
|
|
1458
|
-
// random: Random = scala.util.Random@
|
|
1520
|
+
// random: Random = scala.util.Random@d903a4c
|
|
1459
1521
|
|
|
1460
1522
|
Person.gen.generate(random)
|
|
1461
1523
|
// res14: Person = Person(name = "p", age = -1360544799)
|