@baseplate-dev/react-generators
Version:
React Generators for Baseplate
217 lines • 7.73 kB
JavaScript
import { isEqual, sortBy } from 'es-toolkit';
function indent(text) {
return text
.split('\n')
.map((line) => line && ` ${line}`)
.join('\n');
}
function renderGraphQLArgumentValue(value) {
switch (value.type) {
case 'scalar': {
switch (typeof value.value) {
case 'string': {
return `"${value.value}"`;
}
case 'number':
case 'boolean': {
return value.value.toString();
}
default: {
throw new Error(`Unsupported GraphQL scalar value type: ${typeof value.value}`);
}
}
}
case 'variable': {
return `$${value.variable}`;
}
default: {
throw new Error(`Unknown GraphQL value type: ${value.type}`);
}
}
}
function renderGraphQLArgument({ name, value }) {
return `${name}: ${renderGraphQLArgumentValue(value)}`;
}
function renderGraphQLSimpleField({ name, args, fields, }) {
let fieldDefinition = name;
if (args?.length) {
fieldDefinition += `(${args
.map((arg) => renderGraphQLArgument(arg))
.join(', ')})`;
}
if (fields?.length) {
// recursive
fieldDefinition += ` {\n${indent(renderGraphQLFields(fields))}\n}`;
}
return fieldDefinition;
}
function renderGraphQLSpreadField({ on }) {
return `...${on}`;
}
function renderGraphQLField(field) {
switch (field.type) {
case undefined:
case 'simple': {
return renderGraphQLSimpleField(field);
}
case 'spread': {
return renderGraphQLSpreadField(field);
}
default: {
throw new Error(`Unknown GraphQL field type ${field.type}`);
}
}
}
function renderGraphQLFields(fields) {
const sortedFields = sortBy(fields, [
// Sort by simple fields, spread fields, and then nested fields
(f) => {
if (f.type === 'spread')
return 1;
if (f.fields?.length)
return 2;
return 0;
},
// Sort by order if provided
(f) => (f.type === 'spread' ? 0 : (f.order ?? 0)),
// Sort by name otherwise
(f) => (f.type === 'spread' ? f.on : f.name),
]);
return sortedFields.map((field) => renderGraphQLField(field)).join('\n');
}
export function renderGraphQLFragment({ name, type, fields, }) {
return `fragment ${name} on ${type} {
${indent(renderGraphQLFields(fields))}
}`;
}
function renderGraphQLVariable({ name, type }) {
return `$${name}: ${type}`;
}
export function renderGraphQLRoot({ type, name, variables, fields, }) {
let rootString = type;
if (name) {
rootString += ` ${name}`;
}
if (variables?.length) {
rootString += `(${variables
.map((variable) => renderGraphQLVariable(variable))
.join(', ')})`;
}
rootString += ` {\n${indent(renderGraphQLFields(fields))}\n}`;
return rootString;
}
function isSimpleField(field) {
return field.type === 'simple' || field.type === undefined;
}
/**
* Checks if two `GraphQLField` objects are mergeable.
*
* A mergeable pair of fields must either:
* - Be simple fields with the same name and identical arguments, or
* - Be of type 'spread' with the same `on` property value.
*
* @param fieldOne - The first GraphQL field.
* @param fieldTwo - The second GraphQL field.
* @returns `true` if the fields are mergeable; otherwise, `false`.
* @throws If simple fields have different arguments or an unknown field type is encountered.
*/
function areFieldsMergeable(fieldOne, fieldTwo) {
if (isSimpleField(fieldOne) && isSimpleField(fieldTwo)) {
if (fieldOne.name !== fieldTwo.name) {
return false;
}
// Check if arguments are identical
if (!isEqual(fieldOne.args, fieldTwo.args)) {
throw new Error(`Unable to merge fields with different args: ${fieldOne.name}`);
}
return true;
}
if (fieldOne.type === 'spread' && fieldTwo.type === 'spread') {
return fieldOne.on === fieldTwo.on;
}
if (fieldOne.type !== fieldTwo.type) {
return false;
}
throw new Error(`Unknown type: ${fieldOne.type}`);
}
/**
* Merges an array of `GraphQLField` objects, combining any mergeable fields.
*
* This function iterates through the input fields, merging entries that are determined to be mergeable
* based on `areFieldsMergeable`. Non-mergeable fields are added directly to the result.
*
* @param fields - An array of `GraphQLField` objects to merge.
* @returns An array of merged `GraphQLField` objects.
*/
export function mergeGraphQLFields(fields) {
const mergedFields = [];
for (const field of fields) {
// Find an existing field in the merged results that is mergeable with the current field
const existingField = mergedFields.find((accumField) => areFieldsMergeable(accumField, field));
if (existingField && isSimpleField(existingField) && isSimpleField(field)) {
// Merge the `fields` property if both fields are simple
existingField.fields = mergeGraphQLFields([
...(existingField.fields ?? []),
...(field.fields ?? []),
]);
}
else {
// If no mergeable field is found, add the current field to the result
mergedFields.push(field);
}
}
return mergedFields;
}
/**
* Checks if two `GraphQLFragment` objects are mergeable.
*
* Two fragments are considered mergeable if they have the same name and type.
* If the names match but the types differ, an error is thrown.
*
* @param fragOne - The first GraphQL fragment.
* @param fragTwo - The second GraphQL fragment.
* @returns `true` if the fragments are mergeable; otherwise, `false`.
* @throws If fragments have the same name but different types.
*/
function areFragmentsMergeable(fragOne, fragTwo) {
if (fragOne.name === fragTwo.name) {
if (fragOne.type !== fragTwo.type) {
throw new Error(`Unable to merge fragments with different types: ${fragOne.name}`);
}
return true;
}
return false;
}
/**
* Merges an array of `GraphQLFragment` objects, combining any mergeable fragments.
*
* This function iterates through the input fragments and merges entries that are determined to be
* mergeable based on `areFragmentsMergeable`. If fragments are mergeable, their `fields` are merged
* using `mergeGraphQLFields`. Non-mergeable fragments are added directly to the result.
*
* @param frags - An array of `GraphQLFragment` objects to merge.
* @returns An array of merged `GraphQLFragment` objects.
*/
export function mergeGraphQLFragments(frags) {
const mergedFragments = [];
for (const frag of frags) {
// Find an existing fragment in the merged results that is mergeable with the current fragment
const existingFrag = mergedFragments.find((accumFrag) => areFragmentsMergeable(accumFrag, frag));
if (existingFrag) {
// Merge the `fields` property if the fragments are mergeable
existingFrag.fields = mergeGraphQLFields([
...existingFrag.fields,
...frag.fields,
]);
}
else {
// If no mergeable fragment is found, add the current fragment to the result
mergedFragments.push(frag);
}
}
return mergedFragments;
}
export function areFieldsIdentical(fieldsOne, fieldsTwo) {
return isEqual(fieldsOne, fieldsTwo);
}
//# sourceMappingURL=index.js.map