From 732adc539fe23a5d261c4493e4c3ba614caaff08 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Sat, 4 Apr 2026 16:11:13 +0100 Subject: [PATCH 1/3] fix(tanstack-start): align evlogErrorHandler TS server middleware types --- AGENTS.md | 2 ++ bun.lock | 4 ++- examples/tanstack-start/nitro.config.ts | 7 +++++- examples/tanstack-start/package.json | 3 ++- .../tanstack-start/src/routes/api/admin.ts | 3 +++ .../tanstack-start/src/routes/api/checkout.ts | 3 +++ examples/tanstack-start/vite.config.ts | 25 ++++++++++--------- packages/evlog/package.json | 7 +++++- packages/evlog/src/nitro-v3/middleware.ts | 15 ++++++++--- 9 files changed, 50 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f77cf3d4..858d0a41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -572,6 +572,8 @@ export const Route = createRootRoute({ }) ``` +For TypeScript, `evlog` declares an optional peer on `@tanstack/start-client-core` so `evlogErrorHandler` is typed as TanStack’s `RequestServerFn`. Apps using `@tanstack/react-start` already pull that package in for type resolution. + Use `useRequest()` from `nitro/context` to access the logger in routes: ```typescript diff --git a/bun.lock b/bun.lock index 73d77fad..014320d4 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "evlog-monorepo", @@ -300,6 +299,7 @@ "@nuxt/devtools": "^3.2.4", "@nuxt/schema": "^4.4.2", "@nuxt/test-utils": "^4.0.0", + "@tanstack/start-client-core": "^1.167.7", "@types/express": "^5.0.6", "@types/supertest": "^7.2.0", "acorn": "^8.16.0", @@ -326,6 +326,7 @@ "peerDependencies": { "@nestjs/common": ">=11.1.17", "@nuxt/kit": "^4.4.2", + "@tanstack/start-client-core": "^1.161.0", "ai": ">=6.0.142", "elysia": ">=1.4.28", "express": ">=5.2.1", @@ -343,6 +344,7 @@ "optionalPeers": [ "@nestjs/common", "@nuxt/kit", + "@tanstack/start-client-core", "ai", "elysia", "express", diff --git a/examples/tanstack-start/nitro.config.ts b/examples/tanstack-start/nitro.config.ts index 6f30ec37..e89094c8 100644 --- a/examples/tanstack-start/nitro.config.ts +++ b/examples/tanstack-start/nitro.config.ts @@ -1,6 +1,11 @@ import { defineConfig } from 'nitro' import evlog from 'evlog/nitro/v3' +/* `evlog/nitro/v3` may resolve a different `nitro` version than this example's + * `nitro-nightly`. Runtime behavior matches; align the module slot for tsc. */ +type NitroUserConfig = Parameters[0] +type NitroModuleSlot = NonNullable[number] + export default defineConfig({ experimental: { asyncContext: true, @@ -8,6 +13,6 @@ export default defineConfig({ modules: [ evlog({ env: { service: 'tanstack-start-example' }, - }), + }) as NitroModuleSlot, ], }) diff --git a/examples/tanstack-start/package.json b/examples/tanstack-start/package.json index 704ac41d..5a6b7210 100644 --- a/examples/tanstack-start/package.json +++ b/examples/tanstack-start/package.json @@ -8,7 +8,8 @@ "scripts": { "dev": "vite dev --port 3000", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "typecheck": "tsc --noEmit -p tsconfig.json" }, "dependencies": { "@tailwindcss/vite": "^4.1.18", diff --git a/examples/tanstack-start/src/routes/api/admin.ts b/examples/tanstack-start/src/routes/api/admin.ts index 009a7405..354ab5c4 100644 --- a/examples/tanstack-start/src/routes/api/admin.ts +++ b/examples/tanstack-start/src/routes/api/admin.ts @@ -8,6 +8,9 @@ export const Route = createFileRoute('/api/admin')({ handlers: { PUT: async ({ request }) => { const req = useRequest() + if (!req.context) { + throw new Error('Missing Nitro request context') + } const log = req.context.log as RequestLogger const body = await request.json() as { resourceId: string, changes: Record } diff --git a/examples/tanstack-start/src/routes/api/checkout.ts b/examples/tanstack-start/src/routes/api/checkout.ts index 4417e311..da16f6e7 100644 --- a/examples/tanstack-start/src/routes/api/checkout.ts +++ b/examples/tanstack-start/src/routes/api/checkout.ts @@ -8,6 +8,9 @@ export const Route = createFileRoute('/api/checkout')({ handlers: { POST: async ({ request }) => { const req = useRequest() + if (!req.context) { + throw new Error('Missing Nitro request context') + } const log = req.context.log as RequestLogger const body = await request.json() as { diff --git a/examples/tanstack-start/vite.config.ts b/examples/tanstack-start/vite.config.ts index bbf795ac..ee479fbe 100644 --- a/examples/tanstack-start/vite.config.ts +++ b/examples/tanstack-start/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite' +import { defineConfig, type UserConfig } from 'vite' import { devtools } from '@tanstack/devtools-vite' import tsconfigPaths from 'vite-tsconfig-paths' @@ -8,15 +8,16 @@ import viteReact from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import { nitro } from 'nitro/vite' -const config = defineConfig({ - plugins: [ - devtools(), - nitro({ rollupConfig: { external: [/^@sentry\//] } }), - tsconfigPaths({ projects: ['./tsconfig.json'] }), - tailwindcss(), - tanstackStart(), - viteReact(), - ], -}) +/* Plugins come from packages that bundle their own Vite typings (TanStack devtools, + * nitro/rolldown, etc.). In a workspace, those are nominally different from this + * app's `vite` — assert once here instead of drowning in TS2769 chains. */ +const plugins = [ + devtools(), + nitro({ rollupConfig: { external: [/^@sentry\//] } }), + tsconfigPaths({ projects: ['./tsconfig.json'] }), + tailwindcss(), + tanstackStart(), + viteReact(), +] as UserConfig['plugins'] -export default config +export default defineConfig({ plugins }) diff --git a/packages/evlog/package.json b/packages/evlog/package.json index 8cd74108..df4e04a0 100644 --- a/packages/evlog/package.json +++ b/packages/evlog/package.json @@ -293,6 +293,7 @@ "typecheck": "echo 'Typecheck handled by build'" }, "devDependencies": { + "@tanstack/start-client-core": "^1.167.7", "acorn": "^8.16.0", "@codspeed/vitest-plugin": "^5.2.0", "@nestjs/common": "^11.1.17", @@ -336,7 +337,8 @@ "@nestjs/common": ">=11.1.17", "react-router": ">=7.13.2", "vite": "^7.0.0 || ^8.0.0", - "ai": ">=6.0.142" + "ai": ">=6.0.142", + "@tanstack/start-client-core": "^1.161.0" }, "peerDependenciesMeta": { "@nuxt/kit": { @@ -383,6 +385,9 @@ }, "ai": { "optional": true + }, + "@tanstack/start-client-core": { + "optional": true } } } diff --git a/packages/evlog/src/nitro-v3/middleware.ts b/packages/evlog/src/nitro-v3/middleware.ts index b21c095c..1634b96d 100644 --- a/packages/evlog/src/nitro-v3/middleware.ts +++ b/packages/evlog/src/nitro-v3/middleware.ts @@ -1,6 +1,6 @@ +import type { RequestServerResult } from '@tanstack/start-client-core' import type { RequestLogger } from '../types' import { EvlogError } from '../error' -import { serializeEvlogErrorResponse } from '../nitro' /** * Server middleware handler that catches EvlogError and returns a structured JSON response. @@ -23,10 +23,19 @@ import { serializeEvlogErrorResponse } from '../nitro' * ``` */ -export async function evlogErrorHandler(nextOrOptions: ((...args: any[]) => Promise) | { next: (...args: any[]) => Promise }): Promise { +/** + * TanStack's `RequestServerNextFn` may return synchronously or as a `Promise`, and uses a + * typed `options` argument. A rest-args `any[]` signature keeps `RequestServerNextFn` + * assignable under `strictFunctionTypes`. The return type matches `createMiddleware().server()`. + */ +type EvlogServerMiddlewareNext = (...args: any[]) => unknown | Promise + +export async function evlogErrorHandler( + nextOrOptions: EvlogServerMiddlewareNext | { next: EvlogServerMiddlewareNext }, +): Promise | Response> { const next = typeof nextOrOptions === 'function' ? nextOrOptions : nextOrOptions.next try { - return await next() + return (await Promise.resolve(next())) as RequestServerResult } catch (error: unknown) { if (error instanceof EvlogError || (error && typeof error === 'object' && (error as Error).name === 'EvlogError')) { const evlogError = error as EvlogError From 2bd361ffed7d0b0237e48580d2ba01c84482bd3c Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Sat, 4 Apr 2026 16:19:05 +0100 Subject: [PATCH 2/3] add changeset --- .changeset/tanstack-evlog-error-handler-types.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tanstack-evlog-error-handler-types.md diff --git a/.changeset/tanstack-evlog-error-handler-types.md b/.changeset/tanstack-evlog-error-handler-types.md new file mode 100644 index 00000000..5d9e4576 --- /dev/null +++ b/.changeset/tanstack-evlog-error-handler-types.md @@ -0,0 +1,5 @@ +--- +"evlog": patch +--- + +Align `evlogErrorHandler` with TanStack Start’s `createMiddleware().server()` types: widen `next()` to sync-or-async results, match `RequestServerFn` return typing via `RequestServerResult`, and declare an optional peer on `@tanstack/start-client-core` for accurate declarations ([#235](https://github.com/HugoRCD/evlog/issues/235), [EVL-142](https://linear.app/evlog/issue/EVL-142)). From 619f9c4b14a608a4c80a9b308baaeca64bbf5336 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:20:46 +0000 Subject: [PATCH 3/3] chore: apply automated lint fixes --- bun.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/bun.lock b/bun.lock index 014320d4..7c8beaac 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "evlog-monorepo",