Skip to content

Commit 93f45a2

Browse files
committed
重构 ObjectQL 引擎以优化插件注册逻辑,添加 UI 协议 API,更新 DataEngine 启动流程,新增 ObjectUI 渲染器示例
1 parent 6647592 commit 93f45a2

File tree

5 files changed

+291
-10
lines changed

5 files changed

+291
-10
lines changed

examples/client-lite/index.html

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>ObjectUI Generic Renderer</title>
6+
<style>
7+
body { font-family: -apple-system, sans-serif; padding: 20px; background: #f4f6f8; }
8+
.ui-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
9+
h2 { margin-top: 0; }
10+
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
11+
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
12+
th { background: #fafafa; color: #666; font-weight: 600; }
13+
.badge { padding: 4px 8px; border-radius: 12px; font-size: 12px; }
14+
.badge-done { background: #e3fcef; color: #0d7f56; }
15+
.badge-todo { background: #fff8c5; color: #9a6700; }
16+
</style>
17+
</head>
18+
<body>
19+
20+
<div id="app" class="ui-card">
21+
<div>Loading ObjectUI...</div>
22+
</div>
23+
24+
<!-- 模拟 ObjectUI SDK -->
25+
<script>
26+
// 模拟 API Client (在真实场景中调用实际 Server 端口)
27+
const mockApi = {
28+
// 1. 获取对象的定义 (Schema)
29+
getMeta: async (object) => {
30+
// 模拟 Server 返回 (对应 DataEngine.getMetadata)
31+
return {
32+
name: 'todo',
33+
label: 'Task Manager',
34+
fields: {
35+
title: { type: 'text', label: 'Task Name' },
36+
status: { type: 'select', label: 'Current Status', options: ['Todo', 'Done'] },
37+
priority: { type: 'number', label: 'Priority Level' }
38+
}
39+
};
40+
},
41+
// 2. 获取视图配置 (View Protocol)
42+
getView: async (object) => {
43+
// 模拟 Server 返回 (对应 DataEngine.getView)
44+
return {
45+
type: 'datagrid',
46+
columns: [
47+
{ field: 'title', width: 200 },
48+
{ field: 'status', widget: 'badge' },
49+
{ field: 'priority' }
50+
]
51+
};
52+
},
53+
// 3. 获取数据
54+
getData: async (object) => {
55+
// 模拟 ObjectQL 查询结果
56+
return [
57+
{ id: 1, title: 'Refactor DataEngine', status: 'Done', priority: 1 },
58+
{ id: 2, title: 'Implement ObjectUI', status: 'Todo', priority: 2 },
59+
{ id: 3, title: 'Write Documentation', status: 'Todo', priority: 3 }
60+
];
61+
}
62+
};
63+
64+
// ObjectUI 核心渲染器
65+
class ObjectUIRenderer {
66+
constructor(containerId) {
67+
this.container = document.getElementById(containerId);
68+
}
69+
70+
async renderList(objectName) {
71+
this.container.innerHTML = 'Fetching Protocol & Data...';
72+
73+
// 并行加载:元数据 + 视图定义 + 实际数据
74+
const [meta, view, data] = await Promise.all([
75+
mockApi.getMeta(objectName),
76+
mockApi.getView(objectName),
77+
mockApi.getData(objectName)
78+
]);
79+
80+
// 渲染逻辑
81+
let html = `<h2>${meta.label || objectName}</h2>`;
82+
html += `<table><thead><tr>`;
83+
84+
// 1. 动态生成表头 (根据 View Protocol)
85+
view.columns.forEach(col => {
86+
// 如果 View 没通过 label,就去 Schema 里找
87+
const fieldConfig = meta.fields[col.field] || {};
88+
const label = col.label || fieldConfig.label || col.field;
89+
html += `<th>${label}</th>`;
90+
});
91+
html += `</tr></thead><tbody>`;
92+
93+
// 2. 动态生成数据行
94+
data.forEach(row => {
95+
html += `<tr>`;
96+
view.columns.forEach(col => {
97+
const val = row[col.field];
98+
const fieldConfig = meta.fields[col.field] || {};
99+
100+
// 3. 智能组件渲染 (Widget System)
101+
let cellContent = val;
102+
103+
// 根据 Schema 类型或 View 组件进行特殊渲染
104+
if (col.widget === 'badge' || fieldConfig.type === 'select') {
105+
const badgeClass = `badge-${String(val).toLowerCase()}`;
106+
cellContent = `<span class="badge ${badgeClass}">${val}</span>`;
107+
}
108+
109+
html += `<td>${cellContent}</td>`;
110+
});
111+
html += `</tr>`;
112+
});
113+
114+
html += `</tbody></table>`;
115+
this.container.innerHTML = html;
116+
}
117+
}
118+
119+
// 启动应用
120+
const ui = new ObjectUIRenderer('app');
121+
ui.renderList('todo'); // 只需要名字,剩下的全靠元数据驱动
122+
123+
</script>
124+
</body>
125+
</html>

examples/objectql/src/engine.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ export class ObjectQL {
6060
ql: this,
6161
logger: console,
6262
// Expose the driver registry helper explicitly if needed
63-
drivers: this, // Since `registerDriver` is on `this`, we can alias it or expose `this`
63+
drivers: {
64+
register: (driver: DriverInterface) => this.registerDriver(driver)
65+
},
6466
...this.hostContext
6567
};
6668

examples/server/public/index.html

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>ObjectUI Generic Renderer</title>
6+
<style>
7+
body { font-family: -apple-system, sans-serif; padding: 20px; background: #f4f6f8; }
8+
.ui-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
9+
h2 { margin-top: 0; }
10+
table { width: 100%; border-collapse: collapse; margin-top: 15px; }
11+
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
12+
th { background: #fafafa; color: #666; font-weight: 600; }
13+
.badge { padding: 4px 8px; border-radius: 12px; font-size: 12px; }
14+
.badge-done { background: #e3fcef; color: #0d7f56; }
15+
.badge-todo { background: #fff8c5; color: #9a6700; }
16+
</style>
17+
</head>
18+
<body>
19+
20+
<div id="app" class="ui-card">
21+
<div>Loading ObjectUI...</div>
22+
</div>
23+
24+
<!-- 模拟 ObjectUI SDK -->
25+
<script>
26+
// 真实 API Client
27+
const mockApi = {
28+
// 1. 获取对象的定义 (Schema)
29+
getMeta: async (object) => {
30+
const res = await fetch(`/api/v1/meta/object/${object}`);
31+
return res.json();
32+
},
33+
// 2. 获取视图配置 (View Protocol)
34+
getView: async (object) => {
35+
const res = await fetch(`/api/v1/ui/view/${object}`);
36+
return res.json();
37+
},
38+
// 3. 获取数据
39+
getData: async (object) => {
40+
const res = await fetch(`/api/v1/data/${object}`);
41+
const json = await res.json();
42+
return json.value || [];
43+
}
44+
};
45+
46+
// ObjectUI 核心渲染器
47+
class ObjectUIRenderer {
48+
constructor(containerId) {
49+
this.container = document.getElementById(containerId);
50+
}
51+
52+
async renderList(objectName) {
53+
this.container.innerHTML = 'Fetching Protocol & Data...';
54+
55+
// 并行加载:元数据 + 视图定义 + 实际数据
56+
const [meta, view, data] = await Promise.all([
57+
mockApi.getMeta(objectName),
58+
mockApi.getView(objectName),
59+
mockApi.getData(objectName)
60+
]);
61+
62+
// 渲染逻辑
63+
let html = `<h2>${meta.label || objectName}</h2>`;
64+
html += `<table><thead><tr>`;
65+
66+
// 1. 动态生成表头 (根据 View Protocol)
67+
view.columns.forEach(col => {
68+
// 如果 View 没通过 label,就去 Schema 里找
69+
const fieldConfig = meta.fields[col.field] || {};
70+
const label = col.label || fieldConfig.label || col.field;
71+
html += `<th>${label}</th>`;
72+
});
73+
html += `</tr></thead><tbody>`;
74+
75+
// 2. 动态生成数据行
76+
data.forEach(row => {
77+
html += `<tr>`;
78+
view.columns.forEach(col => {
79+
const val = row[col.field];
80+
const fieldConfig = meta.fields[col.field] || {};
81+
82+
// 3. 智能组件渲染 (Widget System)
83+
let cellContent = val;
84+
85+
// 根据 Schema 类型或 View 组件进行特殊渲染
86+
if ((col.widget === 'badge' || fieldConfig.type === 'select') && val) {
87+
const badgeClass = `badge-${String(val).toLowerCase()}`;
88+
cellContent = `<span class="badge ${badgeClass}">${val}</span>`;
89+
}
90+
91+
html += `<td>${cellContent}</td>`;
92+
});
93+
html += `</tr>`;
94+
});
95+
96+
html += `</tbody></table>`;
97+
this.container.innerHTML = html;
98+
}
99+
}
100+
101+
// 启动应用
102+
const ui = new ObjectUIRenderer('app');
103+
// 'Todo' 是 TodoApp 里的对象名 (注意大小写,SchemaRegistry 也许是敏感的,取决于 TodoApp 定义)
104+
// 假设 TodoApp 定义的是 'Todo'
105+
ui.renderList('Todo');
106+
107+
</script>
108+
</body>
109+
</html>

examples/server/src/index.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { serve } from '@hono/node-server';
2+
import { serveStatic } from '@hono/node-server/serve-static';
23
import { Hono } from 'hono';
34
import { logger } from 'hono/logger';
45
import { cors } from 'hono/cors';
@@ -9,9 +10,6 @@ import { DataEngine } from './kernel/engine';
910
const app = new Hono();
1011
const dataEngine = new DataEngine(); // Engine loads plugins internally now
1112

12-
app.use('*', logger());
13-
app.use('*', cors());
14-
1513
// 3. Define Unified Routes
1614

1715
/**
@@ -104,6 +102,22 @@ app.get('/api/v1/meta/:type/:name', (c) => {
104102
return c.json(item);
105103
});
106104

105+
/**
106+
* UI Protocol API: Get View Definition
107+
* GET /api/v1/ui/view/:object
108+
*/
109+
app.get('/api/v1/ui/view/:object', (c) => {
110+
const objectName = c.req.param('object');
111+
const type = (c.req.query('type') as 'list' | 'form') || 'list';
112+
try {
113+
const view = dataEngine.getView(objectName, type);
114+
if (!view) return c.json({ error: 'View not generated' }, 404);
115+
return c.json(view);
116+
} catch (e: any) {
117+
return c.json({ error: e.message }, 400);
118+
}
119+
});
120+
107121
/**
108122
* Data API: Find
109123
*/
@@ -185,7 +199,8 @@ const port = 3004;
185199

186200
(async () => {
187201
console.log('--- Starting ObjectStack Server ---');
188-
// Plugin loading is now handled by DataEngine constructor/init
202+
// Start Data Engine (Load Plugins -> Init Drivers -> Seed)
203+
await dataEngine.start();
189204

190205
console.log(`Server is running on http://localhost:${port}`);
191206

examples/server/src/kernel/engine.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@ export class DataEngine {
2828
constructor() {
2929
// 1. Initialize Engine with Host Context (Simulated OS services)
3030
this.ql = new ObjectQL({
31-
os: { registerService: () => console.log('OS Service Registered') },
31+
os: {
32+
registerService: () => console.log('OS Service Registered'),
33+
getConfig: async (key: string) => ({}) // Mock Config
34+
},
3235
app: { router: { get: () => {} } },
3336
storage: { set: () => {} },
3437
services: { register: () => {} },
3538
i18n: {}
3639
});
40+
}
3741

42+
async start() {
3843
// 2. Load Plugins (Declarative)
39-
this.loadPlugins();
44+
await this.loadPlugins();
4045

4146
// 3. Start Engine
42-
this.ql.init().then(() => {
43-
this.seed();
44-
}).catch(console.error);
47+
await this.ql.init();
48+
49+
// 4. Seed Data
50+
await this.seed();
4551
}
4652

4753
async loadPlugins() {
@@ -95,6 +101,30 @@ export class DataEngine {
95101
return this.ql.delete(objectName, id);
96102
}
97103

104+
// [New Methods for ObjectUI]
105+
getMetadata(objectName: string) {
106+
return this.ensureSchema(objectName);
107+
}
108+
109+
getView(objectName: string, viewType: 'list' | 'form' = 'list') {
110+
const schema = this.ensureSchema(objectName);
111+
112+
// Auto-Scaffold Default View
113+
if (viewType === 'list') {
114+
return {
115+
type: 'datagrid',
116+
title: `${schema.label || objectName} List`,
117+
columns: Object.keys(schema.fields || {}).map(key => ({
118+
field: key,
119+
label: schema.fields?.[key]?.label || key,
120+
width: 150
121+
})),
122+
actions: ['create', 'edit', 'delete']
123+
};
124+
}
125+
return null;
126+
}
127+
98128
private ensureSchema(name: string): ServiceObject {
99129
const schema = SchemaRegistry.getObject(name);
100130
if (!schema) throw new Error(`Unknown object: ${name}`);

0 commit comments

Comments
 (0)