UNPKG

@dossierhq/graphql

Version:

A library for creating GraphQL servers with Dossier.

196 lines 9.08 kB
/// <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