Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore resolvedData types #7833

Merged
merged 4 commits into from
Aug 24, 2022
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
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 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

`;