Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/sveltekit-3-handle-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"evlog": patch
---

`createEvlogHooks().handleError` and `evlogHandleError()` now accept SvelteKit 3's `handleError` input (`kind`, `error`, `event`) and type-check as Kit 3's `HandleServerError`. On SvelteKit 3, errors that are not an `EvlogError` keep Kit's own status and message instead of being overwritten with `undefined`, which made production responses return HTTP 200. The hook no longer reads Kit 3's deprecated `status` and `message` input properties, so dev mode stops logging deprecation warnings. SvelteKit 2 behavior is unchanged.
18 changes: 10 additions & 8 deletions packages/evlog/src/sveltekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,12 @@ const integration = defineFrameworkIntegration<SvelteKitRequestEvent>({

/**
* SvelteKit `HandleServerError` signature — avoids a hard dependency on `@sveltejs/kit`.
* Kit 2 passes `status` and `message`; Kit 3 passes a `kind` discriminant instead.
*/
type SvelteKitHandleServerError = (input: {
error: unknown
event: { request: Request; url: URL; locals: Record<string, any> }
status: number
message: string
}) => MaybePromise<void | AppError>
} & ({ kind: string } | { status: number; message: string })) => MaybePromise<void | AppError>

type MaybePromise<T> = T | Promise<T>

Expand Down Expand Up @@ -203,8 +202,8 @@ export function evlog(options: EvlogSvelteKitOptions = {}): SvelteKitHandle {
* Create an evlog error handler for SvelteKit.
*
* Logs unhandled errors via `event.locals.log` (if available) and returns
* structured error responses for `EvlogError` instances. For non-evlog errors,
* returns a generic error response with sanitized messages in production.
* structured error responses for `EvlogError` instances. Other errors keep
* SvelteKit's own status and message. Supports SvelteKit 2 and 3.
*
* @example
* ```ts
Expand All @@ -216,7 +215,8 @@ export function evlog(options: EvlogSvelteKitOptions = {}): SvelteKitHandle {
* ```
*/
export function evlogHandleError(): SvelteKitHandleServerError {
return ({ error, event, status, message }) => {
return (input) => {
const { error, event } = input
const logger = isRequestLogger(event.locals.log) ? event.locals.log : undefined

if (logger && error instanceof Error) {
Expand All @@ -229,13 +229,15 @@ export function evlogHandleError(): SvelteKitHandleServerError {
const errorStatus = extractErrorStatus(evlogError)
const response = serializeEvlogErrorResponse(evlogError, event.url.pathname)
return {
message: typeof response.message === 'string' ? response.message : message,
message: evlogError.message,
status: errorStatus,
...readEvlogResponseData(response),
}
}

return { message, status }
// Kit 3 fills in status and message itself; its input getters for them are deprecated
if ('kind' in input) return
return { message: input.message, status: input.status }
}
}

Expand Down
55 changes: 55 additions & 0 deletions packages/evlog/test/frameworks/sveltekit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,61 @@ describe('evlog/sveltekit', () => {

expect(result.message).toBe('Internal Error')
})

describe('SvelteKit 3 input (#911)', () => {
function createKit3Input(kind: 'unknown' | 'framework', error: unknown, event: ReturnType<typeof createMockEvent>) {
const deprecatedReads: string[] = []
const input = { kind, error, event }
// Kit 3 dev mode exposes `status` and `message` as deprecated getters that warn on access
Object.defineProperties(input, {
status: {
get() {
deprecatedReads.push('status')
return 500
},
},
message: {
get() {
deprecatedReads.push('message')
return 'Internal Error'
},
},
})
return { input, deprecatedReads }
}

it('logs an unknown error and returns nothing so Kit keeps its defaults', () => {
const handleError = evlogHandleError()
const event = createMockEvent('GET', '/api/fail')
const logError = vi.fn()
event.locals.log = { error: logError, emit: () => null }
const error = new Error('Something broke')
const { input, deprecatedReads } = createKit3Input('unknown', error, event)

expect(handleError(input)).toBeUndefined()
expect(logError).toHaveBeenCalledWith(error)
expect(deprecatedReads).toEqual([])
})

it('returns the EvlogError status and message', () => {
const handleError = evlogHandleError()
const event = createMockEvent('GET', '/api/checkout')
const error = new EvlogError({ message: 'Payment failed', status: 402, why: 'Card declined' })
const { input, deprecatedReads } = createKit3Input('unknown', error, event)

expect(handleError(input)).toEqual({ message: 'Payment failed', status: 402, why: 'Card declined' })
expect(deprecatedReads).toEqual([])
})

it('returns nothing for a framework error', () => {
const handleError = evlogHandleError()
const event = createMockEvent('GET', '/missing')
const { input, deprecatedReads } = createKit3Input('framework', { status: 404, message: 'Not Found' }, event)

expect(handleError(input)).toBeUndefined()
expect(deprecatedReads).toEqual([])
})
})
})

describe('createEvlogHooks()', () => {
Expand Down
Loading