diff --git a/packages/core/src/artifacts.ts b/packages/core/src/artifacts.ts index f9959c394e9..2efd4e1cac0 100644 --- a/packages/core/src/artifacts.ts +++ b/packages/core/src/artifacts.ts @@ -1,6 +1,6 @@ import fs from 'node:fs/promises' import path from 'node:path' -import { type ChildProcess } from 'node:child_process' +import type { ChildProcess } from 'node:child_process' import { printSchema } from 'graphql' import { getGenerators, formatSchema } from '@prisma/internals' diff --git a/packages/core/src/lib/core/prisma-schema-printer.ts b/packages/core/src/lib/core/prisma-schema-printer.ts index 09bfec300a2..32a17167f0a 100644 --- a/packages/core/src/lib/core/prisma-schema-printer.ts +++ b/packages/core/src/lib/core/prisma-schema-printer.ts @@ -1,10 +1,10 @@ -import { - type ScalarDBField, - type ScalarDBFieldDefault +import type { + ScalarDBField, + ScalarDBFieldDefault } from '../../types' -import { type ResolvedDBField } from './resolve-relationships' -import { type InitialisedList } from './initialise-lists' -import { type ResolvedKeystoneConfig } from '../../types' +import type { ResolvedDBField } from './resolve-relationships' +import type { InitialisedList } from './initialise-lists' +import type { ResolvedKeystoneConfig } from '../../types' import { areArraysEqual, getDBFieldKeyForFieldOnMultiField } from './utils' const modifiers = { diff --git a/packages/core/src/lib/createSystem.ts b/packages/core/src/lib/createSystem.ts index 838d7a66fc1..b7a6089fd78 100644 --- a/packages/core/src/lib/createSystem.ts +++ b/packages/core/src/lib/createSystem.ts @@ -200,8 +200,7 @@ function formatUrl (provider: ResolvedKeystoneConfig['db']['provider'], url: str return url } -export function createSystem (config_: KeystoneConfig | ResolvedKeystoneConfig) { - const config = resolveDefaults(config_ as KeystoneConfig, true) +export function createSystem (config: ResolvedKeystoneConfig) { const lists = initialiseLists(config) const adminMeta = createAdminMeta(config, lists) const graphQLSchema = createGraphQLSchema(config, lists, adminMeta, false) diff --git a/packages/core/src/lib/defaults.ts b/packages/core/src/lib/defaults.ts deleted file mode 100644 index 1648457b830..00000000000 --- a/packages/core/src/lib/defaults.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { type ListenOptions } from 'node:net' -import { - type BaseKeystoneTypeInfo, - type IdFieldConfig, - type KeystoneConfig, - type KeystoneContext, - type ResolvedKeystoneConfig, -} from '../types' -import { - idFieldType -} from '../lib/id-field' - -function injectDefaults (config: KeystoneConfig, defaultIdField: IdFieldConfig) { - // some error checking - for (const [listKey, list] of Object.entries(config.lists)) { - if (list.fields.id) { - throw new Error(`"fields.id" is reserved by Keystone, use "db.idField" for the "${listKey}" list`) - } - - if (list.isSingleton && list.db?.idField) { - throw new Error(`"db.idField" on the "${listKey}" list conflicts with singleton defaults`) - } - } - - const updated: ResolvedKeystoneConfig['lists'] = {} - - for (const [listKey, list] of Object.entries(config.lists)) { - if (list.isSingleton) { - updated[listKey] = { - listKey, - ...list, - fields: { - id: idFieldType({ kind: 'number', type: 'Int' }), - ...list.fields, - }, - } - - continue - } - - updated[listKey] = { - listKey, - ...list, - fields: { - id: idFieldType(list.db?.idField ?? defaultIdField), - ...list.fields, - }, - } - } - - /** @deprecated, TODO: remove in breaking change */ - for (const [listKey, list] of Object.entries(updated)) { - if (list.hooks === undefined) continue - if (list.hooks.validate !== undefined) { - if (list.hooks.validateInput !== undefined) throw new TypeError(`"hooks.validate" conflicts with "hooks.validateInput" for the "${listKey}" list`) - if (list.hooks.validateDelete !== undefined) throw new TypeError(`"hooks.validate" conflicts with "hooks.validateDelete" for the "${listKey}" list`) - continue - } - - list.hooks = { - ...list.hooks, - validate: { - create: list.hooks.validateInput, - update: list.hooks.validateInput, - delete: list.hooks.validateDelete - } - } - } - - return updated -} - -function defaultIsAccessAllowed ({ session, sessionStrategy }: KeystoneContext) { - if (!sessionStrategy) return true - return session !== undefined -} - -export async function noop () {} -function identity (x: T) { return x } - -export function resolveDefaults (config: KeystoneConfig, injectIdField = false): ResolvedKeystoneConfig { - if (!['postgresql', 'sqlite', 'mysql'].includes(config.db.provider)) { - throw new TypeError(`"db.provider" only supports "sqlite", "postgresql" or "mysql"`) - } - - // WARNING: Typescript should prevent this, but any string is useful for Prisma errors - config.db.url ??= 'postgres://' - - const defaultIdField = config.db.idField ?? { kind: 'cuid' } - const cors = - config.server?.cors === true - ? { origin: true, credentials: true } - : config.server?.cors === false - ? null - : config.server?.cors ?? null - - const httpOptions: ListenOptions = { port: 3000 } - if (config?.server && 'port' in config.server) { - httpOptions.port = config.server.port - } - - if (config?.server && 'options' in config.server && config.server.options) { - Object.assign(httpOptions, config.server.options) - } - - return { - types: { - ...config.types, - path: config.types?.path ?? 'node_modules/.keystone/types.ts', - }, - db: { - ...config.db, - shadowDatabaseUrl: config.db?.shadowDatabaseUrl ?? '', - extendPrismaSchema: config.db?.extendPrismaSchema ?? identity, - extendPrismaClient: config.db?.extendPrismaClient ?? identity, - onConnect: config.db.onConnect ?? noop, - prismaClientPath: config.db?.prismaClientPath ?? '@prisma/client', - prismaSchemaPath: config.db?.prismaSchemaPath ?? 'schema.prisma', - idField: config.db?.idField ?? defaultIdField, - enableLogging: config.db.enableLogging === true ? ['query'] - : config.db.enableLogging === false ? [] - : config.db.enableLogging ?? [], - }, - graphql: { - ...config.graphql, - path: config.graphql?.path ?? '/api/graphql', - playground: config.graphql?.playground ?? process.env.NODE_ENV !== 'production', - schemaPath: config.graphql?.schemaPath ?? 'schema.graphql', - extendGraphqlSchema: config.graphql?.extendGraphqlSchema ?? ((s) => s), - }, - lists: injectIdField ? injectDefaults(config, defaultIdField) : config.lists as ResolvedKeystoneConfig['lists'], - server: { - ...config.server, - maxFileSize: config.server?.maxFileSize ?? (200 * 1024 * 1024), // 200 MiB - extendExpressApp: config.server?.extendExpressApp ?? noop, - extendHttpServer: config.server?.extendHttpServer ?? noop, - cors, - options: httpOptions, - }, - session: config.session, - storage: { - ...config.storage - }, - telemetry: config.telemetry ?? true, - ui: { - ...config.ui, - basePath: config.ui?.basePath ?? '', - isAccessAllowed: config.ui?.isAccessAllowed ?? defaultIsAccessAllowed, - isDisabled: config.ui?.isDisabled ?? false, - getAdditionalFiles: config.ui?.getAdditionalFiles ?? [], - pageMiddleware: config.ui?.pageMiddleware ?? noop, - publicPages:config.ui?.publicPages ?? [], - }, - } -} diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 2117692bfbb..3390b51598b 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -1,12 +1,163 @@ -import { resolveDefaults } from './lib/defaults' -import { - type BaseFields, - type BaseKeystoneTypeInfo, - type BaseListTypeInfo, - type KeystoneConfig, - type ListConfig, +import type { + BaseFields, + BaseKeystoneTypeInfo, + BaseListTypeInfo, + IdFieldConfig, + KeystoneConfig, + KeystoneContext, + ListConfig, + ResolvedKeystoneConfig, } from './types' +import type { ListenOptions } from 'node:net' +import { + idFieldType +} from './lib/id-field' + +function injectDefaults (config: KeystoneConfig, defaultIdField: IdFieldConfig) { + // some error checking + for (const [listKey, list] of Object.entries(config.lists)) { + if (list.fields.id) { + throw new Error(`"fields.id" is reserved by Keystone, use "db.idField" for the "${listKey}" list`) + } + + if (list.isSingleton && list.db?.idField) { + throw new Error(`"db.idField" on the "${listKey}" list conflicts with singleton defaults`) + } + } + + const updated: ResolvedKeystoneConfig['lists'] = {} + + for (const [listKey, list] of Object.entries(config.lists)) { + if (list.isSingleton) { + updated[listKey] = { + listKey, + ...list, + fields: { + id: idFieldType({ kind: 'number', type: 'Int' }), + ...list.fields, + }, + } + + continue + } + + updated[listKey] = { + listKey, + ...list, + fields: { + id: idFieldType(list.db?.idField ?? defaultIdField), + ...list.fields, + }, + } + } + + /** @deprecated, TODO: remove in breaking change */ + for (const [listKey, list] of Object.entries(updated)) { + if (list.hooks === undefined) continue + if (list.hooks.validate !== undefined) { + if (list.hooks.validateInput !== undefined) throw new TypeError(`"hooks.validate" conflicts with "hooks.validateInput" for the "${listKey}" list`) + if (list.hooks.validateDelete !== undefined) throw new TypeError(`"hooks.validate" conflicts with "hooks.validateDelete" for the "${listKey}" list`) + continue + } + + list.hooks = { + ...list.hooks, + validate: { + create: list.hooks.validateInput, + update: list.hooks.validateInput, + delete: list.hooks.validateDelete + } + } + } + + return updated +} + +function defaultIsAccessAllowed ({ session, sessionStrategy }: KeystoneContext) { + if (!sessionStrategy) return true + return session !== undefined +} + +async function noop () {} +function identity (x: T) { return x } + +function resolveDefaults (config: KeystoneConfig, injectIdField = false): ResolvedKeystoneConfig { + if (!['postgresql', 'sqlite', 'mysql'].includes(config.db.provider)) { + throw new TypeError(`"db.provider" only supports "sqlite", "postgresql" or "mysql"`) + } + + // WARNING: Typescript should prevent this, but any string is useful for Prisma errors + config.db.url ??= 'postgres://' + + const defaultIdField = config.db.idField ?? { kind: 'cuid' } + const cors = + config.server?.cors === true + ? { origin: true, credentials: true } + : config.server?.cors === false + ? null + : config.server?.cors ?? null + + const httpOptions: ListenOptions = { port: 3000 } + if (config?.server && 'port' in config.server) { + httpOptions.port = config.server.port + } + + if (config?.server && 'options' in config.server && config.server.options) { + Object.assign(httpOptions, config.server.options) + } + + return { + types: { + ...config.types, + path: config.types?.path ?? 'node_modules/.keystone/types.ts', + }, + db: { + ...config.db, + shadowDatabaseUrl: config.db?.shadowDatabaseUrl ?? '', + extendPrismaSchema: config.db?.extendPrismaSchema ?? identity, + extendPrismaClient: config.db?.extendPrismaClient ?? identity, + onConnect: config.db.onConnect ?? noop, + prismaClientPath: config.db?.prismaClientPath ?? '@prisma/client', + prismaSchemaPath: config.db?.prismaSchemaPath ?? 'schema.prisma', + idField: config.db?.idField ?? defaultIdField, + enableLogging: config.db.enableLogging === true ? ['query'] + : config.db.enableLogging === false ? [] + : config.db.enableLogging ?? [], + }, + graphql: { + ...config.graphql, + path: config.graphql?.path ?? '/api/graphql', + playground: config.graphql?.playground ?? process.env.NODE_ENV !== 'production', + schemaPath: config.graphql?.schemaPath ?? 'schema.graphql', + extendGraphqlSchema: config.graphql?.extendGraphqlSchema ?? ((s) => s), + }, + lists: injectIdField ? injectDefaults(config, defaultIdField) : config.lists as ResolvedKeystoneConfig['lists'], + server: { + ...config.server, + maxFileSize: config.server?.maxFileSize ?? (200 * 1024 * 1024), // 200 MiB + extendExpressApp: config.server?.extendExpressApp ?? noop, + extendHttpServer: config.server?.extendHttpServer ?? noop, + cors, + options: httpOptions, + }, + session: config.session, + storage: { + ...config.storage + }, + telemetry: config.telemetry ?? true, + ui: { + ...config.ui, + basePath: config.ui?.basePath ?? '', + isAccessAllowed: config.ui?.isAccessAllowed ?? defaultIsAccessAllowed, + isDisabled: config.ui?.isDisabled ?? false, + getAdditionalFiles: config.ui?.getAdditionalFiles ?? [], + pageMiddleware: config.ui?.pageMiddleware ?? noop, + publicPages:config.ui?.publicPages ?? [], + }, + } +} + export function config (config: KeystoneConfig) { return resolveDefaults(config) } diff --git a/packages/core/src/scripts/dev.ts b/packages/core/src/scripts/dev.ts index 5e533b3fc8f..d368e3d5f42 100644 --- a/packages/core/src/scripts/dev.ts +++ b/packages/core/src/scripts/dev.ts @@ -25,22 +25,29 @@ import { generateTypes, getFormattedGraphQLSchema, } from '../artifacts' -import { type ResolvedKeystoneConfig } from '../types' +import type { ResolvedKeystoneConfig } from '../types' import { printPrismaSchema } from '../lib/core/prisma-schema-printer' import { pkgDir } from '../pkg-dir' import { ExitError, importBuiltKeystoneConfiguration, } from './utils' -import { type Flags } from './cli' -import { noop } from '../lib/defaults' +import type { Flags } from './cli' + +async function noop () {} const devLoadingHTMLFilepath = path.join(pkgDir, 'static', 'dev-loading.html') function stripExtendHttpServer (config: ResolvedKeystoneConfig): ResolvedKeystoneConfig { const { server, ...rest } = config const { extendHttpServer, ...restServer } = server - return { ...rest, server: { ... restServer, extendHttpServer: noop } } + return { + ...rest, + server: { + ...restServer, + extendHttpServer: noop + } + } } function resolvablePromise () { diff --git a/packages/core/src/types/next-fields.ts b/packages/core/src/types/next-fields.ts index 08bda567309..6eb5d4de165 100644 --- a/packages/core/src/types/next-fields.ts +++ b/packages/core/src/types/next-fields.ts @@ -1,13 +1,13 @@ import Decimal from 'decimal.js' import { graphql } from './schema' -import { type BaseListTypeInfo } from './type-info' -import { type CommonFieldConfig } from './config' -import { type DatabaseProvider } from './core' -import { - type JSONValue, - type KeystoneContext, - type MaybePromise, - type StorageConfig +import type { BaseListTypeInfo } from './type-info' +import type { CommonFieldConfig } from './config' +import type { DatabaseProvider } from './core' +import type { + JSONValue, + KeystoneContext, + MaybePromise, + StorageConfig } from '.' export { Decimal }