@data-client/rest
Version:
Quickly define typed REST resources and endpoints
1,110 lines (797 loc) • 82.2 kB
Markdown
# @data-client/rest
## 0.18.1
### Patch Changes
- [`adb82f1`](https://github.com/reactive/data-client/commit/adb82f15c1959ecfc09565a519d2672a2d63178c) - RestEndpoint.remove.name is 'partialUpdate' since it uses 'PATCH'
## 0.18.0
### Minor Changes
- [#3931](https://github.com/reactive/data-client/pull/3931) [`959465a`](https://github.com/reactive/data-client/commit/959465a064db687176e483932987b083f19718eb) - Allow one `Collection` schema to be used both top-level and nested.
Before:
```ts
const getTodos = new Collection([Todo], { argsKey });
const userTodos = new Collection([Todo], { nestKey });
```
After:
```ts
const userTodos = new Collection([Todo], { argsKey, nestKey });
```
- [#3887](https://github.com/reactive/data-client/pull/3887) [`84078d7`](https://github.com/reactive/data-client/commit/84078d7d36bf5cf0fd16a479ce16c48c5d804f32) - **BREAKING**: `Schema.denormalize()` is now `(input, delegate)` instead
of the previous `(input, args, unvisit)` 3-parameter signature.
```ts
// before
denormalize(input, args, unvisit) {
return unvisit(this.schema, input);
}
// after
denormalize(input, delegate) {
return delegate.unvisit(this.schema, input);
}
```
The new [`IDenormalizeDelegate`](https://dataclient.io/rest/api/SchemaSimple)
exposes `unvisit`, `args`, and a new `argsKey(fn)` helper that registers
a memoization dimension when output varies with endpoint args. Reading
`delegate.args` directly does _not_ contribute to cache invalidation —
schemas that branch on args must call `argsKey`. The `fn` reference
doubles as the cache path key, so it must be **referentially stable**
— define it on the instance or at module scope, not inline per call:
```ts
class LensSchema {
constructor({ lens }) {
this.lensSelector = lens; // stable reference across calls
}
denormalize(input, delegate) {
const portfolio = delegate.argsKey(this.lensSelector);
return this.lookup(input, portfolio);
}
}
```
All built-in schemas (`Array`, `Object`, `Values`, `Union`, `Query`,
`Invalidate`, `Lazy`, `Collection`) have been updated. Custom schemas
implementing `SchemaSimple` must update their `denormalize` signature.
`Schema.normalize()` and the `visit()` callback also gain an optional
trailing `parentEntity` argument tracking the nearest enclosing
entity-like schema. This is additive — existing schemas don't need
changes unless they want to use it.
- [#3887](https://github.com/reactive/data-client/pull/3887) [`84078d7`](https://github.com/reactive/data-client/commit/84078d7d36bf5cf0fd16a479ce16c48c5d804f32) - Add [Scalar](https://dataclient.io/rest/api/Scalar) schema for lens-dependent entity fields.
`Scalar` models entity fields whose values vary by a runtime "lens" (such as the
selected portfolio, currency, or locale). Multiple components can render the
same entity through different lenses simultaneously — each sees the correct
values without the entity itself ever being mutated. Lens-dependent values are
stored in a separate cell table and joined at denormalize time from endpoint
args.
New exports: `Scalar`, `schema.Scalar`.
A single `Scalar` instance can serve both as an `Entity.schema` field (parent
entity inferred from the visit) and standalone — inside `Values(Scalar)`,
`[Scalar]`, or `Collection([Scalar])` — for cheap column-only refreshes
(entity bound explicitly via `entity`). Cell pks are derived from the map key
or via `Scalar.entityPk()`, which defaults to `Entity.pk()` so custom and
composite primary keys work with no override:
```ts
import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest';
class Company extends Entity {
id = '';
price = 0;
pct_equity = 0;
shares = 0;
}
const PortfolioScalar = new Scalar({
lens: args => args[0]?.portfolio,
key: 'portfolio',
entity: Company,
});
Company.schema = {
pct_equity: PortfolioScalar,
shares: PortfolioScalar,
};
// Full load — Company rows + scalar cells for the current portfolio
export const getCompanies = new RestEndpoint({
path: '/companies',
searchParams: {} as { portfolio: string },
schema: new Collection([Company], { argsKey: () => ({}) }),
});
// Lens-only refresh — writes to the same Scalar(portfolio) cell table
export const getPortfolioColumns = new RestEndpoint({
path: '/companies/columns',
searchParams: {} as { portfolio: string },
schema: new Collection([PortfolioScalar], {
argsKey: ({ portfolio }) => ({ portfolio }),
}),
});
```
`useSuspense(getCompanies, { portfolio: 'A' })` and
`useSuspense(getCompanies, { portfolio: 'B' })` resolve to different
`pct_equity` / `shares` while sharing the same `Company` row.
`Scalar.queryKey` enumerates cells in its table for the current lens, so
endpoints that use `Scalar` directly as their top-level schema reconstruct
from cache without a network round-trip once the cells are present.
### Patch Changes
- [#3925](https://github.com/reactive/data-client/pull/3925) [`6e8e499`](https://github.com/reactive/data-client/commit/6e8e499441741b58ad35127b517e8d83fc7a58fd) - Fix cached journey being mutated on repeated result-cache hits.
`GlobalCache.getResults` called `paths.shift()` on a cache hit, mutating
the `journey` array stored by reference on the `WeakDependencyMap` `Link`
node. After the first hit stripped the placeholder input slot, every
subsequent hit on the same cached entry would shift off a real
`EntityPath`, progressively losing subscription entries. This could cause
missed `countRef` tracking (premature GC of still-referenced entities)
and incorrect `entityExpiresAt` calculations. The hit path now returns a
non-mutating copy.
- Updated dependencies [[`959465a`](https://github.com/reactive/data-client/commit/959465a064db687176e483932987b083f19718eb), [`84078d7`](https://github.com/reactive/data-client/commit/84078d7d36bf5cf0fd16a479ce16c48c5d804f32), [`6e8e499`](https://github.com/reactive/data-client/commit/6e8e499441741b58ad35127b517e8d83fc7a58fd), [`396d163`](https://github.com/reactive/data-client/commit/396d163e6f4991818519ec33d903a85437483dfd), [`84078d7`](https://github.com/reactive/data-client/commit/84078d7d36bf5cf0fd16a479ce16c48c5d804f32)]:
- @data-client/endpoint@0.18.0
## 0.17.0
### Minor Changes
- [#3914](https://github.com/reactive/data-client/pull/3914) [`930c8ed`](https://github.com/reactive/data-client/commit/930c8ed04b6f9fcb2d131f39cc706a5eeb0edc4d) - resource() accepts [nonFilterArgumentKeys](https://dataclient.io/rest/api/Collection#nonfilterargumentkeys)
```ts
const PostResource = resource({
path: '/:group/posts/:id',
searchParams: {} as { orderBy?: string; author?: string },
schema: Post,
nonFilterArgumentKeys: ['orderBy'],
});
```
## 0.16.6
### Patch Changes
- [#3868](https://github.com/reactive/data-client/pull/3868) [`8a7c8d9`](https://github.com/reactive/data-client/commit/8a7c8d9cc4228a6281849f56121699f237af4b0f) - Add `content` property to RestEndpoint for typed response parsing
Set `content` to control how the response body is parsed, with automatic return type inference:
```ts
const downloadFile = new RestEndpoint({
path: '/files/:id/download',
content: 'blob',
dataExpiryLength: 0,
});
const blob: Blob = await ctrl.fetch(downloadFile, { id: '123' });
```
Accepted values: `'json'`, `'blob'`, `'text'`, `'arrayBuffer'`, `'stream'`.
Non-JSON content types (`'blob'`, `'text'`, `'arrayBuffer'`, `'stream'`) constrain `schema` to
`undefined` at the type level, with a runtime check that throws if a normalizable schema is set.
When `content` is not set, auto-detection now handles binary Content-Types (`image/*`,
`application/octet-stream`, `application/pdf`, etc.) by returning `response.blob()` instead of
corrupting data via `.text()`.
- [#3904](https://github.com/reactive/data-client/pull/3904) [`8af3d5e`](https://github.com/reactive/data-client/commit/8af3d5ee3dbbb637805e99cbd01801d3587bedbd) - Export `CollectionOptions` from the public `@data-client/endpoint` and `@data-client/rest` entrypoints.
- [#3910](https://github.com/reactive/data-client/pull/3910) [`81c63a0`](https://github.com/reactive/data-client/commit/81c63a00c668d805b6b8d0d9efdf2aacb5d32253) - Fix [Collection](https://dataclient.io/rest/api/Collection) extender body types to match their HTTP method semantics
PATCH extenders (`.move`, `.remove`) now type their body as `Partial`, matching
[`partialUpdate`](https://dataclient.io/rest/api/resource#partialupdate). Previously they
required the full body type even though they are PATCH endpoints.
Standalone `RestEndpoint` without an explicit `body` option now derives a typed
body from the Collection's entity schema instead of falling back to `any`.
```ts
const MyResource = resource({
path: '/articles/:id',
schema: Article,
body: {} as { title: string; content: string },
});
// move (PATCH) now accepts partial body
MyResource.getList.move({ id: '1' }, { title: 'new title' });
// push (POST) still requires full body
MyResource.getList.push({ title: 'hi', content: 'there' });
```
- Updated dependencies [[`8af3d5e`](https://github.com/reactive/data-client/commit/8af3d5ee3dbbb637805e99cbd01801d3587bedbd)]:
- @data-client/endpoint@0.16.6
## 0.16.5
### Patch Changes
- [#3866](https://github.com/reactive/data-client/pull/3866) [`16f5d92`](https://github.com/reactive/data-client/commit/16f5d92598de05e92b88af98a9d63eecf27ab819) - Bundle `path-to-regexp` as tree-shaken ESM
Bundle only the functions we use (`compile`, `parse`, `pathToRegexp`) from
`path-to-regexp` into the ESM/browser build via rollup. This eliminates
the CJS/ESM boundary that broke StackBlitz WebContainers and reduces bundle
size by tree-shaking unused exports (`match`, `stringify`, and the `ID` regex).
## 0.16.4
### Patch Changes
- [#3862](https://github.com/reactive/data-client/pull/3862) [`a214720`](https://github.com/reactive/data-client/commit/a214720fd12e1360d00e194b389aa82aff6b91fd) - Fix StackBlitz WebContainers compatibility with path-to-regexp
Use namespace import (`import *`) instead of named imports for the CJS
`path-to-regexp` dependency. Named imports trigger webpack's per-export
presence validation, which fails in StackBlitz's WebContainers environment.
Namespace imports defer property access to runtime, bypassing the check
with no tree-shaking loss.
## 0.16.3
### Patch Changes
- [#3858](https://github.com/reactive/data-client/pull/3858) [`c83a81d`](https://github.com/reactive/data-client/commit/c83a81d4183b4b0f7c3a41f5b49fdba890406a6b) - Fix `maxEntityDepth` missing from Entity types on TypeScript 4.0
`maxEntityDepth` was not included in the TS 4.0 legacy type definitions,
so TypeScript 4.0 users could not set or reference this property on Entity classes.
- [#3860](https://github.com/reactive/data-client/pull/3860) [`886f2cf`](https://github.com/reactive/data-client/commit/886f2cf9032d5bb929b4bab63d61dc34398ca539) - Fix compatibility with StackBlitz WebContainers
Consolidate `path-to-regexp` imports into a single module to avoid
CJS/ESM interop failures in StackBlitz's WebContainers environment,
where webpack could not resolve the `pathToRegexp` named export from
the CJS `path-to-regexp` package.
Also caches `pathToRegexp()` results, improving repeated `testKey()` performance.
- Updated dependencies [[`c83a81d`](https://github.com/reactive/data-client/commit/c83a81d4183b4b0f7c3a41f5b49fdba890406a6b)]:
- @data-client/endpoint@0.16.3
## 0.16.2
### Patch Changes
- [#3847](https://github.com/reactive/data-client/pull/3847) [`e93e820`](https://github.com/reactive/data-client/commit/e93e820a112683badd4020c7c04c2284a0f6d8bf) - Fix TypeScript for `RestEndpoint` subclasses when the path is inferred as `string`
If you extend `RestEndpoint` with a generic such as `O extends RestGenerics = any`, TypeScript can widen a path literal to `string`. Constructor callbacks like `getOptimisticResponse`, `key`, `url`, and `process` could then get the wrong parameter types (or unusable unions), even though calling the endpoint still worked at runtime.
The same problem could show up when you set `searchParams: undefined` explicitly next to a `body` and a widened path. Both cases now type-check as you would expect.
```typescript
import { Entity } from '@data-client/endpoint';
import { RestEndpoint, RestGenerics } from '@data-client/rest';
class Item extends Entity {
readonly id = '';
}
class AppRestEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {}
new AppRestEndpoint({
path: '/items' as string,
schema: Item,
body: {} as { name: string },
getOptimisticResponse(_snap, body) {
body.name;
return body;
},
});
new AppRestEndpoint({
path: '/search' as string,
searchParams: undefined,
schema: Item,
body: {} as { q: string },
getOptimisticResponse(_snap, body) {
body.q;
return body;
},
});
```
## 0.16.1
### Patch Changes
- [#3845](https://github.com/reactive/data-client/pull/3845) [`14095fe`](https://github.com/reactive/data-client/commit/14095fe768625cf11ce3d80d3493571029cf5b67) - Fix type errors when using concrete body types with subclassed RestEndpoint
Subclassing RestEndpoint (the standard pattern for adding auth headers, custom
serialization, etc.) could produce type errors when specifying concrete body types.
```ts
// Before: type error on body ❌
class AuthdEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {}
new AuthdEndpoint({
method: 'PUT',
path: '/users/:id',
body: {} as { username: string; email: string },
});
// After: works correctly ✓
```
## 0.16.0
### Minor Changes
- [#3829](https://github.com/reactive/data-client/pull/3829) [`63633c7`](https://github.com/reactive/data-client/commit/63633c714b5c041e04891255683e5a899c3d3f22) - Add [schema.Lazy](https://dataclient.io/rest/api/Lazy) for deferred relationship denormalization.
`schema.Lazy` wraps a relationship field so denormalization returns raw primary keys
instead of resolved entities. Use `.query` with [useQuery](/docs/api/useQuery) to
resolve on demand in a separate memo/GC scope.
New exports: `schema.Lazy`, `Lazy`
```ts
class Department extends Entity {
buildings: string[] = [];
static schema = {
buildings: new schema.Lazy([Building]),
};
}
// dept.buildings = ['bldg-1', 'bldg-2'] (raw PKs)
const buildings = useQuery(Department.schema.buildings.query, dept.buildings);
```
- [#3783](https://github.com/reactive/data-client/pull/3783) [`1f34136`](https://github.com/reactive/data-client/commit/1f34136f1d0902ee5456089f2d2f9f35c9f4a758) - Add `Collection.moveWith()` for custom move schemas
Analogous to [`addWith()`](https://dataclient.io/rest/api/Collection#addWith), `moveWith()` constructs a custom move schema that controls how entities are added to their destination collection. The remove behavior is automatically derived from the collection type (Array or Values).
New exports: `unshift` merge function for convenience.
```ts
import { Collection, unshift } from '@data-client/rest';
class MyCollection extends Collection {
constructor(schema, options) {
super(schema, options);
this.move = this.moveWith(unshift);
}
}
```
- [#3757](https://github.com/reactive/data-client/pull/3757) [`02555a2`](https://github.com/reactive/data-client/commit/02555a23ef11c0a6c829d795067b634c0594fe14) - RestEndpoint.path and Resource.path syntax updated
Upgrading path-to-regexp from 6 to 8.
- https://github.com/pillarjs/path-to-regexp/releases/tag/v8.0.0
- https://github.com/pillarjs/path-to-regexp/releases/tag/v7.0.0
BREAKING CHANGES:
- /:optional? -> {/:optional}
- /:repeating+ -> /\*repeating (typed as string[])
- /:repeating* -> {/*repeating} (typed as string[])
- /:id(\d+) -> /:id (custom regex removed)
- /:with-dash -> /:"with-dash"
- `(`, `)`, `[`, `]`, `+`, `?`, `!` must be escaped `"\\("`
- `{}()[]+?!:*\` are all characters that need escaping
Migrate using:
`npx skills add https://github.com/reactive/data-client --skill path-to-regexp-v8-migration`
### Patch Changes
- [#3782](https://github.com/reactive/data-client/pull/3782) [`3b85c82`](https://github.com/reactive/data-client/commit/3b85c820f8046a056d9180ff5d2fe1a883b6998c) - Fix `searchParams: undefined` being widened to `any` in TypeScript 6 non-strict mode
TypeScript 6.0 widens `undefined` to `any` during generic inference when `strictNullChecks` is off.
This caused `RestEndpoint` with `searchParams: undefined` to incorrectly accept arbitrary arguments.
- [#3757](https://github.com/reactive/data-client/pull/3757) [`02555a2`](https://github.com/reactive/data-client/commit/02555a23ef11c0a6c829d795067b634c0594fe14) - Fix `undefined` optional path params being interpolated as literal `"undefined"` in URLs
- Updated dependencies [[`63633c7`](https://github.com/reactive/data-client/commit/63633c714b5c041e04891255683e5a899c3d3f22), [`1f34136`](https://github.com/reactive/data-client/commit/1f34136f1d0902ee5456089f2d2f9f35c9f4a758), [`869f28f`](https://github.com/reactive/data-client/commit/869f28fc651ca5e8b0f935089fc0b8d8ce8585cb)]:
- @data-client/endpoint@0.16.0
## 0.15.7
### Patch Changes
- [#3738](https://github.com/reactive/data-client/pull/3738) [`4425a37`](https://github.com/reactive/data-client/commit/4425a371484d3eaed66240ea8c9c1c8874e220f1) - Add `Collection.move` schema and `RestEndpoint.move` extender for moving entities between collections.
`move` removes from collections matching the entity's existing state and adds to collections
matching the new values (from the body/last arg). Works for both Array and Values collections.
Path parameters filter collections by URL segments:
```ts
const UserResource = resource({
path: '/groups/:group/users/:id',
schema: User,
});
// PATCH /groups/five/users/2 - moves user 2 from 'five' group to 'ten' group
await ctrl.fetch(
UserResource.getList.move,
{ group: 'five', id: '2' },
{ id: '2', group: 'ten' },
);
```
Search parameters filter collections by query args:
```ts
const TaskResource = resource({
path: '/tasks/:id',
searchParams: {} as { status: string },
schema: Task,
});
// PATCH /tasks/3 - moves task 3 from 'backlog' to 'in-progress'
await ctrl.fetch(
TaskResource.getList.move,
{ id: '3' },
{ id: '3', status: 'in-progress' },
);
```
- [#3739](https://github.com/reactive/data-client/pull/3739) [`b301d4c`](https://github.com/reactive/data-client/commit/b301d4c65cd720cfe321b02162550ba758f47192) Thanks [@Dark-Brain07](https://github.com/Dark-Brain07)! - Add `toJSON()` to `NetworkError` for structured serialization in devtools and logging
- Updated dependencies [[`4425a37`](https://github.com/reactive/data-client/commit/4425a371484d3eaed66240ea8c9c1c8874e220f1)]:
- @data-client/endpoint@0.15.7
## 0.15.6
### Patch Changes
- [#3726](https://github.com/reactive/data-client/pull/3726) [`9d94eb4`](https://github.com/reactive/data-client/commit/9d94eb46f9fd4513747ae3154298fa3b1d7487e9) - Fix `sideEffect: false` type being lost with `method: 'POST'`
`sideEffect` set explicitly now always takes priority over `method` inference,
including through `.extend()` chains. Previously `sideEffect: false` resolved
to `never`, losing type information.
```ts
// Before: sideEffect typed as `never` ❌
const ep = new RestEndpoint({ method: 'POST', sideEffect: false, ... });
// After: sideEffect typed as `false` ✓
const ep = new RestEndpoint({ method: 'POST', sideEffect: false, ... });
```
## 0.15.5
### Patch Changes
- [`e571bda`](https://github.com/reactive/data-client/commit/e571bdabd136fddee7aa414c91a775c5f66ce094) Thanks [@ntucker](https://github.com/ntucker)! - Add direct exports for schema classes from `@data-client/endpoint`
Schema classes (`Union`, `Invalidate`, `Collection`, `Query`, `Values`, `All`) can now be imported directly instead of requiring the `schema` namespace.
#### Before
```ts
import { schema } from '@data-client/endpoint';
const myUnion = new schema.Union({ users: User, groups: Group }, 'type');
```
#### After
```ts
import { Union } from '@data-client/endpoint';
const myUnion = new Union({ users: User, groups: Group }, 'type');
```
The `schema` namespace export remains available for backward compatibility.
- Updated dependencies [[`e571bda`](https://github.com/reactive/data-client/commit/e571bdabd136fddee7aa414c91a775c5f66ce094)]:
- @data-client/endpoint@0.15.5
## 0.15.4
### Patch Changes
- [#3703](https://github.com/reactive/data-client/pull/3703) [`4fe8779`](https://github.com/reactive/data-client/commit/4fe8779706cb14d9018b3375d07b486a758ccb57) Thanks [@ntucker](https://github.com/ntucker)! - Improve normalize/denormalize performance 10-15%
- Replace `Object.keys().forEach()` with indexed for loops
- Replace `reduce()` with spreading to direct object mutation
- Cache getter results to avoid repeated property lookups
- Centralize arg extraction with pre-allocated loop
- Eliminate Map double-get pattern
#### Microbenchmark Results
| # | Optimization | Before | After | Improvement |
| --- | ---------------------------- | ----------------- | ----------------- | ---------------- |
| 1 | **forEach → forLoop** | 7,164 ops/sec | 7,331 ops/sec | **+2.3%** |
| 2 | **reduce+spread → mutation** | 912 ops/sec | 7,468 ops/sec | **+719% (8.2x)** |
| 3 | **getter repeated → cached** | 1,652,211 ops/sec | 4,426,994 ops/sec | **+168% (2.7x)** |
| 4 | **slice+map → indexed** | 33,221 ops/sec | 54,701 ops/sec | **+65% (1.65x)** |
| 5 | **Map double-get → single** | 23,046 ops/sec | 23,285 ops/sec | **+1%** |
#### Impact Summary by Codepath
| Codepath | Optimizations Applied | Expected Improvement |
| ------------------------------------------ | --------------------- | -------------------- |
| **normalize** (setResponse) | 1, 2, 4 | 10-15% |
| **denormalize** (getResponse) | 1, 2, 4 | 10-15% |
| **Controller queries** (get, getQueryMeta) | 5, 6 | 5-10% |
- Updated dependencies [[`4fe8779`](https://github.com/reactive/data-client/commit/4fe8779706cb14d9018b3375d07b486a758ccb57)]:
- @data-client/endpoint@0.15.4
## 0.15.2
### Patch Changes
- [`243219c`](https://github.com/reactive/data-client/commit/243219ccc1acc8ffcbfe54e8cc4a9027819eb66a) Thanks [@ntucker](https://github.com/ntucker)! - Patch bump due to publishing mishap
- Updated dependencies [[`243219c`](https://github.com/reactive/data-client/commit/243219ccc1acc8ffcbfe54e8cc4a9027819eb66a)]:
- @data-client/endpoint@0.15.2
## 0.15.0
### Minor Changes
- [#3685](https://github.com/reactive/data-client/pull/3685) [`56d575e`](https://github.com/reactive/data-client/commit/56d575e0219d5455df74321aee7bf85c2d490a61) Thanks [@ntucker](https://github.com/ntucker)! - Add [Union](https://dataclient.io/rest/api/Union) support to [schema.Invalidate](https://dataclient.io/rest/api/Invalidate)
and [resource().delete](https://dataclient.io/rest/api/resource#delete) for polymorphic delete operations.
[resource()](https://dataclient.io/rest/api/resource) with Union schema now automatically
wraps the delete endpoint schema in Invalidate:
```ts
const FeedResource = resource({
path: '/feed/:id',
schema: FeedUnion, // Union of Post, Comment, etc.
});
// FeedResource.delete automatically uses Invalidate(FeedUnion)
await ctrl.fetch(FeedResource.delete, { id: '123' });
```
For standalone endpoints, use `schema.Invalidate` directly:
```ts
new schema.Invalidate(MyUnionSchema);
```
- [#3461](https://github.com/reactive/data-client/pull/3461) [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2) Thanks [@ntucker](https://github.com/ntucker)! - Add delegate.INVALID to queryKey
This is used in schema.All.queryKey().
#### Before
```ts
queryKey(args: any, unvisit: any, delegate: IQueryDelegate): any {
if (!found) return INVALID;
}
```
#### After
```ts
queryKey(args: any, unvisit: any, delegate: IQueryDelegate): any {
if (!found) return delegate.INVALID;
}
```
- [#3461](https://github.com/reactive/data-client/pull/3461) [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2) Thanks [@ntucker](https://github.com/ntucker)! - Add delegate.invalidate() to normalization
#### Before
```ts
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.setEntity(this as any, pk, INVALID);
}
```
#### After
```ts
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.invalidate(this as any, pk);
}
```
- [#3449](https://github.com/reactive/data-client/pull/3449) [`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7) Thanks [@ntucker](https://github.com/ntucker)! - BREAKING CHANGE: schema.normalize(...args, addEntity, getEntity, checkLoop) -> schema.normalize(...args, delegate)
We consolidate all 'callback' functions during recursion calls into a single 'delegate' argument.
```ts
/** Helpers during schema.normalize() */
export interface INormalizeDelegate {
/** Action meta-data for this normalize call */
readonly meta: { fetchedAt: number; date: number; expiresAt: number };
/** Gets any previously normalized entity from store */
getEntity: GetEntity;
/** Updates an entity using merge lifecycles when it has previously been set */
mergeEntity(
schema: Mergeable & { indexes?: any },
pk: string,
incomingEntity: any,
): void;
/** Sets an entity overwriting any previously set values */
setEntity(
schema: { key: string; indexes?: any },
pk: string,
entity: any,
meta?: { fetchedAt: number; date: number; expiresAt: number },
): void;
/** Returns true when we're in a cycle, so we should not continue recursing */
checkLoop(key: string, pk: string, input: object): boolean;
}
```
#### Before
```ts
addEntity(this, processedEntity, id);
```
#### After
```ts
delegate.mergeEntity(this, id, processedEntity);
```
- [#3461](https://github.com/reactive/data-client/pull/3461) [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2) Thanks [@ntucker](https://github.com/ntucker)! - Remove `INVALID` symbol export
Schemas can use delegate.invalidate() in normalize() or return delegate.INVALID in queryKey().
- [#3449](https://github.com/reactive/data-client/pull/3449) [`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7) Thanks [@ntucker](https://github.com/ntucker)! - BREAKING CHANGE: schema.queryKey(args, queryKey, getEntity, getIndex) -> schema.queryKey(args, unvisit, delegate)
BREAKING CHANGE: delegate.getIndex() returns the index directly, rather than object.
We consolidate all 'callback' functions during recursion calls into a single 'delegate' argument.
Our recursive call is renamed from queryKey to unvisit, and does not require the last two arguments.
```ts
/** Accessors to the currently processing state while building query */
export interface IQueryDelegate {
getEntity: GetEntity;
getIndex: GetIndex;
}
```
#### Before
```ts
queryKey(args, queryKey, getEntity, getIndex) {
getIndex(schema.key, indexName, value)[value];
getEntity(this.key, id);
return queryKey(this.schema, args, getEntity, getIndex);
}
```
#### After
```ts
queryKey(args, unvisit, delegate) {
delegate.getIndex(schema.key, indexName, value);
delegate.getEntity(this.key, id);
return unvisit(this.schema, args);
}
```
### Patch Changes
- [#3449](https://github.com/reactive/data-client/pull/3449) [`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7) Thanks [@ntucker](https://github.com/ntucker)! - Fix: ensure string id in Entity set when process returns undefined (meaning INVALID)
- [`e2fff91`](https://github.com/reactive/data-client/commit/e2fff911e21864620dba8d9470142af9130aafed) Thanks [@ntucker](https://github.com/ntucker)! - fix: Collection.remove with Unions
- [#3635](https://github.com/reactive/data-client/pull/3635) [`63ee107`](https://github.com/reactive/data-client/commit/63ee107be9d559e6ccbcb2f9c6fd7bf83165e551) Thanks [@ntucker](https://github.com/ntucker)! - Fix `getPage` types when paginationField is in body
```ts
const ep = new RestEndpoint({
path: '/rpc',
method: 'POST',
body: {} as { page?: number; method: string },
paginationField: 'page',
});
// Before: ep.getPage({ page: 2 }, { method: 'get' }) ❌
// After: ep.getPage({ page: 2, method: 'get' }) ✓
```
- [#3684](https://github.com/reactive/data-client/pull/3684) [`53de2ee`](https://github.com/reactive/data-client/commit/53de2eefb891a4783e3f1c7724dc25dc9e6a8e1f) Thanks [@ntucker](https://github.com/ntucker)! - Optimize normalization performance with faster loops and Set-based cycle detection
- [#3560](https://github.com/reactive/data-client/pull/3560) [`ba31c9b`](https://github.com/reactive/data-client/commit/ba31c9b2d3c4ec5620bb64e49daf9b18994b9290) Thanks [@ntucker](https://github.com/ntucker)! - Add Collection.remove
```ts
ctrl.set(MyResource.getList.schema.remove, { id });
```
```ts
const removeItem = MyResource.delete.extend({
schema: MyResource.getList.schema.remove,
});
```
- [#3558](https://github.com/reactive/data-client/pull/3558) [`fcb7d7d`](https://github.com/reactive/data-client/commit/fcb7d7db8061c2a7e12632071ecb9c6ddd8d154f) Thanks [@ntucker](https://github.com/ntucker)! - Normalize delegate.invalidate() first argument only has `key` param.
`indexes` optional param no longer provided as it was never used.
```ts
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.invalidate({ key: this._entity.key }, pk);
return pk;
}
```
- [#3623](https://github.com/reactive/data-client/pull/3623) [`8be3b17`](https://github.com/reactive/data-client/commit/8be3b1725707c4bcbf0fdd6e72ddd78d85fd125f) Thanks [@ntucker](https://github.com/ntucker)! - Add RestEndpoint.remove
Creates a PATCH endpoint that both removes an entity from a Collection and updates the entity with the provided body data.
```ts
const getTodos = new RestEndpoint({
path: '/todos',
schema: new schema.Collection([Todo]),
});
// Removes Todo from collection AND updates it with new data
await ctrl.fetch(
getTodos.remove,
{},
{ id: '123', title: 'Done', completed: true },
);
```
```ts
// Remove user from group list and update their group
await ctrl.fetch(
UserResource.getList.remove,
{ group: 'five' },
{ id: 2, username: 'user2', group: 'newgroup' },
);
// User is removed from the 'five' group list
// AND the user entity is updated with group: 'newgroup'
```
- [#3558](https://github.com/reactive/data-client/pull/3558) [`fcb7d7d`](https://github.com/reactive/data-client/commit/fcb7d7db8061c2a7e12632071ecb9c6ddd8d154f) Thanks [@ntucker](https://github.com/ntucker)! - Unions can query() without type discriminator
#### Before
```tsx
// @ts-expect-error
const event = useQuery(EventUnion, { id });
// event is undefined
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is found
```
#### After
```tsx
const event = useQuery(EventUnion, { id });
// event is found
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is found
```
- [`35552c7`](https://github.com/reactive/data-client/commit/35552c716e3b688d69212654f9f95a05ea26a7f8) Thanks [@ntucker](https://github.com/ntucker)! - Include GPT link badge in readme
- Updated dependencies [[`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7), [`56d575e`](https://github.com/reactive/data-client/commit/56d575e0219d5455df74321aee7bf85c2d490a61), [`e2fff91`](https://github.com/reactive/data-client/commit/e2fff911e21864620dba8d9470142af9130aafed), [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2), [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2), [`53de2ee`](https://github.com/reactive/data-client/commit/53de2eefb891a4783e3f1c7724dc25dc9e6a8e1f), [`ba31c9b`](https://github.com/reactive/data-client/commit/ba31c9b2d3c4ec5620bb64e49daf9b18994b9290), [`fcb7d7d`](https://github.com/reactive/data-client/commit/fcb7d7db8061c2a7e12632071ecb9c6ddd8d154f), [`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7), [`4dde1d6`](https://github.com/reactive/data-client/commit/4dde1d616e38d59b645573b12bbaba2f9cac7895), [`bab907c`](https://github.com/reactive/data-client/commit/bab907ce824c0f7da961d74c9fb8b64ce7c95141), [`5699005`](https://github.com/reactive/data-client/commit/5699005700206306bc70ff8237bf7ceaac241b82), [`939a4b0`](https://github.com/reactive/data-client/commit/939a4b01127ea1df9b4653931593487e4b0c23a2), [`fcb7d7d`](https://github.com/reactive/data-client/commit/fcb7d7db8061c2a7e12632071ecb9c6ddd8d154f), [`1f491a9`](https://github.com/reactive/data-client/commit/1f491a9e0082dca64ad042aaf7d377e17f459ae7), [`35552c7`](https://github.com/reactive/data-client/commit/35552c716e3b688d69212654f9f95a05ea26a7f8)]:
- @data-client/endpoint@0.15.0
## 0.14.25
### Patch Changes
- [#3417](https://github.com/reactive/data-client/pull/3417) [`a6af54c`](https://github.com/reactive/data-client/commit/a6af54c1bc2193de47c96938df74b243cb82bfe6) Thanks [@ntucker](https://github.com/ntucker)! - Update getOptimisticResponse snapshot types to include getResponseMeta
- [#3407](https://github.com/reactive/data-client/pull/3407) [`d84899d`](https://github.com/reactive/data-client/commit/d84899de4fb784375ab7a34fff6fe23a2ed98037) Thanks [@ntucker](https://github.com/ntucker)! - Support dynamic invalidation/deletes
Returning `undefined` from [Entity.process](https://dataclient.io/rest/api/Entity#process)
will cause the [Entity](https://dataclient.io/rest/api/Entity) to be [invalidated](https://dataclient.io/docs/concepts/expiry-policy#invalidate-entity).
This this allows us to invalidate dynamically; based on the particular response data.
```ts
class PriceLevel extends Entity {
price = 0;
amount = 0;
pk() {
return this.price;
}
static process(
input: [number, number],
parent: any,
key: string | undefined,
): any {
const [price, amount] = input;
if (amount === 0) return undefined;
return { price, amount };
}
}
```
- Updated dependencies [[`a6af54c`](https://github.com/reactive/data-client/commit/a6af54c1bc2193de47c96938df74b243cb82bfe6), [`d84899d`](https://github.com/reactive/data-client/commit/d84899de4fb784375ab7a34fff6fe23a2ed98037)]:
- @data-client/endpoint@0.14.25
## 0.14.22
### Patch Changes
- [#3390](https://github.com/reactive/data-client/pull/3390) [`32cccdb`](https://github.com/reactive/data-client/commit/32cccdb921cd8d7643b641a9e8872aa89782a94a) Thanks [@ntucker](https://github.com/ntucker)! - Improve performance by using Map() instead of Object for unbounded keys
## 0.14.21
### Patch Changes
- [#3384](https://github.com/reactive/data-client/pull/3384) [`24ad679`](https://github.com/reactive/data-client/commit/24ad679f58c7eb0d0e6917790b4ebb5ee234e1d3) Thanks [@ntucker](https://github.com/ntucker)! - Reduce bundle sizes by 30% by removing unneeded polyfills
- Updated dependencies [[`24ad679`](https://github.com/reactive/data-client/commit/24ad679f58c7eb0d0e6917790b4ebb5ee234e1d3)]:
- @data-client/endpoint@0.14.21
## 0.14.20
### Patch Changes
- [`c3514c6`](https://github.com/reactive/data-client/commit/c3514c6afa2cd76dafa02adcfad6f6481a34b5de) Thanks [@ntucker](https://github.com/ntucker)! - Remove unnecessary polyfills in build
- Updated dependencies [[`c3514c6`](https://github.com/reactive/data-client/commit/c3514c6afa2cd76dafa02adcfad6f6481a34b5de)]:
- @data-client/endpoint@0.14.20
## 0.14.19
### Patch Changes
- [`cb4fb92`](https://github.com/reactive/data-client/commit/cb4fb922e305502ba8ab99c99b6012e753a87a3a) Thanks [@ntucker](https://github.com/ntucker)! - Remove typing redundancy
- [#3371](https://github.com/reactive/data-client/pull/3371) [`679d76a`](https://github.com/reactive/data-client/commit/679d76a36234dcf5993c0358f94d7e1db0505cc6) Thanks [@ntucker](https://github.com/ntucker)! - Add react-native entry to package.json exports
- [#3353](https://github.com/reactive/data-client/pull/3353) [`165afed`](https://github.com/reactive/data-client/commit/165afed083c0c63e9356bc8d1ee30dee8b916ed6) Thanks [@renovate](https://github.com/apps/renovate)! - Polyfills no longer pollute global scope
- Updated dependencies [[`cb4fb92`](https://github.com/reactive/data-client/commit/cb4fb922e305502ba8ab99c99b6012e753a87a3a), [`679d76a`](https://github.com/reactive/data-client/commit/679d76a36234dcf5993c0358f94d7e1db0505cc6), [`165afed`](https://github.com/reactive/data-client/commit/165afed083c0c63e9356bc8d1ee30dee8b916ed6)]:
- @data-client/endpoint@0.14.19
## 0.14.18
### Patch Changes
- [#3333](https://github.com/reactive/data-client/pull/3333) [`1777546`](https://github.com/reactive/data-client/commit/17775462d236bf714f518b861a63d5ae9f1f6357) Thanks [@renovate](https://github.com/apps/renovate)! - Resource.extend() compatibility with TypeScript 5
Previously [extending existing members](https://dataclient.io/rest/api/resource#extend-override) with no
typed overrides (like [path](https://dataclient.io/rest/api/resource#path)) would not work starting with
TypeScript 5.7.
```ts
const UserResource = UserResourceBase.extend({
partialUpdate: {
getOptimisticResponse(snap, params, body) {
params.id;
params.group;
// @ts-expect-error
params.nothere;
return {
id: params.id,
...body,
};
},
},
});
```
## 0.14.17
### Patch Changes
- [#3281](https://github.com/reactive/data-client/pull/3281) [`99cd041`](https://github.com/reactive/data-client/commit/99cd04152532e13d8fb092ea800d381391d5aacd) Thanks [@ntucker](https://github.com/ntucker)! - Collections work with nested args
This fixes [integration with qs library](https://dataclient.io/rest/api/RestEndpoint#using-qs-library) and more complex search parameters.
- [`25be07f`](https://github.com/reactive/data-client/commit/25be07f51c501003330d758993542bee3bd804e1) Thanks [@ntucker](https://github.com/ntucker)! - Update README to not say 'mixin' twice
- Updated dependencies [[`99cd041`](https://github.com/reactive/data-client/commit/99cd04152532e13d8fb092ea800d381391d5aacd)]:
- @data-client/endpoint@0.14.17
## 0.14.16
### Patch Changes
- [`4580e62`](https://github.com/reactive/data-client/commit/4580e628764ab43de3e4607f8584bc6cb4173021) Thanks [@ntucker](https://github.com/ntucker)! - Update docstring for EntityMixin
- [#3243](https://github.com/reactive/data-client/pull/3243) [`43a955c`](https://github.com/reactive/data-client/commit/43a955c18684b4e0f5c1d79b2504e8ad2910816b) Thanks [@ntucker](https://github.com/ntucker)! - `schema.Entity` -> [EntityMixin](https://dataclient.io/rest/api/EntityMixin)
```ts
import { EntityMixin } from '@data-client/rest';
export class Article {
id = '';
title = '';
content = '';
tags: string[] = [];
}
export class ArticleEntity extends EntityMixin(Article) {}
```
We keep `schema.Entity` for legacy, and add schema.EntityMixin and [EntityMixin](https://dataclient.io/rest/api/EntityMixin) as direct export
- Updated dependencies [[`4580e62`](https://github.com/reactive/data-client/commit/4580e628764ab43de3e4607f8584bc6cb4173021), [`1f7b191`](https://github.com/reactive/data-client/commit/1f7b1913e9301230d9fdae23baba9e3c582e005c), [`43a955c`](https://github.com/reactive/data-client/commit/43a955c18684b4e0f5c1d79b2504e8ad2910816b)]:
- @data-client/endpoint@0.14.16
## 0.14.13
### Patch Changes
- [#3215](https://github.com/reactive/data-client/pull/3215) [`e74f7dc`](https://github.com/reactive/data-client/commit/e74f7dc0750fdf32e455bf2c36eff119afba6476) Thanks [@renovate](https://github.com/apps/renovate)! - Update path-to-regexp for [CVE-2024-45296](https://redirect.github.com/pillarjs/path-to-regexp/security/advisories/GHSA-9wv6-86v2-598j)
## 0.14.12
### Patch Changes
- [`2b10a49`](https://github.com/reactive/data-client/commit/2b10a4997093570b96533ef7b99c89be722a6fa7) Thanks [@ntucker](https://github.com/ntucker)! - Add ResourceInterface export
- [`3b337e7`](https://github.com/reactive/data-client/commit/3b337e74e3f22f2fe48f6eb37084bbf58859bbe1) Thanks [@ntucker](https://github.com/ntucker)! - Add schema table to README
- [`11d4ccf`](https://github.com/reactive/data-client/commit/11d4ccfb4c630c25b847bf59ca1028eed8c2369e) Thanks [@ntucker](https://github.com/ntucker)! - Fix: Collection adders (push/unshift) should _not_ be Queryable
- Updated dependencies [[`3b337e7`](https://github.com/reactive/data-client/commit/3b337e74e3f22f2fe48f6eb37084bbf58859bbe1), [`11d4ccf`](https://github.com/reactive/data-client/commit/11d4ccfb4c630c25b847bf59ca1028eed8c2369e)]:
- @data-client/endpoint@0.14.12
## 0.14.11
### Patch Changes
- [`87a65ba`](https://github.com/reactive/data-client/commit/87a65ba8b5f266a299ac3d9c78b6605deee5f4e2) Thanks [@ntucker](https://github.com/ntucker)! - Fix Entity types for TS 4.0 and below
- [`a436050`](https://github.com/reactive/data-client/commit/a43605035d3791ad73393ce229ed85c8e8f2cb88) Thanks [@ntucker](https://github.com/ntucker)! - Fix README markup
- Updated dependencies [[`87a65ba`](https://github.com/reactive/data-client/commit/87a65ba8b5f266a299ac3d9c78b6605deee5f4e2)]:
- @data-client/endpoint@0.14.11
## 0.14.10
### Patch Changes
- [#3188](https://github.com/reactive/data-client/pull/3188) [`cde7121`](https://github.com/reactive/data-client/commit/cde71212706a46bbfd13dd76e8cfc478b22fe2ab) Thanks [@ntucker](https://github.com/ntucker)! - Do not require [Entity.pk()](https://dataclient.io/rest/api/Entity#pk)
Default implementation uses `this.id`
- [#3188](https://github.com/reactive/data-client/pull/3188) [`cde7121`](https://github.com/reactive/data-client/commit/cde71212706a46bbfd13dd76e8cfc478b22fe2ab) Thanks [@ntucker](https://github.com/ntucker)! - Update README to remove Entity.pk() when it is default ('id')
- Updated dependencies [[`cde7121`](https://github.com/reactive/data-client/commit/cde71212706a46bbfd13dd76e8cfc478b22fe2ab), [`cde7121`](https://github.com/reactive/data-client/commit/cde71212706a46bbfd13dd76e8cfc478b22fe2ab)]:
- @data-client/endpoint@0.14.10
## 0.14.9
### Patch Changes
- [`c263931`](https://github.com/reactive/data-client/commit/c26393176518550a16a2e71aea55a8379f30385e) Thanks [@ntucker](https://github.com/ntucker)! - Update README
- [`366c609`](https://github.com/reactive/data-client/commit/366c609dbda9707f8ecfaef5020c67dd1c7e262b) Thanks [@ntucker](https://github.com/ntucker)! - Make NetworkError messages include more useful information
Add URL so it's clear _what_ is causing issues when response is
not 'ok'
## 0.14.8
### Patch Changes
- [`bad1fb9`](https://github.com/reactive/data-client/commit/bad1fb909f8d60f19450bbf40df00d90e03a61c2) Thanks [@ntucker](https://github.com/ntucker)! - Update package description
## 0.14.6
### Patch Changes
- [#3165](https://github.com/reactive/data-client/pull/3165) [`3fa9eb9`](https://github.com/reactive/data-client/commit/3fa9eb907d8760171da065168796b87e802d6666) Thanks [@ntucker](https://github.com/ntucker)! - [Query](https://dataclient.io/rest/api/Query) can take [Object Schemas](https://dataclient.io/rest/api/Object)
This enables joining arbitrary objects (whose pk works with the same arguments.)
```ts
class Ticker extends Entity {
product_id = '';
price = 0;
pk(): string {
return this.product_id;
}
}
class Stats extends Entity {
product_id = '';
last = 0;
pk(): string {
return this.product_id;
}
}
const queryPrice = new schema.Query(
{ ticker: Ticker, stats: Stats },
({ ticker, stats }) => ticker?.price ?? stats?.last,
);
```
- Updated dependencies [[`3fa9eb9`](https://github.com/reactive/data-client/commit/3fa9eb907d8760171da065168796b87e802d6666)]:
- @data-client/endpoint@0.14.6
## 0.14.4
### Patch Changes
- [`0adad92`](https://github.com/reactive/data-client/commit/0adad9209265c388eb6d334afe681610bccfb877) Thanks [@ntucker](https://github.com/ntucker)! - Update debugging link
- Updated dependencies [[`0adad92`](https://github.com/reactive/data-client/commit/0adad9209265c388eb6d334afe681610bccfb877)]:
- @data-client/endpoint@0.14.4
## 0.14.3
### Patch Changes
- [`501cb82`](https://github.com/reactive/data-client/commit/501cb82c999030fd269b40eb760ae0dda568c569) Thanks [@ntucker](https://github.com/ntucker)! - Remove name in toJSON() for Entities
- [`501cb82`](https://github.com/reactive/data-client/commit/501cb82c999030fd269b40eb760ae0dda568c569) Thanks [@ntucker](https://github.com/ntucker)! - Add toString() to Collection
- [`3058a8a`](https://github.com/reactive/data-client/commit/3058a8a7738eeea0a197c9ba2db2e8ee51e2fca3) Thanks [@ntucker](https://github.com/ntucker)! - Collection non-known (not Array/Values) key format improvement
Now wraps in parens `()`: "(Todo)"
- [#3158](https://github.com/reactive/data-client/pull/3158) [`34e2e51`](https://github.com/reactive/data-client/commit/34e2e51e89908649f1297c23a71cdafecf1d3b6f) Thanks [@ntucker](https://github.com/ntucker)! - createResource() -> [resource()](https://dataclient.io/rest/api/resource)
Note: `createResource` is still exported (it is the same)
- Updated dependencies [[`501cb82`](https://github.com/reactive/data-client/commit/501cb82c999030fd269b40eb760ae0dda568c569), [`501cb82`](https://github.com/reactive/data-client/commit/501cb82c999030fd269b40eb760ae0dda568c569), [`3058a8a`](https://github.com/reactive/data-client/commit/3058a8a7738eeea0a197c9ba2db2e8ee51e2fca3)]:
- @data-client/endpoint@0.14.3
## 0.14.1
### Patch Changes
- [#3151](https://github.com/reactive/data-client/pull/3151) [`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f) Thanks [@ntucker](https://github.com/ntucker)! - Collection.key is shorter and more readable
`[Todo]` for Arrays or `{Todo}` for Values
- [#3151](https://github.com/reactive/data-client/pull/3151) [`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f) Thanks [@ntucker](https://github.com/ntucker)! - fix: Collection.key robust against class name mangling
- [#3151](https://github.com/reactive/data-client/pull/3151) [`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f) Thanks [@ntucker](https://github.com/ntucker)! - Collections now work with polymorhpic schemas like Union
Collections.key on polymorphic types lists their possible Entity keys: `[PushEvent;PullRequestEvent]`
- Updated dependencies [[`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f), [`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f), [`428d618`](https://github.com/reactive/data-client/commit/428d618ce057d4eef23592a64ec9d1c6fb82f43f)]:
- @data-client/endpoint@0.14.1
## 0.14.0
### Minor Changes
- [#3134](https://github.com/reactive/data-client/pull/3134) [`2ad1811`](https://github.com/reactive/data-client/commit/2ad1811149cdc419f6462ace08efdb7766195b36) Thanks [@ntucker](https://github.com/ntucker)! - Change Schema.normalize `visit()` interface; removing non-contextual arguments.
```ts
/** Visits next data + schema while recurisvely normalizing */
export interface Visit {
(schema: any, value: any, parent: any, key: any, args: readonly any[]): any;
creating?: boolean;
}
```
This results in a 10% normalize performance boost.
```ts title="Before"
processedEntity[key] = visit(
processedEntity[key],
processedEntity,
key,
this.schema[key],
addEntity,
visitedEntities,
storeEntities,
args,
);
```
```ts title="After"
processedEntity[key] = visit(
this.schema[key],
processedEntity[key],
processedEntity,
key,
args,
);
```
The information needed from these arguments are provided by [closing](<https://en.wikipedia.org/wiki/Closure_(computer_programming)>) `visit()` around them.
- [#3134](https://github.com/reactive/data-client/pull/3134) [`2ad1811`](https://github.com/reactive/data-client/commit/2ad1811149cdc419f6462ace08efdb7766195b36) Thanks [@ntucker](https://github.com/ntucker)! - Change Schema.normalize interface from direct data access,