Skip to content

Commit

Permalink
Improve usage of generated types (#7001)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored and JedWatson committed Nov 30, 2021
1 parent fb7844a commit f455498
Show file tree
Hide file tree
Showing 63 changed files with 742 additions and 694 deletions.
30 changes: 30 additions & 0 deletions .changeset/stale-pumpkins-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@keystone-next/keystone': major
'@keystone-next/auth': major
'@keystone-next/fields-document': major
'@keystone-next/cloudinary': major
---

- The following types have been renamed:
- `BaseGeneratedListTypes``BaseListTypeInfo`
- `ItemRootValue``BaseItem`
- `ListInfo``ListGraphQLTypes`
- `TypesForList``GraphQLTypesForList`
- `FieldTypeFunc` now has a required type parameter which must satisfy `BaseListTypeInfo`
- The following types now have a required type parameter which must satisfy `BaseKeystoneTypeInfo`:
- `ServerConfig`
- `CreateRequestContext`
- `AdminUIConfig`
- `DatabaseConfig`
- `ListOperationAccessControl`
- `MaybeSessionFunction`
- `MaybeItemFunction`
- `GraphQLResolver` and `GraphQLSchemaExtension` now have a required type parameter which must satisfy `KeystoneContext`
- `KeystoneGraphQLAPI` no longer has a type parameter
- The first parameter to the resolver in a `virtual` field will be typed as the item type if the list is typed with `Keystone.Lists` or `Keystone.Lists.ListKey`, otherwise it will be typed as `unknown`
- The `item`/`originalItem` arguments in hooks/access control will now receive the `Item` type if the list is typed with `Keystone.Lists` or `Keystone.Lists.ListKey`, otherwise it will be typed as `BaseItem`
- `args` has been removed from `BaseListTypeInfo`
- `inputs.orderBy` and `all` has been added to `BaseListTypeInfo`
- In `.keystone/types`:
- `ListKeyListTypeInfo` has been moved to `Lists.ListKey.TypeInfo`
- `KeystoneContext` has been renamed to `Context`
12 changes: 5 additions & 7 deletions docs/pages/docs/guides/custom-fields.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ We define our field type `myInt` and the corresponding type `MyIntFieldConfig` w

```ts
import {
BaseGeneratedListTypes,
BaseListTypeInfo,
FieldTypeFunc,
CommonFieldConfig,
fieldType,
Expand All @@ -34,18 +34,16 @@ import {
} from '@keystone-6/keystone/types';
import { graphql } from '@keystone-6/keystone';

export type MyIntFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> =
CommonFieldConfig<TGeneratedListTypes> & {
export type MyIntFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
isIndexed?: boolean | 'unique';
};

export const myInt =
<TGeneratedListTypes extends BaseGeneratedListTypes>({
<ListTypeInfo extends BaseListTypeInfo>({
isIndexed,
isRequired,
defaultValue,
...config
}: MyIntFieldConfig<TGeneratedListTypes> = {}): FieldTypeFunc =>
}: MyIntFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> =>
meta =>
fieldType({
kind: 'scalar',
Expand Down
2 changes: 0 additions & 2 deletions examples-staging/basic/next-env.d.ts

This file was deleted.

3 changes: 0 additions & 3 deletions examples-staging/basic/next.config.js

This file was deleted.

2 changes: 0 additions & 2 deletions examples-staging/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@
"@keystone-ui/fields": "^6.0.0",
"@keystone-ui/icons": "^5.0.0",
"@keystone-ui/tooltip": "^5.0.0",
"@preconstruct/next": "^3.0.1",
"@types/react": "^17.0.35",
"graphql": "^15.7.2",
"graphql-tag": "^2.12.6",
"next": "^12.0.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"uuid": "^8.3.2"
Expand Down
126 changes: 61 additions & 65 deletions examples-staging/basic/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import {
file,
} from '@keystone-6/keystone/fields';
import { document } from '@keystone-6/fields-document';
// import { cloudinaryImage } from '@keystone-6/cloudinary';
import { KeystoneListsAPI } from '@keystone-6/keystone/types';
import { v4 } from 'uuid';
import { componentBlocks } from './admin/fieldViews/Content';
import { KeystoneListsTypeInfo } from '.keystone/types';
import * as Keystone from '.keystone/types';

type AccessArgs = {
session?: {
Expand All @@ -35,65 +33,67 @@ export const access = {

const randomNumber = () => Math.round(Math.random() * 10);

export const lists = {
User: list({
ui: {
listView: {
initialColumns: ['name', 'posts', 'avatar'],
},
const User: Keystone.Lists.User = list({
ui: {
listView: {
initialColumns: ['name', 'posts', 'avatar'],
},
fields: {
/** The user's first and last name. */
name: text({ validation: { isRequired: true } }),
/** Email is used to log into the system. */
email: text({ isIndexed: 'unique', validation: { isRequired: true } }),
/** Avatar upload for the users profile, stored locally */
avatar: image(),
attachment: file(),
/** Used to log in. */
password: password(),
/** Administrators have more access to various lists and fields. */
isAdmin: checkbox({
access: {
read: access.isAdmin,
create: access.isAdmin,
update: access.isAdmin,
},
fields: {
/** The user's first and last name. */
name: text({ validation: { isRequired: true } }),
/** Email is used to log into the system. */
email: text({ isIndexed: 'unique', validation: { isRequired: true } }),
/** Avatar upload for the users profile, stored locally */
avatar: image(),
attachment: file(),
/** Used to log in. */
password: password(),
/** Administrators have more access to various lists and fields. */
isAdmin: checkbox({
access: {
read: access.isAdmin,
create: access.isAdmin,
update: access.isAdmin,
},
ui: {
createView: {
fieldMode: args => (access.isAdmin(args) ? 'edit' : 'hidden'),
},
ui: {
createView: {
fieldMode: args => (access.isAdmin(args) ? 'edit' : 'hidden'),
},
itemView: {
fieldMode: args => (access.isAdmin(args) ? 'edit' : 'read'),
},
itemView: {
fieldMode: args => (access.isAdmin(args) ? 'edit' : 'read'),
},
}),
roles: text({}),
phoneNumbers: relationship({
ref: 'PhoneNumber.user',
many: true,
ui: {
// TODO: Work out how to use custom views to customise the card + edit / create forms
// views: './admin/fieldViews/user/phoneNumber',
displayMode: 'cards',
cardFields: ['type', 'value'],
inlineEdit: { fields: ['type', 'value'] },
inlineCreate: { fields: ['type', 'value'] },
linkToItem: true,
// removeMode: 'delete',
},
}),
roles: text({}),
phoneNumbers: relationship({
ref: 'PhoneNumber.user',
many: true,
ui: {
// TODO: Work out how to use custom views to customise the card + edit / create forms
// views: './admin/fieldViews/user/phoneNumber',
displayMode: 'cards',
cardFields: ['type', 'value'],
inlineEdit: { fields: ['type', 'value'] },
inlineCreate: { fields: ['type', 'value'] },
linkToItem: true,
// removeMode: 'delete',
},
}),
posts: relationship({ ref: 'Post.author', many: true }),
randomNumber: virtual({
field: graphql.field({
type: graphql.Float,
resolve() {
return randomNumber();
},
}),
posts: relationship({ ref: 'Post.author', many: true }),
randomNumber: virtual({
field: graphql.field({
type: graphql.Float,
resolve() {
return randomNumber();
},
}),
}),
},
}),
}),
},
});

export const lists: Keystone.Lists = {
User,
PhoneNumber: list({
ui: {
isHidden: true,
Expand Down Expand Up @@ -132,7 +132,7 @@ export const lists = {
}),
Post: list({
fields: {
title: text(),
title: text({ access: {} }),
// TODO: expand this out into a proper example project
// Enable this line to test custom field views
// test: text({ ui: { views: require.resolve('./admin/fieldViews/Test.tsx') } }),
Expand Down Expand Up @@ -193,7 +193,8 @@ export const lists = {
}),
};

export const extendGraphqlSchema = graphQLSchemaExtension({
// note this usage of the type is important because it tests that the generated types work
export const extendGraphqlSchema = graphQLSchemaExtension<Keystone.Context>({
typeDefs: gql`
type Query {
randomNumber: RandomNumber
Expand All @@ -215,13 +216,8 @@ export const extendGraphqlSchema = graphQLSchemaExtension({
},
Mutation: {
createRandomPosts(root, args, context) {
// TODO: add a way to verify access control here, e.g
// await context.verifyAccessControl(userIsAdmin);
const data = Array.from({ length: 238 }).map((x, i) => ({ title: `Post ${i}` }));
// note this usage of the type is important because it tests that the generated
// KeystoneListsTypeInfo extends Record<string, BaseGeneratedListTypes>
const query = context.query as KeystoneListsAPI<KeystoneListsTypeInfo>;
return query.Post.createMany({ data });
return context.query.Post.createMany({ data });
},
},
Query: {
Expand Down
19 changes: 0 additions & 19 deletions examples-staging/basic/tsconfig.json

This file was deleted.

3 changes: 1 addition & 2 deletions examples-staging/ecommerce/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { KeystoneGraphQLAPI, KeystoneListsAPI } from '@keystone-6/keystone/types';
import { KeystoneListsAPI } from '@keystone-6/keystone/types';

// NOTE -- these types are commented out in main because they aren't generated by the build (yet)
// To get full List and GraphQL API type support, uncomment them here and use them below
Expand All @@ -22,7 +22,6 @@ export type Session = {
};

export type ListsAPI = KeystoneListsAPI<any /* KeystoneListsTypeInfo */>;
export type GraphqlAPI = KeystoneGraphQLAPI<any /* KeystoneListsTypeInfo */>;

export type AccessArgs = {
session?: Session;
Expand Down
10 changes: 5 additions & 5 deletions examples/custom-field/stars-field/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
BaseGeneratedListTypes,
BaseListTypeInfo,
fieldType,
FieldTypeFunc,
CommonFieldConfig,
Expand All @@ -13,18 +13,18 @@ import { graphql } from '@keystone-6/keystone';
// and a different input in the Admin UI
// https://github.com/keystonejs/keystone/tree/main/packages/keystone/src/fields/types/integer

export type StarsFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> =
CommonFieldConfig<TGeneratedListTypes> & {
export type StarsFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
isIndexed?: boolean | 'unique';
maxStars?: number;
};

export const stars =
<TGeneratedListTypes extends BaseGeneratedListTypes>({
<ListTypeInfo extends BaseListTypeInfo>({
isIndexed,
maxStars = 5,
...config
}: StarsFieldConfig<TGeneratedListTypes> = {}): FieldTypeFunc =>
}: StarsFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> =>
meta =>
fieldType({
// this configures what data is stored in the database
Expand Down
2 changes: 1 addition & 1 deletion examples/testing/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const lists = {
item: {
update: async ({ session, item, context }) => {
const task = await context.query.Task.findOne({
where: { id: item.id },
where: { id: item.id.toString() },
query: 'assignedTo { id }',
});
return !!(session?.itemId && session.itemId === task.assignedTo?.id);
Expand Down
7 changes: 4 additions & 3 deletions examples/virtual-field/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { list, graphql } from '@keystone-6/keystone';
import { select, relationship, text, timestamp, virtual } from '@keystone-6/keystone/fields';
import { BaseItem } from '@keystone-6/keystone/types';

export const lists = {
Post: list({
Expand Down Expand Up @@ -58,7 +59,7 @@ export const lists = {
args: {
length: graphql.arg({ type: graphql.nonNull(graphql.Int), defaultValue: 200 }),
},
resolve(item, { length }) {
resolve(item: BaseItem, { length }) {
if (!item.content) {
return null;
}
Expand All @@ -78,7 +79,7 @@ export const lists = {
authorName: virtual({
field: graphql.field({
type: graphql.String,
async resolve(item, args, context) {
async resolve(item: BaseItem, args, context) {
const { author } = await context.query.Post.findOne({
where: { id: item.id.toString() },
query: 'author { name }',
Expand All @@ -99,7 +100,7 @@ export const lists = {
field: lists =>
graphql.field({
type: lists.Post.types.output,
async resolve(item, args, context) {
async resolve(item: BaseItem, args, context) {
const { posts } = await context.query.Author.findOne({
where: { id: item.id.toString() },
query: `posts(
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/src/gql/getBaseAuthSchema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ItemRootValue } from '@keystone-6/keystone/types';
import type { BaseItem } from '@keystone-6/keystone/types';
import { graphql } from '@keystone-6/keystone';
import { AuthGqlNames, SecretFieldImpl } from '../types';

Expand All @@ -21,7 +21,7 @@ export function getBaseAuthSchema<I extends string, S extends string>({
}) {
const ItemAuthenticationWithPasswordSuccess = graphql.object<{
sessionToken: string;
item: ItemRootValue;
item: BaseItem;
}>()({
name: gqlNames.ItemAuthenticationWithPasswordSuccess,
fields: {
Expand Down Expand Up @@ -50,7 +50,7 @@ export function getBaseAuthSchema<I extends string, S extends string>({
authenticatedItem: graphql.field({
type: graphql.union({
name: 'AuthenticatedItem',
types: [base.object(listKey) as graphql.ObjectType<ItemRootValue>],
types: [base.object(listKey) as graphql.ObjectType<BaseItem>],
resolveType: (root, context) => context.session?.listKey,
}),
resolve(root, args, { session, db }) {
Expand Down
4 changes: 2 additions & 2 deletions packages/auth/src/gql/getInitFirstItemSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { graphql } from '@keystone-6/keystone';
import { ItemRootValue } from '@keystone-6/keystone/types';
import { BaseItem } from '@keystone-6/keystone/types';
import { assertInputObjectType, GraphQLInputObjectType, GraphQLSchema } from 'graphql';

import { AuthGqlNames, InitFirstItemConfig } from '../types';
Expand All @@ -18,7 +18,7 @@ export function getInitFirstItemSchema({
gqlNames: AuthGqlNames;
graphQLSchema: GraphQLSchema;
ItemAuthenticationWithPasswordSuccess: graphql.ObjectType<{
item: ItemRootValue;
item: BaseItem;
sessionToken: string;
}>;
}) {
Expand Down
Loading

0 comments on commit f455498

Please sign in to comment.