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.
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.
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.
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.
{"post":{"id":"5","author":{"id":"123","name":"Jim"},"content":"Happy day","createdAt":"2019-01-23T06:07:48.311Z"}}
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'; }
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.
Não adicione sua chave primária, como id, à lista de indexes, pois ela já é otimizada.