Skip to content

Commit 762191c

Browse files
committed
docs:Improve 0.18 blog post
1 parent d0aa990 commit 762191c

1 file changed

Lines changed: 41 additions & 32 deletions

File tree

website/blog/2026-04-24-v0.18-scalar-typed-downloads.md

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ title: 'v0.18: Scalar, Typed File Downloads, Filter-aware Collections'
33
description: Lens-dependent entity fields with Scalar, typed file downloads via RestEndpoint content, automatic binary handling, and resource() nonFilterArgumentKeys for sort-aware collections
44
authors: [ntucker]
55
tags: [releases, rest, schema, endpoint, collection]
6-
draft: true
76
---
87

98
import 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
4348
selected portfolio, currency, or locale. Multiple components can render the **same** entity through
4449
different lenses simultaneously, each seeing the correct values, while the entity itself never
4550
changes. Lens-dependent values live in a separate cell table and are joined at denormalize time
4651
from 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
5356
standalone inside [`Values`](/rest/api/Values), [`[Scalar]`](/rest/api/Array),
5457
or [`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
5760
composite primary keys work without an override.
5861

5962
The 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
7073
entities['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
7477
to the same cell table — see the [Scalar documentation](/rest/api/Scalar) for a full walkthrough
7578
of 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)
7982
the 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
113116
Setting `content` to a non-JSON value enforces `schema: undefined` at the type level, since binary data cannot be
114117
normalized. 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)
136139
declare which argument keys are *not* used to filter the collection results — typically sort,
137140
pagination, 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
161164
shared — `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:
167170
nonFilterArgumentKeys: /^(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

199202
The 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

206215
Skip 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

243252
The codemod handles class methods, object methods, function declarations, TypeScript interface
244253
signatures, 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
252261
cache 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

292301
Skip 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.
296305
The previous signature was `(input, parent, key, args, visit, delegate, parentEntity?)`;
297306
the 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
357366
at 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
364373
definition:
365374

366375
<DiffEditor>

0 commit comments

Comments
 (0)