From 46018662e0238e58fef5c36e662bb8f364cb7eeb Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:03:00 +1100 Subject: [PATCH 01/11] reduce number of promises to await in setupTestRunner --- tests/api-tests/test-runner.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/api-tests/test-runner.ts b/tests/api-tests/test-runner.ts index 653101866c1..16583a2c0e9 100644 --- a/tests/api-tests/test-runner.ts +++ b/tests/api-tests/test-runner.ts @@ -221,14 +221,16 @@ export function setupTestSuite ({ identifier?: string }) { const result = setupTestEnv({ config: config_, serve, identifier }) - const connectPromise = result.then((x) => x.connect()) + const connectPromise = result.then((x) => { + x.connect() + return x + }) afterAll(async () => { await (await result).disconnect() }) return async () => { - await connectPromise - return await result + return await connectPromise } } From 3bbbd0261db99133bac4fa394c4af7ae1fd951c0 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:37:25 +1100 Subject: [PATCH 02/11] reduce complexity of cannotForItem --- packages/core/src/lib/core/access-control.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/lib/core/access-control.ts b/packages/core/src/lib/core/access-control.ts index 5f9592340dc..5015c84085d 100644 --- a/packages/core/src/lib/core/access-control.ts +++ b/packages/core/src/lib/core/access-control.ts @@ -22,10 +22,8 @@ import { type InitialisedList } from './initialise-lists' import { type InputFilter } from './where-inputs' export function cannotForItem (operation: string, list: InitialisedList) { - return ( - `You cannot ${operation} that ${list.listKey}` + - (operation === 'create' ? '' : ' - it may not exist') - ) + if (operation === 'create') return `You cannot ${operation} that ${list.listKey}` + return `You cannot ${operation} that ${list.listKey} - it may not exist` } export function cannotForItemFields ( From 8bdadf2bfab13132bd908373c49cb32b335818ca Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:48:09 +1100 Subject: [PATCH 03/11] rm duplicate Awaited type --- packages/core/src/lib/core/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/lib/core/utils.ts b/packages/core/src/lib/core/utils.ts index 5edbdb19a76..4c9996f77d0 100644 --- a/packages/core/src/lib/core/utils.ts +++ b/packages/core/src/lib/core/utils.ts @@ -87,8 +87,6 @@ export const isFulfilled = (arg: PromiseSettledResult): arg is PromiseFulf export const isRejected = (arg: PromiseSettledResult): arg is PromiseRejectedResult => arg.status === 'rejected' -type Awaited = T extends PromiseLike ? U : T - export async function promiseAllRejectWithAllErrors ( promises: readonly [...T] ): Promise<{ [P in keyof T]: Awaited }> { From c62cc6c697d96a43932d98e2e5730a66d3e426ca Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:55:32 +1100 Subject: [PATCH 04/11] rm prismaNamespace caching --- .../artifacts.ts | 1 - packages/core/src/artifacts.ts | 11 ------ packages/core/src/context.ts | 2 +- .../core/src/lib/context/createContext.ts | 38 +++++++++++++++---- .../src/lib/core/mutations/create-update.ts | 4 +- packages/core/src/lib/core/utils.ts | 19 +--------- packages/core/src/lib/createSystem.ts | 26 +++++++------ packages/core/src/types/context.ts | 17 ++++++++- packages/core/src/types/type-info.ts | 3 +- tests/api-tests/test-runner.ts | 4 +- 10 files changed, 66 insertions(+), 59 deletions(-) diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/artifacts.ts b/packages/core/src/___internal-do-not-use-will-break-in-patch/artifacts.ts index 679bf4451c6..4acfec5ebdd 100644 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/artifacts.ts +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/artifacts.ts @@ -4,4 +4,3 @@ export { generatePrismaAndGraphQLSchemas, getCommittedArtifacts, } from '../artifacts' -export type { PrismaModule } from '../artifacts' diff --git a/packages/core/src/artifacts.ts b/packages/core/src/artifacts.ts index 362113c0f57..b7684ef2d84 100644 --- a/packages/core/src/artifacts.ts +++ b/packages/core/src/artifacts.ts @@ -217,14 +217,3 @@ async function generatePrismaClient (prismaSchemaPath: string, dataProxy: boolea }) ) } - -export type PrismaModule = { - PrismaClient: { - new (args: unknown): any - } - Prisma: { - DbNull: unknown - JsonNull: unknown - [key: string]: unknown - } -} diff --git a/packages/core/src/context.ts b/packages/core/src/context.ts index 3a2d7267881..d7a71d247d4 100644 --- a/packages/core/src/context.ts +++ b/packages/core/src/context.ts @@ -11,6 +11,6 @@ export function getContext ( PrismaModule: unknown ): KeystoneContext { const system = createSystem(initConfig(config)) - const { context } = system.getKeystone(PrismaModule as any) + const { context } = system.getKeystone(PrismaModule) return context } diff --git a/packages/core/src/lib/context/createContext.ts b/packages/core/src/lib/context/createContext.ts index 008915e8f26..99398e753cf 100644 --- a/packages/core/src/lib/context/createContext.ts +++ b/packages/core/src/lib/context/createContext.ts @@ -1,9 +1,20 @@ -import type { IncomingMessage, ServerResponse } from 'http' -import { type ExecutionResult, graphql, type GraphQLSchema, print } from 'graphql' -import type { KeystoneContext, KeystoneGraphQLAPI, KeystoneConfig } from '../../types' - -import type { PrismaClient } from '../core/utils' -import type { InitialisedList } from '../core/initialise-lists' +import { + type IncomingMessage, + type ServerResponse +} from 'http' +import { + type ExecutionResult, + type GraphQLSchema, + graphql, + print +} from 'graphql' +import { + type KeystoneContext, + type KeystoneGraphQLAPI, + type KeystoneConfig +} from '../../types' + +import { type InitialisedList } from '../core/initialise-lists' import { createImagesContext } from '../assets/createImagesContext' import { createFilesContext } from '../assets/createFilesContext' import { getDbFactory, getQueryFactory } from './api' @@ -14,12 +25,17 @@ export function createContext ({ graphQLSchema, graphQLSchemaSudo, prismaClient, + prismaTypes }: { config: KeystoneConfig lists: Record graphQLSchema: GraphQLSchema graphQLSchemaSudo: GraphQLSchema - prismaClient: PrismaClient + prismaClient: unknown + prismaTypes: { + DbNull: unknown, + JsonNull: unknown + } }) { const dbFactories: Record> = {} for (const [listKey, list] of Object.entries(lists)) { @@ -110,12 +126,18 @@ export function createContext ({ // TODO: deprecated, remove in breaking change gqlNames: (listKey: string) => lists[listKey].graphql.names, - // TODO: rename __internal ? + // TODO: deprecated, remove in breaking change ...(config.experimental?.contextInitialisedLists ? { experimental: { initialisedLists: lists }, } : {}), + + __internal: { + prisma: { + ...prismaTypes + } + } } const _dbFactories = sudo ? dbFactoriesSudo : dbFactories diff --git a/packages/core/src/lib/core/mutations/create-update.ts b/packages/core/src/lib/core/mutations/create-update.ts index 4696c3e1158..69eea88d428 100644 --- a/packages/core/src/lib/core/mutations/create-update.ts +++ b/packages/core/src/lib/core/mutations/create-update.ts @@ -5,7 +5,6 @@ import { promiseAllRejectWithAllErrors, getDBFieldKeyForFieldOnMultiField, type IdType, - getPrismaNamespace, } from '../utils' import { type InputFilter, resolveUniqueWhereInput, type UniqueInputFilter } from '../where-inputs' import { @@ -390,8 +389,7 @@ function transformInnerDBField ( value: unknown ) { if (dbField.kind === 'scalar' && dbField.scalar === 'Json' && value === null) { - const Prisma = getPrismaNamespace(context) - return Prisma.DbNull + return context.__internal.prisma.DbNull } return value } diff --git a/packages/core/src/lib/core/utils.ts b/packages/core/src/lib/core/utils.ts index 4c9996f77d0..052ab73c141 100644 --- a/packages/core/src/lib/core/utils.ts +++ b/packages/core/src/lib/core/utils.ts @@ -1,6 +1,5 @@ import pluralize from 'pluralize' -import { type PrismaModule } from '../../artifacts' -import type { BaseItem, KeystoneConfig, KeystoneContext } from '../../types' +import type { BaseItem, KeystoneConfig } from '../../types' import { getGqlNames } from '../../types/utils' import { humanize } from '../utils' import type { PrismaFilter, UniquePrismaFilter } from './where-inputs' @@ -148,22 +147,6 @@ export function getDBFieldKeyForFieldOnMultiField (fieldKey: string, subField: s return `${fieldKey}_${subField}` } -const prismaNamespaces = new WeakMap() - -export function setPrismaNamespace (prismaClient: object, prismaNamespace: PrismaModule['Prisma']) { - prismaNamespaces.set(prismaClient, prismaNamespace) -} - -// this accepts the context instead of the prisma client because the prisma client on context is `any` -// so by accepting the context, it'll be less likely the wrong thing will be passed. -export function getPrismaNamespace (context: KeystoneContext) { - const limit = prismaNamespaces.get(context.prisma) - if (limit === undefined) { - throw new Error('unexpected prisma namespace not set for prisma client') - } - return limit -} - export function areArraysEqual (a: readonly unknown[], b: readonly unknown[]) { return a.length === b.length && a.every((x, i) => x === b[i]) } diff --git a/packages/core/src/lib/createSystem.ts b/packages/core/src/lib/createSystem.ts index b17e981d3e7..3c2039feb28 100644 --- a/packages/core/src/lib/createSystem.ts +++ b/packages/core/src/lib/createSystem.ts @@ -5,13 +5,11 @@ import { } from '../types' import { GraphQLError } from 'graphql' -import { type PrismaModule } from '../artifacts' import { allowAll } from '../access' import { createAdminMeta } from './create-admin-meta' import { createGraphQLSchema } from './createGraphQLSchema' import { createContext } from './context/createContext' import { initialiseLists, type InitialisedList } from './core/initialise-lists' -import { setPrismaNamespace } from './core/utils' function getSudoGraphQLSchema (config: KeystoneConfig) { // This function creates a GraphQLSchema based on a modified version of the provided config. @@ -70,7 +68,7 @@ function getSudoGraphQLSchema (config: KeystoneConfig) { // return createGraphQLSchema(transformedConfig, lists, null, true); } -function injectNewDefaults (prismaClient: any, lists: Record) { +function injectNewDefaults (prismaClient: unknown, lists: Record) { for (const listKey in lists) { const list = lists[listKey] @@ -79,7 +77,8 @@ function injectNewDefaults (prismaClient: any, lists: Record { - const prePrismaClient = new prismaModule.PrismaClient({ + getKeystone: (PM: any) => { + const prePrismaClient = new PM.PrismaClient({ datasources: { [config.db.provider]: { url: formatUrl(config.db.provider, config.db.url) @@ -171,25 +170,28 @@ export function createSystem (config: KeystoneConfig) { }) const prismaClient = injectNewDefaults(prePrismaClient, lists) - setPrismaNamespace(prismaClient, prismaModule.Prisma) const context = createContext({ + config, + lists, graphQLSchema, graphQLSchemaSudo, - config, prismaClient, - lists, + prismaTypes: { + DbNull: PM.Prisma.DbNull, + JsonNull: PM.Prisma.JsonNull, + }, }) return { // TODO: replace with server.onStart, remove in breaking change async connect () { - await prismaClient.$connect() + await (prismaClient as any).$connect() await config.db.onConnect?.(context) }, // TODO: only used by tests, remove in breaking change async disconnect () { - await prismaClient.$disconnect() + await (prismaClient as any).$disconnect() }, context, } diff --git a/packages/core/src/types/context.ts b/packages/core/src/types/context.ts index 284c0fe816f..d19137e9ac1 100644 --- a/packages/core/src/types/context.ts +++ b/packages/core/src/types/context.ts @@ -19,16 +19,29 @@ export type KeystoneContext + session?: TypeInfo['session'] + /** @deprecated */ gqlNames: (listKey: string) => InitialisedList['graphql']['names'] + + /** @deprecated */ experimental?: { /** @deprecated This value is only available if you have config.experimental.contextInitialisedLists = true. * This is not a stable API and may contain breaking changes in `patch` level releases. */ initialisedLists: Record } - sessionStrategy?: SessionStrategy - session?: TypeInfo['session'] + + /** + * WARNING: may change in patch + */ + __internal: { + prisma: { + DbNull: unknown + JsonNull: unknown + } + } } // List item API diff --git a/packages/core/src/types/type-info.ts b/packages/core/src/types/type-info.ts index 21ceaf25c02..2ffdc76613e 100644 --- a/packages/core/src/types/type-info.ts +++ b/packages/core/src/types/type-info.ts @@ -15,8 +15,9 @@ export type BaseListTypeInfo = { uniqueWhere: { readonly id?: string | number | null } & GraphQLInput orderBy: Record } + /** - * WARNING: may be renamed in patch + * WARNING: may change in patch */ prisma: { create: Record diff --git a/tests/api-tests/test-runner.ts b/tests/api-tests/test-runner.ts index 16583a2c0e9..22ff51f855b 100644 --- a/tests/api-tests/test-runner.ts +++ b/tests/api-tests/test-runner.ts @@ -24,7 +24,7 @@ import { import { type BaseKeystoneTypeInfo, } from '@keystone-6/core/types' -import { generatePrismaAndGraphQLSchemas, type PrismaModule } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/artifacts' +import { generatePrismaAndGraphQLSchemas } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/artifacts' import { pushPrismaSchemaToDatabase } from '../../packages/core/src/lib/migrations' import { dbProvider, type FloatingConfig } from './utils' @@ -63,7 +63,7 @@ async function getTestPrismaModuleInner (prismaSchemaPath: string, datamodel: st } } -const prismaModuleCache = new Map() +const prismaModuleCache = new Map() async function getTestPrismaModule (prismaSchemaPath: string, schema: string) { if (prismaModuleCache.has(schema)) return prismaModuleCache.get(schema)! return prismaModuleCache.set(schema, await getTestPrismaModuleInner(prismaSchemaPath, schema)).get(schema)! From 9baa92002ee7048b848c46b9b673d0b538838bb0 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:37:36 +1100 Subject: [PATCH 05/11] align validate* hooks --- packages/core/src/lib/core/mutations/hooks.ts | 2 +- .../core/src/lib/core/mutations/validation.ts | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/core/src/lib/core/mutations/hooks.ts b/packages/core/src/lib/core/mutations/hooks.ts index 70d0522e0e0..9d818661e77 100644 --- a/packages/core/src/lib/core/mutations/hooks.ts +++ b/packages/core/src/lib/core/mutations/hooks.ts @@ -1,5 +1,5 @@ import { extensionError } from '../graphql-errors' -import type { InitialisedList } from '../initialise-lists' +import { type InitialisedList } from '../initialise-lists' export async function runSideEffectOnlyHook< HookName extends 'beforeOperation' | 'afterOperation', diff --git a/packages/core/src/lib/core/mutations/validation.ts b/packages/core/src/lib/core/mutations/validation.ts index e6fe398efa7..52f833be247 100644 --- a/packages/core/src/lib/core/mutations/validation.ts +++ b/packages/core/src/lib/core/mutations/validation.ts @@ -3,9 +3,9 @@ import type { InitialisedList } from '../initialise-lists' type DistributiveOmit = T extends any ? Omit : never -type UpdateCreateHookArgs = Parameters< - Exclude ->[0] +type UpdateCreateHookArgs = Parameters>[0] +type DeleteHookArgs = Parameters>[0] + export async function validateUpdateCreate ({ list, hookArgs, @@ -14,9 +14,9 @@ export async function validateUpdateCreate ({ hookArgs: DistributiveOmit }) { const messages: string[] = [] - const fieldsErrors: { error: Error, tag: string }[] = [] - // Field validation hooks + + // field validation hooks await Promise.all( Object.entries(list.fields).map(async ([fieldKey, field]) => { const addValidationError = (msg: string) => @@ -33,12 +33,14 @@ export async function validateUpdateCreate ({ throw extensionError('validateInput', fieldsErrors) } - // List validation hooks + // list validation hooks const addValidationError = (msg: string) => messages.push(`${list.listKey}: ${msg}`) try { await list.hooks.validateInput({ ...hookArgs, addValidationError }) } catch (error: any) { - throw extensionError('validateInput', [{ error, tag: `${list.listKey}.hooks.validateInput` }]) + throw extensionError('validateInput', [ + { error, tag: `${list.listKey}.hooks.validateInput` } + ]) } if (messages.length) { @@ -46,7 +48,6 @@ export async function validateUpdateCreate ({ } } -type DeleteHookArgs = Parameters>[0] export async function validateDelete ({ list, hookArgs, @@ -56,7 +57,8 @@ export async function validateDelete ({ }) { const messages: string[] = [] const fieldsErrors: { error: Error, tag: string }[] = [] - // Field validation + + // field validation hooks await Promise.all( Object.entries(list.fields).map(async ([fieldKey, field]) => { const addValidationError = (msg: string) => @@ -68,10 +70,12 @@ export async function validateDelete ({ } }) ) + if (fieldsErrors.length) { throw extensionError('validateDelete', fieldsErrors) } - // List validation + + // list validation hooks const addValidationError = (msg: string) => messages.push(`${list.listKey}: ${msg}`) try { await list.hooks.validateDelete({ ...hookArgs, addValidationError }) @@ -80,6 +84,7 @@ export async function validateDelete ({ { error, tag: `${list.listKey}.hooks.validateDelete` }, ]) } + if (messages.length) { throw validationFailureError(messages) } From a5a949cddaac3670974583b9954c7741c4a85dbd Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:33:24 +1100 Subject: [PATCH 06/11] align comments --- packages/core/src/lib/core/mutations/create-update.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/src/lib/core/mutations/create-update.ts b/packages/core/src/lib/core/mutations/create-update.ts index 69eea88d428..da990be6e4c 100644 --- a/packages/core/src/lib/core/mutations/create-update.ts +++ b/packages/core/src/lib/core/mutations/create-update.ts @@ -81,8 +81,10 @@ export async function createOne ( const operationAccess = await getOperationAccess(list, context, 'create') if (!operationAccess) throw accessDeniedError(cannotForItem('create', list)) + // operation const { item, afterOperation } = await createSingle(createInput, list, context) + // after operation await afterOperation(item) return item @@ -99,8 +101,12 @@ export async function createMany ( // throw for each attempt if (!operationAccess) throw accessDeniedError(cannotForItem('create', list)) + // operation const { item, afterOperation } = await createSingle({ data }, list, context) + + // after operation await afterOperation(item) + return item }) } @@ -136,6 +142,7 @@ async function updateSingle ( item ) + // operation const updatedItem = await context.prisma[list.listKey].update({ where: { id: item.id }, data, @@ -143,6 +150,7 @@ async function updateSingle ( // after operation await afterOperation(updatedItem) + return updatedItem } From 4b07201777cf90aa919fc1daa5d225873571b3ce Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:30:47 +1100 Subject: [PATCH 07/11] prefer a generator to flatMap --- .../src/lib/core/mutations/create-update.ts | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/core/src/lib/core/mutations/create-update.ts b/packages/core/src/lib/core/mutations/create-update.ts index da990be6e4c..ad3d1e8757e 100644 --- a/packages/core/src/lib/core/mutations/create-update.ts +++ b/packages/core/src/lib/core/mutations/create-update.ts @@ -408,18 +408,25 @@ function transformForPrismaClient ( context: KeystoneContext ) { return Object.fromEntries( - Object.entries(data).flatMap(([fieldKey, value]) => { - const { dbField } = fields[fieldKey] - if (dbField.kind === 'multi') { - return Object.entries(value).map(([innerFieldKey, fieldValue]) => { - return [ - getDBFieldKeyForFieldOnMultiField(fieldKey, innerFieldKey), - transformInnerDBField(dbField.fields[innerFieldKey], context, fieldValue), - ] - }) - } + [...function* () { + for (const fieldKey in data) { + const value = data[fieldKey] + const { dbField } = fields[fieldKey] + + if (dbField.kind === 'multi') { + for (const innerFieldKey in value) { + const innerFieldValue = value[innerFieldKey] + yield [ + getDBFieldKeyForFieldOnMultiField(fieldKey, innerFieldKey), + transformInnerDBField(dbField.fields[innerFieldKey], context, innerFieldValue), + ] + } - return [[fieldKey, transformInnerDBField(dbField, context, value)]] - }) + continue + } + + yield [fieldKey, transformInnerDBField(dbField, context, value)] + } + }()] ) } From 92385b4d8eeead2b7123051ec2807bd7ccfe6707 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:32:49 +1100 Subject: [PATCH 08/11] organise imports --- .../src/lib/core/mutations/create-update.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/core/src/lib/core/mutations/create-update.ts b/packages/core/src/lib/core/mutations/create-update.ts index ad3d1e8757e..aacfcd0a8d8 100644 --- a/packages/core/src/lib/core/mutations/create-update.ts +++ b/packages/core/src/lib/core/mutations/create-update.ts @@ -1,12 +1,23 @@ -import type { KeystoneContext, BaseItem } from '../../../types' -import type { ResolvedDBField } from '../resolve-relationships' -import type { InitialisedList } from '../initialise-lists' import { + type BaseItem, + type KeystoneContext +} from '../../../types' +import { + type ResolvedDBField +} from '../resolve-relationships' +import { + type InitialisedList +} from '../initialise-lists' +import { + type IdType, promiseAllRejectWithAllErrors, getDBFieldKeyForFieldOnMultiField, - type IdType, } from '../utils' -import { type InputFilter, resolveUniqueWhereInput, type UniqueInputFilter } from '../where-inputs' +import { + type InputFilter, + type UniqueInputFilter, + resolveUniqueWhereInput, +} from '../where-inputs' import { accessDeniedError, extensionError, From 5987c81088b4cf669d8f9c900663ef9fea749941 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:37:06 +1100 Subject: [PATCH 09/11] dont use && for functions --- packages/core/src/lib/core/initialise-lists.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/lib/core/initialise-lists.ts b/packages/core/src/lib/core/initialise-lists.ts index c15aded3874..5e0ffdfc229 100644 --- a/packages/core/src/lib/core/initialise-lists.ts +++ b/packages/core/src/lib/core/initialise-lists.ts @@ -154,8 +154,8 @@ function getIsEnabled (listKey: string, listConfig: KeystoneConfig['lists'][stri create: notOmit, update: notOmit, delete: notOmit, - filter: notOmit && defaultIsFilterable, - orderBy: notOmit && defaultIsOrderable, + filter: notOmit ? defaultIsFilterable : false, + orderBy: notOmit ? defaultIsOrderable : false, } } From 993e08ecf4b2a5a63519b41de1e38fc390ace4d3 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:39:54 +1100 Subject: [PATCH 10/11] prefer to use sudoContext, not dbItemAPI variables --- packages/auth/src/gql/getInitFirstItemSchema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/auth/src/gql/getInitFirstItemSchema.ts b/packages/auth/src/gql/getInitFirstItemSchema.ts index 3c250b9d265..5db6cfdb0e4 100644 --- a/packages/auth/src/gql/getInitFirstItemSchema.ts +++ b/packages/auth/src/gql/getInitFirstItemSchema.ts @@ -47,10 +47,10 @@ export function getInitFirstItemSchema ({ throw new Error('No session implementation available on context') } - const dbItemAPI = context.sudo().db[listKey] + const sudoContext = context.sudo() // should approximate hasInitFirstItemConditions - const count = await dbItemAPI.count({}) + const count = await sudoContext.db[listKey].count() if (count !== 0) { throw new Error('Initial items can only be created when no items exist in that list') } @@ -59,7 +59,7 @@ export function getInitFirstItemSchema ({ // this is strictly speaking incorrect. the db API will do GraphQL coercion on a value which has already been coerced // (this is also mostly fine, the chance that people are using things where // the input value can't round-trip like the Upload scalar here is quite low) - const item = await dbItemAPI.createOne({ data: { ...data, ...itemData } }) + const item = await sudoContext.db[listKey].createOne({ data: { ...data, ...itemData } }) const sessionToken = (await context.sessionStrategy.start({ data: { listKey, itemId: item.id.toString() }, context, From c7aecb4b5d799ce6abe9a35c2d730ebb04527e0d Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:33:09 +1100 Subject: [PATCH 11/11] prefer type unknown to any --- .../core/src/lib/context/createContext.ts | 2 +- .../context/executeGraphQLFieldToRootVal.ts | 6 +- .../src/lib/core/mutations/access-control.ts | 6 +- .../core/src/lib/core/queries/resolvers.ts | 9 ++- packages/core/src/lib/core/utils.ts | 71 +------------------ packages/core/src/lib/core/where-inputs.ts | 26 +++---- packages/core/src/types/prisma.ts | 16 +++++ packages/core/src/types/type-info.ts | 4 +- 8 files changed, 43 insertions(+), 97 deletions(-) create mode 100644 packages/core/src/types/prisma.ts diff --git a/packages/core/src/lib/context/createContext.ts b/packages/core/src/lib/context/createContext.ts index 99398e753cf..bb7197d728c 100644 --- a/packages/core/src/lib/context/createContext.ts +++ b/packages/core/src/lib/context/createContext.ts @@ -9,9 +9,9 @@ import { print } from 'graphql' import { + type KeystoneConfig, type KeystoneContext, type KeystoneGraphQLAPI, - type KeystoneConfig } from '../../types' import { type InitialisedList } from '../core/initialise-lists' diff --git a/packages/core/src/lib/context/executeGraphQLFieldToRootVal.ts b/packages/core/src/lib/context/executeGraphQLFieldToRootVal.ts index a4375c44982..a17f1f5060e 100644 --- a/packages/core/src/lib/context/executeGraphQLFieldToRootVal.ts +++ b/packages/core/src/lib/context/executeGraphQLFieldToRootVal.ts @@ -145,7 +145,7 @@ function getRootValGivenOutputType (originalType: OutputType, value: any): any { return value[rawField] } -export function executeGraphQLFieldToRootVal (field: GraphQLField) { +export function executeGraphQLFieldToRootVal (field: GraphQLField) { const { argumentNodes, variableDefinitions } = getVariablesForGraphQLField(field) const document: DocumentNode = { kind: Kind.DOCUMENT, @@ -174,7 +174,7 @@ export function executeGraphQLFieldToRootVal (field: GraphQLField) { const type = getTypeForField(field.type) - const fieldConfig: RequiredButStillAllowUndefined> = { + const fieldConfig: RequiredButStillAllowUndefined> = { args: argsToArgsConfig(field.args), astNode: undefined, deprecationReason: field.deprecationReason, @@ -195,7 +195,7 @@ export function executeGraphQLFieldToRootVal (field: GraphQLField) { }) return async ( - args: Record, + args: Record, context: KeystoneContext, rootValue: Record = {} ) => { diff --git a/packages/core/src/lib/core/mutations/access-control.ts b/packages/core/src/lib/core/mutations/access-control.ts index 61590692c5d..05602f7e859 100644 --- a/packages/core/src/lib/core/mutations/access-control.ts +++ b/packages/core/src/lib/core/mutations/access-control.ts @@ -5,11 +5,13 @@ import type { InitialisedList } from '../initialise-lists' import { cannotForItem, cannotForItemFields } from '../access-control' import { type InputFilter, + type UniqueInputFilter, resolveUniqueWhereInput, resolveWhereInput, - type UniqueInputFilter, - type UniquePrismaFilter, } from '../where-inputs' +import { + type UniquePrismaFilter, +} from '../../../types/prisma' async function getFilteredItem ( list: InitialisedList, diff --git a/packages/core/src/lib/core/queries/resolvers.ts b/packages/core/src/lib/core/queries/resolvers.ts index f8d96f01634..c9c798c80b0 100644 --- a/packages/core/src/lib/core/queries/resolvers.ts +++ b/packages/core/src/lib/core/queries/resolvers.ts @@ -8,13 +8,16 @@ import { } from '../../../types' import { getOperationAccess, getAccessFilters } from '../access-control' import { - type PrismaFilter, - type UniquePrismaFilter, type UniqueInputFilter, type InputFilter, resolveUniqueWhereInput, resolveWhereInput, } from '../where-inputs' +import { + type PrismaFilter, + type UniquePrismaFilter, +} from '../../../types/prisma' + import { limitsExceededError, userInputError } from '../graphql-errors' import { type InitialisedList } from '../initialise-lists' import { getDBFieldKeyForFieldOnMultiField } from '../utils' @@ -202,7 +205,7 @@ async function resolveOrderBy ( } export async function count ( - { where }: { where: Record }, + { where }: { where: Record }, list: InitialisedList, context: KeystoneContext, info: GraphQLResolveInfo, diff --git a/packages/core/src/lib/core/utils.ts b/packages/core/src/lib/core/utils.ts index 052ab73c141..0dddcb28dbf 100644 --- a/packages/core/src/lib/core/utils.ts +++ b/packages/core/src/lib/core/utils.ts @@ -1,76 +1,7 @@ import pluralize from 'pluralize' -import type { BaseItem, KeystoneConfig } from '../../types' +import { type KeystoneConfig } from '../../types' import { getGqlNames } from '../../types/utils' import { humanize } from '../utils' -import type { PrismaFilter, UniquePrismaFilter } from './where-inputs' - -declare const prisma: unique symbol - -// note prisma "promises" aren't really Promises, they have `then`, `catch` and `finally` but they don't start executation immediately -// so if you don't call .then/catch/finally/use it in $transaction, the operation will never happen -export type PrismaPromise = Promise & { [prisma]: true } - -type PrismaModel = { - count: (arg: { - where?: PrismaFilter - take?: number - skip?: number - // this is technically wrong because relation orderBy but we're not doing that yet so it's fine - orderBy?: readonly Record[] - }) => PrismaPromise - findMany: (arg: { - where?: PrismaFilter - take?: number - skip?: number - cursor?: UniquePrismaFilter - // this is technically wrong because relation orderBy but we're not doing that yet so it's fine - orderBy?: readonly Record[] - include?: Record - select?: Record - }) => PrismaPromise - delete: (arg: { where: UniquePrismaFilter }) => PrismaPromise - deleteMany: (arg: { where: PrismaFilter }) => PrismaPromise - findUnique: (args: { - where: UniquePrismaFilter - include?: Record - select?: Record - }) => PrismaPromise - findFirst: (args: { - where: PrismaFilter - include?: Record - select?: Record - }) => PrismaPromise - create: (args: { - data: Record - include?: Record - select?: Record - }) => PrismaPromise - update: (args: { - where: UniquePrismaFilter - data: Record - include?: Record - select?: Record - }) => PrismaPromise -} - -export type UnwrapPromise> = TPromise extends Promise - ? T - : never - -export type UnwrapPromises[]> = { - // unsure about this conditional - [Key in keyof T]: Key extends number ? UnwrapPromise : never; -} - -// please do not make this type be the value of KeystoneContext['prisma'] -// this type is meant for generic usage, KeystoneContext should be generic over a PrismaClient -// and we should generate a KeystoneContext type in node_modules/.keystone/types which passes in the user's PrismaClient type -// so that users get right PrismaClient types specifically for their project -export type PrismaClient = { - $disconnect(): Promise - $connect(): Promise - $transaction[]>(promises: [...T]): UnwrapPromises -} & Record // this is wrong // all the things should be generic over the id type diff --git a/packages/core/src/lib/core/where-inputs.ts b/packages/core/src/lib/core/where-inputs.ts index 5db6ff64a42..bd7b852a8a5 100644 --- a/packages/core/src/lib/core/where-inputs.ts +++ b/packages/core/src/lib/core/where-inputs.ts @@ -1,6 +1,13 @@ -import type { DBField, KeystoneContext } from '../../types' +import { + type DBField, + type KeystoneContext, +} from '../../types' +import { + type PrismaFilter, + type UniquePrismaFilter, +} from '../../types/prisma' import { userInputError } from './graphql-errors' -import type { InitialisedList } from './initialise-lists' +import { type InitialisedList } from './initialise-lists' import { getDBFieldKeyForFieldOnMultiField } from './utils' export type InputFilter = Record & { @@ -9,23 +16,8 @@ export type InputFilter = Record & { OR?: InputFilter[] NOT?: InputFilter[] } -export type PrismaFilter = Record & { - _____?: 'prisma filter' - AND?: PrismaFilter[] | PrismaFilter - OR?: PrismaFilter[] | PrismaFilter - NOT?: PrismaFilter[] | PrismaFilter - // just so that if you pass an array to something expecting a PrismaFilter, you get an error - length?: undefined - // so that if you pass a promise, you get an error - then?: undefined -} export type UniqueInputFilter = Record & { _____?: 'unique input filter' } -export type UniquePrismaFilter = Record & { - _____?: 'unique prisma filter' - // so that if you pass a promise, you get an error - then?: undefined -} export async function resolveUniqueWhereInput ( inputFilter: UniqueInputFilter, diff --git a/packages/core/src/types/prisma.ts b/packages/core/src/types/prisma.ts new file mode 100644 index 00000000000..e78cc89ecf8 --- /dev/null +++ b/packages/core/src/types/prisma.ts @@ -0,0 +1,16 @@ +export type PrismaFilter = Record & { + _____?: 'prisma filter' + AND?: PrismaFilter[] | PrismaFilter + OR?: PrismaFilter[] | PrismaFilter + NOT?: PrismaFilter[] | PrismaFilter + // just so that if you pass an array to something expecting a PrismaFilter, you get an error + length?: undefined + // so that if you pass a promise, you get an error + then?: undefined +} + +export type UniquePrismaFilter = Record & { + _____?: 'unique prisma filter' + // so that if you pass a promise, you get an error + then?: undefined +} diff --git a/packages/core/src/types/type-info.ts b/packages/core/src/types/type-info.ts index 2ffdc76613e..16f71623ba4 100644 --- a/packages/core/src/types/type-info.ts +++ b/packages/core/src/types/type-info.ts @@ -1,4 +1,6 @@ -import { type KeystoneContext } from './context' +import { + type KeystoneContext +} from './context' import { type BaseItem } from './next-fields' type GraphQLInput = Record