Pular para o conteúdo principal

GQLEntity

O GraphQL tem uma forma padrão de definir a pk, que é com um campo id.

GQLEntity já vem com um campo id automaticamente, que é usado como pk.

extends

GQLEntity estende Entity

Uso​

import { GQLEntity } from '@data-client/graphql';
import { User } from './User';

export class Article extends GQLEntity {
  title = '';
  content = '';
  author = User.fromJS();
  tags: string[] = [];
  createdAt = Temporal.Instant.fromEpochMilliseconds(0);

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

static schema é uma definição declarativa dos campos a serem processados. Neste caso, author é outra Entity a ser extraída, e createdAt será convertido de uma string para um objeto Date.

dica

Entities são associadas a GQLEndpoints usando o segundo argumento de query ou mutate.

Outras sobrescritas de membros estáticos permitem personalizar o ciclo de vida dos dados, como visto abaixo.

Ciclo de vida dos dados​

Métodos​

pk(parent?, key?, args?): string?​

PK significa primary key (chave primária) e tem o objetivo de fornecer um meio padrão de obter um identificador de chave para qualquer Entity.

O GraphQL usa o campo id como o identificador global de objeto padrão.

pk() {
return this.id;
}

Campo estático key: string​

Isso define a key da própria Entity, e não de uma instância. Precisa ser um valor globalmente único.

aviso

O padrão é this.name; no entanto, isso pode quebrar em builds de produção que alteram os nomes das classes. Isso costuma ser conhecido como class name mangling.

Nesses casos, você pode sobrescrever key ou desativar o mangling de classes.

class User extends GQLEntity {
username = '';

static key = 'User';
}

Método estático process(input, parent, key, args): processedEntity​

Executado no início da normalização desta entity. O valor retornado é salvo no store.

Padrão: simplesmente copiar a resposta ({...input})

Veja como sobrescrever para construir buscas reversas para dados relacionais

Método estático mergeWithStore(existingMeta, incomingMeta, existing, incoming): mergedValue​

static mergeWithStore(
existingMeta: {
date: number;
fetchedAt: number;
},
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
const shouldUpdate = this.shouldUpdate(
existingMeta,
incomingMeta,
existing,
incoming,
);

if (shouldUpdate) {
// distinct types are not mergeable (like delete symbol), so just replace
if (typeof incoming !== typeof existing) {
return incoming;
} else {
return this.shouldReorder(
existingMeta,
incomingMeta,
existing,
incoming,
)
? this.merge(incoming, existing)
: this.merge(existing, incoming);
}
} else {
return existing;
}
}

mergeWithStore() é chamado durante a normalização quando uma entity processada já é encontrada no store.

Ele chama shouldUpdate(), shouldReorder() e, possivelmente, merge()

Método estático shouldUpdate(existingMeta, incomingMeta, existing, incoming): boolean​

static shouldUpdate(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return existingMeta.fetchedAt <= incomingMeta.fetchedAt;
}

Impedindo atualizações​

shouldUpdate também pode ser usado para interromper antecipadamente a atualização de uma entity.

import deepEqual from 'deep-equal';

class Article extends GQLEntity {
title = '';
content = '';
published = false;

static shouldUpdate(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return !deepEqual(incoming, existing);
}
}

Método estático shouldReorder(existingMeta, incomingMeta, existing, incoming): boolean​

static shouldReorder(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return incomingMeta.fetchedAt < existingMeta.fetchedAt;
}

Um valor de retorno true inverterá a ordem dos argumentos da entity recebida e da entity no store no merge. Com o merge padrão, isso fará com que os campos das entities existentes sobrescrevam os das recebidas, em vez do contrário.

Exemplo​

import { GQLEntity } from '@data-client/graphql';

export class LatestPriceEntity extends GQLEntity {
  updatedAt = 0;
  price = '0.0';
  symbol = '';

  static shouldReorder(
    existingMeta: { date: number; fetchedAt: number },
    incomingMeta: { date: number; fetchedAt: number },
    existing: { updatedAt: number },
    incoming: { updatedAt: number },
  ) {
    return incoming.updatedAt < existing.updatedAt;
  }
}

Método estático merge(existing, incoming): mergedValue​

static merge(existing: any, incoming: any) {
return {
...existing,
...incoming,
};
}

O merge é usado para tratar os casos em que uma entity recebida já foi encontrada. Ele é chamado diretamente quando a mesma entity é encontrada em uma única resposta. Por padrão, também é chamado quando mergeWithStore() determina que a entity recebida deve ser mesclada com uma entity já persistida no store do Reactive Data Client.

Veja como sobrescrever para construir buscas reversas para dados relacionais

Método estático mergeMetaWithStore(existingMeta, incomingMeta, existing, incoming): meta​

static mergeMetaWithStore(
existingMeta: {
expiresAt: number;
date: number;
fetchedAt: number;
},
incomingMeta: { expiresAt: number; date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return this.shouldReorder(existingMeta, incomingMeta, existing, incoming)
? existingMeta
: incomingMeta;
}

mergeMetaWithStore() é chamado durante a normalização quando uma entity processada já é encontrada no store.

Método estático queryKey(args, queryKey, getEntity, getIndex): pk?​

Este método permite que Entities sejam Queryable, possibilitando o acesso ao store sem um endpoint.

Sobrescrevê-lo permite personalizar ou desativar completamente esse comportamento.

Retornar undefined desabilitará esse comportamento.

Retornar uma string pk tentará buscar essa entity e usá-la na resposta.

Quando usado, a política de expiração é calculada com base nos próprios metadados da entity.

Por padrão, usa o primeiro argumento para buscar em pk() e indexes

Método estático createIfValid(processedEntity): Entity | undefined​

Chamado ao desnormalizar uma entity. Cria uma instância desta classe se ela for considerada 'válida'.

Retornar undefined resultará em status de expiração Invalid, assim como Invalidate.

A expiração Invalid geralmente significa que os hooks entrarão em um estado de carregamento e tentarão um novo fetch.

static createIfValid(props): AbstractInstanceType<this> | undefined {
if (this.validate(props)) {
return undefined as any;
}
return this.fromJS(props);
}

Método estático validate(processedEntity): errorMessage?​

Executado tanto na normalização quanto na desnormalização. Retornar uma string indica um erro (a string é a mensagem).

Durante a normalização, uma falha de validação resultará em um erro para aquele fetch.

Durante a desnormalização, uma falha de validação marcará aquele resultado como 'inválido' e, assim, bloqueará até que um resultado seja buscado.

Por padrão, faz algumas verificações básicas de existência de campos somente no modo de desenvolvimento. Sobrescreva para desativar ou personalizar.

Usando validação em endpoints com campos incompletos

Método estático fromJS(props): Entity​

Método factory que copia props para uma nova instância. Use-o em vez de new MyEntity(), para garantir que as props padrão sejam sobrescritas.

Campos​

Campo estático schema: { [k: keyof this]: Schema }​

Define membros de entities relacionadas ou a desserialização de campos, como Date e BigNumber.

Fixtures
query getPost($id: ID!) { post(id: $id) { id author createdAt content title } } {"id":"123"}
{"post":{"id":"5","author":{"id":"123","name":"Jim"},"content":"Happy day","createdAt":"2019-01-23T06:07:48.311Z"}}
▶User
▶Post
import { GQLEntity } from '@data-client/graphql';
import { Temporal } from 'temporal-polyfill';
import { User } from './User';

export class Post extends GQLEntity {
  author = User.fromJS({});
  createdAt = Temporal.Instant.fromEpochMilliseconds(0);
  content = '';
  title = '';

  static schema = {
    author: User,
    createdAt: Temporal.Instant.from,
  };
  static key = 'Post';
}
▶PostPage
Resultado
Store▶

Membros opcionais​

As referências a entities aqui, cujos valores padrão na própria definição do Record são considerados 'opcionais'

class User extends GQLEntity {
friend: User | null = null; // this field is optional
lastUpdated = Temporal.Instant.fromEpochMilliseconds(0);

static schema = {
friend: User,
lastUpdated: Temporal.Instant.from,
};
}

Campo estático indexes?: (keyof this)[]​

Indexes aumentam o desempenho ao fazer buscas baseadas nesses parâmetros. Adicione à lista os nomes de campos (como slug, username) que você deseja enviar como params para buscas posteriores.

observação

Não adicione sua chave primária, como id, à lista de indexes, pois ela já é otimizada.