# Thinking in Schemas

Consider a typical blog post. The API response for a single post might look something like this:

```json
{
  "id": "123",
  "author": {
    "id": "1",
    "name": "Paul"
  },
  "title": "My awesome blog post",
  "comments": [
    {
      "id": "324",
      "createdAt": "2013-05-29T00:00:00-04:00",
      "commenter": {
        "id": "2",
        "name": "Nicole"
      }
    },
    {
      "id": "544",
      "createdAt": "2013-05-30T00:00:00-04:00",
      "commenter": {
        "id": "1",
        "name": "Paul"
      }
    }
  ]
}
```

## Declarative definitions

We have two nested [entity](https://dataclient.io/rest/api/Entity.md) types within our `article`: `users` and `comments`. Using various [schema](https://dataclient.io/rest/api/Entity.md#schema), we can normalize all three entity types down:

```typescript
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';

class User extends Entity {
  id = '';
  name = '';
}

class Comment extends Entity {
  id = '';
  createdAt = Temporal.Instant.fromEpochMilliseconds(0);
  commenter = User.fromJS();

  static schema = {
    commenter: User,
    createdAt: Temporal.Instant.from,
  };
}

class Article extends Entity {
  id = '';
  title = '';
  author = User.fromJS();
  comments: Comment[] = [];

  static schema = {
    author: User,
    comments: [Comment],
  };
}
```

```javascript
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';

class User extends Entity { }

class Comment extends Entity {
  static schema = {
    commenter: User,
    createdAt: Temporal.Instant.from,
  };
}

class Article extends Entity {
  static schema = {
    author: User,
    comments: [Comment],
  };
}
```

## Normalize

```js
import { normalize } from '@data-client/normalizr';

const args = [{ id: '123' }];
const normalizedData = normalize(Article, originalData, args);
```

Now, `normalizedData` will create a single serializable source of truth for all entities:

```js
{
  result: "123",
  entities: {
    articles: {
      "123": {
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: [ "324", "544" ]
      }
    },
    users: {
      "1": { "id": "1", "name": "Paul" },
      "2": { "id": "2", "name": "Nicole" }
    },
    comments: {
      "324": {
        id: "324",
        createdAt: "2013-05-29T00:00:00-04:00",
        commenter: "2"
      },
      "544": {
        id: "544",
        createdAt: "2013-05-30T00:00:00-04:00",
        commenter: "1"
      }
    }
  },
  // contents excluded for brevity
  indexes,
  entitiesMeta,
}
```

## Denormalize

```js
import { denormalize } from '@data-client/normalizr';

const denormalizedData = denormalize(
  Article,
  normalizedData.result,
  normalizedData.entities,
  args,
);
```

Now, `denormalizedData` will instantiate the classes, ensuring all instances of the same member (like `Paul`) are referentially equal:

```js
Article {
  id: '123',
  title: 'My awesome blog post',
  author: User { id: '1', name: 'Paul' },
  comments: [
    Comment {
      id: '324',
      createdAt: Instant [Temporal.Instant] {},
      commenter: [User { id: '2', name: 'Nicole' }]
    },
    Comment {
      id: '544',
      createdAt: Instant [Temporal.Instant] {},
      commenter: [User { id: '1', name: 'Paul' }]
    }
  ]
}
```

### MemoCache

`MemoCache` is a singleton that can be used to maintain referential equality between calls as well
as potentially improved performance by 2000%. Its methods are memoized.

#### memo.denormalize

```js
import { MemoCache } from '@data-client/normalizr';

// you can construct a new memo anytime you want to reset the cache
const memo = new MemoCache();

const { data, paths } = memo.denormalize(
  Article,
  normalizedData.result,
  normalizedData.entities,
  args,
);
const { data: data2 } = memo.denormalize(
  Article,
  normalizedData.result,
  normalizedData.entities,
  args,
);

// referential equality maintained between calls
assert(data === data2);
```

`memo.denormalize()` is just like [denormalize()](#denormalize) above but includes `paths` as part of the return value. `paths`
is an Array of paths of all entities included in the result.

#### memo.query

`memo.query()` allows denormalizing [Queryable](#queryable) based on args alone, rather than a normalized input.

```ts
const data = memo.query(
  Article,
  args,
  normalizedData,
);
```

## Queryable

`Queryable` Schemas allow store access without an endpoint. They achieve this using the
[queryKey](https://dataclient.io/rest/api/Entity.md#queryKey) method that produces the results normally stored in the endpoint cache.

This enables their use in these additional cases:

- [useQuery()](https://dataclient.io/docs/api/useQuery.md) - Rendering in React
- [schema.Query()](https://dataclient.io/rest/api/Query.md) - As input to produce a computed memoization.
- [ctrl.get](https://dataclient.io/docs/api/Controller.md#get)/[snap.get](https://dataclient.io/docs/api/Snapshot.md#get)
  - [Managers](https://dataclient.io/docs/concepts/managers.md)
  - React with [useController()](https://dataclient.io/docs/api/useController.md)
  - [RestEndpoint.getOptimisticResponse](https://dataclient.io/rest/api/RestEndpoint.md#getoptimisticresponse)
  - [Unit testing hooks](https://dataclient.io/docs/guides/unit-testing-hooks.md) with [renderDataHook()](https://dataclient.io/docs/api/renderDataHook.md)
- [memo.query()](#memoquery)
- Improve performance of [useSuspense](https://dataclient.io/docs/api/useSuspense.md), [useDLE](https://dataclient.io/docs/api/useDLE.md) by rendering before endpoint resolution

`Querables` include [Entity](https://dataclient.io/rest/api/Entity.md), [All](https://dataclient.io/rest/api/All.md), [Collection](https://dataclient.io/rest/api/Collection.md), [Query](https://dataclient.io/rest/api/Query.md),
[Union](https://dataclient.io/rest/api/Union.md), and [Scalar](https://dataclient.io/rest/api/Scalar.md). [Lazy](https://dataclient.io/rest/api/Lazy.md) fields produce a Queryable via their [`.query`](https://dataclient.io/rest/api/Lazy.md#query) accessor.

```ts
interface Queryable {
  queryKey(
    args: readonly any[],
    queryKey: (...args: any) => any,
    getEntity: GetEntity,
    getIndex: GetIndex,
    // `{}` means non-void
  ): {};
}
```

## Schema Overview

| 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                                                                   | ✅                                                               |
