-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
379 additions
and
371 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@keystone-next/types': patch | ||
--- | ||
|
||
Refactored types to isolate base keystone type definitions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import type { KeystoneContext } from './core'; | ||
import type { BaseGeneratedListTypes, GqlNames } from './utils'; | ||
|
||
// TODO: This is only a partial typing of the core Keystone class. | ||
// We should definitely invest some time into making this more correct. | ||
export type BaseKeystone = { | ||
adapters: Record<string, any>; | ||
createList: ( | ||
key: string, | ||
config: { | ||
fields: Record<string, any>; | ||
access: any; | ||
queryLimits?: { maxResults?: number }; | ||
schemaDoc?: string; | ||
listQueryName?: string; | ||
itemQueryName?: string; | ||
hooks?: Record<string, any>; | ||
} | ||
) => BaseKeystoneList; | ||
connect: () => Promise<void>; | ||
lists: Record<string, BaseKeystoneList>; | ||
}; | ||
|
||
// TODO: This needs to be reviewed and expanded | ||
export type BaseKeystoneList = { | ||
key: string; | ||
fieldsByPath: Record<string, BaseKeystoneField>; | ||
fields: BaseKeystoneField[]; | ||
adapter: { itemsQuery: (args: Record<string, any>, extra: Record<string, any>) => any }; | ||
adminUILabels: { | ||
label: string; | ||
singular: string; | ||
plural: string; | ||
path: string; | ||
}; | ||
gqlNames: GqlNames; | ||
listQuery( | ||
args: BaseGeneratedListTypes['args']['listQuery'], | ||
context: KeystoneContext, | ||
gqlName?: string, | ||
info?: any, | ||
from?: any | ||
): Promise<Record<string, any>[]>; | ||
listQueryMeta( | ||
args: BaseGeneratedListTypes['args']['listQuery'], | ||
context: KeystoneContext, | ||
gqlName?: string, | ||
info?: any, | ||
from?: any | ||
): { | ||
getCount: () => Promise<number>; | ||
}; | ||
itemQuery( | ||
args: { where: { id: string } }, | ||
context: KeystoneContext, | ||
gqlName?: string, | ||
info?: any | ||
): Promise<Record<string, any>>; | ||
createMutation( | ||
data: Record<string, any>, | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>>; | ||
createManyMutation( | ||
data: Record<string, any>[], | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>[]>; | ||
updateMutation( | ||
id: string, | ||
data: Record<string, any>, | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>>; | ||
updateManyMutation( | ||
data: Record<string, any>, | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>[]>; | ||
deleteMutation( | ||
id: string, | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>>; | ||
deleteManyMutation( | ||
ids: string[], | ||
context: KeystoneContext, | ||
mutationState?: any | ||
): Promise<Record<string, any>[]>; | ||
}; | ||
|
||
type BaseKeystoneField = { | ||
gqlCreateInputFields: (arg: { schemaName: string }) => void; | ||
getBackingTypes: () => Record<string, { optional: true; type: 'string | null' }>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,277 @@ | ||
import type { FieldAccessControl } from './schema/access-control'; | ||
import type { BaseGeneratedListTypes, JSONValue, GqlNames, MaybePromise } from './utils'; | ||
import type { ListHooks } from './schema/hooks'; | ||
import { SessionStrategy } from './session'; | ||
import { SchemaConfig } from './schema'; | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { GraphQLSchema, ExecutionResult, DocumentNode } from 'graphql'; | ||
import { SerializedAdminMeta } from './admin-meta'; | ||
|
||
export type { ListHooks }; | ||
|
||
export type AdminFileToWrite = | ||
| { | ||
mode: 'write'; | ||
src: string; | ||
outputPath: string; | ||
} | ||
| { | ||
mode: 'copy'; | ||
inputPath: string; | ||
outputPath: string; | ||
}; | ||
|
||
export type KeystoneAdminUIConfig = { | ||
/** Enables certain functionality in the Admin UI that expects the session to be an item */ | ||
enableSessionItem?: boolean; | ||
/** A function that can be run to validate that the current session should have access to the Admin UI */ | ||
isAccessAllowed?: (args: { session: any }) => MaybePromise<boolean>; | ||
/** An array of page routes that can be accessed without passing the isAccessAllowed check */ | ||
publicPages?: string[]; | ||
/** The basePath for the Admin UI App */ | ||
path?: string; | ||
getAdditionalFiles?: ((system: KeystoneSystem) => MaybePromise<AdminFileToWrite[]>)[]; | ||
pageMiddleware?: (args: { | ||
req: IncomingMessage; | ||
session: any; | ||
isValidSession: boolean; | ||
system: KeystoneSystem; | ||
}) => MaybePromise<{ kind: 'redirect'; to: string } | void>; | ||
}; | ||
|
||
// DatabaseAPIs is used to provide access to the underlying database abstraction through | ||
// context and other developer-facing APIs in Keystone, so they can be used easily. | ||
|
||
// The implementation is very basic, and assumes there's a single adapter keyed by the constructor | ||
// name. Since there's no option _not_ to do that using the new config, we probably don't need | ||
// anything more sophisticated than this. | ||
export type DatabaseAPIs = { | ||
knex?: any; | ||
mongoose?: any; | ||
prisma?: any; | ||
}; | ||
|
||
export type KeystoneConfig = { | ||
db: { | ||
adapter: 'mongoose' | 'knex'; | ||
url: string; | ||
onConnect?: (args: KeystoneContext) => Promise<void>; | ||
}; | ||
graphql?: { | ||
path?: string; | ||
queryLimits?: { | ||
maxTotalResults?: number; | ||
}; | ||
}; | ||
session?: () => SessionStrategy<any>; | ||
ui?: KeystoneAdminUIConfig; | ||
server?: { | ||
/** Configuration options for the cors middleware. Set to true to core Keystone defaults */ | ||
cors?: any; | ||
}; | ||
} & SchemaConfig; | ||
|
||
export type MaybeItemFunction<T> = | ||
| T | ||
| ((args: { | ||
session: any; | ||
item: { id: string | number; [path: string]: any }; | ||
}) => MaybePromise<T>); | ||
export type MaybeSessionFunction<T extends string | boolean> = | ||
| T | ||
| ((args: { session: any }) => MaybePromise<T>); | ||
|
||
export type FieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> = { | ||
access?: FieldAccessControl<TGeneratedListTypes>; | ||
hooks?: ListHooks<TGeneratedListTypes>; | ||
label?: string; | ||
ui?: { | ||
views?: string; | ||
description?: string; | ||
createView?: { | ||
fieldMode?: MaybeSessionFunction<'edit' | 'hidden'>; | ||
}; | ||
listView?: { | ||
fieldMode?: MaybeSessionFunction<'read' | 'hidden'>; | ||
}; | ||
itemView?: { | ||
fieldMode?: MaybeItemFunction<'edit' | 'read' | 'hidden'>; | ||
}; | ||
}; | ||
}; | ||
|
||
export type FieldType<TGeneratedListTypes extends BaseGeneratedListTypes> = { | ||
/** | ||
* The real keystone type for the field | ||
*/ | ||
type: any; | ||
/** | ||
* The config for the field | ||
*/ | ||
config: FieldConfig<TGeneratedListTypes>; | ||
/** | ||
* The resolved path to the views for the field type | ||
*/ | ||
views: string; | ||
getAdminMeta?: (listKey: string, path: string, adminMeta: SerializedAdminMeta) => JSONValue; | ||
}; | ||
|
||
/* TODO: Review these types */ | ||
type FieldDefaultValueArgs<T> = { context: KeystoneContext; originalInput?: T }; | ||
export type FieldDefaultValue<T> = | ||
| T | ||
| null | ||
| MaybePromise<(args: FieldDefaultValueArgs<T>) => T | null | undefined>; | ||
|
||
export type KeystoneSystem = { | ||
keystone: BaseKeystone; | ||
config: KeystoneConfig; | ||
adminMeta: SerializedAdminMeta; | ||
graphQLSchema: GraphQLSchema; | ||
createContext: (args: { | ||
sessionContext?: SessionContext<any>; | ||
skipAccessControl?: boolean; | ||
}) => KeystoneContext; | ||
sessionImplementation?: { | ||
createContext( | ||
req: IncomingMessage, | ||
res: ServerResponse, | ||
system: KeystoneSystem | ||
): Promise<SessionContext<any>>; | ||
}; | ||
views: string[]; | ||
}; | ||
|
||
export type AccessControlContext = { | ||
getListAccessControlForUser: any; // TODO | ||
getFieldAccessControlForUser: any; // TODO | ||
}; | ||
|
||
export type SessionContext<T> = { | ||
// Note: session is typed like this to acknowledge the default session shape | ||
// if you're using keystone's built-in session implementation, but we don't | ||
// actually know what it will look like. | ||
session?: { itemId: string; listKey: string; data?: Record<string, any> } | any; | ||
startSession(data: T): Promise<string>; | ||
endSession(): Promise<void>; | ||
}; | ||
|
||
export type KeystoneContext = { | ||
schemaName: 'public'; | ||
lists: KeystoneListsAPI<any>; | ||
totalResults: number; | ||
keystone: BaseKeystone; | ||
graphql: KeystoneGraphQLAPI<any>; | ||
/** @deprecated */ | ||
executeGraphQL: any; // TODO: type this | ||
/** @deprecated */ | ||
gqlNames: (listKey: string) => Record<string, string>; // TODO: actual keys | ||
maxTotalResults: number; | ||
createContext: KeystoneSystem['createContext']; | ||
} & AccessControlContext & | ||
Partial<SessionContext<any>> & | ||
DatabaseAPIs; | ||
|
||
export type GraphQLResolver = (root: any, args: any, context: KeystoneContext) => any; | ||
|
||
export type GraphQLSchemaExtension = { | ||
typeDefs: string; | ||
resolvers: Record<string, Record<string, GraphQLResolver>>; | ||
}; | ||
|
||
type GraphQLExecutionArguments = { | ||
context?: any; | ||
query: string | DocumentNode; | ||
variables: Record<string, any>; | ||
}; | ||
export type KeystoneGraphQLAPI< | ||
// this is here because it will be used soon | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
KeystoneListsTypeInfo extends Record<string, BaseGeneratedListTypes> | ||
> = { | ||
createContext: KeystoneSystem['createContext']; | ||
schema: GraphQLSchema; | ||
|
||
run: (args: GraphQLExecutionArguments) => Promise<Record<string, any>>; | ||
raw: (args: GraphQLExecutionArguments) => Promise<ExecutionResult>; | ||
}; | ||
|
||
type ResolveFields = { readonly resolveFields?: false | string }; | ||
|
||
export type KeystoneListsAPI< | ||
KeystoneListsTypeInfo extends Record<string, BaseGeneratedListTypes> | ||
> = { | ||
[Key in keyof KeystoneListsTypeInfo]: { | ||
findMany( | ||
args: KeystoneListsTypeInfo[Key]['args']['listQuery'] & ResolveFields | ||
): Promise<readonly KeystoneListsTypeInfo[Key]['backing'][]>; | ||
findOne( | ||
args: { readonly where: { readonly id: string } } & ResolveFields | ||
): Promise<KeystoneListsTypeInfo[Key]['backing'] | null>; | ||
count(args: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise<number>; | ||
updateOne( | ||
args: { | ||
readonly id: string; | ||
readonly data: KeystoneListsTypeInfo[Key]['inputs']['update']; | ||
} & ResolveFields | ||
): Promise<KeystoneListsTypeInfo[Key]['backing'] | null>; | ||
updateMany( | ||
args: { | ||
readonly data: readonly { | ||
readonly id: string; | ||
readonly data: KeystoneListsTypeInfo[Key]['inputs']['update']; | ||
}[]; | ||
} & ResolveFields | ||
): Promise<(KeystoneListsTypeInfo[Key]['backing'] | null)[] | null>; | ||
createOne( | ||
args: { readonly data: KeystoneListsTypeInfo[Key]['inputs']['create'] } & ResolveFields | ||
): Promise<KeystoneListsTypeInfo[Key]['backing'] | null>; | ||
createMany( | ||
args: { | ||
readonly data: readonly { readonly data: KeystoneListsTypeInfo[Key]['inputs']['update'] }[]; | ||
} & ResolveFields | ||
): Promise<(KeystoneListsTypeInfo[Key]['backing'] | null)[] | null>; | ||
deleteOne( | ||
args: { readonly id: string } & ResolveFields | ||
): Promise<KeystoneListsTypeInfo[Key]['backing'] | null>; | ||
deleteMany( | ||
args: { readonly ids: readonly string[] } & ResolveFields | ||
): Promise<(KeystoneListsTypeInfo[Key]['backing'] | null)[] | null>; | ||
}; | ||
}; | ||
|
||
const preventInvalidUnderscorePrefix = (str: string) => str.replace(/^__/, '_'); | ||
|
||
// TODO: don't duplicate this between here and packages/keystone/ListTypes/list.js | ||
export function getGqlNames({ | ||
listKey, | ||
itemQueryName: _itemQueryName, | ||
listQueryName: _listQueryName, | ||
}: { | ||
listKey: string; | ||
itemQueryName: string; | ||
listQueryName: string; | ||
}): GqlNames { | ||
return { | ||
outputTypeName: listKey, | ||
itemQueryName: _itemQueryName, | ||
listQueryName: `all${_listQueryName}`, | ||
listQueryMetaName: `_all${_listQueryName}Meta`, | ||
listMetaName: preventInvalidUnderscorePrefix(`_${_listQueryName}Meta`), | ||
listSortName: `Sort${_listQueryName}By`, | ||
deleteMutationName: `delete${_itemQueryName}`, | ||
updateMutationName: `update${_itemQueryName}`, | ||
createMutationName: `create${_itemQueryName}`, | ||
deleteManyMutationName: `delete${_listQueryName}`, | ||
updateManyMutationName: `update${_listQueryName}`, | ||
createManyMutationName: `create${_listQueryName}`, | ||
whereInputName: `${_itemQueryName}WhereInput`, | ||
whereUniqueInputName: `${_itemQueryName}WhereUniqueInput`, | ||
updateInputName: `${_itemQueryName}UpdateInput`, | ||
createInputName: `${_itemQueryName}CreateInput`, | ||
updateManyInputName: `${_listQueryName}UpdateInput`, | ||
createManyInputName: `${_listQueryName}CreateInput`, | ||
relateToManyInputName: `${_itemQueryName}RelateToManyInput`, | ||
relateToOneInputName: `${_itemQueryName}RelateToOneInput`, | ||
}; | ||
} |
Oops, something went wrong.