Skip to content

Commit 09d50fd

Browse files
committed
feat: implement dispatcher plugin for enhanced route management and deprecate legacy HttpDispatcher
1 parent bd86f71 commit 09d50fd

9 files changed

Lines changed: 337 additions & 94 deletions

File tree

packages/adapters/hono/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,22 @@ export interface ObjectStackHonoOptions {
88
}
99

1010
/**
11-
* Creates a Hono application tailored for ObjectStack
12-
* Fully compliant with @objectstack/spec
11+
* @deprecated Use `HonoServerPlugin` + `createRestApiPlugin()` + `createDispatcherPlugin()` instead.
12+
* This function bundles all routes into a single Hono app using the legacy HttpDispatcher.
13+
* The plugin-based approach provides better modularity and separation of concerns.
14+
*
15+
* Migration:
16+
* ```ts
17+
* // Before:
18+
* const app = createHonoApp({ kernel, prefix: '/api/v1' });
19+
*
20+
* // After:
21+
* import { createRestApiPlugin } from '@objectstack/rest';
22+
* import { createDispatcherPlugin } from '@objectstack/runtime';
23+
* kernel.use(new HonoServerPlugin({ port: 3000 }));
24+
* kernel.use(createRestApiPlugin());
25+
* kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
26+
* ```
1327
*/
1428
export function createHonoApp(options: ObjectStackHonoOptions) {
1529
const app = new Hono();

packages/cli/src/commands/serve.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ export const serveCommand = new Command('serve')
235235
} catch (e: any) {
236236
// @objectstack/rest is optional
237237
}
238+
239+
// Register Dispatcher plugin (auth, graphql, analytics, packages, hub, storage, automation)
240+
try {
241+
const { createDispatcherPlugin } = await import('@objectstack/runtime');
242+
await kernel.use(createDispatcherPlugin());
243+
trackPlugin('Dispatcher');
244+
} catch (e: any) {
245+
// optional
246+
}
238247
}
239248

240249
// ── Studio UI (--ui) ────────────────────────────────────────────

packages/plugins/plugin-hono-server/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212
"dependencies": {
1313
"@hono/node-server": "^1.2.0",
1414
"@objectstack/core": "workspace:*",
15-
"@objectstack/hono": "workspace:*",
16-
"@objectstack/runtime": "workspace:*",
1715
"@objectstack/spec": "workspace:*",
18-
"@objectstack/types": "workspace:*",
1916
"hono": "^4.0.0"
2017
},
2118
"devDependencies": {

packages/plugins/plugin-hono-server/src/hono-plugin.test.ts

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
22
import { HonoServerPlugin } from './hono-plugin';
33
import { PluginContext } from '@objectstack/core';
4-
import { createHonoApp } from '@objectstack/hono';
54
import { HonoHttpServer } from './adapter';
65

76
vi.mock('fs', async (importOriginal) => {
@@ -12,11 +11,6 @@ vi.mock('fs', async (importOriginal) => {
1211
};
1312
});
1413

15-
// Mock dependencies
16-
vi.mock('@objectstack/hono', () => ({
17-
createHonoApp: vi.fn(),
18-
}));
19-
2014
vi.mock('@hono/node-server/serve-static', () => ({
2115
serveStatic: vi.fn(() => (c: any, next: any) => next())
2216
}));
@@ -64,10 +58,6 @@ describe('HonoServerPlugin', () => {
6458
hook: vi.fn(),
6559
getService: vi.fn()
6660
};
67-
68-
(createHonoApp as any).mockReturnValue({
69-
// Mock Hono App structure if needed
70-
});
7161
});
7262

7363
it('should initialize and register server', async () => {
@@ -78,49 +68,28 @@ describe('HonoServerPlugin', () => {
7868
expect(HonoHttpServer).toHaveBeenCalled();
7969
});
8070

81-
it('should create and mount Hono app on start', async () => {
71+
it('should register IHttpServer service on init', async () => {
8272
const plugin = new HonoServerPlugin();
8373
await plugin.init(context as PluginContext);
84-
await plugin.start(context as PluginContext);
85-
86-
expect(createHonoApp).toHaveBeenCalledWith(expect.objectContaining({
87-
kernel: kernel,
88-
prefix: '/api/v1'
89-
}));
9074

91-
// Access the mocked server instance
92-
const serverInstance = (HonoHttpServer as any).mock.instances[0];
93-
expect(serverInstance.mount).toHaveBeenCalledWith('/', expect.anything());
75+
expect(context.registerService).toHaveBeenCalledWith('http.server', expect.any(Object));
76+
expect(context.registerService).toHaveBeenCalledWith('http-server', expect.any(Object));
9477
});
9578

96-
it('should respect REST server configuration for prefix', async () => {
97-
const plugin = new HonoServerPlugin({
98-
restConfig: {
99-
api: {
100-
version: 'v2',
101-
basePath: '/custom'
102-
}
103-
}
104-
});
105-
79+
it('should start without errors', async () => {
80+
const plugin = new HonoServerPlugin();
10681
await plugin.init(context as PluginContext);
10782
await plugin.start(context as PluginContext);
10883

109-
expect(createHonoApp).toHaveBeenCalledWith(expect.objectContaining({
110-
prefix: '/custom/v2'
111-
}));
84+
// Plugin should register kernel:ready hook to start listening
85+
expect(context.hook).toHaveBeenCalledWith('kernel:ready', expect.any(Function));
11286
});
11387

114-
it('should handle errors during app creation', async () => {
115-
(createHonoApp as any).mockImplementation(() => {
116-
throw new Error('Creation failed');
117-
});
118-
88+
it('should handle errors gracefully on start', async () => {
89+
// Simulate a start that doesn't crash even without routes
11990
const plugin = new HonoServerPlugin();
12091
await plugin.init(context as PluginContext);
121-
await plugin.start(context as PluginContext);
122-
123-
expect(logger.error).toHaveBeenCalledWith('Failed to create standard Hono app', expect.any(Error));
92+
await expect(plugin.start(context as PluginContext)).resolves.not.toThrow();
12493
});
12594

12695
it('should configure static files and SPA fallback when enabled', async () => {

packages/plugins/plugin-hono-server/src/hono-plugin.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import { Plugin, PluginContext, IHttpServer, ApiRegistry } from '@objectstack/core';
2-
import { ObjectStackProtocol } from '@objectstack/spec/api';
3-
import {
4-
ApiRegistryEntryInput,
5-
ApiEndpointRegistrationInput,
1+
import { Plugin, PluginContext, IHttpServer } from '@objectstack/core';
2+
import {
63
RestServerConfig,
74
} from '@objectstack/spec/api';
85
import { HonoHttpServer } from './adapter';
9-
import { createHonoApp } from '@objectstack/hono';
10-
import { HttpDispatcher } from '@objectstack/runtime';
116
import { serveStatic } from '@hono/node-server/serve-static';
127
import * as fs from 'fs';
138
import * as path from 'path';
@@ -54,7 +49,11 @@ export interface HonoPluginOptions {
5449
* Hono Server Plugin
5550
*
5651
* Provides HTTP server capabilities using Hono framework.
57-
* Registers routes for ObjectStack Runtime Protocol.
52+
* Registers the IHttpServer service so other plugins can register routes.
53+
*
54+
* Route registration is handled by plugins:
55+
* - `@objectstack/rest` → CRUD, metadata, discovery, UI, batch
56+
* - `createDispatcherPlugin()` → auth, graphql, analytics, packages, etc.
5857
*/
5958
export class HonoServerPlugin implements Plugin {
6059
name = 'com.objectstack.server.hono';
@@ -99,40 +98,10 @@ export class HonoServerPlugin implements Plugin {
9998
}
10099

101100
/**
102-
* Start phase - Bind routes and start listening
101+
* Start phase - Configure static files and start listening
103102
*/
104103
start = async (ctx: PluginContext) => {
105104
ctx.logger.debug('Starting Hono server plugin');
106-
107-
// Use Standard ObjectStack Runtime Hono App
108-
try {
109-
const kernel = ctx.getKernel();
110-
const config = this.options.restConfig || {};
111-
// Calculate prefix similar to before
112-
const apiVersion = config.api?.version || 'v1';
113-
const basePath = config.api?.basePath || '/api';
114-
const apiPath = config.api?.apiPath || `${basePath}/${apiVersion}`;
115-
116-
const app = createHonoApp({
117-
kernel,
118-
prefix: apiPath // Use the calculated path
119-
});
120-
121-
ctx.logger.debug('Mounting ObjectStack Runtime App', { prefix: apiPath });
122-
// Use the mount method we added to HonoHttpServer
123-
this.server.mount('/', app as any);
124-
125-
// Register Standard Discovery Endpoint
126-
const rawApp = this.server.getRawApp();
127-
const dispatcher = new HttpDispatcher(kernel);
128-
rawApp.get('/.well-known/objectstack', (c) => {
129-
return c.json({ data: dispatcher.getDiscoveryInfo(apiPath) });
130-
});
131-
ctx.logger.debug('Registered standard discovery endpoint', { path: '/.well-known/objectstack', target: apiPath });
132-
133-
} catch (e: any) {
134-
ctx.logger.error('Failed to create standard Hono app', e);
135-
}
136105

137106
// Configure Static Files & SPA Fallback
138107
const mounts: StaticMount[] = this.options.staticMounts || [];

0 commit comments

Comments
 (0)