Skip to content

Commit 7c036c2

Browse files
committed
添加 HonoServerPlugin,支持 API 路由和中间件;重构 DataEngine 以支持运行时插件的安装和启动;创建 types.ts 定义 RuntimePlugin 接口
1 parent 54dde17 commit 7c036c2

File tree

6 files changed

+169
-3
lines changed

6 files changed

+169
-3
lines changed

packages/driver-memory/src/memory-driver.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import {
1414
export class InMemoryDriver implements DriverInterface {
1515
name = 'in-memory-driver';
1616
version = '0.0.1';
17+
18+
// Duck-typed RuntimePlugin hook
19+
install(ctx: any) {
20+
if (ctx.engine && ctx.engine.ql && typeof ctx.engine.ql.registerDriver === 'function') {
21+
ctx.engine.ql.registerDriver(this);
22+
}
23+
}
1724

1825
supports = {
1926
transactions: false,

packages/runtime/src/data-engine.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,16 @@ export class DataEngine {
2626
console.log('[DataEngine] Starting...');
2727

2828
// 0. Register Provided Plugins
29-
this.plugins.forEach(p => {
30-
console.log(`[DataEngine] Loading Plugin: ${p.id || p.name}`);
29+
for (const p of this.plugins) {
30+
// Check if it is a Runtime Plugin (System Capability)
31+
if ('onStart' in p || 'install' in p) {
32+
console.log(`[DataEngine] Loading Runtime Plugin: ${p.name}`);
33+
if (p.install) await p.install({ engine: this });
34+
continue;
35+
}
36+
37+
// Otherwise treat as App Manifest
38+
console.log(`[DataEngine] Loading App Manifest: ${p.id || p.name}`);
3139
SchemaRegistry.registerPlugin(p);
3240

3341
// Register Objects from App/Plugin
@@ -37,7 +45,7 @@ export class DataEngine {
3745
console.log(`[DataEngine] Registered Object: ${obj.name}`);
3846
}
3947
}
40-
});
48+
}
4149

4250
// 1. Load Drivers (Default to Memory if none provided in plugins)
4351
// TODO: Detect driver from plugins. For now, we still hard load memory driver if needed?
@@ -60,6 +68,14 @@ export class DataEngine {
6068

6169
// 3. Seed Data
6270
await this.seed();
71+
72+
// 4. Start Runtime Plugins
73+
for (const p of this.plugins) {
74+
if (('onStart' in p) && typeof p.onStart === 'function') {
75+
console.log(`[DataEngine] Starting Plugin: ${p.name}`);
76+
await p.onStart({ engine: this });
77+
}
78+
}
6379
}
6480

6581
async seed() {

packages/runtime/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ export { ObjectStackRuntimeProtocol } from './protocol';
88

99
// Re-export common types from spec for convenience
1010
export type { DriverInterface, DriverOptions, QueryAST } from '@objectstack/spec';
11+
12+
export * from './types';
13+

packages/runtime/src/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { DataEngine } from './data-engine';
2+
3+
export interface RuntimeContext {
4+
engine: DataEngine;
5+
}
6+
7+
export interface RuntimePlugin {
8+
name: string;
9+
install?: (ctx: RuntimeContext) => void | Promise<void>;
10+
onStart?: (ctx: RuntimeContext) => void | Promise<void>;
11+
}

packages/server/src/hono-plugin.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { serve } from '@hono/node-server';
2+
import { serveStatic } from '@hono/node-server/serve-static';
3+
import { Hono } from 'hono';
4+
import { cors } from 'hono/cors';
5+
import { logger } from 'hono/logger';
6+
import { RuntimePlugin, RuntimeContext, ObjectStackRuntimeProtocol } from '@objectstack/runtime';
7+
8+
export interface HonoPluginOptions {
9+
port?: number;
10+
staticRoot?: string;
11+
}
12+
13+
export class HonoServerPlugin implements RuntimePlugin {
14+
name = 'hono-server';
15+
private options: HonoPluginOptions;
16+
private app: Hono;
17+
18+
constructor(options: HonoPluginOptions = {}) {
19+
this.options = {
20+
port: 3000,
21+
...options
22+
};
23+
this.app = new Hono();
24+
}
25+
26+
install(ctx: RuntimeContext) {
27+
const { engine } = ctx;
28+
const protocol = new ObjectStackRuntimeProtocol(engine);
29+
30+
// Middleware
31+
this.app.use('*', logger());
32+
this.app.use('*', cors());
33+
34+
// --- Bind Protocol to Hono ---
35+
36+
// 1. Discovery
37+
this.app.get('/api/v1', (c) => c.json(protocol.getDiscovery()));
38+
39+
// 2. Meta
40+
this.app.get('/api/v1/meta', (c) => c.json(protocol.getMetaTypes()));
41+
this.app.get('/api/v1/meta/:type', (c) => c.json(protocol.getMetaItems(c.req.param('type'))));
42+
this.app.get('/api/v1/meta/:type/:name', (c) => {
43+
try {
44+
return c.json(protocol.getMetaItem(c.req.param('type'), c.req.param('name')));
45+
} catch (e: any) {
46+
return c.json({ error: e.message }, 404);
47+
}
48+
});
49+
50+
// 3. Data
51+
this.app.get('/api/v1/data/:object', async (c) => {
52+
try {
53+
const result = await protocol.findData(c.req.param('object'), c.req.query());
54+
return c.json(result);
55+
} catch (e: any) {
56+
return c.json({ error: e.message }, 404);
57+
}
58+
});
59+
60+
this.app.get('/api/v1/data/:object/:id', async (c) => {
61+
try {
62+
const result = await protocol.getData(c.req.param('object'), c.req.param('id'));
63+
return c.json(result);
64+
} catch (e: any) {
65+
return c.json({ error: e.message }, 404);
66+
}
67+
});
68+
69+
this.app.post('/api/v1/data/:object', async (c) => {
70+
try {
71+
const body = await c.req.json();
72+
const result = await protocol.createData(c.req.param('object'), body);
73+
return c.json(result, 201);
74+
} catch (e: any) {
75+
return c.json({ error: e.message }, 400);
76+
}
77+
});
78+
79+
this.app.patch('/api/v1/data/:object/:id', async (c) => {
80+
try {
81+
const body = await c.req.json();
82+
const result = await protocol.updateData(c.req.param('object'), c.req.param('id'), body);
83+
return c.json(result);
84+
} catch (e: any) {
85+
return c.json({ error: e.message }, 400);
86+
}
87+
});
88+
89+
this.app.delete('/api/v1/data/:object/:id', async (c) => {
90+
try {
91+
const result = await protocol.deleteData(c.req.param('object'), c.req.param('id'));
92+
return c.json(result);
93+
} catch (e: any) {
94+
return c.json({ error: e.message }, 400);
95+
}
96+
});
97+
98+
// 4. UI Protocol
99+
this.app.get('/api/v1/ui/view/:object', (c) => {
100+
try {
101+
// @ts-ignore
102+
const view = protocol.getUiView(c.req.param('object'), c.req.query('type') || 'list');
103+
return c.json(view);
104+
} catch (e: any) {
105+
return c.json({ error: e.message }, 404);
106+
}
107+
});
108+
109+
// Static Files
110+
if (this.options.staticRoot) {
111+
this.app.get('/', serveStatic({ root: this.options.staticRoot, path: 'index.html' }));
112+
this.app.get('/*', serveStatic({ root: this.options.staticRoot }));
113+
}
114+
115+
console.log(`[HonoPlugin] Installed routes and middleware.`);
116+
}
117+
118+
async onStart(ctx: RuntimeContext) {
119+
const port = this.options.port;
120+
console.log(`[HonoPlugin] Starting server...`);
121+
console.log(`✅ Server is running on http://localhost:${port}`);
122+
123+
serve({
124+
fetch: this.app.fetch,
125+
port
126+
});
127+
}
128+
}

packages/server/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CoreRestApiPlugin } from './plugins/rest-api';
99

1010
export * from './plugin';
1111
export { CoreRestApiPlugin };
12+
export { HonoServerPlugin } from './hono-plugin';
1213

1314
export interface ServerConfig {
1415
port?: number;

0 commit comments

Comments
 (0)