@zio.dev/zio-blocks 0.0.33 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,158 @@
1
+ ---
2
+ id: config
3
+ title: "Config"
4
+ sidebar_label: "Config"
5
+ ---
6
+
7
+ `zio.blocks.config` provides typed configuration loading, feature flags, provenance tracking, rollout selection, and source adapters for YAML, JSON, and HOCON. The module is synchronous and zero-dependency: configuration is loaded from `ConfigSource`, flags are read through `FlagSource`, and typed decoding is derived from `Schema[A]`.
8
+
9
+ ## Installation
10
+
11
+ ```scala
12
+ libraryDependencies += "dev.zio" %% "zio-blocks-config" % "0.0.51"
13
+ libraryDependencies += "dev.zio" %% "zio-blocks-config-yaml" % "0.0.51"
14
+ libraryDependencies += "dev.zio" %% "zio-blocks-config-json" % "0.0.51"
15
+ libraryDependencies += "dev.zio" %% "zio-blocks-config-hocon" % "0.0.51"
16
+ ```
17
+
18
+ For Scala.js:
19
+
20
+ ```scala
21
+ libraryDependencies += "dev.zio" %%% "zio-blocks-config" % "0.0.51"
22
+ ```
23
+
24
+ Supported Scala versions: 2.13.x and 3.x.
25
+
26
+ ## Core Types
27
+
28
+ At the center of the module are two source abstractions:
29
+
30
+ ```scala
31
+ import zio.blocks.maybe.Maybe
32
+
33
+ trait FlagSource {
34
+ def sourceId: String
35
+ def get(name: String): Maybe[SourceValue[String]]
36
+ }
37
+
38
+ trait ConfigSource extends FlagSource {
39
+ def get(key: String): Maybe[SourceValue[String]]
40
+ def all(prefix: String): Map[String, SourceValue[String]]
41
+ }
42
+ ```
43
+
44
+ `ConfigSource` extends `FlagSource`, so the same source can power both typed config loading and simple scalar flags.
45
+
46
+ ## Loading Typed Configuration
47
+
48
+ Use `Config.load[A]` when you want a typed result and explicit errors:
49
+
50
+ ```scala
51
+ import zio.blocks.config._
52
+ import zio.blocks.scope.Unscoped
53
+
54
+ final case class AppConfig(host: String, port: Int) derives Schema, Unscoped
55
+
56
+ val source = ConfigSource.fromMap(
57
+ Map("app.host" -> "localhost", "app.port" -> "8080"),
58
+ "example"
59
+ )
60
+
61
+ val loaded = Config.load[AppConfig](source.prefix("app"))
62
+ ```
63
+
64
+ The main entry points are:
65
+
66
+ ```scala
67
+ Config.load[A](source)
68
+ Config.loadOrThrow[A](source)
69
+ Config.loadWithProvenance[A](source)
70
+ ```
71
+
72
+ `Config.loadWithProvenance` returns a `ProvenanceMap[A]`, which lets you inspect where resolved values came from.
73
+
74
+ ## Wiring Config into Dependency Graphs
75
+
76
+ For application wiring, prefer `Config.wire[A]` or `Config.wire[A](prefix)` so decoding stays inside the dependency graph instead of being done manually at startup:
77
+
78
+ ```scala
79
+ Config.wire[AppConfig]
80
+ Config.wire[AppConfig]("app")
81
+ ```
82
+
83
+ That keeps `ConfigSource` as the injected input and the typed config as the derived output.
84
+
85
+ ## Working with Sources
86
+
87
+ `ConfigSource` supports composition and key transformation:
88
+
89
+ ```scala
90
+ val defaults = ConfigSource.fromMap(Map("db.host" -> "localhost"), "defaults")
91
+ val env = ConfigSource.fromMap(Map("db.port" -> "5432"), "env")
92
+
93
+ val combined = env.orElse(defaults)
94
+ val scoped = combined.prefix("db")
95
+ ```
96
+
97
+ Common adapters:
98
+
99
+ - `ConfigSource.fromMap(...)`
100
+ - `ConfigSource.fromYaml(...)` (requires `config-yaml` dependency)
101
+ - `ConfigSource.fromJson(...)` (requires `config-json` dependency)
102
+ - `ConfigSource.fromHocon(...)` (requires `config-hocon` dependency)
103
+
104
+ ## Static and Dynamic Flags
105
+
106
+ `StaticFlag[A]` resolves once, during object initialization:
107
+
108
+ ```scala
109
+ import zio.blocks.config._
110
+
111
+ object poolSize extends StaticFlag[Int](10)
112
+
113
+ val size: Int = poolSize()
114
+ ```
115
+
116
+ `DynamicFlag[A]` keeps an updatable rollout expression and evaluates it on demand.
117
+
118
+ `StaticFlag` names are derived from the Scala object's fully qualified name, so custom `FlagSource` registrations must use that exact key.
119
+
120
+ `FlagSource.Registry` is consulted in registration order, so the first registered source wins when multiple sources provide the same flag.
121
+
122
+ :::note
123
+ Register `FlagSource`s before the first reference to a `StaticFlag` object. Once a static flag object is initialized, later registrations do not retroactively change its resolved value.
124
+ :::
125
+
126
+ ## Rollout DSL
127
+
128
+ `Rollout` selects values based on a path and an optional percentage bucket:
129
+
130
+ ```scala
131
+ import zio.blocks.config._
132
+
133
+ val bucket = Rollout.bucketFor("user-123")
134
+ val choice = Rollout.select("true@prod/50%;false", "prod", bucket)
135
+ ```
136
+
137
+ The percentage uses slash-separated syntax (`prod/50%`). For the example above, `choice` is `Maybe.present("true")` for roughly half of the `prod` buckets and `Maybe.present("false")` otherwise. Bare values act as catch-all fallbacks and should come last.
138
+
139
+ ## Provenance
140
+
141
+ Every resolved value carries provenance information through `SourceValue` and `Provenance`:
142
+
143
+ ```scala
144
+ val source = ConfigSource.fromMap(Map("db.host" -> "localhost"), "defaults")
145
+ val host = source.get("db.host")
146
+ ```
147
+
148
+ `host` contains both the raw value and a `Provenance.Resolved` entry that records the source id and key.
149
+
150
+ ## Format Adapters
151
+
152
+ The format modules flatten structured documents into dot-separated keys:
153
+
154
+ - YAML: nested mappings become `a.b.c`
155
+ - JSON: arrays are indexed as `items.0`, `items.1`, ...
156
+ - HOCON: substitutions are resolved before flattening
157
+
158
+ Use these adapters when you want the convenience of file formats but still want a single `ConfigSource` API for decoding, composition, and provenance.
@@ -116,7 +116,7 @@ val config = ctx.get[Config] // Compile-time proof it exists
116
116
  Add the ZIO Blocks Context module to your `build.sbt`:
117
117
 
118
118
  ```scala
119
- libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.33"
119
+ libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.51"
120
120
  ```
121
121
 
122
122
  ## Construction
@@ -544,7 +544,7 @@ cd zio-blocks
544
544
  package context
545
545
 
546
546
  import zio.blocks.context._
547
- import util.ShowExpr.show
547
+ import zio.sbt.ExprEval.show
548
548
 
549
549
  // Context.empty creates an empty, type-safe dependency container.
550
550
  // Use Context.apply(...) to construct contexts with 1–10 values.
@@ -621,7 +621,7 @@ sbt "schema-examples/runMain context.ContextConstructionExample"
621
621
  package context
622
622
 
623
623
  import zio.blocks.context._
624
- import util.ShowExpr.show
624
+ import zio.sbt.ExprEval.show
625
625
 
626
626
  // Context#get[A] retrieves a value by type with compile-time proof of existence.
627
627
  // Context#getOption[A] retrieves a value if present, returning None if missing.
@@ -706,7 +706,7 @@ sbt "schema-examples/runMain context.ContextRetrievalExample"
706
706
  package context
707
707
 
708
708
  import zio.blocks.context._
709
- import util.ShowExpr.show
709
+ import zio.sbt.ExprEval.show
710
710
 
711
711
  // Context is immutable; modification methods return new contexts.
712
712
  // Context#add expands the context with a new value (or replaces if type exists).
@@ -0,0 +1,346 @@
1
+ ---
2
+ id: datastar
3
+ title: "Datastar"
4
+ ---
5
+
6
+ `zio-blocks-datastar` provides a type-safe Scala SDK for [Datastar](https://data-star.dev/), the hypermedia framework that brings reactive UIs via server-sent events (SSE). It builds on `zio-blocks-html` for DOM construction and `zio-blocks-schema` for JSON serialization.
7
+
8
+ ## Installation
9
+
10
+ ```scala
11
+ // JVM
12
+ libraryDependencies += "dev.zio" %% "zio-blocks-datastar" % "0.0.51"
13
+
14
+ // Scala.js
15
+ libraryDependencies += "dev.zio" %%% "zio-blocks-datastar" % "0.0.51"
16
+ ```
17
+
18
+ ## Signals
19
+
20
+ A `Signal[A]` represents a named reactive signal on the client. Use `:=` to pair it with a value, producing a `SignalUpdate` ready for SSE transmission:
21
+
22
+ ```scala
23
+ import zio.http.datastar._
24
+ import zio.blocks.schema.Schema
25
+
26
+ case class User(name: String, age: Int)
27
+ object User {
28
+ implicit val schema: Schema[User] = Schema.derived
29
+ }
30
+
31
+ val count = Signal[Int]("count")
32
+ val username = Signal[String]("username")
33
+
34
+ // Create signal updates (serializes via Schema's cached JSON codec)
35
+ val update1 = count := 0
36
+ val update2 = username := "Alice"
37
+ ```
38
+
39
+ ## SSE Events
40
+
41
+ `DatastarEvent` is the sealed trait representing events sent to the browser. Use the companion object's builder methods:
42
+
43
+ ### patchSignals
44
+
45
+ Send signal updates to the client:
46
+
47
+ ```scala
48
+ val sse = DatastarEvent
49
+ .patchSignals(count := 42, username := "Bob")
50
+ .renderSSE
51
+
52
+ // With options:
53
+ val sseWithOptions = DatastarEvent
54
+ .patchSignals(count := 1)
55
+ .withOnlyIfMissing
56
+ .withEventId("evt-1")
57
+ .withRetry(5000)
58
+ .renderSSE
59
+ ```
60
+
61
+ ### patchElements
62
+
63
+ Send DOM fragments to the client:
64
+
65
+ ```scala
66
+ import zio.blocks.html._
67
+
68
+ val sse = DatastarEvent
69
+ .patchElements(div(id := "status")("Online"))
70
+ .withSelector(CssSelector.id("status"))
71
+ .withMode(ElementPatchMode.Inner)
72
+ .withViewTransition
73
+ .renderSSE
74
+ ```
75
+
76
+ ### executeScript
77
+
78
+ Run JavaScript on the client:
79
+
80
+ ```scala
81
+ val sse = DatastarEvent
82
+ .executeScript(js"console.log('hello')")
83
+ .renderSSE
84
+ ```
85
+
86
+ ### removeElements
87
+
88
+ Remove elements matching a CSS selector:
89
+
90
+ ```scala
91
+ val sse = DatastarEvent
92
+ .removeElements(CssSelector.id("old-item"))
93
+ .renderSSE
94
+ ```
95
+
96
+ ## Attribute DSL
97
+
98
+ Import `zio.http.datastar._` to get all `data-*` attribute constructors. These integrate with the `zio-blocks-html` DSL:
99
+
100
+ ```scala
101
+ import zio.blocks.html._
102
+ import zio.http.datastar._
103
+
104
+ val count = Signal[Int]("count")
105
+ val username = Signal[String]("username")
106
+
107
+ div(
108
+ dataSignals(count := 0),
109
+ dataText := count
110
+ )(
111
+ span()("Count: "),
112
+ button(dataOn.click := js"$count++")("Increment")
113
+ )
114
+ ```
115
+
116
+ Datastar expression positions are intentionally stricter than generic HTML/JS templating.
117
+ Raw `String` values are rejected in Datastar expression attributes; use `js"..."`
118
+ for expressions, or typed values like `Signal`, `SignalUpdate`, and `DatastarRef`.
119
+
120
+ ```scala
121
+ dataText := count
122
+ dataText := count.ref
123
+ dataOn.click := js"$count++"
124
+
125
+ // does not compile:
126
+ // dataOn.click := "$count++"
127
+ ```
128
+
129
+ ### Available Attributes
130
+
131
+ | Method | Datastar Attribute | Description |
132
+ |--------|-------------------|-------------|
133
+ | `dataSignals(signal)` | `data-signals:name` | Declare one keyed signal with an initial value |
134
+ | `dataSignals(update, updates...)` | `data-signals` | Patch multiple signals with one object expression |
135
+ | `dataBind(signal)` | `data-bind:name` | Two-way bind an input to a signal |
136
+ | `dataText` | `data-text` | Set element text content |
137
+ | `dataShow` | `data-show` | Conditionally show/hide element |
138
+ | `dataClass("name")` | `data-class:name` | Toggle CSS class |
139
+ | `dataOn.click` | `data-on:click` | Event handler |
140
+ | `dataComputed(signal)` | `data-computed:name` | Computed signal |
141
+ | `dataEffect` | `data-effect` | Side-effect expression |
142
+ | `dataIndicator(signal)` | `data-indicator:name` | Loading indicator signal |
143
+ | `dataRef("name")` | `data-ref:name` | Element reference |
144
+ | `dataInit` | `data-init` | Initialization expression |
145
+
146
+ ### Event Modifiers
147
+
148
+ Chain modifiers on `dataOn` before assigning a handler:
149
+
150
+ ```scala
151
+ // Debounce input by 300ms
152
+ dataOn.input.debounce(300) := username
153
+
154
+ // Click with prevent default, only once
155
+ dataOn.click.prevent.once := js"handleSubmit()"
156
+
157
+ // Throttle scroll, listen on window
158
+ dataOn.scroll.throttle(100).window := js"onScroll()"
159
+ ```
160
+
161
+ Available modifiers: `debounce`, `debounceLeading`, `throttle`, `throttleLeading`, `delay`, `once`, `passive`, `capture`, `stop`, `prevent`, `outside`, `window`, `document`, `viewTransition`.
162
+
163
+ ### Case Modifiers
164
+
165
+ `dataOn` and `dataSignals(signal)` builders expose `.camel`, `.kebab`, `.snake`, and `.pascal` to control how the attribute key is cased. The default for `dataOn` is kebab; for `dataSignals(signal)` it is camel.
166
+
167
+ ```scala
168
+ // Renders data-on:myCustomEvent (camel, explicit)
169
+ dataOn("myCustomEvent").camel := js"handleIt()"
170
+
171
+ // Renders data-signals:my-signal__case.kebab (kebab override)
172
+ dataSignals(count := 0).kebab
173
+ ```
174
+
175
+ The suffix `__case.<modifier>` is appended to the attribute name only when the chosen case differs from the builder's default.
176
+
177
+ ### Advanced Triggers
178
+
179
+ #### dataOnIntersect
180
+
181
+ Fires when the element enters (or exits) the viewport. Modifiers map directly to Datastar's intersection observer options:
182
+
183
+ ```scala
184
+ // Fire once when 50% of the element is visible
185
+ div(dataOnIntersect.half.once := js"loadContent()")()
186
+
187
+ // Fire when element exits the viewport, debounced
188
+ div(dataOnIntersect.exit.debounce(200) := js"cleanup()")()
189
+ ```
190
+
191
+ Available modifiers: `once`, `half`, `full`, `exit`, `threshold(pct: Double)`, `delay(millis)`, `debounce(millis)`, `throttle(millis)`, `viewTransition`.
192
+
193
+ #### dataOnInterval
194
+
195
+ Runs an expression on a repeating timer:
196
+
197
+ ```scala
198
+ // Poll every second
199
+ div(dataOnInterval.duration(1000) := js"$count++")(count.ref.text)
200
+
201
+ // Leading-edge interval with view transitions
202
+ div(dataOnInterval.durationLeading(500).viewTransition := js"refresh()")()
203
+ ```
204
+
205
+ #### dataOnSignalPatch / dataOnSignalPatchFilter
206
+
207
+ `dataOnSignalPatch` runs whenever the server patches signals. Use `dataOnSignalPatchFilter` to restrict which patches trigger the handler:
208
+
209
+ ```scala
210
+ div(
211
+ dataOnSignalPatch.debounce(100) := js"onPatch()",
212
+ dataOnSignalPatchFilter := js"$count > 0"
213
+ )()
214
+ ```
215
+
216
+ Available modifiers on `dataOnSignalPatch`: `delay(millis)`, `debounce(millis)`, `throttle(millis)`.
217
+
218
+ ### Style and Attribute Binding
219
+
220
+ #### dataStyle
221
+
222
+ Bind inline styles, either as a whole-object expression or per-property:
223
+
224
+ ```scala
225
+ // Keyed: sets a single CSS property
226
+ div(dataStyle("color") := js"$isDark ? 'white' : 'black'")()
227
+
228
+ // Unkeyed: bind an entire style object
229
+ div(dataStyle := js"{color: $color, fontSize: $size + 'px'}")()
230
+ ```
231
+
232
+ #### dataAttr
233
+
234
+ Bind arbitrary HTML attributes:
235
+
236
+ ```scala
237
+ div(dataAttr("aria-label") := js"$label")()
238
+ div(dataAttr("tabindex") := js"$isActive ? 0 : -1")()
239
+ ```
240
+
241
+ ### Morph Control
242
+
243
+ Mark elements so Datastar's morphing algorithm treats them specially:
244
+
245
+ ```scala
246
+ // Exclude element and its children from morphing
247
+ div(dataIgnore)()
248
+
249
+ // Exclude only the element itself, not its children
250
+ div(dataIgnoreSelf)()
251
+
252
+ // Prevent the element from being morphed (keep existing DOM node)
253
+ div(dataIgnoreMorph)()
254
+
255
+ // Preserve a specific attribute across morphs
256
+ input(dataPreserveAttr("value"))()
257
+ ```
258
+
259
+ ### JSON Signals
260
+
261
+ `dataJsonSignals` passes a raw JSON string as the signals expression, useful when signals are serialized server-side:
262
+
263
+ ```scala
264
+ div(dataJsonSignals := js"""{"count":0,"username":""}""")()
265
+ ```
266
+
267
+ ## ElementPatchMode
268
+
269
+ Controls how `patchElements` applies DOM content to the target:
270
+
271
+ | Mode | Description |
272
+ |------|-------------|
273
+ | `Outer` | Morphs the target element in place (default, omitted from SSE) |
274
+ | `Inner` | Replaces the target's children |
275
+ | `Replace` | Replaces the target element |
276
+ | `Prepend` | Inserts before the first child |
277
+ | `Append` | Inserts after the last child |
278
+ | `Before` | Inserts before the target element |
279
+ | `After` | Inserts after the target element |
280
+ | `Remove` | Removes the target element |
281
+
282
+ ```scala
283
+ DatastarEvent
284
+ .patchElements(li(id := "new")("item"))
285
+ .withSelector(CssSelector.id("list"))
286
+ .withMode(ElementPatchMode.Append)
287
+ .renderSSE
288
+ ```
289
+
290
+ ## CssSelector
291
+
292
+ `CssSelector` (from `zio.blocks.html`) constructs CSS selectors with type-safe combinators:
293
+
294
+ ```scala
295
+ import zio.blocks.html._
296
+
297
+ CssSelector.id("main") // #main
298
+ CssSelector.`class`("active") // .active
299
+ CssSelector.element("div") // div
300
+ CssSelector.raw(".foo > .bar") // .foo > .bar
301
+ CssSelector.universal // *
302
+ ```
303
+
304
+ Selectors compose with operators:
305
+
306
+ ```scala
307
+ val sel = CssSelector.element("ul") > CssSelector.element("li") // ul > li
308
+ val grouped = CssSelector.id("a") | CssSelector.id("b") // #a, #b
309
+ ```
310
+
311
+ ## Signal.dynamic
312
+
313
+ `Signal[A]("name")` validates the signal name at compile time for string literals. Use `Signal.dynamic[A](name)` when the name is only known at runtime:
314
+
315
+ ```scala
316
+ val fieldName = computeFieldName() // runtime string
317
+ val sig = Signal.dynamic[String](fieldName) // validated at runtime
318
+
319
+ sig := "hello" // works like any other Signal
320
+ ```
321
+
322
+ `dynamic` throws `IllegalArgumentException` if the name is invalid. The same validation rules apply: dot-separated JavaScript identifiers, no `__`.
323
+
324
+ ## SSE Streaming
325
+
326
+ `DatastarEvent` has no ZIO or HTTP framework dependency. It just produces strings. To stream events, set the response content-type to `text/event-stream` and write each `renderSSE` result to the response body:
327
+
328
+ ```scala
329
+ // Framework-agnostic pseudocode
330
+ response.setHeader("Content-Type", "text/event-stream")
331
+ response.setHeader("Cache-Control", "no-cache")
332
+
333
+ // Send an initial signal patch
334
+ response.write(DatastarEvent.patchSignals(count := 0).renderSSE)
335
+
336
+ // Stream DOM updates as data changes
337
+ val fragment = div(id := "result")(computedContent)
338
+ response.write(
339
+ DatastarEvent
340
+ .patchElements(fragment)
341
+ .withSelector(CssSelector.id("result"))
342
+ .renderSSE
343
+ )
344
+ ```
345
+
346
+ Each `renderSSE` call returns a self-contained SSE block — multiple events can be written sequentially to the same stream.