Skip to content

Commit

Permalink
Restore resolvedData types (#7833)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Cousens <dcousens@users.noreply.github.com>
  • Loading branch information
Noviny and dcousens committed Sep 15, 2022
1 parent da503b4 commit e97631b
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 109 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-snakes-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': patch
---

Fixes types for `resolvedData`, and the return types for `resolveInput` hooks.
313 changes: 206 additions & 107 deletions packages/core/src/lib/schema-type-printer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,136 +14,235 @@ 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')};`;
};
function 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(({ type, defaultValue, name }) => {
const maybe = type instanceof GraphQLNonNull && defaultValue === undefined ? '' : '?';
return ` readonly ${name}${maybe}: ${printTypeReference(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,
prismaKey,
operation,
}: {
listKey: string;
fieldKey: string;
prismaKey: string;
operation: string;
}) {
return ` ${fieldKey}?: import('.prisma/client').Prisma.${listKey}${operation}Input["${prismaKey}"];`;
}

function printInterimMultiFieldType({
listKey,
fieldKey,
operation,
fields,
}: {
listKey: string;
fieldKey: string;
operation: string;
fields: { [key: string]: unknown };
}) {
return [
` ${fieldKey}: {`,
...Object.keys(fields).map(subFieldKey => {
const prismaKey = `${fieldKey}_${subFieldKey}`;
return ' ' + printInterimFieldType({ listKey, fieldKey: subFieldKey, prismaKey, operation });
}),
` };`,
].join('\n');
}

function printInterimType<L extends InitialisedList>(
list: L,
listKey: string,
typename: string,
operation: 'Create' | 'Update'
) {
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, prismaKey: fieldKey, operation });
}),
`};`,
].join('\n');
}

function printListTypeInfo<L extends InitialisedList>(listKey: string, list: L) {
// prettier-ignore
const {
whereInputName,
whereUniqueInputName,
createInputName,
updateInputName,
listOrderName,
} = getGqlNames(list);
const listTypeInfoName = `Lists.${listKey}.TypeInfo`;

// prettier-ignore
return [
`export type ${listKey} = import('@keystone-6/core').ListConfig<${listTypeInfoName}, any>;`,
`namespace ${listKey} {`,
` export type Item = import('.prisma/client').${listKey};`,
` export type TypeInfo = {`,
` key: "${listKey}";`,
` fields: ${Object.keys(list.fields).map(x => `"${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 = {
ID: 'string',
Boolean: 'boolean',
String: 'string',
Int: 'number',
Float: 'number',
JSON: 'import("@keystone-6/core/types").JSONValue',
Decimal: 'import("@keystone-6/core/types").Decimal | string',
};

const printedTypes = printInputTypesFromSchema(graphQLSchema, scalars);

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

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

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: Record<string, any>; // TODO: actual types
update: Record<string, any>; // TODO: actual types
};
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 = `
export type Context = import('@keystone-6/core/types').KeystoneContext<TypeInfo>;
export type TypeInfo = {
lists: {${allListsStr}
};
prisma: import('.prisma/client').PrismaClient;
};
${
''
// we need to reference the `TypeInfo` above in another type that is also called `TypeInfo`
}
type __TypeInfo = TypeInfo;

export type Lists = {
[Key in keyof TypeInfo['lists']]?: import('@keystone-6/core').ListConfig<TypeInfo['lists'][Key], any>
} & Record<string, import('@keystone-6/core').ListConfig<any, any>>;
`;
return printedTypes + listsNamespaceStr + postlude;
return [
printInputTypesFromSchema(graphQLSchema, {
ID: 'string',
Boolean: 'boolean',
String: 'string',
Int: 'number',
Float: 'number',
JSON: 'import("@keystone-6/core/types").JSONValue',
Decimal: 'import("@keystone-6/core/types").Decimal | string',
}),
'',
interimCreateUpdateTypes.join('\n\n'),
'',
'',
'export declare namespace Lists {',
...listsNamespaces,
'}',
`export type Context = import('@keystone-6/core/types').KeystoneContext<TypeInfo>;`,
'',
'export type TypeInfo = {',
` lists: {`,
...listsTypeInfo,
` };`,
` prisma: import('.prisma/client').PrismaClient;`,
`};`,
``,
// we need to reference the `TypeInfo` above in another type that is also called `TypeInfo`
`type __TypeInfo = TypeInfo;`,
``,
`export type Lists = {`,
` [Key in keyof TypeInfo['lists']]?: import('@keystone-6/core').ListConfig<TypeInfo['lists'][Key], any>`,
`} & Record<string, import('@keystone-6/core').ListConfig<any, any>>;`,
``,
`export {}`,
``,
].join('\n');
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ export type KeystoneAdminUISortDirection =
| "ASC"
| "DESC";
type ResolvedTodoCreateInput = {
id?: import('.prisma/client').Prisma.TodoCreateInput["id"];
title?: import('.prisma/client').Prisma.TodoCreateInput["title"];
};
type ResolvedTodoUpdateInput = {
id?: import('.prisma/client').Prisma.TodoUpdateInput["id"];
title?: import('.prisma/client').Prisma.TodoUpdateInput["title"];
};
export declare namespace Lists {
export type Todo = import('@keystone-6/core').ListConfig<Lists.Todo.TypeInfo, any>;
Expand All @@ -123,8 +133,8 @@ export declare namespace Lists {
orderBy: TodoOrderByInput;
};
prisma: {
create: Record<string, any>; // TODO: actual types
update: Record<string, any>; // TODO: actual types
create: ResolvedTodoCreateInput
update: ResolvedTodoUpdateInput
};
all: __TypeInfo;
};
Expand All @@ -145,6 +155,8 @@ export type Lists = {
[Key in keyof TypeInfo['lists']]?: import('@keystone-6/core').ListConfig<TypeInfo['lists'][Key], any>
} & Record<string, import('@keystone-6/core').ListConfig<any, any>>;
export {}
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ node_modules/.keystone/types.js ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
`;

0 comments on commit e97631b

Please sign in to comment.