Fix discovery API path missing /api/v1 prefix on Vercel deployment#1064
Fix discovery API path missing /api/v1 prefix on Vercel deployment#1064
Conversation
- Updated ObjectQL protocol getDiscovery() to accept optional prefix parameter - Modified SERVICE_CONFIG to use relative paths instead of hardcoded /api/v1 prefix - Updated RestServer to pass basePath to protocol.getDiscovery() - Updated GetDiscoveryRequestSchema to include optional prefix field - This fixes the issue where discovery returned hardcoded /api/v1 paths on Vercel Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/be36951d-a538-45c9-a17f-126fdc152c53 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@copilot 你开发的不对,现在问题都关键是本地运行的时候是正常的,但是部署之后返回的API路由不对,导致API console坏掉了,查一下这两个部署有什么区别 |
Root cause: all adapters registered discovery only at GET prefix (root),
not at GET prefix/discovery. When frontend fetched /api/v1/discovery, the
catch-all stripped the prefix and called dispatch('/discovery') which called
getDiscoveryInfo('') with empty prefix, returning /data instead of /api/v1/data.
Locally this was hidden because MSW mode doesn't register real services,
so the wrong routes were never used. On Vercel, all services are registered,
breaking the API console.
Fixes:
- http-dispatcher: add optional prefix param to dispatch() so discovery
handler uses the correct prefix instead of empty string
- hono adapter: add explicit GET prefix/discovery route (Vercel fix)
- fastify, nuxt adapters: add explicit GET prefix/discovery route
- sveltekit, nextjs adapters: handle 'discovery' segment before catch-all
- express adapter: pass prefix to dispatch() (already had explicit route)
- MSW plugin: add explicit baseUrl/discovery handler + pass baseUrl to dispatch()
- Revert wrong protocol.ts/rest-server.ts/spec changes from previous commit
- Update hono test assertions for new 6th prefix parameter
Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/ae9fa3e0-4bf7-45e8-b4ee-4816ad2f6917
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
找到根本原因了。问题不在 本地(MSW模式)vs Vercel 的关键区别:
根本原因:所有 adapter 在处理 修复(commit d84fb63):
|
There was a problem hiding this comment.
Pull request overview
Fixes discovery responses returning routes without the deployment prefix (e.g. /data instead of /api/v1/data) by ensuring the dispatcher and adapters preserve/pass the API prefix when serving GET .../discovery, particularly in Vercel deployments where the prefix-stripping catch-all path was previously invoked.
Changes:
- Extended
HttpDispatcher.dispatch()with an optionalprefixparameter and used it for discovery generation. - Added explicit
GET ${prefix}/discoveryhandling (or equivalent segment handling) across adapters and ensured adapter catch-alls passprefixintodispatch(). - Updated Hono adapter tests to cover
/discoveryand the Vercel delegation pattern, and updated dispatch-argument assertions there.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime/src/http-dispatcher.ts | Adds optional prefix to dispatch() and forwards it into discovery generation. |
| packages/plugins/plugin-msw/src/msw-plugin.ts | Adds explicit MSW handlers for ${baseUrl} and ${baseUrl}/discovery and passes baseUrl into dispatch(). |
| packages/adapters/sveltekit/src/index.ts | Handles GET .../discovery explicitly and passes prefix into dispatch(). |
| packages/adapters/nuxt/src/index.ts | Registers GET ${prefix}/discovery and passes prefix into dispatch(). |
| packages/adapters/nextjs/src/index.ts | Handles GET .../discovery segment explicitly and passes prefix into dispatch(). |
| packages/adapters/hono/src/index.ts | Registers GET ${prefix}/discovery and passes prefix into dispatch(). |
| packages/adapters/hono/src/hono.test.ts | Adds /discovery coverage and updates dispatch() call assertions for the new prefix arg. |
| packages/adapters/fastify/src/index.ts | Registers GET ${prefix}/discovery and passes prefix into dispatch(). |
| packages/adapters/express/src/index.ts | Passes prefix into dispatch() for the catch-all. |
| const subPath = urlPath.substring(prefix.length); | ||
| const method = request.method; | ||
| const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? request.body : undefined; | ||
| const result = await dispatcher.dispatch(method, subPath, body, request.query, { request: request.raw }); | ||
| const result = await dispatcher.dispatch(method, subPath, body, request.query, { request: request.raw }, prefix); | ||
| return sendResult(result, reply); |
There was a problem hiding this comment.
The adapter now passes a 6th prefix argument to dispatcher.dispatch(...), but the existing Fastify adapter unit tests still assert the old 5-argument call signature (see packages/adapters/fastify/src/fastify.test.ts). This will cause the test suite to fail unless the expectations are updated to include the prefix (e.g. '/api' / custom prefix) and ideally add coverage for the new GET ${prefix}/discovery route.
| const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') | ||
| ? await readBody(event) | ||
| : undefined; | ||
| const query = getQuery(event); | ||
| const result = await dispatcher.dispatch(method, subPath, body, query, { request: event.node.req }); | ||
| const result = await dispatcher.dispatch(method, subPath, body, query, { request: event.node.req }, prefix); | ||
| return toResponse(event, result); |
There was a problem hiding this comment.
The Nuxt adapter now passes a 6th prefix argument to dispatcher.dispatch(...), but packages/adapters/nuxt/src/nuxt.test.ts still asserts the old 5-argument call signature. Update those expectations to include the prefix and add a test for GET ${prefix}/discovery to prevent regressions of this specific bug.
| const queryParams: Record<string, any> = {}; | ||
| url.searchParams.forEach((val, key) => { queryParams[key] = val; }); | ||
|
|
||
| const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request }); | ||
| const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request }, prefix); | ||
| return toResponse(result); |
There was a problem hiding this comment.
The SvelteKit adapter now passes a 6th prefix argument to dispatcher.dispatch(...), but packages/adapters/sveltekit/src/sveltekit.test.ts still asserts the old 5-argument signature. Update the test expectations to include the prefix, and consider adding a dedicated test for GET ${prefix}/discovery (added above) since that’s the primary regression being fixed.
| const url = new URL(req.url); | ||
| const queryParams: Record<string, any> = {}; | ||
| url.searchParams.forEach((val, key) => queryParams[key] = val); | ||
|
|
||
| const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest }); | ||
| const result = await dispatcher.dispatch(method, path, body, queryParams, { request: rawRequest }, options.prefix || '/api'); | ||
| return toResponse(result); |
There was a problem hiding this comment.
The Next.js adapter now passes a 6th prefix argument to dispatcher.dispatch(...), but packages/adapters/nextjs/src/nextjs.test.ts still asserts the old 5-argument call signature. Update those expectations to include the prefix, and add coverage for the new GET .../discovery branch (segments === ['discovery']) to ensure the Vercel case stays fixed.
| http.get(`*${baseUrl}`, async () => { | ||
| if (this.dispatcher) { | ||
| return HttpResponse.json({ data: await this.dispatcher.getDiscoveryInfo(baseUrl) }); | ||
| } | ||
| return HttpResponse.json({ data: { version: 'v1', url: baseUrl } }); | ||
| }), | ||
| http.get(`*${baseUrl}/discovery`, async () => { | ||
| if (this.dispatcher) { | ||
| return HttpResponse.json({ data: await this.dispatcher.getDiscoveryInfo(baseUrl) }); | ||
| } | ||
| return HttpResponse.json({ data: { version: 'v1', url: baseUrl } }); | ||
| }) |
There was a problem hiding this comment.
The fallback discovery payload returned when this.dispatcher is unavailable ({ version: 'v1', url: baseUrl }) does not match the shape returned by HttpDispatcher.getDiscoveryInfo() or even the more complete fallback used by the /.well-known/objectstack handler above. This can break clients that rely on fields like routes/endpoints/features even in MSW fallback mode. Consider reusing the same fallback object as the well-known handler (or a minimal stub that still includes routes and features keys with sensible defaults) for consistency across discovery endpoints.
Problem
After deploying to Vercel, the discovery endpoint (
GET /api/v1/discovery) returns API paths without the/api/v1prefix. For example, routes appear as/auth,/datainstead of/api/v1/auth,/api/v1/data, causing the API console to break. Locally this issue was hidden because the MSW-based dev environment only registers minimal services, so the wrong routes were never actually used.Root Cause
All framework adapters registered an explicit discovery handler only at
GET prefix(e.g.,GET /api/v1), but not atGET prefix/discovery(e.g.,GET /api/v1/discovery). When the frontend fetched/api/v1/discovery, the catch-all handler stripped the prefix and calleddispatcher.dispatch('GET', '/discovery', ...). Insidedispatch(), the discovery branch calledgetDiscoveryInfo('')with an empty prefix, returning routes like/dataand/authinstead of/api/v1/dataand/api/v1/auth.On Vercel, all services (auth, ai, security, audit, feed, etc.) are fully registered, so the broken routes from discovery were actually consumed by the API console, causing it to fail. Locally with MSW, only minimal services are registered and the console falls back to hardcoded defaults, masking the bug.
Changes
packages/runtime/src/http-dispatcher.tsprefixparameter (6th arg, backward-compatible) todispatch()methoddispatch()now passesprefixtogetDiscoveryInfo()when handling the/discoveryroute, instead of using an empty stringAll adapters — Added explicit
GET ${prefix}/discoveryroutepackages/adapters/hono/src/index.ts— Added explicitGET ${prefix}/discoveryroute callinggetDiscoveryInfo(prefix)(primary Vercel fix)packages/adapters/fastify/src/index.ts— Same fixpackages/adapters/nuxt/src/index.ts— Same fixpackages/adapters/sveltekit/src/index.ts— Handlesdiscoverysegment before catch-allpackages/adapters/nextjs/src/index.ts— Handlesdiscoverysegment before catch-allpackages/adapters/express/src/index.ts— Passes prefix todispatch()(already had explicit/discoveryroute)packages/plugins/plugin-msw/src/msw-plugin.tsGET baseUrl/discoveryMSW handler with correct prefixbaseUrlas prefix todispatcher.dispatch()in the catch-allpackages/adapters/hono/src/hono.test.tsdispatch()call assertions to include the newprefix6th argumentGET /api/discoveryandGET /api/v1/discoveryendpoints via the Vercel delegation patternExample
This ensures discovery responses always include the correct API prefix across all deployment environments (Vercel, local, etc.).