bug(datastar): `readSignals` reads GET signals from header instead of query parameter
## Bug Description
`readSignals` for GET requests reads signals from the `datastar` **header**, but the Datastar JS client sends them as a **query parameter** `?datastar={...}`.
## Current Behavior (wrong)
```scala
// DatastarPackageBase.scala
def readSignals[T: Schema](request: Request): IO[String, T] =
if (request.method == Method.GET) {
ZIO.fromEither {
request
.header[String]("datastar") // ← reads from HEADER
```
## Expected Behavior
For GET requests, Datastar sends signals as a query parameter, not a header.
From the Datastar source ([`fetch.ts:113-115`](https://github.com/starfederation/datastar/blob/main/library/src/plugins/actions/fetch.ts)):
```typescript
if (method === 'GET') {
queryParams.set('datastar', body) // ← sends as QUERY PARAMETER
} else {
req.body = body // ← body for POST/PUT/PATCH/DELETE
}
```
The `Datastar-Request: true` header is just a boolean flag indicating the request comes from Datastar — it does NOT contain the signal data.
## Fix
```scala
def readSignals[T: Schema](request: Request): IO[String, T] =
if (request.method == Method.GET) {
ZIO.fromEither {
request
.queryParam("datastar") // ← read from query parameter
.toRight(new Exception("Missing 'datastar' query parameter"))
.left.map(_.getMessage())
.flatMap(_.fromJson[T](zio.schema.codec.JsonCodec.jsonDecoder(Schema[T])))
}
} else {
request.body.asJson[T].mapError(_.getMessage)
}
```
## Test Fix
`ReadSignalsSpec` also needs to be updated — it currently adds signals as a header in test requests, which matches the buggy implementation:
```scala
// Current (wrong — matches buggy code):
val request = Request
.get(URL.root / "test")
.addHeader(Header.Custom("datastar", jsonData))
// Should be:
val request = Request
.get((URL.root / "test").withQueryParams(QueryParams("datastar" -> jsonData)))
```
## Additional Bugs Found
While reviewing the datastar SDK, the following additional bugs were identified:
1. **`DatastarRetry` is missing 2 of 4 valid values** — Datastar supports `'auto' | 'error' | 'always' | 'never'` but the SDK only models `Auto` and `Error`.
2. **`DatastarRequestCancellation` has invalid `Cleanup` value** — Datastar only supports `'auto' | 'disabled' | AbortController`. Also uses PascalCase (`"Auto"`) but Datastar expects lowercase (`"auto"`).
3. **`Signal.ref` throws for non-primitive types** — Datastar `$signalName` works for any type. The runtime restriction is incorrect.
4. **`SignalUpdate.astToExpression` doesn't escape `'` in strings** — generates broken JS for values containing single quotes.
关闭于 2026-03-14 0 条评论