Skip to content

fix: unified catch-all dispatch pattern across all adapters to resolve Vercel 404s#931

Merged
hotlong merged 5 commits into
mainfrom
copilot/fix-http-404-api-routes
Mar 18, 2026
Merged

fix: unified catch-all dispatch pattern across all adapters to resolve Vercel 404s#931
hotlong merged 5 commits into
mainfrom
copilot/fix-http-404-api-routes

Conversation

Copilot AI commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

All 7 adapters (Hono, Express, Fastify, Next.js, Nuxt, SvelteKit, NestJS) manually registered individual routes for each API namespace — and all were missing /packages, /analytics, /automation, /i18n, and /ui. This caused 404s on Vercel (and any other deployment) for those routes.

Rather than adding the missing routes one by one to each adapter, this PR introduces a unified catch-all pattern: each adapter now delegates to HttpDispatcher.dispatch() for all non-framework-specific routes. New routes added to dispatch() automatically work in every adapter with zero adapter-side code changes.

Unified catch-all architecture

Each adapter keeps only routes that require framework-specific handling:

  • Auth — needs auth service integration (getServiceAsync('auth'))
  • Storage — needs framework-specific formData/file parsing
  • GraphQL — returns raw result (not HttpDispatcherResult)
  • Discovery — uses custom response wrapper format ({ data: ... })

Everything else (meta, data, packages, analytics, automation, i18n, ui, openapi, custom API endpoints, and any future routes) goes through a single catch-all that delegates to dispatcher.dispatch().

Adapters updated

  • @objectstack/honocreateHonoApp()
  • @objectstack/expresscreateExpressRouter()
  • @objectstack/fastifyobjectStackPlugin()
  • @objectstack/nextjscreateRouteHandler()
  • @objectstack/nuxtcreateH3Router()
  • @objectstack/sveltekitcreateRequestHandler()
  • @objectstack/nestjsObjectStackController

Tests

  • Hono adapter tests rewritten for catch-all dispatch pattern (37 tests passing)
  • Tests verify correct method, path, body, and query param forwarding through dispatch()

Impact

  • ~290 lines of duplicated route-registration code deleted across adapters
  • Resolves 404 errors for /api/v1/meta, /api/v1/packages, and all other previously unregistered routes
  • Future-proof: new routes in HttpDispatcher.dispatch() are automatically available in all adapters
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Vercel部署后 /api/v1/meta 和 /api/v1/packages 路由404,导致前端无法加载元数据和包列表</issue_title>
<issue_description>## 问题描述
在部署 ObjectStack Studio 到 Vercel 后,前端在 Server模式下请求 /api/v1/meta/api/v1/packages 都返回 404,导致元数据及包列表无法正常加载。

复现步骤

  1. Vercel正确设置:VITE_RUNTIME_MODE=serverVITE_SERVER_URL=""(empty,same-origin)
  2. 前端浏览器启动后尝试请求:
    • GET /api/v1/meta → 404
    • GET /api/v1/packages → 404

核心分析

  • /api/v1/meta 路由虽然在 @objectstack/hono 适配器注册,但返回404,说明对应 dispatcher/broker 没有正确桥接 metadata服务。
  • /api/v1/packages 路由根本没有注册;createHonoApp() 只覆盖了 discovery, auth, graphql, meta, data, storage 等路由,缺少 packages。

影响

  • 前端无法加载元数据、包列表,核心功能不可用。
  • 404报错会阻断页面正常渲染,造成用户困扰。

推荐修复方案

  1. 补齐 createHonoApp() 适配器所有标准路由(见 HonoServerPlugin discovery routes),尤其是 packages 等缺失命名空间。
  2. 检查并修正 HttpDispatcher.handleMetadata() 及 broker shim 的桥接,确保元数据能正确返回。
  3. 补充测试,确保 serverless 部署下路由齐全、元数据和包列表可用。

  • 已分析全部问题及相关代码,建议架构级修复,不建议简单patch。
    </issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel

vercel Bot commented Mar 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Mar 18, 2026 2:42pm
spec Ready Ready Preview, Comment Mar 18, 2026 2:42pm

Request Review

… meta query param passthrough

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] [Bug] Fix 404 error for /api/v1/meta and /api/v1/packages fix: register missing API routes in createHonoApp, fix meta query passthrough Mar 18, 2026
Copilot AI requested a review from hotlong March 18, 2026 14:16
@hotlong

hotlong commented Mar 18, 2026

Copy link
Copy Markdown
Collaborator

@copilot 每个adaptor都要单独这样修改吗?没有统一的方案吗?

…ono, Express, Fastify, Next.js, Nuxt, SvelteKit, NestJS)

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ispatch pattern

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>

Copilot AI commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot 每个adaptor都要单独这样修改吗?没有统一的方案吗?

Good point. HttpDispatcher.dispatch() already serves as a centralized router for all route prefixes. I've refactored all 7 adapters (Hono, Express, Fastify, Next.js, Nuxt, SvelteKit, NestJS) to use a unified catch-all pattern that delegates to dispatch() instead of registering each route individually.

Each adapter now only keeps explicit routes for framework-specific needs:

  • Auth — auth service check (getServiceAsync('auth'))
  • Storage — formData/file parsing
  • GraphQL — raw result format
  • Discovery — response wrapper

Everything else (meta, data, packages, analytics, automation, i18n, ui, openapi, custom endpoints) goes through the catch-all. This means new routes added to HttpDispatcher.dispatch() will automatically work in all adapters with zero adapter-side changes.

Net result: ~290 lines deleted across adapters. See commit eb4d435.

Copilot AI changed the title fix: register missing API routes in createHonoApp, fix meta query passthrough fix: unified catch-all dispatch pattern across all adapters to resolve Vercel 404s Mar 18, 2026
@hotlong
hotlong marked this pull request as ready for review March 18, 2026 14:42
Copilot AI review requested due to automatic review settings March 18, 2026 14:42
@hotlong
hotlong merged commit be64bc0 into main Mar 18, 2026
4 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes adapter routing by moving most API namespaces behind a single catch-all that forwards requests to HttpDispatcher.dispatch(), preventing missing-route 404s (notably in Vercel/serverless deployments) and reducing duplicated route registration logic across adapters.

Changes:

  • Replaces per-namespace routing (meta/data/etc) with a unified catch-all delegating to HttpDispatcher.dispatch() across adapters, keeping only framework-specific routes explicit (auth/storage/graphql/discovery).
  • Updates the Hono adapter test suite to validate catch-all dispatch forwarding (method/path/body/query).
  • Adds adapter changelog entries describing the new routing approach.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/adapters/sveltekit/src/index.ts Removes explicit meta/data handlers; adds catch-all dispatch() forwarding.
packages/adapters/sveltekit/CHANGELOG.md Documents the catch-all dispatch change.
packages/adapters/nuxt/src/index.ts Removes explicit meta/data; adds catch-all dispatch() handler and keeps explicit framework-specific routes.
packages/adapters/nuxt/CHANGELOG.md Documents the catch-all dispatch change.
packages/adapters/nextjs/src/index.ts Removes explicit meta/data; adds catch-all dispatch() forwarding for remaining namespaces.
packages/adapters/nextjs/CHANGELOG.md Documents the catch-all dispatch change.
packages/adapters/nestjs/src/index.ts Routes meta/data through dispatch() and adds controller-level catch-all.
packages/adapters/nestjs/CHANGELOG.md Documents the catch-all dispatch change and meta/data routing update.
packages/adapters/hono/src/index.ts Converts to explicit (auth/graphql/storage/discovery) + catch-all dispatch() pattern.
packages/adapters/hono/src/hono.test.ts Rewrites tests to validate catch-all dispatch() behavior and query/body forwarding.
packages/adapters/hono/CHANGELOG.md Documents the catch-all dispatch change and the Vercel 404 fix.
packages/adapters/fastify/src/index.ts Removes explicit meta/data; adds catch-all dispatch() forwarding.
packages/adapters/fastify/CHANGELOG.md Documents the catch-all dispatch change.
packages/adapters/express/src/index.ts Removes explicit meta/data; adds catch-all dispatch() forwarding.
packages/adapters/express/CHANGELOG.md Documents the catch-all dispatch change.

Comment on lines 173 to 179
let path = req.params[0] || '';
if (req.url.includes('/meta')) {
path = req.url.split('/meta')[1].split('?')[0];
}

// Use injected body or fallback to req.body
const payload = body || req.body;

const result = await this.service.dispatcher.handleMetadata(path, { request: req }, req.method, payload);
const result = await this.service.dispatcher.dispatch(req.method, '/meta' + path, body || req.body, query, { request: req });
return this.normalizeResponse(result, res);
Comment on lines +220 to +231
// Catch-all: delegate remaining routes to dispatcher.dispatch()
// Handles packages, analytics, automation, i18n, ui, openapi,
// custom API endpoints, and any future routes.
@All('*')
async catchAll(@Req() req: any, @Res() res: any, @Body() body: any, @Query() query: any) {
try {
// Extract the sub-path after /api
const path = req.url.split('/api')[1]?.split('?')[0] || '';
const method = req.method;
const payload = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? body : undefined;
const result = await this.service.dispatcher.dispatch(method, path, payload, query, { request: req });
return this.normalizeResponse(result, res);
Comment on lines +3 to +11
## 3.2.8

### Patch Changes

- fix: unified catch-all dispatch pattern — `createHonoApp()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
- fix: resolves 404 errors for `/api/v1/meta` and `/api/v1/packages` after Vercel deployment
- Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
- Added comprehensive tests for the catch-all dispatch pattern

Comment on lines +169 to 176
fastify.all(`${prefix}/*`, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const urlPath = request.url.split('?')[0];
const subPath = urlPath.substring(`${prefix}/storage`.length);
const subPath = urlPath.substring(prefix.length);
const method = request.method;
const file = (request as any).file || (request.body as any)?.file;
const result = await dispatcher.handleStorage(subPath, method, file, { request: request.raw });
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? request.body : undefined;
const result = await dispatcher.dispatch(method, subPath, body, request.query, { request: request.raw });
return sendResult(result, reply);
Comment on lines +207 to 214
const subPath = urlPath.substring(prefix.length);
const method = event.method;
const body = (method === 'POST' || method === 'PATCH')
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH')
? await readBody(event)
: {};
: undefined;
const query = getQuery(event);
const result = await dispatcher.handleData(subPath, method, body, query, { request: event.node.req });
return toResponse(event, result);
} catch (err: any) {
return errorJson(event, err.message || 'Internal Server Error', err.statusCode || 500);
}
}),
);

// --- Storage ---
router.use(
`${prefix}/storage/**`,
defineEventHandler(async (event) => {
try {
const urlPath = (event.path || event.node.req.url || '').split('?')[0];
const subPath = urlPath.substring(`${prefix}/storage`.length);
const method = event.method;
const file = undefined; // File upload requires multipart parsing (e.g., formidable)
const result = await dispatcher.handleStorage(subPath, method, file, { request: event.node.req });
const result = await dispatcher.dispatch(method, subPath, body, query, { request: event.node.req });
return toResponse(event, result);
Comment on lines +169 to +180
const subPath = path || '';

let body: any = undefined;
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
body = await request.json().catch(() => ({}));
}

const queryParams: Record<string, any> = {};
url.searchParams.forEach((val, key) => { queryParams[key] = val; });

const result = await dispatcher.dispatch(method, subPath, body, queryParams, { request });
return toResponse(result);
Comment on lines +124 to +139
// --- 4. Catch-all: delegate to dispatcher.dispatch() ---
// Handles meta, data, packages, analytics, automation, i18n, ui,
// openapi, custom API endpoints, and any future routes.
const path = '/' + segments.join('/');

let body: any = undefined;
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
body = await req.json().catch(() => ({}));
}

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 });
return toResponse(result);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Vercel部署后 /api/v1/meta 和 /api/v1/packages 路由404,导致前端无法加载元数据和包列表

3 participants