Skip to content

Commit 54d7fc1

Browse files
committed
添加插件系统,定义 ServerPlugin 接口并实现 CoreRestApiPlugin;重构 ObjectStackServer 以支持插件加载和初始化
1 parent cc9738d commit 54d7fc1

File tree

4 files changed

+249
-195
lines changed

4 files changed

+249
-195
lines changed

examples/objectql/src/data-engine.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,16 @@ export class DataEngine {
2727

2828
// 0. Register Provided Plugins
2929
this.plugins.forEach(p => {
30-
console.log(`[DataEngine] Loading Plugin: ${p.name}`);
30+
console.log(`[DataEngine] Loading Plugin: ${p.id || p.name}`);
3131
SchemaRegistry.registerPlugin(p);
32+
33+
// Register Objects from App/Plugin
34+
if (p.objects) {
35+
for (const obj of p.objects) {
36+
SchemaRegistry.registerObject(obj);
37+
console.log(`[DataEngine] Registered Object: ${obj.name}`);
38+
}
39+
}
3240
});
3341

3442
// 1. Load Drivers (Default to Memory if none provided in plugins)

packages/server/src/index.ts

Lines changed: 43 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { Hono } from 'hono';
44
import { logger } from 'hono/logger';
55
import { cors } from 'hono/cors';
66
import { SchemaRegistry, DataEngine } from '@objectstack/objectql';
7+
import { ServerPlugin } from './plugin';
8+
import { CoreRestApiPlugin } from './plugins/rest-api';
9+
10+
export * from './plugin';
11+
export { CoreRestApiPlugin };
712

813
export interface ServerConfig {
914
port?: number;
@@ -12,13 +17,18 @@ export interface ServerConfig {
1217
path?: string;
1318
};
1419
logger?: boolean;
20+
/**
21+
* List of plugins to load.
22+
* Can include ObjectStack Manifests (Apps) or ServerPlugins (Runtime Logic).
23+
*/
1524
plugins?: any[];
1625
}
1726

1827
export class ObjectStackServer {
1928
public app: Hono;
2029
public engine: DataEngine;
2130
private config: ServerConfig;
31+
private runtimePlugins: ServerPlugin[] = [];
2232

2333
constructor(config: ServerConfig = {}) {
2434
this.config = {
@@ -29,20 +39,50 @@ export class ObjectStackServer {
2939
};
3040

3141
this.app = new Hono();
32-
this.engine = new DataEngine(this.config.plugins);
42+
43+
// Separate Manifests (DataEngine) from Runtime Plugins (Server)
44+
const manifests: any[] = [];
45+
46+
// Always load Core REST API first (it can be overridden if needed, but it's core)
47+
this.runtimePlugins.push(CoreRestApiPlugin);
48+
49+
if (this.config.plugins) {
50+
this.config.plugins.forEach(p => {
51+
if (this.isServerPlugin(p)) {
52+
this.runtimePlugins.push(p);
53+
} else {
54+
manifests.push(p);
55+
}
56+
});
57+
}
58+
59+
// Initialize Engine with Manifests
60+
this.engine = new DataEngine(manifests);
3361

3462
this.initializeMiddleware();
35-
this.initializeRoutes();
63+
this.initializePlugins();
3664
this.initializeStatic();
3765
}
3866

67+
private isServerPlugin(p: any): p is ServerPlugin {
68+
return p && typeof p.install === 'function';
69+
}
70+
3971
private initializeMiddleware() {
4072
if (this.config.logger) {
4173
this.app.use('*', logger());
4274
}
4375
this.app.use('*', cors());
4476
}
4577

78+
private initializePlugins() {
79+
console.log(`[Server] Loading ${this.runtimePlugins.length} runtime plugins...`);
80+
for (const plugin of this.runtimePlugins) {
81+
console.log(`[Server] Installing plugin: ${plugin.name}`);
82+
plugin.install(this);
83+
}
84+
}
85+
4686
private initializeStatic() {
4787
if (this.config.static) {
4888
const root = this.config.static.root;
@@ -51,198 +91,6 @@ export class ObjectStackServer {
5191
}
5292
}
5393

54-
private initializeRoutes() {
55-
// 1. Discovery
56-
this.app.get('/api/v1', (c) => {
57-
return c.json({
58-
name: 'ObjectOS Server',
59-
version: '1.0.0',
60-
environment: process.env.NODE_ENV || 'development',
61-
routes: {
62-
discovery: '/api/v1',
63-
metadata: '/api/v1/meta',
64-
data: '/api/v1/data',
65-
auth: '/api/v1/auth',
66-
ui: '/api/v1/ui'
67-
},
68-
capabilities: {
69-
search: true,
70-
files: true
71-
}
72-
});
73-
});
74-
75-
// 2. Metadata: List Types
76-
this.app.get('/api/v1/meta', (c) => {
77-
const types = SchemaRegistry.getRegisteredTypes();
78-
return c.json({
79-
data: types.map(type => ({
80-
type,
81-
href: `/api/v1/meta/${type}s`, // Convention: pluralize
82-
count: SchemaRegistry.listItems(type).length
83-
}))
84-
});
85-
});
86-
87-
// 3. Metadata: List Items by Type
88-
this.app.get('/api/v1/meta/:type', (c) => {
89-
const typePlural = c.req.param('type');
90-
91-
// Simple Singularization Mapping
92-
const typeMap: Record<string, string> = {
93-
'objects': 'object',
94-
'apps': 'app',
95-
'flows': 'flow',
96-
'reports': 'report',
97-
'plugins': 'plugin',
98-
'kinds': 'kind'
99-
};
100-
const type = typeMap[typePlural] || typePlural; // Fallback to direct pass
101-
102-
const items = SchemaRegistry.listItems(type);
103-
104-
const summaries = items.map((item: any) => ({
105-
id: item.id,
106-
name: item.name,
107-
label: item.label,
108-
type: item.type,
109-
icon: item.icon,
110-
description: item.description,
111-
...(type === 'object' ? { path: `/api/v1/data/${item.name}` } : {}),
112-
self: `/api/v1/meta/${typePlural}/${item.name || item.id}`
113-
}));
114-
115-
return c.json({ data: summaries });
116-
});
117-
118-
// 4. Metadata: Get Single Item
119-
this.app.get('/api/v1/meta/:type/:name', (c) => {
120-
const typePlural = c.req.param('type');
121-
const name = c.req.param('name');
122-
123-
const typeMap: Record<string, string> = {
124-
'objects': 'object',
125-
'apps': 'app',
126-
'flows': 'flow',
127-
'reports': 'report',
128-
'plugins': 'plugin',
129-
'kinds': 'kind'
130-
};
131-
const type = typeMap[typePlural] || typePlural;
132-
133-
const item = SchemaRegistry.getItem(type, name);
134-
if (!item) return c.json({ error: `Metadata not found: ${type}/${name}` }, 404);
135-
136-
return c.json(item);
137-
});
138-
139-
// 5. UI: View Definition
140-
this.app.get('/api/v1/ui/view/:object', (c) => {
141-
const objectName = c.req.param('object');
142-
const type = (c.req.query('type') as 'list' | 'form') || 'list';
143-
try {
144-
const view = this.engine.getView(objectName, type);
145-
if (!view) return c.json({ error: 'View not generated' }, 404);
146-
return c.json(view);
147-
} catch (e: any) {
148-
return c.json({ error: e.message }, 400);
149-
}
150-
});
151-
152-
// 6. Data: Find
153-
this.app.get('/api/v1/data/:object', async (c) => {
154-
const objectName = c.req.param('object');
155-
const query = c.req.query();
156-
157-
try {
158-
// TODO: Map query params to cleaner AST if needed, or Engine does simple mapping
159-
// e.g. ?sort=-name -> { sort: ['-name'] }
160-
const result = await this.engine.find(objectName, query);
161-
return c.json(result);
162-
} catch (e: any) {
163-
return c.json({ error: e.message }, 400);
164-
}
165-
});
166-
167-
// 7. Data: Query (Advanced AST)
168-
this.app.post('/api/v1/data/:object/query', async (c) => {
169-
const objectName = c.req.param('object');
170-
const body = await c.req.json();
171-
172-
try {
173-
// Body is Partial<QueryAST>
174-
// Engine find expects (object, filters/options)
175-
// If engine.find signature supports AST passing, we pass it.
176-
// Currently engine.find(object, filters: any).
177-
// Let's assume engine handles it or we adapt it.
178-
// For now, pass body as the filter/options object.
179-
const result = await this.engine.find(objectName, body);
180-
return c.json(result);
181-
} catch (e: any) {
182-
return c.json({ error: e.message }, 400);
183-
}
184-
});
185-
186-
// 8. Data: Get
187-
this.app.get('/api/v1/data/:object/:id', async (c) => {
188-
const objectName = c.req.param('object');
189-
const id = c.req.param('id');
190-
try {
191-
const result = await this.engine.get(objectName, id);
192-
return c.json(result);
193-
} catch (e: any) {
194-
return c.json({ error: e.message }, 404);
195-
}
196-
});
197-
198-
// 9. Data: Create
199-
this.app.post('/api/v1/data/:object', async (c) => {
200-
const objectName = c.req.param('object');
201-
const body = await c.req.json();
202-
try {
203-
const result = await this.engine.create(objectName, body);
204-
return c.json(result, 201);
205-
} catch (e: any) {
206-
return c.json({ error: e.message }, 400);
207-
}
208-
});
209-
210-
// 10. Data: Update
211-
this.app.patch('/api/v1/data/:object/:id', async (c) => {
212-
const objectName = c.req.param('object');
213-
const id = c.req.param('id');
214-
const body = await c.req.json();
215-
try {
216-
const result = await this.engine.update(objectName, id, body);
217-
return c.json(result);
218-
} catch (e: any) {
219-
return c.json({ error: e.message }, 400);
220-
}
221-
});
222-
223-
// 11. Data: Delete
224-
this.app.delete('/api/v1/data/:object/:id', async (c) => {
225-
const objectName = c.req.param('object');
226-
const id = c.req.param('id');
227-
try {
228-
const result = await this.engine.delete(objectName, id);
229-
return c.json(result);
230-
} catch (e: any) {
231-
return c.json({ error: e.message }, 400);
232-
}
233-
});
234-
235-
// 12. Data: Batch Operations
236-
this.app.post('/api/v1/data/:object/batch', async (c) => {
237-
// TODO: Implement batch in Engine
238-
return c.json({ error: 'Not implemented' }, 501);
239-
});
240-
this.app.delete('/api/v1/data/:object/batch', async (c) => {
241-
// TODO: Implement batch in Engine
242-
return c.json({ error: 'Not implemented' }, 501);
243-
});
244-
}
245-
24694
public async start() {
24795
console.log('--- Starting ObjectStack Server ---');
24896
await this.engine.start();
@@ -255,3 +103,4 @@ export class ObjectStackServer {
255103
});
256104
}
257105
}
106+

packages/server/src/plugin.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { ObjectStackServer } from './index';
2+
3+
export interface ServerPlugin {
4+
name: string;
5+
version?: string;
6+
install: (server: ObjectStackServer) => void | Promise<void>;
7+
}

0 commit comments

Comments
 (0)