Skip to main content

Thinking in Schemas

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

{
"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 types within our article: users and comments. Using various schema, we can normalize all three entity types down:

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

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

pk() {
return this.id;
}
}

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

pk() {
return this.id;
}

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

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

pk() {
return this.id;
}

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

Normalize

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

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

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

{
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"
}
}
},
}

Denormalize

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

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

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

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' }]
}
]
}

Schema Overview

SchemaDescriptionData TypeMutableHas A
Entityhas a primary keyObject
Objectmaps with known keysObject🛑
Arrayvariably sized listsArray🛑
Valuesvariably sized maps (arbitrary keys)Object🛑
Alllist of all entities of a kindArray🛑Entity
Collectionexpandable Arrays or ObjectsObject or ArrayArray or Values
Unionselect from many different typesPolymorphic ObjectEntity
Invalidateremove an entityObjectEntity