@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.
- 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 +292 -50
- 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} +5 -5
- 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} +13 -9
- 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 +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- 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 +5 -19
- package/sidebars.js +238 -43
- 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
|
@@ -39,13 +39,13 @@ The bidirectional data flow looks like this:
|
|
|
39
39
|
`As` is part of `zio-blocks-schema`:
|
|
40
40
|
|
|
41
41
|
```scala
|
|
42
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
42
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
For Scala.js and Scala Native, use `%%%`:
|
|
46
46
|
|
|
47
47
|
```scala
|
|
48
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
48
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -89,7 +89,7 @@ manualAs.from(Long.MaxValue)
|
|
|
89
89
|
// SchemaError(
|
|
90
90
|
// List(
|
|
91
91
|
// ConversionFailed(
|
|
92
|
-
// source = DynamicOptic(
|
|
92
|
+
// source = DynamicOptic(ArraySeq()),
|
|
93
93
|
// details = "overflow",
|
|
94
94
|
// cause = None
|
|
95
95
|
// )
|
|
@@ -213,7 +213,7 @@ boxAs.from(LongBox(Long.MaxValue))
|
|
|
213
213
|
// SchemaError(
|
|
214
214
|
// List(
|
|
215
215
|
// ConversionFailed(
|
|
216
|
-
// source = DynamicOptic(
|
|
216
|
+
// source = DynamicOptic(ArraySeq()),
|
|
217
217
|
// details = "Value 9223372036854775807 is out of range for Int [-2147483648, 2147483647]",
|
|
218
218
|
// cause = None
|
|
219
219
|
// )
|
|
@@ -240,7 +240,7 @@ trait As[A, B] {
|
|
|
240
240
|
|
|
241
241
|
```scala
|
|
242
242
|
val revAs: As[LongBox, IntBox] = boxAs.reverse
|
|
243
|
-
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@
|
|
243
|
+
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@29c128e0
|
|
244
244
|
|
|
245
245
|
revAs.into(LongBox(5L))
|
|
246
246
|
// res11: Either[SchemaError, IntBox] = Right(IntBox(5))
|
|
@@ -302,7 +302,7 @@ We import `As.reverseInto` and use it to obtain the reverse `Into[Int, String]`:
|
|
|
302
302
|
import As.reverseInto
|
|
303
303
|
|
|
304
304
|
val intToStr: Into[Int, String] = reverseInto[String, Int]
|
|
305
|
-
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$
|
|
305
|
+
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$19927/0x00007f2996e03990@597c011
|
|
306
306
|
intToStr.into(42)
|
|
307
307
|
// res14: Either[SchemaError, String] = Right("42")
|
|
308
308
|
```
|
|
@@ -387,7 +387,7 @@ numericAs.from(LongModel(Long.MaxValue))
|
|
|
387
387
|
// SchemaError(
|
|
388
388
|
// List(
|
|
389
389
|
// ConversionFailed(
|
|
390
|
-
// source = DynamicOptic(
|
|
390
|
+
// source = DynamicOptic(ArraySeq()),
|
|
391
391
|
// details = "Value 9223372036854775807 is out of range for Int [-2147483648, 2147483647]",
|
|
392
392
|
// cause = None
|
|
393
393
|
// )
|
|
@@ -584,4 +584,4 @@ result
|
|
|
584
584
|
|
|
585
585
|
`As[A, B]` is defined in `zio.blocks.schema` alongside `Into[A, B]`. Because `As` is a subtype of `Into`, the two type classes compose naturally: you can derive an outer `As` from inner `As` instances, or mix `As` and custom `Into` instances when some fields need one-way or custom logic.
|
|
586
586
|
|
|
587
|
-
For a full reference on one-way conversions and the derivation rules that `As` builds on, see [Into](
|
|
587
|
+
For a full reference on one-way conversions and the derivation rules that `As` builds on, see [Into](into.md).
|
|
@@ -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.
|
|
@@ -241,7 +241,7 @@ Into[Long, Int].into(Long.MaxValue)
|
|
|
241
241
|
// SchemaError(
|
|
242
242
|
// List(
|
|
243
243
|
// ConversionFailed(
|
|
244
|
-
// source = DynamicOptic(
|
|
244
|
+
// source = DynamicOptic(ArraySeq()),
|
|
245
245
|
// details = "Value 9223372036854775807 is out of range for Int [-2147483648, 2147483647]",
|
|
246
246
|
// cause = None
|
|
247
247
|
// )
|
|
@@ -253,7 +253,7 @@ Into[Double, Int].into(3.14)
|
|
|
253
253
|
// SchemaError(
|
|
254
254
|
// List(
|
|
255
255
|
// ConversionFailed(
|
|
256
|
-
// source = DynamicOptic(
|
|
256
|
+
// source = DynamicOptic(ArraySeq()),
|
|
257
257
|
// details = "Value 3.14 cannot be precisely converted to Int",
|
|
258
258
|
// cause = None
|
|
259
259
|
// )
|
|
@@ -399,7 +399,7 @@ conv.into(Raw(Long.MaxValue))
|
|
|
399
399
|
// SchemaError(
|
|
400
400
|
// List(
|
|
401
401
|
// ConversionFailed(
|
|
402
|
-
// source = DynamicOptic(
|
|
402
|
+
// source = DynamicOptic(ArraySeq()),
|
|
403
403
|
// details = "Value 9223372036854775807 is out of range for Int [-2147483648, 2147483647]",
|
|
404
404
|
// cause = None
|
|
405
405
|
// )
|
|
@@ -623,7 +623,7 @@ validate.into(PersonRaw("Bob", 200))
|
|
|
623
623
|
// SchemaError(
|
|
624
624
|
// List(
|
|
625
625
|
// ConversionFailed(
|
|
626
|
-
// source = DynamicOptic(
|
|
626
|
+
// source = DynamicOptic(ArraySeq()),
|
|
627
627
|
// details = "Validation failed for field 'age': NonEmptyChunk(200 did not satisfy between(0, 150))",
|
|
628
628
|
// cause = None
|
|
629
629
|
// )
|
|
@@ -854,7 +854,7 @@ result
|
|
|
854
854
|
// SchemaError(
|
|
855
855
|
// List(
|
|
856
856
|
// ExpectationMismatch(
|
|
857
|
-
// source = DynamicOptic(
|
|
857
|
+
// source = DynamicOptic(ArraySeq()),
|
|
858
858
|
// expectation = "Expected a record"
|
|
859
859
|
// )
|
|
860
860
|
// )
|
|
@@ -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):
|
|
@@ -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
|