Skip to content
Open
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
9 changes: 9 additions & 0 deletions .changeset/remediate-ci-failures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@keystone-6/core": patch
---

This patch remediates critical CI build failures and functional regressions introduced in recent validation hook refactoring:

- **Build Stability**: Resolved `MISSING_EXPORT` and Rollup bundling errors in `@keystone-6/core` by restoring robust type signatures and standardized export patterns.
- **Validation Preservation**: Fixed a regression where field-level validation messages were swallowed if a list-level hook crashed. Field errors and explicit validation messages are now prioritized and propagated correctly.
- **Access Control Transparency**: Resolved a regression where database-level errors during unique item checks were silently swallowed. These are now correctly rethrown for infrastructure failures, while maintaining consistent "item may not exist" feedback for access-denied and schema-omitted cases.
13 changes: 12 additions & 1 deletion packages/core/src/lib/core/access-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,18 @@ export async function checkUniqueItemExists(
try {
const item = await context.db[foreignList.listKey].findOne({ where: uniqueInput })
if (item !== null) return uniqueWhere
} catch (err) {}
} catch (err: any) {
// If it's an access denied error or a schema support error from context.db, we swallow it and throw our own
// to keep the error message consistent with "item may not exist".
// But if it's a real database error (e.g. connection, timeout, malformed query),
// we MUST rethrow it so the developer knows what happened.
if (
err?.extensions?.code !== 'KS_ACCESS_DENIED' &&
!err.message.startsWith('This query is not supported by the GraphQL schema')
) {
throw err
}
}

throw accessDeniedError(cannotForItem(operation, foreignList))
}
17 changes: 12 additions & 5 deletions packages/core/src/lib/core/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { extensionError, validationFailureError } from './graphql-errors'
import { type InitialisedList } from './initialise-lists'
import type { InitialisedList } from './initialise-lists'

export async function validate({
list,
Expand Down Expand Up @@ -46,15 +46,22 @@ export async function validate({
const addValidationError = (msg: string) => void messages.push(`${list.listKey}: ${msg}`)
const hook = list.hooks.validate[operation]

let listHookError: any
try {
await hook({ ...hookArgs, addValidationError } as never) // TODO: FIXME
} catch (error: any) {
throw extensionError('validateInput', [{ error, tag: `${list.listKey}.hooks.validateInput` }])
listHookError = error
}

if (messages.length) {
throw validationFailureError(messages)
}

if (listHookError) {
throw extensionError('validateInput', [
{ error: listHookError, tag: `${list.listKey}.hooks.validateInput` },
])
}
}
}

Expand Down Expand Up @@ -83,14 +90,14 @@ export async function runSideEffectOnlyHook<
Object.entries(list.fields).map(async ([fieldKey, field]) => {
if (shouldRunFieldLevelHook(fieldKey)) {
try {
await field.hooks[hookName][operation]({
await (field.hooks[hookName][operation] as any)({
...args,
fieldKey,
itemField: args.item?.[fieldKey],
inputFieldData: args.inputData?.[fieldKey],
resolvedFieldData: args.resolvedData?.[fieldKey],
originalItemField: (args as any).originalItem?.[fieldKey],
} as any) // TODO: FIXME any
})
} catch (error: any) {
fieldsErrors.push({ error, tag: `${list.listKey}.${fieldKey}.hooks.${hookName}` })
}
Expand All @@ -104,7 +111,7 @@ export async function runSideEffectOnlyHook<

// list hooks
try {
await list.hooks[hookName][operation](args as any) // TODO: FIXME any
await (list.hooks[hookName][operation] as any)(args)
} catch (error: any) {
throw extensionError(hookName, [{ error, tag: `${list.listKey}.hooks.${hookName}` }])
}
Expand Down
Loading