Define data schemas - Entity, Collection, Union, Query, pk/primary key, normalize/denormalize, relational/nested data, polymorphic types, Invalidate, Values
Define schemas to represent the JSON returned by an endpoint. Compose these to represent the data expected.
{[key:string]: Schema} - immutable objects[Schema] - immutable listsnew Collection(Values(Schema)) - mutable/growable mapsnew Query(Queryable) - memoized programmatic selectors
const queryRemainingTodos = new Query(
TodoResource.getList.schema,
entries => entries.filter(todo => !todo.completed).length,
);
const groupTodoByUser = new Query(
TodoResource.getList.schema,
todos => Object.groupBy(todos, todo => todo.userId),
);
Entity subclass defines defaults for all non-optional serialised fields.pk() only when the primary key ≠ id.pk() return type is number | string | undefinedEntity.process(value, parent, key, args) to insert fields based on args/urlstatic schema (optional) for nested schemas or deserialization functions
process() → pk() → validate() → visit nested schemas (recurse into schema fields) → mergeWithStore() which calls shouldUpdate() and maybe shouldReorder() + merge(); metadata via mergeMetaWithStore().createIfValid() → validate() → fromJS() → unvisit nested schemas (recurse into schema fields).To define polymorphic resources (e.g., events), use Union and a discriminator field.
import { Union } from '@data-client/rest'; // also available from @data-client/endpoint
export abstract class Event extends Entity {
type: EventType = 'Issue'; // discriminator field is shared
/* ... */
}
export class PullRequestEvent extends Event { /* ... */ }
export class IssuesEvent extends Event { /* ... */ }
export const EventResource = resource({
path: '/users/:login/events/public/:id',
schema: new Union(
{
PullRequestEvent,
IssuesEvent,
// ...other event types...
},
'type', // discriminator field
),
});
Collections wrap Array or Values schemas to enable mutations (add/remove/move).
pk() uses argsKey(...args) or nestKey(parent, key), then serializes the result. Without either option, it defaults to argsKey: params => ({ ...params }), using all endpoint args as the collection key.
argsKey — derive pk from endpoint arguments (default)nestKey — derive pk from parent entity for nested shared-state collectionsDefault createCollectionFilter uses nonFilterArgumentKeys (default: keys starting with 'order') to exclude non-filter args when matching collections. This affects which existing collections receive new items from push/unshift/assign/move.
Override as function, RegExp, or string[]:
new Collection([Todo], { nonFilterArgumentKeys: /orderBy|sortDir/ })
All usable with ctrl.set() (local-only) or via RestEndpoint extenders (network).
| Method | Type | Description |
|---|---|---|
push | Array | Entity |
unshift | Array | Entity |
assign | Values | Merge entries into map |
remove | Both | Remove items by value from matching collections |
move | Both | Remove from collections matching existing state, add to collections matching new state |
addWith(merge, filter?) | Both | Custom creation schema (used internally by push/unshift/assign) |
moveWith(merge) | Both | Custom move schema (control insertion order, e.g., unshift merge for prepending) |
When an endpoint returns partial or differently-shaped data for an entity already in cache (e.g., a metadata endpoint, a stats endpoint, a lazy-load expansion endpoint), use the same Entity as the schema — don't create a wrapper entity.
See partial-entities for patterns and examples.
schema on every resource/entity/collection for normalizationEntity.schema for client-side joinsDenormalize<> type from rest/endpoint/graphql instead of InstanceType<>. This will handle all schemas like Unions, not just Entity.fromJS() or assign default properties for class fieldsEntity.schema for client-side joinsFor detailed API documentation, see the references directory: