@@ -3,11 +3,12 @@ title: 'v0.18: Scalar, Typed File Downloads, Filter-aware Collections'
33description : Lens-dependent entity fields with Scalar, typed file downloads via RestEndpoint content, automatic binary handling, and resource() nonFilterArgumentKeys for sort-aware collections
44authors : [ntucker]
55tags : [releases, rest, schema, endpoint, collection]
6- draft : true
76---
87
98import ScalarDemo from '../../docs/rest/shared/\_ ScalarDemo.mdx';
109
10+ v0.18 focuses on richer data modeling for values that vary by request context, simpler typed downloads, and collection matching that better reflects real API filters.
11+
1112** New Features:**
1213
1314- [ Scalar schema] ( /blog/2026/04/24/v0.18-scalar-typed-downloads#scalar ) - Lens-dependent entity fields (e.g. portfolio-specific values) without ever mutating the underlying entity
@@ -17,8 +18,12 @@ import ScalarDemo from '../../docs/rest/shared/\_ScalarDemo.mdx';
1718** Other Improvements:**
1819
1920- [ Binary Content-Type auto-detection] ( /blog/2026/04/24/v0.18-scalar-typed-downloads#binary-auto-detection ) - Images, PDFs, and other binary responses are handled automatically with no configuration ([ #3868 ] ( https://github.com/reactive/data-client/pull/3868 ) )
20- - [ Collection extender body types match HTTP method semantics] ( /rest/api/Collection ) - PATCH extenders (` .move ` , ` .remove ` ) accept partial bodies; standalone ` RestEndpoint ` derives a typed body from the Collection's entity schema ([ #3910 ] ( https://github.com/reactive/data-client/pull/3910 ) )
21- - Export [ ` CollectionOptions ` ] ( /rest/api/Collection ) from ` @data-client/endpoint ` and ` @data-client/rest ` for typed Collection construction ([ #3904 ] ( https://github.com/reactive/data-client/pull/3904 ) )
21+ - [ Collection extender body types match HTTP method semantics] ( /rest/api/Collection ) - PATCH extenders (` .move ` , ` .remove ` ) accept partial bodies; standalone [ RestEndpoint] ( /rest/api/RestEndpoint ) derives a typed body from the [ Collection] ( /rest/api/Collection ) 's entity schema ([ #3910 ] ( https://github.com/reactive/data-client/pull/3910 ) )
22+ - Export [ ` CollectionOptions ` ] ( /rest/api/Collection ) from ` @data-client/endpoint ` and ` @data-client/rest ` for typed [ Collection] ( /rest/api/Collection ) construction ([ #3904 ] ( https://github.com/reactive/data-client/pull/3904 ) )
23+
24+ <ScalarDemo />
25+
26+ The example shows the same ` Company ` entities rendered through different portfolio lenses. Stable [ Entity] ( /rest/api/Entity ) fields are reused, while lens-dependent values are selected from separate [ ` Scalar ` ] ( /rest/api/Scalar ) cells.
2227
2328** [ Breaking Changes:] ( /blog/2026/04/24/v0.18-scalar-typed-downloads#migration-guide ) **
2429
@@ -39,21 +44,19 @@ import SkillTabs from '@site/src/components/SkillTabs';
3944
4045## Scalar {#scalar}
4146
42- [ Scalar] ( /rest/api/Scalar ) handles entity fields whose values depend on a runtime "lens" — like the
47+ [ Scalar] ( /rest/api/Scalar ) handles [ Entity ] ( /rest/api/Entity ) fields whose values depend on a runtime "lens" — like the
4348selected portfolio, currency, or locale. Multiple components can render the ** same** entity through
4449different lenses simultaneously, each seeing the correct values, while the entity itself never
4550changes. Lens-dependent values live in a separate cell table and are joined at denormalize time
4651from endpoint args.
4752
48- <ScalarDemo />
49-
5053` name ` and ` price ` references stay stable across portfolio switches because the ` Company ` entity
51- itself never changes — only the ` Scalar ` cell selected by the current lens does. A single ` Scalar `
52- instance can serve both as an ` Entity.schema ` field (parent entity inferred from the visit) and
54+ itself never changes — only the [ ` Scalar ` ] ( /rest/api/Scalar ) cell selected by the current lens does. A single [ ` Scalar ` ] ( /rest/api/Scalar )
55+ instance can serve both as an [ ` Entity.schema ` ] ( /rest/api/Entity#schema ) field (parent entity inferred from the visit) and
5356standalone inside [ ` Values ` ] ( /rest/api/Values ) , [ ` [Scalar] ` ] ( /rest/api/Array ) ,
5457or [ ` Collection([Scalar]) ` ] ( /rest/api/Collection ) for cheap column-only refreshes (entity bound
55- explicitly via ` entity ` ). Cell pks are derived from the map key or via
56- [ ` Scalar.entityPk() ` ] ( /rest/api/Scalar#entityPk ) , which defaults to ` Entity.pk() ` so custom and
58+ explicitly via [ ` entity ` ] ( /rest/api/Scalar#entity ) ). Cell pks are derived from the map key or via
59+ [ ` Scalar.entityPk() ` ] ( /rest/api/Scalar#entityPk ) , which defaults to [ ` Entity.pk() ` ] ( /rest/api/Entity#pk ) so custom and
5760composite primary keys work without an override.
5861
5962The normalized store keeps the entity stable and the lens cells separate:
@@ -70,11 +73,11 @@ entities['Scalar(portfolio)']['Company|1|A'] = { pct_equity: 0.50, shares: 10000
7073entities['Scalar(portfolio)']['Company|1|B'] = { pct_equity: 0.30, shares: 6000 }
7174```
7275
73- The demo above pairs ` getCompanies ` with a cheap ` getPortfolioColumns ` lens-only refresh writing
76+ The demo pairs ` getCompanies ` with a cheap ` getPortfolioColumns ` lens-only refresh writing
7477to the same cell table — see the [ Scalar documentation] ( /rest/api/Scalar ) for a full walkthrough
7578of the caching behavior.
7679
77- ` Scalar ` also implements [ queryKey] ( /rest/api/Scalar#queryKey ) so it participates directly in
80+ [ ` Scalar ` ] ( /rest/api/Scalar ) also implements [ queryKey] ( /rest/api/Scalar#queryKey ) so it participates directly in
7881[ useQuery] ( /docs/api/useQuery ) , [ Controller.get] ( /docs/api/Controller#get ) , and [ schema.Query] ( /rest/api/Query ) —
7982the full [ Queryable] ( /rest/api/schema#queryable ) surface — enumerating its cells for the current lens.
8083
@@ -113,7 +116,7 @@ Previously, file downloads required a verbose `parseResponse` override. Now it's
113116Setting ` content ` to a non-JSON value enforces ` schema: undefined ` at the type level, since binary data cannot be
114117normalized. A runtime check provides a clear error message if a normalizable schema is accidentally used.
115118
116- Works with ` extend() ` and subclasses:
119+ Works with [ ` extend() ` ] ( /rest/api/RestEndpoint#extend ) and [ subclasses] ( /rest/api/RestEndpoint#inheritance ) :
117120
118121``` typescript
119122// Extend a JSON endpoint into a blob downloader
@@ -132,7 +135,7 @@ class BlobEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
132135
133136## resource() ` nonFilterArgumentKeys ` {#non-filter-argument-keys}
134137
135- [ ` nonFilterArgumentKeys ` ] ( /rest/api/Collection#nonfilterargumentkeys ) lets [ resource()] ( /rest/api/resource )
138+ [ ` nonFilterArgumentKeys ` ] ( /rest/api/Collection#nonFilterArgumentKeys ) lets [ resource()] ( /rest/api/resource )
136139declare which argument keys are * not* used to filter the collection results — typically sort,
137140pagination, or display options. Without it, every distinct sort or page args would create a separate
138141[ Collection] ( /rest/api/Collection ) bucket, and newly created items wouldn't appear in lists with
@@ -157,7 +160,7 @@ const PostResource = resource({
157160});
158161```
159162
160- Now ` group ` and ` author ` are filter keys (they bucket separate Collections), but ` orderBy ` is
163+ Now ` group ` and ` author ` are filter keys (they bucket separate [ Collections] ( /rest/api/Collection ) ), but ` orderBy ` is
161164shared — ` PostResource.getList.push() ` adds the new post to * all* ` orderBy ` variants of a given
162165` { group, author } ` bucket.
163166
@@ -167,7 +170,7 @@ Accepts a `string[]`, `RegExp`, or predicate function:
167170nonFilterArgumentKeys : / ^ (orderBy| page| cursor)$ / ,
168171```
169172
170- [ #3914 ] ( https://github.com/reactive/data-client/pull/3914 ) - [ ` nonFilterArgumentKeys ` docs] ( /rest/api/Collection#nonfilterargumentkeys )
173+ [ #3914 ] ( https://github.com/reactive/data-client/pull/3914 ) - [ ` nonFilterArgumentKeys ` docs] ( /rest/api/Collection#nonFilterArgumentKeys )
171174
172175## Binary Content-Type auto-detection {#binary-auto-detection}
173176
@@ -197,15 +200,21 @@ This upgrade requires updating all package versions simultaneously.
197200<PkgTabs pkgs =" @data-client/react@^0.18.0 @data-client/rest@^0.18.0 @data-client/endpoint@^0.18.0 @data-client/core@^0.18.0 @data-client/vue@^0.18.0 @data-client/test@^0.18.0 @data-client/img@^0.18.0 " upgrade />
198201
199202The breaking changes in this release affect only ** custom [ Schema] ( /rest/api/CustomSchema ) implementations** .
200- If you only use built-in schemas (` Entity ` , ` resource() ` , ` Collection ` , ` Union ` , ` Values ` , ` Array ` ,
201- ` Object ` , ` Query ` , ` Invalidate ` , ` Lazy ` , ` Scalar ` ), the upgrade is drop-in. Otherwise, run the
202- codemod from the [ hero] ( #scalar ) above to migrate custom schemas, then read on for the manual cases.
203+ If you only use [ ` resource() ` ] ( /rest/api/resource ) and built-in schemas ([ ` Entity ` ] ( /rest/api/Entity ) ,
204+ [ ` Collection ` ] ( /rest/api/Collection ) , [ ` Union ` ] ( /rest/api/Union ) , [ ` Values ` ] ( /rest/api/Values ) ,
205+ [ ` Array ` ] ( /rest/api/Array ) , [ ` Object ` ] ( /rest/api/Object ) , [ ` Query ` ] ( /rest/api/Query ) ,
206+ [ ` Invalidate ` ] ( /rest/api/Invalidate ) , [ ` Lazy ` ] ( /rest/api/Lazy ) , [ ` Scalar ` ] ( /rest/api/Scalar ) ), the upgrade is drop-in. Otherwise,
207+ run the codemod to migrate custom schemas, then read on for the manual cases:
208+
209+ ``` bash
210+ npx jscodeshift -t https://dataclient.io/codemods/v0.18.js --extensions=ts,tsx,js,jsx src/
211+ ```
203212
204213### Schema.denormalize() takes a delegate {#denormalize-delegate}
205214
206215Skip this section if you don't implement custom [ Schema] ( /rest/api/CustomSchema ) classes.
207216
208- ` Schema.denormalize() ` is now ` (input, delegate) ` instead of the previous 3-parameter
217+ [ ` Schema.denormalize() ` ] ( /rest/api/CustomSchema ) is now ` (input, delegate) ` instead of the previous 3-parameter
209218` (input, args, unvisit) ` signature. The new
210219[ ` IDenormalizeDelegate ` ] ( /rest/api/CustomSchema ) exposes ` unvisit ` , ` args ` , and a new ` argsKey(fn) ` helper.
211220[ #3887 ] ( https://github.com/reactive/data-client/pull/3887 )
@@ -242,13 +251,13 @@ class Wrapper {
242251
243252The codemod handles class methods, object methods, function declarations, TypeScript interface
244253signatures, and pass-through ` someSchema.denormalize(input, args, unvisit) ` calls. It also adds
245- the ` IDenormalizeDelegate ` import as an inline ` type ` specifier on your existing
254+ the [ ` IDenormalizeDelegate ` ] ( /rest/api/CustomSchema ) import as an inline ` type ` specifier on your existing
246255` @data-client/* ` import (or creates a new ` import type ` line if none is found).
247256
248257#### args-dependent output
249258
250- Reading ` delegate.args ` directly does ** not** contribute to memoization. If your schema's * output*
251- depends on those args (e.g. a lens), declare the dependency through ` delegate.argsKey(fn) ` so the
259+ Reading [ ` delegate.args ` ] ( /rest/api/CustomSchema ) directly does ** not** contribute to memoization. If your schema's * output*
260+ depends on those args (e.g. a lens), declare the dependency through [ ` delegate.argsKey(fn) ` ] ( /rest/api/CustomSchema ) so the
252261cache buckets correctly. The codemod cannot infer this — update by hand.
253262
254263` argsKey ` returns ` fn(args) ` for convenience, and the function reference doubles as the cache path
@@ -291,8 +300,8 @@ AI-assisted migration is also available:
291300
292301Skip this section if you don't implement custom [ Schema] ( /rest/api/CustomSchema ) classes.
293302
294- ` Schema.normalize() ` now reads endpoint args and the recursive visitor from
295- [ ` INormalizeDelegate ` ] ( /rest/api/CustomSchema ) , matching the new denormalize delegate shape.
303+ [ ` Schema.normalize() ` ] ( /rest/api/CustomSchema ) now reads endpoint args and the recursive visitor from
304+ [ ` INormalizeDelegate ` ] ( /rest/api/CustomSchema ) , matching the new [ denormalize delegate] ( /rest/api/CustomSchema ) shape.
296305The previous signature was ` (input, parent, key, args, visit, delegate, parentEntity?) ` ;
297306the new signature is ` (input, parent, key, delegate, parentEntity?) ` .
298307[ #3934 ] ( https://github.com/reactive/data-client/pull/3934 )
@@ -346,21 +355,21 @@ normalize(input, parent, key, delegate, parentEntity) {
346355}
347356```
348357
349- ` delegate.visit() ` already carries the same args for recursive normalization, so nested
350- schemas do not need args passed explicitly.
358+ [ ` delegate.visit() ` ] ( /rest/api/CustomSchema ) already carries the same args for recursive normalization, so nested
359+ [ schemas] ( /rest/api/schema ) do not need args passed explicitly.
351360
352361#### Additive: ` parentEntity ` on normalize
353362
354- The normalize delegate's ` visit() ` callback internally tracks the nearest enclosing
355- entity-like schema and forwards it to ` Schema.normalize() ` as the optional trailing
356- ` parentEntity ` argument. New schemas can opt in to discover their containing entity
363+ The [ normalize delegate] ( /rest/api/CustomSchema ) 's ` visit() ` callback internally tracks the nearest enclosing
364+ entity-like schema and forwards it to [ ` Schema.normalize() ` ] ( /rest/api/CustomSchema ) as the optional trailing
365+ ` parentEntity ` argument. New [ schemas] ( /rest/api/schema ) can opt in to discover their containing entity
357366at normalize time (used internally by [ Scalar] ( /rest/api/Scalar ) ).
358367
359368#### Optional: consolidate Collection definitions {#collection-consolidation}
360369
361- ` Collection ` can now use both [ ` argsKey ` ] ( /rest/api/Collection#argsKey ) and
370+ [ ` Collection ` ] ( /rest/api/Collection ) can now use both [ ` argsKey ` ] ( /rest/api/Collection#argsKey ) and
362371[ ` nestKey ` ] ( /rest/api/Collection#nestKey ) on the same instance. As an optional
363- cleanup, replace paired top-level and nested Collections with one shared
372+ cleanup, replace paired top-level and nested [ Collections] ( /rest/api/Collection ) with one shared
364373definition:
365374
366375<DiffEditor >
0 commit comments