@dossierhq/graphql
Version:
A library for creating GraphQL servers with Dossier.
196 lines • 9.08 kB
JavaScript
/// <reference types="./DataLoaders.d.ts" />
import { ContentTraverseNodeType, isComponentListField, isComponentSingleField, isReferenceItemField, isReferenceListField, isReferenceSingleField, isRichTextEntityLinkNode, isRichTextEntityNode, isRichTextSingleField, traverseContentField, } from '@dossierhq/core';
import { getRequestedChildFields } from './utils/getRequestedChildFields.js';
export async function loadPublishedEntity(schema, context, reference) {
const publishedClient = context.publishedClient.valueOrThrow();
const result = await publishedClient.getEntity(reference);
return buildResolversForPublishedEntity(schema, result.valueOrThrow());
}
export async function loadPublishedEntityList(schema, context, ids) {
const publishedClient = context.publishedClient.valueOrThrow();
const results = await publishedClient.getEntityList(ids.map((id) => ({ id })));
return results
.valueOrThrow()
.map((result) => result.isOk()
? buildResolversForPublishedEntity(schema, result.value)
: buildErrorResolver(result));
}
function buildErrorResolver(result) {
return result.toError();
}
export async function loadPublishedEntitiesSample(schema, context, query, options) {
const publishedClient = context.publishedClient.valueOrThrow();
const result = await publishedClient.getEntitiesSample(query, options);
if (result.isError()) {
throw result.toError();
}
return {
...result.value,
items: result.value.items.map((it) => buildResolversForPublishedEntity(schema, it)),
};
}
export async function loadPublishedEntities(schema, context, query, paging, info) {
const publishedClient = context.publishedClient.valueOrThrow();
return buildResolversForConnection(() => publishedClient.getEntities(query, paging), () => publishedClient.getEntitiesTotalCount(query), (it) => buildResolversForPublishedEntity(schema, it), info);
}
function buildResolversForPublishedEntity(schema, entity) {
const entitySpec = schema.getEntityTypeSpecification(entity.info.type);
if (!entitySpec) {
throw new Error(`Couldn't find entity spec for type: ${entity.info.type}`);
}
const result = { ...entity };
resolveFields(schema, entitySpec, result, false);
return result;
}
async function buildResolversForConnection(connectionLoader, totalCountLoader, nodeResolver, info) {
const requestedFields = getRequestedChildFields(info);
const loadConnection = requestedFields.has('edges') || requestedFields.has('pageInfo');
const loadTotalCount = requestedFields.has('totalCount');
// Load in parallel, only load what's needed
const [connection, totalCount] = await Promise.all([
loadConnection ? connectionLoader().then((it) => it.valueOrThrow()) : null,
loadTotalCount ? totalCountLoader().then((it) => it.valueOrThrow()) : null,
]);
if (loadConnection && connection === null) {
// No results
return null;
}
return {
pageInfo: connection?.pageInfo,
edges: connection?.edges.map((edge) => ({
cursor: edge.cursor,
node: edge.node.isOk() ? nodeResolver(edge.node.value) : buildErrorResolver(edge.node),
})),
totalCount,
};
}
export async function loadAdminEntity(schema, context, reference) {
const client = context.client.valueOrThrow();
const result = await client.getEntity(reference);
return buildResolversForAdminEntity(schema, result.valueOrThrow());
}
export async function loadAdminEntityList(schema, context, ids) {
const client = context.client.valueOrThrow();
const results = await client.getEntityList(ids.map((id) => ({ id })));
return results
.valueOrThrow()
.map((result) => result.isOk()
? buildResolversForAdminEntity(schema, result.value)
: buildErrorResolver(result));
}
export function buildResolversForAdminEntity(schema, entity) {
const entitySpec = schema.getEntityTypeSpecification(entity.info.type);
if (!entitySpec) {
throw new Error(`Couldn't find entity spec for type: ${entity.info.type}`);
}
const payload = {
...entity,
changelogEvents: (args, context, info) => {
const { query, first, after, last, before } = args;
const paging = { first, after, last, before };
return loadChangelogEvents(context, { ...query, entity: { id: entity.id } }, paging, info);
},
};
resolveFields(schema, entitySpec, payload, true);
return payload;
}
export async function loadAdminEntitiesSample(schema, context, query, options) {
const client = context.client.valueOrThrow();
const result = await client.getEntitiesSample(query, options);
const payload = result.valueOrThrow();
return {
...payload,
items: payload.items.map((it) => buildResolversForAdminEntity(schema, it)),
};
}
export function loadAdminEntities(schema, context, query, paging, info) {
const client = context.client.valueOrThrow();
return buildResolversForConnection(() => client.getEntities(query, paging), () => client.getEntitiesTotalCount(query), (it) => buildResolversForAdminEntity(schema, it), info);
}
function resolveFields(schema, spec, item, isAdmin) {
const fields = isComponent(item) ? item : item.fields;
for (const fieldSpec of spec.fields) {
const value = fields[fieldSpec.name];
if (isRichTextSingleField(fieldSpec, value) && value) {
const ids = extractEntityIdsForRichTextField(schema, fieldSpec, value);
fields[fieldSpec.name] = {
root: value.root,
entities: ids.length === 0
? []
: (_args, context, _info) => {
return isAdmin
? loadAdminEntityList(schema, context, ids)
: loadPublishedEntityList(schema, context, ids);
},
};
}
else if (isReferenceSingleField(fieldSpec, value) && value) {
fields[fieldSpec.name] = (_args, context, _info) => isAdmin
? loadAdminEntity(schema, context, value)
: loadPublishedEntity(schema, context, value);
}
else if (isReferenceListField(fieldSpec, value) && value && value.length > 0) {
fields[fieldSpec.name] = (_args, context, _info) => {
const ids = value.map((it) => it.id);
return isAdmin
? loadAdminEntityList(schema, context, ids)
: loadPublishedEntityList(schema, context, ids);
};
}
else if (isComponentSingleField(fieldSpec, value) && value) {
fields[fieldSpec.name] = buildResolversForValue(schema, value, isAdmin);
}
else if (isComponentListField(fieldSpec, value) && value && value.length > 0) {
fields[fieldSpec.name] = value.map((it) => buildResolversForValue(schema, it, isAdmin));
}
}
}
function extractEntityIdsForRichTextField(schema, fieldSpec, value) {
const referencesCollector = createReferencesCollector();
for (const node of traverseContentField(schema, [fieldSpec.name], fieldSpec, value)) {
referencesCollector.collect(node);
}
return referencesCollector.result;
}
//TODO we have two identical (three similar) implementations of this function, should it move to core?
function createReferencesCollector() {
const references = new Set();
return {
collect: (node) => {
switch (node.type) {
case ContentTraverseNodeType.fieldItem:
if (isReferenceItemField(node.fieldSpec, node.value) && node.value) {
references.add(node.value.id);
}
break;
case ContentTraverseNodeType.richTextNode: {
const richTextNode = node.node;
if (isRichTextEntityNode(richTextNode) || isRichTextEntityLinkNode(richTextNode)) {
references.add(richTextNode.reference.id);
}
break;
}
}
},
get result() {
return [...references];
},
};
}
function buildResolversForValue(schema, component, isAdmin) {
const componentSpec = schema.getComponentTypeSpecification(component.type);
if (!componentSpec) {
throw new Error(`Couldn't find component spec for type: ${component.type}`);
}
const result = { ...component };
resolveFields(schema, componentSpec, result, isAdmin);
return result;
}
export async function loadChangelogEvents(context, query, paging, info) {
const client = context.client.valueOrThrow();
return buildResolversForConnection(() => client.getChangelogEvents(query, paging), () => client.getChangelogEventsTotalCount(query), (it) => it, info);
}
export function isComponent(item) {
return 'type' in item;
}
//# sourceMappingURL=DataLoaders.js.map