Skip to content

Commit

Permalink
rewrite schema type printer
Browse files Browse the repository at this point in the history
  • Loading branch information
dcousens committed Aug 24, 2022
1 parent 6f2439b commit 926bc79
Showing 1 changed file with 165 additions and 110 deletions.
275 changes: 165 additions & 110 deletions packages/core/src/lib/schema-type-printer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,100 +14,176 @@ import { InitialisedList } from './core/types-for-lists';

const introspectionTypesSet = new Set(introspectionTypes);

let printEnumTypeDefinition = (type: GraphQLEnumType) => {
return `export type ${type.name} =\n${type
.getValues()
.map(x => ` | ${JSON.stringify(x.name)}`)
.join('\n')};`;
const printEnumTypeDefinition = (type: GraphQLEnumType) => {
return [
`export type ${type.name} =`,
type
.getValues()
.map(x => ` | ${JSON.stringify(x.name)}`)
.join('\n') + ';',
].join('\n');
};

function printInputTypesFromSchema(schema: GraphQLSchema, scalars: Record<string, string>) {
let printTypeReferenceWithoutNullable = (
type: GraphQLNamedType | GraphQLList<GraphQLType>
): string => {
if (type instanceof GraphQLList) {
return `ReadonlyArray<${printTypeReference(type.ofType)}> | ${printTypeReference(
type.ofType
)}`;
}
let name = type.name;
if (type instanceof GraphQLScalarType) {
if (scalars[name] === undefined) {
return 'any';
}
return `Scalars[${JSON.stringify(name)}]`;
}
return name;
};
let printTypeReference = (type: GraphQLType): string => {
if (type instanceof GraphQLNonNull) {
return printTypeReferenceWithoutNullable(type.ofType);
}
return `${printTypeReferenceWithoutNullable(type)} | null`;
};
let printInputObjectTypeDefinition = (type: GraphQLInputObjectType) => {
let str = `export type ${type.name} = {\n`;
for (const field of Object.values(type.getFields())) {
str += ` readonly ${field.name}${
field.type instanceof GraphQLNonNull && field.defaultValue === undefined ? '' : '?'
}: ${printTypeReference(field.type)};\n`;
}
function printTypeReference(type: GraphQLType, scalars: Record<string, string>): string {
if (type instanceof GraphQLNonNull) {
return printTypeReferenceWithoutNullable(type.ofType, scalars);
}
return `${printTypeReferenceWithoutNullable(type, scalars)} | null`;
}

str += '};';
return str;
};
let typeString = 'type Scalars = {\n';
for (let scalar in scalars) {
typeString += ` readonly ${scalar}: ${scalars[scalar]};\n`;
function printTypeReferenceWithoutNullable(
type: GraphQLNamedType | GraphQLList<GraphQLType>,
scalars: Record<string, string>
): string {
if (type instanceof GraphQLList) {
return `ReadonlyArray<${printTypeReference(type.ofType, scalars)}> | ${printTypeReference(
type.ofType,
scalars
)}`;
}
typeString += '};';

const name = type.name;
if (type instanceof GraphQLScalarType) {
if (scalars[name] === undefined) return 'any';
return `Scalars[${JSON.stringify(name)}]`;
}

return name;
}

function printInputObjectTypeDefinition(
type: GraphQLInputObjectType,
scalars: Record<string, string>
) {
return [
`export type ${type.name} = {`,
...Object.values(type.getFields()).map(field => {
const maybe = field.type instanceof GraphQLNonNull && field.defaultValue === undefined ? '' : '?';
return ` readonly ${field.name}${maybe}: ${printTypeReference(field.type, scalars)};`;
}),
'};',
].join('\n');
}

function printInputTypesFromSchema(schema: GraphQLSchema, scalars: Record<string, string>) {
const output = [
'type Scalars = {',
...Object.keys(scalars).map(scalar => ` readonly ${scalar}: ${scalars[scalar]};`),
'};',
];

for (const type of Object.values(schema.getTypeMap())) {
// We don't want to print TS types for the built-in GraphQL introspection types
// they won't be used for anything we want to print here.
if (introspectionTypesSet.has(type)) continue;
if (type instanceof GraphQLInputObjectType) {
typeString += '\n\n' + printInputObjectTypeDefinition(type);
output.push('', printInputObjectTypeDefinition(type, scalars));
}
if (type instanceof GraphQLEnumType) {
typeString += '\n\n' + printEnumTypeDefinition(type);
output.push('', printEnumTypeDefinition(type));
}
}
return typeString + '\n\n';

return output.join('\n');
}

function printInterimFieldType({
listKey,
fieldKey,
operation,
}: {
listKey: string;
fieldKey: string;
operation: string;
}) {
return ` ${fieldKey}?: import('.prisma/client').Prisma.${listKey}${operation}Input["${fieldKey}"];`;
}

function printInterimMultiFieldType({
listKey,
fieldKey,
operation,
fields,
}: {
listKey: string;
fieldKey: string;
operation: string;
fields: { [key: string]: unknown };
}) {
return [
` ${fieldKey}: {`,
...Object.keys(fields).map(subFieldKey => {
return ` ${subFieldKey}?: import('.prisma/client').Prisma.${listKey}${operation}Input["${fieldKey}_${subFieldKey}"];`;
}),
` };`,
].join('\n');
}

function printInterimType<L extends InitialisedList>(
list: L,
listKey: string,
typename: string,
type: 'Create' | 'Update'
operation: 'Create' | 'Update'
) {
let resolvedTypeString = `type Resolved${typename} = {\n`;
for (let [fieldKey, { dbField }] of Object.entries(list.fields)) {
if (dbField.kind === 'multi') {
resolvedTypeString +=
` ${fieldKey}: {\n` +
Object.keys(dbField.fields)
.map(
key =>
` ${key}?: import('.prisma/client').Prisma.${listKey}${type}Input["${fieldKey}_${key}"];`
)
.join('\n') +
'\n }\n';
} else if (dbField.kind === 'none') {
resolvedTypeString += ` ${fieldKey}?: undefined\n`;
} else {
resolvedTypeString += ` ${fieldKey}?: import('.prisma/client').Prisma.${listKey}${type}Input["${fieldKey}"];\n`;
}
}
resolvedTypeString += '};\n\n';
return resolvedTypeString;
return [
`type Resolved${typename} = {`,
...Object.entries(list.fields).map(([fieldKey, { dbField }]) => {
if (dbField.kind === 'none') return ` ${fieldKey}?: undefined\n`;
if (dbField.kind === 'multi') {
return printInterimMultiFieldType({
listKey,
fieldKey,
operation,
fields: dbField.fields,
});
}

return printInterimFieldType({ listKey, fieldKey, operation });
}),
`};`,
].join('\n')
}

function printListTypeInfo <L extends InitialisedList>(listKey: string, list: L) {
const {
whereInputName,
whereUniqueInputName,
createInputName,
updateInputName,
listOrderName,
} = getGqlNames(list);

const listTypeInfoName = `Lists.${listKey}.TypeInfo`;
return [
`export type ${listKey} = import('@keystone-6/core').ListConfig<${listTypeInfoName}, any>;`,
`namespace ${listKey} {`,
` export type Item = import('.prisma/client').${listKey};`,
` export type TypeInfo = {`,
` key: ${JSON.stringify(listKey)};`,
` fields: ${Object.keys(list.fields).map(x => JSON.stringify(x)).join(' | ')}`,
` item: Item;`,
` inputs: {`,
` where: ${whereInputName};`,
` uniqueWhere: ${whereUniqueInputName};`,
` create: ${createInputName};`,
` update: ${updateInputName};`,
` orderBy: ${listOrderName};`,
` };`,
` prisma: {`,
` create: Resolved${createInputName}`,
` update: Resolved${updateInputName}`,
` };`,
` all: __TypeInfo;`,
` };`,
`}`
].map(line => ` ${line}`).join('\n');
}

export function printGeneratedTypes(
graphQLSchema: GraphQLSchema,
lists: Record<string, InitialisedList>
) {
let scalars = {
const scalars = {
ID: 'string',
Boolean: 'boolean',
String: 'string',
Expand All @@ -118,60 +194,40 @@ export function printGeneratedTypes(
};

const printedTypes = printInputTypesFromSchema(graphQLSchema, scalars);

let allListsStr = '';
let listsNamespaceStr = '\nexport declare namespace Lists {';
let interimCreateUpdateTypes = '';
const interimCreateUpdateTypes = [];
const listsTypeInfo = [];
const listsNamespaces = [];

for (const [listKey, list] of Object.entries(lists)) {
const gqlNames = getGqlNames(list);

interimCreateUpdateTypes +=
printInterimType(list, listKey, gqlNames.createInputName, 'Create') +
printInterimType(list, listKey, gqlNames.updateInputName, 'Update');

const listTypeInfoName = `Lists.${listKey}.TypeInfo`;

allListsStr += `\n readonly ${listKey}: ${listTypeInfoName};`;
listsNamespaceStr += `
export type ${listKey} = import('@keystone-6/core').ListConfig<${listTypeInfoName}, any>;
namespace ${listKey} {
export type Item = import('.prisma/client').${listKey};
export type TypeInfo = {
key: ${JSON.stringify(listKey)};
fields: ${Object.keys(list.fields)
.map(x => JSON.stringify(x))
.join(' | ')}
item: Item;
inputs: {
where: ${gqlNames.whereInputName};
uniqueWhere: ${gqlNames.whereUniqueInputName};
create: ${gqlNames.createInputName};
update: ${gqlNames.updateInputName};
orderBy: ${gqlNames.listOrderName};
};
prisma: {
create: Resolved${gqlNames.createInputName}
update: Resolved${gqlNames.updateInputName}
};
all: __TypeInfo;
};
}`;
interimCreateUpdateTypes.push(printInterimType(list, listKey, gqlNames.createInputName, 'Create'));
interimCreateUpdateTypes.push(printInterimType(list, listKey, gqlNames.updateInputName, 'Update'));

listsTypeInfo.push(` readonly ${listKey}: ${listTypeInfoName};`);
listsNamespaces.push(printListTypeInfo(listKey, list));
}
listsNamespaceStr += '\n}';

const postlude = `
return `${printedTypes}
${interimCreateUpdateTypes.join('\n\n')}
export declare namespace Lists {
${listsNamespaces.join('\n')}
}
export type Context = import('@keystone-6/core/types').KeystoneContext<TypeInfo>;
export type TypeInfo = {
lists: {${allListsStr}
lists: {
` + listsTypeInfo.join('\n') + `
};
prisma: import('.prisma/client').PrismaClient;
};
${
''
// we need to reference the `TypeInfo` above in another type that is also called `TypeInfo`
}
` +
// we need to reference the `TypeInfo` above in another type that is also called `TypeInfo`
`
type __TypeInfo = TypeInfo;
export type Lists = {
Expand All @@ -180,5 +236,4 @@ export type Lists = {
export {}
`;
return printedTypes + interimCreateUpdateTypes + listsNamespaceStr + postlude;
}

0 comments on commit 926bc79

Please sign in to comment.