@zio.dev/zio-blocks 0.0.51 → 0.0.56

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 (166) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +6 -0
  4. package/guides/getting-started-with-mux.md +0 -112
  5. package/guides/query-dsl-extending.md +1 -1
  6. package/guides/query-dsl-fluent-builder.md +1 -1
  7. package/guides/query-dsl-reified-optics.md +1 -1
  8. package/guides/query-dsl-sql.md +395 -1
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +131 -70
  12. package/guides/zio-schema-migration.md +6 -6
  13. package/index.md +200 -559
  14. package/package.json +1 -1
  15. package/reference/async.md +1379 -531
  16. package/reference/chunk.md +3 -3
  17. package/reference/codegen/index.md +1 -1
  18. package/reference/combinators.md +4 -4
  19. package/reference/config/config-decoder.md +460 -0
  20. package/reference/config/config-source.md +489 -0
  21. package/reference/config/errors.md +278 -0
  22. package/reference/config/flags.md +369 -0
  23. package/reference/config/formats.md +314 -0
  24. package/reference/config/index.md +304 -0
  25. package/reference/config/rollout.md +336 -0
  26. package/reference/context.md +6 -49
  27. package/reference/data-migration.md +269 -0
  28. package/reference/datastar/attributes.md +302 -0
  29. package/reference/datastar/events.md +234 -0
  30. package/reference/datastar/index.md +256 -0
  31. package/reference/datastar/signals.md +230 -0
  32. package/reference/datastar/sse.md +295 -0
  33. package/reference/datastar.md +2 -2
  34. package/reference/docs.md +2 -2
  35. package/reference/endpoint/bulk-creation.md +96 -0
  36. package/reference/endpoint/endpoint.md +1 -0
  37. package/reference/endpoint/index.md +9 -89
  38. package/reference/endpoint/path-codec.md +12 -24
  39. package/reference/endpoint/route-pattern.md +4 -6
  40. package/reference/endpoint/segment-codec.md +19 -32
  41. package/reference/html.md +313 -9
  42. package/reference/htmx/index.md +4 -52
  43. package/reference/htmx/response-headers.md +240 -0
  44. package/reference/http-model/headers.md +735 -0
  45. package/reference/http-model/index.md +3 -1
  46. package/reference/http-model/model.md +107 -71
  47. package/reference/http-model/schema-codecs.md +522 -0
  48. package/reference/http-model/schema.md +6 -3
  49. package/reference/http-model/server-sent-event.md +341 -0
  50. package/reference/jwt.md +195 -0
  51. package/reference/maybe.md +128 -11
  52. package/reference/media-type.md +2 -2
  53. package/reference/mux.mdx +7 -2
  54. package/reference/openapi.md +3 -3
  55. package/reference/projection.md +654 -0
  56. package/reference/resource-management/index.md +1 -1
  57. package/reference/resource-management/resource.md +2 -98
  58. package/reference/resource-management/scope.md +1 -209
  59. package/reference/resource-management/wire.md +4 -50
  60. package/reference/ringbuffer/advanced.mdx +1 -1
  61. package/reference/ringbuffer/index.mdx +3 -3
  62. package/reference/ringbuffer/mpmc.mdx +38 -4
  63. package/reference/ringbuffer/mpsc.mdx +36 -4
  64. package/reference/ringbuffer/spmc.mdx +1 -1
  65. package/reference/ringbuffer/spsc.mdx +87 -15
  66. package/reference/schema/allows.md +0 -96
  67. package/reference/schema/binding.md +2 -2
  68. package/reference/schema/built-in-codecs/avro.md +2 -2
  69. package/reference/schema/built-in-codecs/bson.md +50 -20
  70. package/reference/schema/built-in-codecs/csv.md +2 -2
  71. package/reference/schema/built-in-codecs/index.md +3 -3
  72. package/reference/schema/built-in-codecs/json/index.md +2 -2
  73. package/reference/schema/built-in-codecs/json/json.md +1 -0
  74. package/reference/schema/built-in-codecs/messagepack.md +3 -3
  75. package/reference/schema/built-in-codecs/thrift.md +2 -2
  76. package/reference/schema/built-in-codecs/toon.md +3 -3
  77. package/reference/schema/built-in-codecs/yaml.md +2 -2
  78. package/reference/schema/codec.md +11 -11
  79. package/reference/schema/dynamic-optic.md +48 -3
  80. package/reference/schema/dynamic-schema.md +3 -3
  81. package/reference/schema/index.md +2 -0
  82. package/reference/schema/path-interpolator.md +2 -0
  83. package/reference/schema/reflect-transformer.md +140 -0
  84. package/reference/schema/schema-evolution/as.md +4 -4
  85. package/reference/schema/schema-evolution/into.md +2 -2
  86. package/reference/schema/schema-expr.md +2 -2
  87. package/reference/schema/schema-search.md +263 -0
  88. package/reference/schema/schema.md +10 -2
  89. package/reference/schema/type-class-derivation.md +1 -1
  90. package/reference/smithy.md +502 -3
  91. package/reference/sql/db-codec-deriver.md +3 -3
  92. package/reference/sql/db-codec.md +22 -22
  93. package/reference/sql/db-con.md +4 -4
  94. package/reference/sql/db-connection.md +1 -1
  95. package/reference/sql/db-param.md +1 -1
  96. package/reference/sql/db-result-reader.md +4 -2
  97. package/reference/sql/db-tx.md +46 -14
  98. package/reference/sql/ddl.md +1 -1
  99. package/reference/sql/frag.md +44 -10
  100. package/reference/sql/index.md +7 -7
  101. package/reference/sql/repo.md +15 -15
  102. package/reference/sql/sql-dialect.md +1 -1
  103. package/reference/sql/sql-logger.md +1 -1
  104. package/reference/sql/sql-name-mapper.md +3 -3
  105. package/reference/sql/table-metadata.md +3 -3
  106. package/reference/sql/table.md +10 -10
  107. package/reference/sql/transactor-zio.md +1 -1
  108. package/reference/sql/transactor.md +21 -11
  109. package/reference/sql-zio.md +2 -2
  110. package/reference/streams/core/index.md +32 -0
  111. package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
  112. package/reference/streams/{sink.md → core/sink.md} +331 -353
  113. package/reference/streams/{stream.md → core/stream.md} +919 -209
  114. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  115. package/reference/streams/execution-and-compatibility/index.md +35 -0
  116. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  117. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  118. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  119. package/reference/streams/index.md +140 -67
  120. package/reference/streams/primitives/index.md +30 -0
  121. package/reference/streams/primitives/reader.md +1992 -0
  122. package/reference/streams/{writer.md → primitives/writer.md} +254 -98
  123. package/reference/telemetry/common/any-value.md +90 -0
  124. package/reference/telemetry/common/attribute-key.md +87 -0
  125. package/reference/telemetry/common/attributes.md +118 -0
  126. package/reference/telemetry/common/index.md +39 -0
  127. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  128. package/reference/telemetry/common/resource.md +34 -0
  129. package/reference/telemetry/index.md +311 -0
  130. package/reference/telemetry/logging/index.md +197 -0
  131. package/reference/telemetry/logging/log-enrichment.md +72 -0
  132. package/reference/telemetry/logging/log-formatter.md +100 -0
  133. package/reference/telemetry/logging/log-record-processor.md +56 -0
  134. package/reference/telemetry/logging/log-record.md +44 -0
  135. package/reference/telemetry/logging/log-writer.md +64 -0
  136. package/reference/telemetry/logging/logger-provider.md +142 -0
  137. package/reference/telemetry/logging/logger.md +83 -0
  138. package/reference/telemetry/logging/severity.md +62 -0
  139. package/reference/telemetry/metrics/index.md +150 -0
  140. package/reference/telemetry/metrics/instruments.md +183 -0
  141. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  142. package/reference/telemetry/metrics/meter-provider.md +76 -0
  143. package/reference/telemetry/metrics/meter.md +98 -0
  144. package/reference/telemetry/metrics/metric-data.md +57 -0
  145. package/reference/telemetry/otel/custom-exporter.md +216 -0
  146. package/reference/telemetry/otel/index.md +212 -0
  147. package/reference/telemetry/tracing/index.md +155 -0
  148. package/reference/telemetry/tracing/sampler.md +89 -0
  149. package/reference/telemetry/tracing/span-builder.md +57 -0
  150. package/reference/telemetry/tracing/span-context.md +39 -0
  151. package/reference/telemetry/tracing/span-data.md +32 -0
  152. package/reference/telemetry/tracing/span-kind.md +55 -0
  153. package/reference/telemetry/tracing/span-processor.md +53 -0
  154. package/reference/telemetry/tracing/span-status.md +47 -0
  155. package/reference/telemetry/tracing/span.md +117 -0
  156. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  157. package/reference/telemetry/tracing/tracer.md +52 -0
  158. package/reference/typeid.md +0 -64
  159. package/sidebars.js +365 -185
  160. package/undocumented-report.md +528 -270
  161. package/reference/config.md +0 -158
  162. package/reference/streams/concurrent-operators.md +0 -106
  163. package/reference/streams/reader.md +0 -1284
  164. package/reference/streams/scala-2-compatibility.md +0 -55
  165. package/reference/streams/zero-boxing.md +0 -275
  166. package/reference/telemetry.md +0 -693
@@ -0,0 +1,234 @@
1
+ ---
2
+ id: events
3
+ title: "Event Handlers"
4
+ sidebar_label: "Event Handlers"
5
+ ---
6
+
7
+ `dataOn` opens the event side of the attribute DSL: sixteen predefined DOM events, fourteen chainable modifiers, and four case modifiers, all rendering into a single `data-on:<event>__<modifiers>` attribute. Four sibling builders cover triggers that are not DOM events — intersection, interval, signal patches, and initialization. Core types: `DataOn`, `PartialDataOn`, `EventModifier`, `CaseModifier`, `DataOnIntersect`, `DataOnInterval`, `DataOnSignalPatch`, `DataInit`. The shape of the builder:
8
+
9
+ ```scala
10
+ final class DataOn private (name: String, modifiers: Maybe[EventModifier], caseModifier: CaseModifier) {
11
+ def debounce(millis: Long): DataOn
12
+ def once: DataOn
13
+ def :=[T](value: T)(implicit toDatastarExpr: ToDatastarExpr[T]): Dom.Attribute
14
+ }
15
+ ```
16
+
17
+ ## Motivation
18
+
19
+ An event handler in Datastar is an attribute whose name encodes both the event and its options. `data-on:input__debounce.300ms` debounces; `data-on:click__once__prevent` fires once and calls `preventDefault`. The name is structured, order-insensitive between modifiers, and entirely stringly-typed on the wire.
20
+
21
+ Written by hand that is a lot of punctuation to get right, and a modifier that Datastar does not recognize is silently ignored — the handler still fires, just without the debouncing you thought you had. A search box that issues a request per keystroke looks like it works, right up until it doesn't.
22
+
23
+ `dataOn` turns the whole name into a method chain. The event is a method, each modifier is a method, and the attribute name is assembled for you. A modifier that does not exist is a compile error, and the rendered name is correct by construction.
24
+
25
+ ## Quick Showcase
26
+
27
+ Chain modifiers before assigning the handler:
28
+
29
+ ```scala
30
+ import zio.blocks.html._
31
+ import zio.http.datastar._
32
+
33
+ val term = Signal[String]("term")
34
+
35
+ val box = input(
36
+ dataBind(term),
37
+ dataOn.input.debounce(300) := js"@get('/search')"
38
+ )
39
+ ```
40
+
41
+ The modifier becomes part of the attribute name:
42
+
43
+ ```scala
44
+ box.renderMinified
45
+ // res0: String = "<input data-bind:term data-on:input__debounce.300ms=\"@get(&#x27;/search&#x27;)\"/>"
46
+ ```
47
+
48
+ ## Predefined Events
49
+
50
+ Sixteen events have dedicated methods, each returning a `DataOn` ready for modifiers or assignment:
51
+
52
+ | Category | Methods |
53
+ | -------- | ------- |
54
+ | Pointer | `dataOn.click`, `dataOn.mouseover`, `dataOn.mouseout`, `dataOn.mouseenter`, `dataOn.mouseleave` |
55
+ | Keyboard | `dataOn.keydown`, `dataOn.keyup`, `dataOn.keypress` |
56
+ | Form | `dataOn.submit`, `dataOn.input`, `dataOn.change`, `dataOn.focus`, `dataOn.blur` |
57
+ | Window | `dataOn.scroll`, `dataOn.resize`, `dataOn.load` |
58
+
59
+ Each renders as `data-on:<event>`:
60
+
61
+ ```scala
62
+ import zio.blocks.html._
63
+ import zio.http.datastar._
64
+ ```
65
+
66
+ The method name is the event name — `dataOn.click`, `dataOn.submit`, `dataOn.input` — so nothing needs looking up:
67
+
68
+ ```scala
69
+ button(dataOn.click := js"@post('/save')").renderMinified
70
+ // res2: String = "<button data-on:click=\"@post(&#x27;/save&#x27;)\"></button>"
71
+ form(dataOn.submit := js"@post('/submit')").renderMinified
72
+ // res3: String = "<form data-on:submit=\"@post(&#x27;/submit&#x27;)\"></form>"
73
+ ```
74
+
75
+ ### `dataOn.apply` — any other event
76
+
77
+ For an event without a dedicated method, `dataOn(name)` takes it as a string. The name is validated, so a structurally invalid event name fails rather than rendering a broken attribute:
78
+
79
+ ```scala
80
+ div(dataOn("animationend") := js"@get('/done')").renderMinified
81
+ // res4: String = "<div data-on:animationend=\"@get(&#x27;/done&#x27;)\"></div>"
82
+ ```
83
+
84
+ `dataOn` on its own is a `PartialDataOn` — the builder that the event methods live on — so `dataOn` alone is not a complete attribute.
85
+
86
+ ## Event Modifiers
87
+
88
+ Fourteen modifiers chain before `:=`, each contributing a `__` suffix.
89
+
90
+ | Method | Renders | Effect |
91
+ | ------ | ------- | ------ |
92
+ | `debounce(ms)` | `__debounce.<ms>ms` | Fire after quiet period |
93
+ | `debounceLeading(ms)` | `__debounce.<ms>ms.leading` | Fire immediately, then suppress |
94
+ | `throttle(ms)` | `__throttle.<ms>ms` | At most once per interval |
95
+ | `throttleLeading(ms)` | `__throttle.<ms>ms.leading` | Leading-edge throttle |
96
+ | `delay(ms)` | `__delay.<ms>ms` | Wait before firing |
97
+ | `DataOn#once` | `__once` | Fire at most once |
98
+ | `DataOn#passive` | `__passive` | Passive listener |
99
+ | `DataOn#capture` | `__capture` | Capture phase |
100
+ | `DataOn#stop` | `__stop` | `stopPropagation` |
101
+ | `DataOn#prevent` | `__prevent` | `preventDefault` |
102
+ | `DataOn#outside` | `__outside` | Fire on events outside the element |
103
+ | `DataOn#window` | `__window` | Listen on the window object |
104
+ | `DataOn#document` | `__document` | Listen on the document object |
105
+ | `DataOn#viewTransition` | `__viewTransition` | Wrap the resulting patch in a view transition |
106
+
107
+ Modifiers combine, and the rendered name carries each one:
108
+
109
+ ```scala
110
+ import zio.blocks.html._
111
+ import zio.http.datastar._
112
+ ```
113
+
114
+ A click that fires once and suppresses the default action needs two, `DataOn#once` and `DataOn#prevent`:
115
+
116
+ ```scala
117
+ button(dataOn.click.once.prevent := js"@post('/subscribe')").renderMinified
118
+ // res6: String = "<button data-on:click__once__prevent=\"@post(&#x27;/subscribe&#x27;)\"></button>"
119
+ ```
120
+
121
+ `DataOn#outside` is the one worth knowing about: it inverts the target, firing when the event happens anywhere *but* this element — which is how a dropdown closes when you click away:
122
+
123
+ ```scala
124
+ div(dataOn.click.outside := js"$$open = false").renderMinified
125
+ // res7: String = "<div data-on:click__outside=\"$open = false\"></div>"
126
+ ```
127
+
128
+ Repeating a timing or target modifier does not stack: the chain is normalized to the last effective value before the name is rendered, so chaining `DataOn#debounce` twice keeps only the second value.
129
+
130
+ Modifiers are represented by the `EventModifier` ADT — `Debounce`, `Throttle`, `Delay`, `Once`, `Passive`, `Capture`, `Stop`, `Prevent`, `Outside`, `Window`, `Document`, `ViewTransition`, and `And` for combination — but the builder methods are the intended interface.
131
+
132
+ Each trigger has its own modifier ADT, and they are not interchangeable: `EventModifier` for `dataOn`, `IntersectModifier` for `dataOnIntersect` (which adds `Half`, `Full`, `Exit`, and `Threshold`), `OnIntervalModifier` for `dataOnInterval` (`Duration`, `ViewTransition`), `OnSignalPatchModifier` for `dataOnSignalPatch` (`Delay`, `Debounce`, `Throttle`), and `InitModifier` for `dataInit` (`Delay`, `ViewTransition`). Every one of them has an `And` variant, which is how a chain of builder calls accumulates. You never need to construct these directly — the builder methods do it — but they are what a modifier chain is made of.
133
+
134
+ ## Case Modifiers
135
+
136
+ `CaseModifier` controls how the event name is cased in the rendered attribute. Its four variants — `Camel`, `Kebab`, `Snake`, and `Pascal` — are selected by the same-named builder methods `DataOn#camel`, `DataOn#kebab`, `DataOn#snake`, and `DataOn#pascal`. The suffix appears only when the requested case differs from the builder's default, which for `dataOn` is kebab:
137
+
138
+ ```scala
139
+ import zio.blocks.html._
140
+ import zio.http.datastar._
141
+ ```
142
+
143
+ A custom event whose real name is camelCase needs the modifier. Note what it actually does: the attribute key stays kebab-cased either way, and the modifier is what tells Datastar to convert it back:
144
+
145
+ ```scala
146
+ div(dataOn("myCustomEvent").camel := js"@get('/handle')").renderMinified
147
+ // res9: String = "<div data-on:my-custom-event__case.camel=\"@get(&#x27;/handle&#x27;)\"></div>"
148
+ div(dataOn("myCustomEvent") := js"@get('/handle')").renderMinified
149
+ // res10: String = "<div data-on:my-custom-event=\"@get(&#x27;/handle&#x27;)\"></div>"
150
+ ```
151
+
152
+ So `DataOn#camel` does not change the rendered key — it appends `__case.camel` alongside it. The default differs per builder: `dataOn` defaults to kebab, while `dataSignals(signal)` defaults to camel, and a modifier matching the default is omitted. That is why the same case call is a no-op on one and meaningful on the other.
153
+
154
+ Note also that the non-DOM triggers render as their own hyphenated attribute names — `data-on-intersect`, not `data-on:intersect` — since the trigger is the attribute rather than a key on it.
155
+
156
+ ## Non-DOM Triggers
157
+
158
+ Four builders fire on something other than a DOM event. Each has its own modifier set and its own `:=`.
159
+
160
+ ### `dataOnIntersect` — element enters the viewport
161
+
162
+ Fires when the element becomes visible, which is the basis for infinite scroll and lazy loading:
163
+
164
+ | Modifier | Effect |
165
+ | -------- | ------ |
166
+ | `DataOnIntersect#once` | Fire only the first time |
167
+ | `half` | Require 50% visibility |
168
+ | `full` | Require 100% visibility |
169
+ | `exit` | Fire on leaving rather than entering |
170
+ | `threshold(pct)` | Require an explicit visibility fraction |
171
+ | `delay(ms)`, `debounce(ms)`, `throttle(ms)` | Timing control |
172
+ | `DataOnIntersect#viewTransition` | Wrap the patch in a view transition |
173
+
174
+ A sentinel element at the end of a list is the canonical use:
175
+
176
+ ```scala
177
+ import zio.blocks.html._
178
+ import zio.http.datastar._
179
+ ```
180
+
181
+ Loading the next page once, when the sentinel is fully visible:
182
+
183
+ ```scala
184
+ div(dataOnIntersect.once.full := js"@get('/page/2')").renderMinified
185
+ // res12: String = "<div data-on-intersect__once__full=\"@get(&#x27;/page/2&#x27;)\"></div>"
186
+ ```
187
+
188
+ ### `dataOnInterval` — fire on a timer
189
+
190
+ Polls on an interval, with `DataOnInterval#duration` setting the period, `DataOnInterval#durationLeading` firing immediately as well, and `DataOnInterval#viewTransition` wrapping the resulting patch:
191
+
192
+ ```scala
193
+ div(dataOnInterval.duration(5000) := js"@get('/status')").renderMinified
194
+ // res13: String = "<div data-on-interval__duration.5000ms=\"@get(&#x27;/status&#x27;)\"></div>"
195
+ ```
196
+
197
+ Without a `DataOnInterval#duration`, the attribute renders bare and Datastar applies its default interval.
198
+
199
+ ### `dataOnSignalPatch` — react to signal changes
200
+
201
+ Fires when signals are patched, with `DataOnSignalPatch#delay`, `DataOnSignalPatch#debounce`, and `DataOnSignalPatch#throttle` for timing. Pair it with `dataOnSignalPatchFilter` to narrow which signals count:
202
+
203
+ ```scala
204
+ div(dataOnSignalPatch.debounce(200) := js"@get('/recalculate')").renderMinified
205
+ // res14: String = "<div data-on-signal-patch__debounce.200ms=\"@get(&#x27;/recalculate&#x27;)\"></div>"
206
+ ```
207
+
208
+ ### `dataInit` — fire once on load
209
+
210
+ Runs when Datastar first processes the element, with `DataInit#delay` and `DataInit#viewTransition`:
211
+
212
+ ```scala
213
+ div(dataInit := js"@get('/bootstrap')").renderMinified
214
+ // res15: String = "<div data-init=\"@get(&#x27;/bootstrap&#x27;)\"></div>"
215
+ ```
216
+
217
+ This is the hook for fetching initial content that is too expensive to render server-side on first paint.
218
+
219
+ ## Choosing a Trigger
220
+
221
+ | You want to react to | Use |
222
+ | -------------------- | --- |
223
+ | A user interaction | `dataOn.<event>` |
224
+ | An event with no dedicated method | `dataOn("name")` |
225
+ | The element scrolling into view | `dataOnIntersect` |
226
+ | The passage of time | `dataOnInterval` |
227
+ | Signal state changing | `dataOnSignalPatch` |
228
+ | The page loading | `dataInit` |
229
+
230
+ ## Integration Points
231
+
232
+ Every builder here ends in `:=`, which goes through `ToDatastarExpr` and returns a `Dom.Attribute` — see [Attributes](./attributes.md) for that type class and why raw `String` values are rejected. Handler expressions typically read and write [Signals](./signals.md) and call Datastar's `@get`/`@post` actions, which the server answers with [Server-Sent Events](./sse.md).
233
+
234
+ The `js"..."` interpolator producing those expressions, and the `Dom` types the attributes attach to, come from [HTML](../html.md).
@@ -0,0 +1,256 @@
1
+ ---
2
+ id: index
3
+ title: "Datastar"
4
+ sidebar_label: "Datastar"
5
+ ---
6
+
7
+ `zio.http.datastar` builds [Datastar](https://data-star.dev) hypermedia applications: it renders the `data-*` attributes that make a page reactive, and it produces the SSE events that patch that page from the server. Core types: `Signal`, `SignalUpdate`, `DatastarEvent`, `DataOn`, `DatastarAttrKey`, `ToDatastarExpr`, `ElementPatchMode`. The two halves of the module:
8
+
9
+ ```scala
10
+ final class Signal[A] private (val name: String) {
11
+ def :=(value: A)(implicit schema: Schema[A]): SignalUpdate[A]
12
+ def ref: DatastarRef
13
+ }
14
+
15
+ sealed trait DatastarEvent {
16
+ def renderSSE: String
17
+ }
18
+ ```
19
+
20
+ ## Introduction
21
+
22
+ Datastar puts application state in the browser as named *signals*, and drives changes to it declaratively. A page says what it reacts to — "when this button is clicked, POST to `/increment`" — using `data-*` attributes, and the server replies with events that patch elements or signals in place. No client-side framework code, no JSON API to design, no separate view model.
23
+
24
+ This module is the Scala side of that contract. It gives you a typed way to write the attributes, a typed way to name and update signals, and a builder for each kind of SSE event, so both directions of the exchange are checked by the compiler rather than assembled from strings.
25
+
26
+ Two things it deliberately does not do: it does not ship a server, and it does not ship the Datastar JavaScript. It produces `Dom` attributes and SSE payload strings, which you serve with whatever HTTP layer you already have.
27
+
28
+ ## Motivation
29
+
30
+ The natural way to write Datastar from a server language is string interpolation — `s"data-on-click=\"@post('/increment')\""` — and it goes wrong in the usual ways. A misspelled signal name fails silently in the browser. An attribute name typo produces an attribute Datastar ignores. A value that should be a Datastar expression gets a Scala `String`, which renders as a literal instead of an expression, and the page just does nothing.
31
+
32
+ This module closes each of those:
33
+
34
+ - **Signal names are validated, literals at compile time.** `Signal[Int]("count")` checks the name during compilation; `Signal.dynamic` defers the same check to runtime for names you compute.
35
+ - **Raw strings are rejected in expression positions.** `ToDatastarExpr` is deliberately ambiguous for `String`, so passing one is a compile error that names the fix. Expressions come from the `js"..."` interpolator or from typed signals.
36
+ - **Attributes are `Dom.Attribute` values**, so they compose with the rest of the HTML DSL and cannot be misplaced into text content. The helpers also settle the naming convention for you: plain attributes render with a hyphen (`data-text`), and keyed ones with a colon (`data-on:click`, `data-class:active`, `data-computed:total`).
37
+ - **SSE events are builders**, so each event kind offers the options it accepts and rendering emits the exact protocol field names. The one exception is `executeScript`, which returns an element-patch builder and so exposes `selector` and `mode` that do not apply to it.
38
+
39
+ ## Installation
40
+
41
+ Add the module to your build:
42
+
43
+ ```scala
44
+ libraryDependencies += "dev.zio" %% "zio-blocks-datastar" % "0.0.56"
45
+ ```
46
+
47
+ For Scala.js, use `%%%` instead of `%%`:
48
+
49
+ ```scala
50
+ libraryDependencies += "dev.zio" %%% "zio-blocks-datastar" % "0.0.56"
51
+ ```
52
+
53
+ Supported Scala versions: 2.13.x and 3.x.
54
+
55
+ :::note[The package is `zio.http.datastar`]
56
+ Despite living under `zio/blocks/datastar` in the source tree, the package is `zio.http.datastar`. The package object extends `DatastarAttributes`, so a single wildcard import brings every `data*` helper into scope.
57
+ :::
58
+
59
+ ## Overview
60
+
61
+ The module divides along the direction data flows.
62
+
63
+ ### Signals — state that lives in the browser
64
+
65
+ `Signal[A]` is a named, typed handle on a client-side signal. `Signal#:=` pairs it with a value to produce a `SignalUpdate[A]`, serialized through the type's JSON codec. `Signal#ref` produces a `DatastarRef`, the `$name` form used inside expressions. See [Signals](./signals.md).
66
+
67
+ ### Attributes — declaring reactivity on the page
68
+
69
+ `DatastarAttributes` supplies 27 `data*` helpers — `dataText`, `dataShow`, `dataBind`, `dataClass`, `dataComputed`, and the rest — each returning either a `Dom.Attribute` or a `DatastarAttrKey` awaiting a value. `ToDatastarExpr` governs what may be assigned. See [Attributes](./attributes.md).
70
+
71
+ ### Events — reacting to the user
72
+
73
+ `dataOn` opens the event side: sixteen predefined events, fourteen chainable modifiers for debouncing, throttling, and propagation control, and four specialized triggers for intersection, interval, signal-patch, and init. See [Event Handlers](./events.md).
74
+
75
+ ### SSE — patching the page from the server
76
+
77
+ `DatastarEvent` builds the four server-to-browser events: patch elements, patch signals, execute a script, and remove elements. `ElementPatchMode` chooses where content lands, and `EventType` names the protocol event. See [Server-Sent Events](./sse.md).
78
+
79
+ ## How They Work Together
80
+
81
+ A Datastar interaction is a loop, and the module sits on both ends of it:
82
+
83
+ ```
84
+ 1. Server renders HTML html DSL + data* attributes
85
+ 2. Browser becomes reactive Datastar reads data-* and wires up signals
86
+ 3. User acts an event matching a data-on:* fires
87
+ 4. Browser issues a request @post('/increment') from the expression
88
+ 5. Server responds with SSE DatastarEvent.patchSignals / patchElements
89
+ 6. Browser applies the patch signals update, elements morph in place
90
+ └─> back to 3
91
+ ```
92
+
93
+ The types involved at each end:
94
+
95
+ ```
96
+ BROWSER SIDE (rendered into HTML)
97
+
98
+ Signal[A] ──ref──> DatastarRef ──> "$count" used inside js"..." expressions
99
+ │
100
+ └─:=─> SignalUpdate[A] ──> {"count": 42} JSON via Schema[A].jsonCodec
101
+
102
+ dataOn.click ──> DataOn ──modifiers──> DataOn ──:=──> Dom.Attribute
103
+ │ data-on:click__debounce.300ms
104
+ ├─ EventModifier (14: debounce, throttle,
105
+ │ once, passive, stop, …)
106
+ └─ CaseModifier (__case.camel | kebab | snake | pascal)
107
+
108
+ dataText / dataShow / dataClass(…) ──> DatastarAttrKey ──:=──> Dom.Attribute
109
+ ▲
110
+ ToDatastarExpr guards the value:
111
+ js"…" and Signal ok, raw String rejected
112
+
113
+ SERVER SIDE (rendered into an SSE stream)
114
+
115
+ DatastarEvent.patchElements(dom) ──> PatchElementsBuilder ──renderSSE──> String
116
+ │ ├─ selector(CssSelector)
117
+ │ ├─ mode(ElementPatchMode)
118
+ │ ├─ viewTransition / namespace
119
+ │ └─ eventId / retry
120
+ ├─ patchSignals(updates*) ──> PatchSignalsBuilder ├─ onlyIfMissing
121
+ ├─ executeScript(js) ──────> PatchElementsBuilder
122
+ └─ removeElements(sel) ────> RemoveElementsBuilder
123
+
124
+ renderSSE delegates to zio.http.ServerSentEvent, so the result is
125
+ standard SSE with a Datastar-specific event name and data body.
126
+ ```
127
+
128
+ ## Common Patterns
129
+
130
+ Four shapes cover most Datastar work.
131
+
132
+ ### A Counter, End to End
133
+
134
+ The smallest complete loop: a signal, an attribute that displays it, a button that asks the server to change it, and an SSE event that does:
135
+
136
+ ```scala
137
+ import zio.blocks.html._
138
+ import zio.http.datastar._
139
+
140
+ val count = Signal[Int]("count")
141
+
142
+ val page = div(
143
+ dataSignals(count := 0),
144
+ span(dataText := count),
145
+ button(dataOn.click := js"@post('/increment')", "increment")
146
+ )
147
+ ```
148
+
149
+ Rendering gives ordinary HTML that Datastar can read:
150
+
151
+ ```scala
152
+ page.renderMinified
153
+ // res0: String = "<div data-signals=\"{&quot;count&quot;: 0}\"><span data-text=\"$count\"></span><button data-on:click=\"@post(&#x27;/increment&#x27;)\">increment</button></div>"
154
+ ```
155
+
156
+ The handler for `/increment` replies with a signal patch rather than a JSON body:
157
+
158
+ ```scala
159
+ DatastarEvent.patchSignals(count := 1).renderSSE
160
+ // res1: String = """event: datastar-patch-signals
161
+ // data: signals {"count":1}
162
+ //
163
+ // """
164
+ ```
165
+
166
+ ### Deriving State Instead of Storing It
167
+
168
+ `dataComputed` defines a signal whose value is an expression over other signals, so the derived value never has to be kept in sync:
169
+
170
+ ```scala
171
+ import zio.blocks.html._
172
+ import zio.http.datastar._
173
+
174
+ val price = Signal[Double]("price")
175
+ val quantity = Signal[Int]("quantity")
176
+ val total = Signal[Double]("total")
177
+
178
+ val form = div(
179
+ dataSignals(price := 9.99, quantity := 1),
180
+ dataComputed(total) := js"$$price * $$quantity",
181
+ span(dataText := total)
182
+ )
183
+ ```
184
+
185
+ The computed attribute carries the expression, and the browser recomputes it whenever either input changes:
186
+
187
+ ```scala
188
+ form.renderMinified
189
+ // res3: String = "<div data-signals=\"{&quot;price&quot;: 9.99, &quot;quantity&quot;: 1}\" data-computed:total=\"$price * $quantity\"><span data-text=\"$total\"></span></div>"
190
+ ```
191
+
192
+ ### Rate-Limiting a Chatty Event
193
+
194
+ Modifiers chain on `dataOn` before the handler is assigned, which is how a keystroke-driven search avoids one request per character:
195
+
196
+ ```scala
197
+ import zio.blocks.html._
198
+ import zio.http.datastar._
199
+
200
+ val term = Signal[String]("term")
201
+
202
+ val search = input(
203
+ dataBind(term),
204
+ dataOn.input.debounce(300) := js"@get('/search')"
205
+ )
206
+ ```
207
+
208
+ The modifier becomes part of the attribute name, so the browser applies it without any JavaScript of yours:
209
+
210
+ ```scala
211
+ search.renderMinified
212
+ // res5: String = "<input data-bind:term data-on:input__debounce.300ms=\"@get(&#x27;/search&#x27;)\"/>"
213
+ ```
214
+
215
+ ### Patching a Fragment Rather Than the Page
216
+
217
+ `DatastarEvent.patchElements` with a selector and a mode replaces part of the page, leaving the rest — and its signal state — untouched:
218
+
219
+ ```scala
220
+ import zio.blocks.html._
221
+ import zio.http.datastar._
222
+
223
+ val row = tr(td("Widget"), td("in stock"))
224
+
225
+ val event = DatastarEvent
226
+ .patchElements(row)
227
+ .selector(CssSelector.id("inventory"))
228
+ .mode(ElementPatchMode.Append)
229
+ .renderSSE
230
+ ```
231
+
232
+ The rendered event names the selector and mode as protocol fields:
233
+
234
+ ```scala
235
+ event
236
+ // res7: String = """event: datastar-patch-elements
237
+ // data: selector #inventory
238
+ // data: mode append
239
+ // data: elements <tr><td>Widget</td><td>in stock</td></tr>
240
+ //
241
+ // """
242
+ ```
243
+
244
+ ## Integration Points
245
+
246
+ The module is a thin typed layer over three other blocks, and adds no dependency of its own:
247
+
248
+ - **`zio-blocks-html`** supplies `Dom`, `Dom.Attribute`, `Js`, `CssSelector`, and the `ToJs` type class. Every attribute helper returns a `Dom.Attribute`, so Datastar attributes compose with the HTML DSL exactly like `id` or `class` — see [HTML](../html.md).
249
+ - **`zio-blocks-schema`** supplies the JSON codec behind `Signal#:=`. A signal of type `A` needs a `Schema[A]`, and the serialized form is whatever that schema's JSON codec produces — see [Schema](../schema/index.md).
250
+ - **`zio-http-model`** supplies `ServerSentEvent`, which `DatastarEvent#renderSSE` delegates to for the wire format. The Datastar-specific part is the event name and the structured `data:` body — see [ServerSentEvent](../http-model/server-sent-event.md).
251
+
252
+ Within the module, the dependency direction is one-way: attributes and events both consume `Signal`, and neither knows about the other. Nothing in the SSE layer reads the attribute DSL.
253
+
254
+ ## Next Steps
255
+
256
+ Start with [Signals](./signals.md), which every other page builds on, then [Attributes](./attributes.md) for the declarative surface and [Event Handlers](./events.md) for triggers. [Server-Sent Events](./sse.md) covers the server half.