# Entity and Data Normalization

[Entities](https://dataclient.io/rest/api/Entity.md) have a primary key. This enables easy access via a lookup table.
This makes it easy to find, update, create, or delete the same data - no matter what
endpoint it was used in.

**State**

![Entities cache](/img/entities.png "Entities cache")

**Response**

```json
[
  { "id": 1, "title": "this is an entity" },
  { "id": 2, "title": "this is the second entity" }
]
```

**Endpoint**

```typescript
const getPresentations = new Endpoint(
  () => fetch(`/presentations`).then(res => res.json()),
  { schema: new Collection([Presentation]) },
);
```

**Entity**

```typescript
class Presentation extends Entity {
  id = '';
  title = '';

  static key = 'Presentation';
}
```

**Component**

```tsx
import { useSuspense } from '@data-client/react';
import { getPresentations } from './api/Presentation';

export function PresentationsPage() {
  const presentation = useSuspense(getPresentations);
  return presentation.map(presentation => (
    <div key={presentation.pk()}>{presentation.title}</div>
  ));
}
```

Extracting entities from a response is known as `normalization`. Accessing a response reverses
the process via `denormalization`.

> **Info: Global Referential Equality**
>
> Using entities expands Reactive Data Client' global referential equality guarantee beyond the granularity of
> an entire endpoint response.

## Mutations and Dynamic Data

When an endpoint changes data, this is known as a [side effect](https://dataclient.io/rest/guides/side-effects.md). Marking an endpoint with [sideEffect: true](https://dataclient.io/rest/api/Endpoint.md#sideeffect)
tells Reactive Data Client that this endpoint is not idempotent, and thus should not be allowed in hooks
that may call the endpoint an arbitrary number of times like [useSuspense()](https://dataclient.io/docs/api/useSuspense.md) or [useFetch()](https://dataclient.io/docs/api/useFetch.md)

By including the changed data in the endpoint's response, Reactive Data Client is able to able to update
any entities it extracts by specifying the schema.

**Create**

```typescript
import { RestEndpoint, schema } from '@data-client/rest';

const todoCreate = new RestEndpoint({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos',
  method: 'POST',
  schema: new Collection([Todo]).push,
});
```

<details>

<summary>Example Usage</summary>

```tsx
import { useController } from '@data-client/react';
import { todoCreate } from './api/Todo';
import Form from './Form';
import FormField from './FormField';

export default function NewTodoForm() {
  const ctrl = useController();
  return (
    <Form
      onSubmit={e => ctrl.fetch(todoCreate, new FormData(e.target))}
    >
      <FormField name="title" />
    </Form>
  );
}
```

</details>

**Update**

```typescript
import { RestEndpoint } from '@data-client/rest';

const todoUpdate = new RestEndpoint({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  method: 'PUT',
  schema: Todo,
});
```

<details>

<summary>Example Usage</summary>

```tsx
import { useController, useSuspense } from '@data-client/react';
import { todoDetail, todoUpdate } from './api/Todo';
import Form from './Form';
import FormField from './FormField';

export default function UpdateTodoForm({ id }: { id: number }) {
  const todo = useSuspense(todoDetail, { id });
  const ctrl = useController();
  return (
    <Form
      onSubmit={e =>
        ctrl.fetch(todoUpdate, { id }, new FormData(e.target))
      }
      initialValues={todo}
    >
      <FormField name="title" />
    </Form>
  );
}
```

</details>

**Delete**

```typescript
import { Invalidate, RestEndpoint } from '@data-client/rest';

const todoDelete = new RestEndpoint({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  method: 'DELETE',
  schema: new Invalidate(Todo),
});
```

<details>

<summary>Example Usage</summary>

```tsx
import { useController } from '@data-client/react';
import { todoDelete, type Todo } from './api/Todo';

export default function TodoWithDelete({ todo }: { todo: Todo }) {
  const ctrl = useController();
  return (
    <div>
      {todo.title}
      <button onClick={() => ctrl.fetch(todoDelete, { id: todo.id })}>
        Delete
      </button>
    </div>
  );
}
```

</details>

> **Info**
>
> Mutations automatically update the normalized cache, resulting in consistent and fresh data.

## Schema

Schemas are a declarative definition of how to [process responses](https://dataclient.io/rest/api/schema.md)

- [where](https://dataclient.io/rest/api/schema.md) to expect [Entities](https://dataclient.io/rest/api/Entity.md)
- Functions to [deserialize fields](https://dataclient.io/rest/guides/network-transform.md#deserializing-fields)

```typescript
import { RestEndpoint, Collection } from '@data-client/rest';

const getTodoList = new RestEndpoint({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos',
  schema: new Collection([Todo]),
});
```

Placing our [Entity](https://dataclient.io/rest/api/Entity.md) `Todo` in an array [Collection](https://dataclient.io/rest/api/Collection.md), allows us to easly
[push](https://dataclient.io/rest/api/RestEndpoint.md#push) or [unshift](https://dataclient.io/rest/api/RestEndpoint.md#unshift) new `Todos` on it.

Aside from array, there are a few more 'schemas' provided for various patterns. The first two (Object and Array)
have shorthands of using object and array literals.

| Data Type                                                           | Mutable | Schema                                                             | Description                                                                                | [Queryable](https://dataclient.io/rest/api/schema.md#queryable) |
| ------------------------------------------------------------------- | ------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | ✅       | [Entity](https://dataclient.io/rest/api/Entity.md)                 | single _unique_ object                                                                     | ✅                                                               |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | ✅       | [Union(Entity)](https://dataclient.io/rest/api/Union.md)           | polymorphic objects (`A \| B`)                                                             | ✅                                                               |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) | 🛑      | [Object](https://dataclient.io/rest/api/Object.md)                 | statically known keys                                                                      | 🛑                                                              |
| [Object](https://en.wikipedia.org/wiki/Object_\(computer_science\)) |         | [Invalidate(Entity)](https://dataclient.io/rest/api/Invalidate.md) | [delete an entity](https://dataclient.io/docs/concepts/expiry-policy.md#invalidate-entity) | 🛑                                                              |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\))   | ✅       | [Collection(Array)](https://dataclient.io/rest/api/Collection.md)  | growable lists                                                                             | ✅                                                               |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\))   | 🛑      | [Array](https://dataclient.io/rest/api/Array.md)                   | immutable lists                                                                            | 🛑                                                              |
| [List](https://en.wikipedia.org/wiki/List_\(abstract_data_type\))   |         | [All](https://dataclient.io/rest/api/All.md)                       | list of all entities of a kind                                                             | ✅                                                               |
| [Map](https://en.wikipedia.org/wiki/Associative_array)              | ✅       | [Collection(Values)](https://dataclient.io/rest/api/Collection.md) | growable maps                                                                              | ✅                                                               |
| [Map](https://en.wikipedia.org/wiki/Associative_array)              | 🛑      | [Values](https://dataclient.io/rest/api/Values.md)                 | immutable maps                                                                             | 🛑                                                              |
| [Scalar](https://en.wikipedia.org/wiki/Scalar_\(mathematics\))      | ✅       | [Scalar](https://dataclient.io/rest/api/Scalar.md)                 | lens-dependent entity fields                                                               | ✅                                                               |
| any                                                                 |         | [Query(Queryable)](https://dataclient.io/rest/api/Query.md)        | memoized custom transforms                                                                 | ✅                                                               |
| any                                                                 |         | [Lazy(Schema)](https://dataclient.io/rest/api/Lazy.md)             | deferred denormalization                                                                   | ✅                                                               |

[Learn more](https://dataclient.io/rest/api/schema.md)

### Nesting

Additionally, [Entities](https://dataclient.io/rest/api/Entity.md) themselves can specify [nested schemas](https://dataclient.io/rest/guides/relational-data.md)
by specifying a [static schema](https://dataclient.io/rest/api/Entity.md#schema) member.

**Entity**

```typescript
import { Entity } from '@data-client/endpoint';

class Todo extends Entity {
  id = 0;
  user = User.fromJS();
  title = '';
  completed = false;

  static key = 'Todo';

  static schema = {
    user: User,
  };
}

class User extends Entity {
  id = 0;
  username = '';

  static key = 'User';
}
```

**Response**

```json
{
  "id": 5,
  "user": {
    "id": 10,
    "username": "bob"
  },
  "title": "Write some Entities",
  "completed": false
}
```

[Learn more](https://dataclient.io/rest/guides/relational-data.md)

### Data Representations

Additionally, functions can be [used as a schema](https://dataclient.io/rest/guides/network-transform.md#deserializing-fields). This will be called during denormalization.
This might be useful with representations like [bignumber](https://mikemcl.github.io/bignumber.js/) or [temporal instant](https://tc39.es/proposal-temporal/docs/instant.html)

```ts
import { Entity } from '@data-client/endpoint';

class Todo extends Entity {
  id = 0;
  user = User.fromJS();
  title = '';
  completed = false;
  dueDate = Temporal.Instant.fromEpochMilliseconds(0);

  static key = 'Todo';

  static schema = {
    user: User,
    dueDate: Temporal.Instant.from,
  };
}
```

> **Info**
>
> Due to the global referential equality guarantee - construction of members only occurs once
> per update.

## Store Inspection (debugging)

[DevTools browser extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)
can be installed to inspect and [debug the store](https://dataclient.io/docs/getting-started/debugging.md).

![browser-devtools](/img/devtool-state.png "Reactive Data Client devtools")

[Data Client Debugging Guide »](https://dataclient.io/docs/getting-started/debugging.md)

## Benchmarks

Entity-level memoization delivers up to **20x** denormalization performance and **90x** faster mutation propagation
compared to non-normalized approaches. See the full [Performance](https://dataclient.io/docs/concepts/performance.md) page for
normalization benchmarks results as well as full React integration benchmarks.
