Skip to content

Commit 29a03e1

Browse files
committed
feat: enhance HTTP server integration and route registration in ensureApp
1 parent d44069b commit 29a03e1

1 file changed

Lines changed: 32 additions & 44 deletions

File tree

apps/server/server/index.ts

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
*/
1212

1313
import { createHonoApp } from '@objectstack/hono';
14-
import { createOriginMatcher, hasWildcardPattern } from '@objectstack/plugin-hono-server';
14+
import { createOriginMatcher, hasWildcardPattern, HonoHttpServer } from '@objectstack/plugin-hono-server';
1515
import { getRequestListener } from '@hono/node-server';
1616
import { ObjectKernel, createRestApiPlugin, createDispatcherPlugin, KernelManager } from '@objectstack/runtime';
1717
import type { EnvironmentDriverRegistry } from '@objectstack/runtime';
18-
import { Hono } from 'hono';
18+
import type { Hono } from 'hono';
1919
import stackConfig from '../objectstack.config.js';
20-
import { listTemplates } from './templates/registry.js';
2120

2221
// ---------------------------------------------------------------------------
2322
// Runtime shape returned by ensureBoot()
@@ -42,6 +41,20 @@ let _bootPromise: Promise<BootResult> | null = null;
4241
async function bootKernel(): Promise<BootResult> {
4342
const kernel = new ObjectKernel();
4443

44+
// 0. Register an `http.server` (IHttpServer) adapter BEFORE plugins so
45+
// that any plugin's start() hook can resolve `ctx.getService('http.server')`
46+
// and register routes on it. This is the official ObjectStack
47+
// protocol for plugin-supplied HTTP routes (see IHttpServer in
48+
// @objectstack/spec/contracts and HonoServerPlugin's reference
49+
// implementation). The Vercel entrypoint cannot use HonoServerPlugin
50+
// itself because we don't want plugin-hono-server to call listen() —
51+
// Vercel hands us a request directly. Reusing the same adapter class
52+
// keeps route-registration semantics identical between local
53+
// (`objectstack dev`) and serverless deployments.
54+
const httpServer = new HonoHttpServer();
55+
kernel.registerService('http.server', httpServer);
56+
kernel.registerService('http-server', httpServer); // alias for backward compatibility
57+
4558
// 1. Config plugins (control-plane preset + MultiProjectPlugin)
4659
for (const plugin of stackConfig.plugins ?? []) {
4760
await kernel.use(plugin as any);
@@ -107,51 +120,24 @@ async function ensureBoot(): Promise<BootResult> {
107120
// Hono app factory
108121
// ---------------------------------------------------------------------------
109122

110-
function envFlag(name: string): boolean {
111-
return ['1', 'true', 'yes', 'on'].includes((process.env[name] ?? '').trim().toLowerCase());
112-
}
113-
114123
async function ensureApp(): Promise<Hono> {
115124
if (_app) return _app;
116125

117126
const { kernel } = await ensureBoot();
118-
// envRegistry / kernelManager are resolved by HttpDispatcher from the
119-
// kernel's service registry (MultiProjectPlugin registered them during
120-
// bootKernel), so they do NOT need to be passed explicitly here.
121-
const inner = createHonoApp({ kernel, prefix: '/api/v1' });
122127

123-
// Vercel entrypoint does NOT load plugin-hono-server, so the
124-
// `http.server` service is never registered. The route plugins in
125-
// `multi-project-plugins.ts` early-return when that service is
126-
// missing, leaving `/studio/runtime-config` and `/cloud/templates`
127-
// unmounted (404 / empty list).
128-
//
129-
// We can't simply call `inner.get(...)` after createHonoApp() because
130-
// it has already registered an `app.all('${prefix}/*')` dispatcher
131-
// catch-all that wins on registration order. Instead wrap `inner` in
132-
// an outer Hono whose own routes are matched first, then fall
133-
// through to `inner.fetch()` for everything else.
134-
if (envFlag('OBJECTSTACK_MULTI_PROJECT')) {
135-
const templatesPayload = listTemplates().map(({ id, label, description, category }) => ({
136-
id,
137-
label,
138-
description,
139-
category,
140-
}));
141-
const outer = new Hono();
142-
outer.get('/api/v1/studio/runtime-config', (c) =>
143-
c.json({ singleProject: false }));
144-
outer.get('/api/v1/cloud/templates', (c) =>
145-
c.json({
146-
success: true,
147-
data: { templates: templatesPayload, total: templatesPayload.length },
148-
}));
149-
outer.all('*', (c) => inner.fetch(c.req.raw));
150-
_app = outer;
151-
} else {
152-
_app = inner;
153-
}
128+
// Plugins have already registered their routes onto the IHttpServer
129+
// (HonoHttpServer) we created in bootKernel(). Pull out its underlying
130+
// Hono so those plugin routes are matched FIRST, then mount the
131+
// dispatcher app underneath via `outer.route('/', inner)` — Hono uses
132+
// registration-order priority, so the plugin routes win the match
133+
// against the dispatcher's catch-all `/api/v1/*` handler.
134+
const httpServer = kernel.getService<HonoHttpServer>('http.server');
135+
const outer = httpServer.getRawApp();
136+
137+
const inner = createHonoApp({ kernel, prefix: '/api/v1' });
138+
outer.route('/', inner);
154139

140+
_app = outer;
155141
return _app;
156142
}
157143

@@ -353,15 +339,17 @@ export default getRequestListener(async (request, env) => {
353339
const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType;
354340
const body = extractBody(incoming, method, contentTypeStr);
355341
if (body != null) {
356-
return await app.fetch(
342+
const response = await app.fetch(
357343
new Request(url, { method, headers: request.headers, body }),
358344
);
345+
return withCorsHeaders(response, request);
359346
}
360347
}
361348

362-
return await app.fetch(
349+
const response = await app.fetch(
363350
new Request(url, { method, headers: request.headers }),
364351
);
352+
return withCorsHeaders(response, request);
365353
});
366354

367355
export const config = {

0 commit comments

Comments
 (0)