Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 0 additions & 100 deletions packages/core/src/generators/arguments.ts

This file was deleted.

83 changes: 25 additions & 58 deletions packages/core/src/generators/schema.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import { schemaComposer } from 'graphql-compose';
import { composeWithJson } from 'graphql-compose-json';
import { cloneDeep, merge } from 'lodash-es';
import { merge } from 'lodash-es';
import plur from 'plur';
import { cacheSchema, checkCacheForSchema } from '../cache/cache';
import {
generateArgsForAllItemQuery,
generateArgsForManyItemQuery,
generateArgsForSingleItemQuery,
} from '../generators/arguments';
import resolveQueryArgs from '../resolvers/arguments';
} from '../query-executor/graphql-arguments';
import { createQueryExecutor } from '../query-executor';
import {
ConfigResult,
ContentGraphSnapshot,
ContentNode,
EntryNode,
LoadedFlatbreadConfig,
} from '../types';
import {
getNodeIdentifier,
normalizeIdentifier,
normalizeOptionalIdentifier,
} from '../utils/ids';
import { produceRecords, validateRecords } from '../records';
import { generateCollection } from './generateCollection';

Expand Down Expand Up @@ -80,6 +75,17 @@ export async function generateSchema(
schemaComposer.clear();

const preknownSchemaFragments = fetchPreknownSchemaFragments(config);
const executor = createQueryExecutor({
collections: contentNodesByCollection,
relations: Object.fromEntries(
config.content.map(({ collection, refs }) => [collection, refs ?? {}])
),
fieldNameTransform: config.fieldNameTransform,
preknownSchemaFragments,
overridesByCollection: Object.fromEntries(
config.content.map(({ collection, overrides }) => [collection, overrides])
),
});

/**
* For each content collection, reduce the nodes therein to one singular node containing the set of all fields in the collection. This reduced node for each collection then is fed into a GraphQL collection composer to recursively generate a GraphQL schema.
Expand Down Expand Up @@ -113,6 +119,8 @@ export async function generateSchema(

// Main builder loop - iterate through each content type and generate query resolvers + relationships for it
for (const [type, schema] of Object.entries(schemaArray)) {
const refs =
config.content.find((content) => content.collection === type)?.refs ?? {};
const pluralType = plur(type, 2);
const pluralTypeQueryName = 'all' + pluralType;

Expand All @@ -136,68 +144,27 @@ export async function generateSchema(
name: 'findById',
type: () => schema,
description: `Find one ${type} by its ID`,
args: generateArgsForSingleItemQuery(),
resolve: (rp: ResolverPayload) => {
const idToFind = normalizeOptionalIdentifier(
rp.args.id,
`${type} query argument "id"`
);

if (idToFind === undefined) {
return undefined;
}

return cloneDeep(contentNodesByCollection[type]).find(
(node: ContentNode) => getNodeIdentifier(node, type) === idToFind
);
},
args: generateArgsForSingleItemQuery() as never,
resolve: (rp: ResolverPayload) =>
executor.findById({ name: type, refs }, rp.args.id),
});

schema.addResolver({
name: 'findMany',
type: () => [schema],
description: `Find many ${pluralType} by their IDs`,
args: generateArgsForManyItemQuery(pluralType),
resolve: (rp: ResolverPayload) => {
if (rp.args.ids !== undefined && !Array.isArray(rp.args.ids)) {
throw new Error(
`${type} query argument "ids" must be an array of identifiers.`
);
}
const idsArg = rp.args.ids ?? [];
const idsToFind = idsArg.map((id: unknown): string =>
normalizeIdentifier(id, `${type} query argument "ids"`)
);
const matches =
cloneDeep(contentNodesByCollection[type])?.filter(
(node: ContentNode) =>
idsToFind?.includes(getNodeIdentifier(node, type))
) ?? [];
return resolveQueryArgs(matches, rp.args, config, {
type: {
name: type,
pluralName: pluralType,
pluralQueryName: pluralTypeQueryName,
},
});
},
args: generateArgsForManyItemQuery(pluralType) as never,
resolve: (rp: ResolverPayload) =>
executor.findMany({ name: type, refs }, rp.args.ids, rp.args),
});

schema.addResolver({
name: 'all',
args: generateArgsForAllItemQuery(pluralType),
args: generateArgsForAllItemQuery(pluralType) as never,
type: () => [schema],
description: `Return a set of ${pluralType}`,
resolve: (rp: ResolverPayload) => {
const nodes = cloneDeep(contentNodesByCollection[type]);
return resolveQueryArgs(nodes, rp.args, config, {
type: {
name: type,
pluralName: pluralType,
pluralQueryName: pluralTypeQueryName,
},
});
},
resolve: (rp: ResolverPayload) =>
executor.all({ name: type, refs }, rp.args),
});

schemaComposer.Query.addFields({
Expand Down
119 changes: 119 additions & 0 deletions packages/core/src/query-executor/graphql-arguments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Generates the accepted arguments for an 'All' query on a content type.
*
* @param pluralType plural name of the content type
*/
export function generateArgsForAllItemQuery(
pluralType: string
): Record<string, unknown> {
return {
...skip(),
...limit(pluralType),
...order(pluralType, 'ASC'),
...sortBy(pluralType),
...filter(pluralType),
};
}

/**
* Generates the accepted arguments for a 'many-item' query on a content type.
*
* @param pluralType plural name of the content type
*/
export function generateArgsForManyItemQuery(
pluralType: string
): Record<string, unknown> {
return {
ids: {
type: '[ID]',
},
...skip(),
...limit(pluralType),
...order(pluralType, 'ASC'),
...sortBy(pluralType),
};
}

/**
* Generates the accepted arguments for a 'single-item' query on a content type.
*/
export function generateArgsForSingleItemQuery(): Record<string, unknown> {
return {
id: {
type: 'ID',
},
};
}

/**
* Argument for skipping the first `n` items from the query results.
*/
function skip(): Record<string, unknown> {
return {
skip: {
description: 'Skip the first `n` results',
type: 'Int',
},
};
}

/**
* Argument for limiting the maximum number of items in the query results.
*
* @param pluralType plural name of the content type
*/
function limit(pluralType: string): Record<string, unknown> {
return {
limit: {
description: `The maximum number of ${pluralType} to return`,
type: 'Int',
},
};
}

/**
* Argument for ordering the direction of sorting items in the query results.
*
* @param pluralType plural name of the content type
* @param defaultValue default order to use if not explicitly specified in the query
*/
function order(
pluralType: string,
defaultValue: 'ASC' | 'DESC' = 'ASC'
): Record<string, unknown> {
return {
order: {
description: `Which order to return ${pluralType} in`,
type: `enum Order { ASC DESC }`,
defaultValue,
},
};
}

/**
* Argument for the field to sort items by in the query.
*
* @param pluralType plural name of the content type
*/
function sortBy(pluralType: string): Record<string, unknown> {
return {
sortBy: {
description: `The field to sort ${pluralType} by`,
type: 'String',
},
};
}

/**
* Argument for the deep filter to apply to items in the query results.
*
* @param pluralType plural name of the content type
*/
function filter(pluralType: string): Record<string, unknown> {
return {
filter: {
description: `Filter ${pluralType} by a JSON object`,
type: 'JSON',
},
};
}
Loading
Loading