Release v20.60.1
Summary
Command and query validation had drifted apart. A validator behaved differently — or not at all — depending on
whether the value it guarded arrived on a command or as a query argument, and some rules ran only in the browser.
Whether a value is well formed is a property of its type, not of the operation carrying it, so both pipelines now
share one traversal and the client is generated from the same rules the server enforces.
Added
ConceptValidator<T>rules are now generated into the client, so a concept declared once is validated in the browser everywhere it appears on a command property or query parameter
Changed
- Query arguments are validated with the same traversal as command properties: a validator nested inside an argument, inside a collection, or on a concept now runs. Previously it only ran when the argument's own type had a validator
- Queries that carry a validator which previously never ran will now reject invalid arguments. Requests that succeeded before may return 400
- Observable queries now run client-side validation, on both
perform()andsubscribe(). The rules were always generated but the proxy discarded them, so an observable query carrying a validator begins rejecting on upgrade — including asubscribe()started before its arguments are available, which anotEmptyrule will now reject. Gate such subscriptions withObservableQueryWhen. A rejected subscription delivers an invalid result to the subscriber instead of an empty one, so "these arguments are wrong" is distinguishable from "no data yet" - Validation failures now report member names the way the client does — camelCased, and attributed to the field rather than to a concept's inner value. A failure previously reported as
Emailoremail.Valueis nowemail. This applies to commands as well as queries, and is what lets a form match a server rejection to the field that caused it - Query validators resolve from the request scope, so they can depend on scoped services
Fixed
- A validator that throws while validating a query is reported as a validation failure rather than a server error
- A query taking an injected dependency alongside its arguments now has its client-side rules generated; the shape check previously required the argument model to mirror the dependency too, and silently emitted no rules
- A concept reachable both through a query's argument model and through the parameter itself no longer has its rule, and its message, emitted twice
- A query with no arguments no longer binds an unrelated same-named
Parameterstype DateOnlyandTimeOnlyvalues are no longer walked as though their calendar components were part of the model- Validation rules carrying a string argument (such as
Matches) or a message containing an apostrophe now generate valid TypeScript
Security
- Validators declared against a query's argument set are now enforced on the server. They were previously applied only by the generated client proxy, so a request made directly to the endpoint bypassed them entirely