From 2d6a1fdfeb218dba16b15c3584f4122492ad4153 Mon Sep 17 00:00:00 2001 From: Finn <38991189+simplyzetax@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:12:38 +0200 Subject: [PATCH] fix(workers): keep the type of an annotated ctx in withEvlog and defineWorkerFetch --- .changeset/workers-ctx-type.md | 5 +++ packages/evlog/src/workers/index.ts | 20 ++++++++---- .../evlog/test/workers/ctx-typing.test.ts | 31 +++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 .changeset/workers-ctx-type.md create mode 100644 packages/evlog/test/workers/ctx-typing.test.ts diff --git a/.changeset/workers-ctx-type.md b/.changeset/workers-ctx-type.md new file mode 100644 index 000000000..517dcc68f --- /dev/null +++ b/.changeset/workers-ctx-type.md @@ -0,0 +1,5 @@ +--- +"evlog": patch +--- + +`withEvlog` and `defineWorkerFetch` (`evlog/workers`) keep the type of an annotated `ctx`, so a handler typed with Cloudflare's `ExecutionContext` reaches `ctx.exports`, `ctx.props` and `ctx.passThroughOnException()` without a cast. An unannotated `ctx` is still `WorkerExecutionContext`. diff --git a/packages/evlog/src/workers/index.ts b/packages/evlog/src/workers/index.ts index 81fc1bde4..d8cbdaef1 100644 --- a/packages/evlog/src/workers/index.ts +++ b/packages/evlog/src/workers/index.ts @@ -93,16 +93,20 @@ export function initWorkersLogger(options: LoggerConfig = {}): void { * return new Response('ok') * }) * ``` + * + * @typeParam TCtx - The handler's `ctx`. Unannotated, it is {@link WorkerExecutionContext}; annotate `ctx` (for + * example as `ExecutionContext` from `@cloudflare/workers-types`) or pass the type argument to reach the rest of + * Cloudflare's context, such as `ctx.exports` or `ctx.props`. */ -export function defineWorkerFetch( +export function defineWorkerFetch( handler: ( request: Request, env: TEnv, - ctx: WorkerExecutionContext, + ctx: TCtx, log: AuditableLogger, ) => Response | Promise, ): { - fetch: (request: Request, env: TEnv, ctx: WorkerExecutionContext) => Promise + fetch: (request: Request, env: TEnv, ctx: TCtx) => Promise } { return { fetch(request, env, ctx) { @@ -194,17 +198,21 @@ const integration = defineFrameworkIntegration({ * }, * ) * ``` + * + * @typeParam TCtx - The handler's `ctx`. Unannotated, it is {@link WorkerExecutionContext}; annotate `ctx` (for + * example as `ExecutionContext` from `@cloudflare/workers-types`) or pass the type argument to reach the rest of + * Cloudflare's context, such as `ctx.exports` or `ctx.props`. */ -export function withEvlog( +export function withEvlog( handler: ( request: Request, env: TEnv, - ctx: WorkerExecutionContext, + ctx: TCtx, log: AuditableLogger, ) => Response | Promise, options: EvlogWorkersOptions = {}, ): { - fetch: (request: Request, env: TEnv, ctx: WorkerExecutionContext) => Promise + fetch: (request: Request, env: TEnv, ctx: TCtx) => Promise } { return { async fetch(request, env, ctx) { diff --git a/packages/evlog/test/workers/ctx-typing.test.ts b/packages/evlog/test/workers/ctx-typing.test.ts new file mode 100644 index 000000000..73a4d6b49 --- /dev/null +++ b/packages/evlog/test/workers/ctx-typing.test.ts @@ -0,0 +1,31 @@ +import { describe, expectTypeOf, it } from 'vitest' +import type { WorkerExecutionContext } from '../../src/workers' +import { defineWorkerFetch, withEvlog } from '../../src/workers' + +// Cloudflare's ExecutionContext beyond waitUntil, without depending on @cloudflare/workers-types +interface ExecutionContext extends WorkerExecutionContext { + passThroughOnException(): void + props: unknown + exports: Record +} + +describe('evlog/workers ctx typing', () => { + it('keeps the type of an annotated ctx', () => { + const worker = withEvlog((_request, _env: unknown, ctx: ExecutionContext) => { + ctx.passThroughOnException() + return new Response(Object.keys(ctx.exports).join(',')) + }) + expectTypeOf(worker.fetch).parameter(2).toEqualTypeOf() + + const handler = defineWorkerFetch((_request, _env: unknown, ctx: ExecutionContext) => new Response(String(ctx.props))) + expectTypeOf(handler.fetch).parameter(2).toEqualTypeOf() + }) + + it('types an unannotated ctx as WorkerExecutionContext', () => { + const worker = withEvlog((_request, _env, ctx) => { + expectTypeOf(ctx).toEqualTypeOf() + return new Response('ok') + }) + expectTypeOf(worker.fetch).parameter(2).toEqualTypeOf() + }) +})