Skip to content

Commit 09fa25b

Browse files
committed
feat: 添加获取对象定义的辅助方法,优化数据源驱动注册逻辑,更新查询方法以支持更灵活的查询结构
1 parent 50969ee commit 09fa25b

File tree

2 files changed

+177
-25
lines changed

2 files changed

+177
-25
lines changed

packages/objectql/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ObjectQL Engine
2+
3+
**ObjectQL** is a schema-driven, cross-datasource query engine for the [ObjectStack](https://github.com/steedos/objectstack) ecosystem. It acts as a virtual "Meta-Database" that unifies access to SQL, NoSQL, and API data sources under a single semantic layer.
4+
5+
## Features
6+
7+
- **Protocol Agnostic**: Uses standard `ObjectSchema` and `QueryAST` from `@objectstack/spec`.
8+
- **Cross-Datasource**: Routes queries to the correct driver (Postgres, MongoDB, Redis, etc.) based on Object definition.
9+
- **Unified API**: Single `find`, `insert`, `update`, `delete` API regardless of the underlying storage.
10+
- **Plugin System**: Load objects and logic via standard Manifests.
11+
- **Middleware**: (Planned) Support for Triggers, Hooks, and Validators.
12+
13+
## Usage
14+
15+
```typescript
16+
import { ObjectQL } from '@objectstack/objectql';
17+
import { MemoryDriver } from '@objectstack/driver-memory'; // Example driver
18+
19+
async function main() {
20+
// 1. Initialize Engine
21+
const ql = new ObjectQL();
22+
23+
// 2. Register Drivers
24+
const memDriver = new MemoryDriver({ name: 'default' });
25+
ql.registerDriver(memDriver, true);
26+
27+
// 3. Load Schema (via Plugin/Manifest)
28+
await ql.use({
29+
name: 'my-app',
30+
objects: [
31+
{
32+
name: 'todo',
33+
fields: {
34+
title: { type: 'text' },
35+
completed: { type: 'boolean' }
36+
},
37+
datasource: 'default'
38+
}
39+
]
40+
});
41+
42+
await ql.init();
43+
44+
// 4. Execute Queries
45+
// Insert
46+
await ql.insert('todo', { title: 'Buy Milk', completed: false });
47+
48+
// Find (Simple)
49+
const todos = await ql.find('todo', { completed: false });
50+
51+
// Find (Advanced AST)
52+
const results = await ql.find('todo', {
53+
where: {
54+
title: { $contains: 'Milk' }
55+
},
56+
limit: 10,
57+
orderBy: [{ field: 'title', order: 'desc' }]
58+
});
59+
60+
console.log(results);
61+
}
62+
```
63+
64+
## Architecture
65+
66+
- **SchemaRegistry**: Central store for all metadata (Objects, Apps, Config).
67+
- **DriverRegistry**: Manages connections to physical data sources.
68+
- **QueryPlanner**: (Internal) Normalizes simplified queries into `QueryAST`.
69+
- **Executor**: Routes AST to the correct driver.
70+
71+
## Roadmap
72+
73+
- [x] Basic CRUD
74+
- [x] Driver Routing
75+
- [ ] Cross-Object Joins (Federation)
76+
- [ ] Validation Layer (Zod)
77+
- [ ] Access Control (ACL)
78+
- [ ] Caching Layer

packages/objectql/src/index.ts

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,51 @@ export class ObjectQL {
111111
}
112112
}
113113

114+
/**
115+
* Helper to get object definition
116+
*/
117+
getSchema(objectName: string) {
118+
return SchemaRegistry.getObject(objectName);
119+
}
120+
114121
/**
115122
* Helper to get the target driver
116123
*/
117-
private getDriver(_object: string): DriverInterface {
118-
// TODO: Look up Object definition to see if it specifies a specific datasource/driver
119-
// For now, always return default
120-
if (!this.defaultDriver) {
121-
throw new Error('[ObjectQL] No drivers registered!');
124+
private getDriver(objectName: string): DriverInterface {
125+
const object = SchemaRegistry.getObject(objectName);
126+
127+
// 1. If object definition exists, check for explicit datasource
128+
if (object) {
129+
const datasourceName = object.datasource || 'default';
130+
131+
// If configured for 'default', try to find the default driver
132+
if (datasourceName === 'default') {
133+
if (this.defaultDriver && this.drivers.has(this.defaultDriver)) {
134+
return this.drivers.get(this.defaultDriver)!;
135+
}
136+
// Fallback: If 'default' not explicitly set, use the first available driver?
137+
// Better to be strict.
138+
} else {
139+
// Specific datasource requested
140+
if (this.drivers.has(datasourceName)) {
141+
return this.drivers.get(datasourceName)!;
142+
}
143+
// If not found, fall back to default? Or error?
144+
// Standard behavior: Error if specific datasource is missing.
145+
throw new Error(`[ObjectQL] Datasource '${datasourceName}' configured for object '${objectName}' is not registered.`);
146+
}
122147
}
123-
return this.drivers.get(this.defaultDriver)!;
148+
149+
// 2. Fallback for ad-hoc objects or missing definitions
150+
// If we have a default driver, use it.
151+
if (this.defaultDriver) {
152+
if (!object) {
153+
console.warn(`[ObjectQL] Object '${objectName}' not found in registry. Using default driver.`);
154+
}
155+
return this.drivers.get(this.defaultDriver)!;
156+
}
157+
158+
throw new Error(`[ObjectQL] No driver available for object '${objectName}'`);
124159
}
125160

126161
/**
@@ -148,29 +183,70 @@ export class ObjectQL {
148183
// Data Access Methods
149184
// ============================================
150185

151-
async find(object: string, filters: any = {}, options?: DriverOptions) {
186+
async find(object: string, query: any = {}, options?: DriverOptions) {
152187
const driver = this.getDriver(object);
153-
console.log(`[ObjectQL] Finding ${object} on ${driver.name}...`);
154188

155-
// Transform simplified filters to QueryAST
156-
// This is a simplified "Mock" transform.
157-
// Real implementation would parse complex JSON or FilterBuilders.
158-
const ast: QueryAST = {
159-
object, // Add missing required field
160-
// Pass through if it looks like AST, otherwise empty
161-
// In this demo, we assume the caller passes a simplified object or raw AST
162-
where: filters.filters || filters.where || undefined,
163-
limit: filters.limit || filters.top || 100,
164-
orderBy: filters.orderBy || filters.sort || []
165-
};
189+
// Normalize QueryAST
190+
let ast: QueryAST;
191+
if (query.where || query.fields || query.orderBy || query.limit) {
192+
// It's likely a QueryAST or partial QueryAST
193+
// Ensure 'object' is set correctly
194+
ast = {
195+
object, // Force object name to match the call
196+
...query
197+
} as QueryAST;
198+
} else {
199+
// It's a direct filter object (Simplified syntax)
200+
// e.g. find('account', { name: 'Acme' })
201+
ast = {
202+
object,
203+
where: query
204+
} as QueryAST;
205+
}
206+
207+
// Default limit protection
208+
if (ast.limit === undefined) {
209+
ast.limit = 100;
210+
}
166211

167212
return driver.find(object, ast, options);
168213
}
169214

215+
async findOne(object: string, idOrQuery: string | any, options?: DriverOptions) {
216+
const driver = this.getDriver(object);
217+
218+
let ast: QueryAST;
219+
if (typeof idOrQuery === 'string') {
220+
ast = {
221+
object,
222+
where: { _id: idOrQuery }
223+
};
224+
} else {
225+
// Assume query object
226+
// reuse logic from find() or just wrap it
227+
if (idOrQuery.where || idOrQuery.fields) {
228+
ast = { object, ...idOrQuery };
229+
} else {
230+
ast = { object, where: idOrQuery };
231+
}
232+
}
233+
// Limit 1 for findOne
234+
ast.limit = 1;
235+
236+
return driver.findOne(object, ast, options);
237+
}
238+
170239
async insert(object: string, data: Record<string, any>, options?: DriverOptions) {
171240
const driver = this.getDriver(object);
172-
console.log(`[ObjectQL] Creating ${object} on ${driver.name}...`);
173-
// 1. Validate Schema
241+
242+
// 1. Get Schema
243+
const schema = SchemaRegistry.getObject(object);
244+
245+
if (schema) {
246+
// TODO: Validation Logic
247+
// validate(schema, data);
248+
}
249+
174250
// 2. Run "Before Insert" Triggers
175251

176252
const result = await driver.create(object, data, options);
@@ -179,15 +255,13 @@ export class ObjectQL {
179255
return result;
180256
}
181257

182-
async update(object: string, id: string, data: Record<string, any>, options?: DriverOptions) {
258+
async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions) {
183259
const driver = this.getDriver(object);
184-
console.log(`[ObjectQL] Updating ${object} ${id}...`);
185260
return driver.update(object, id, data, options);
186261
}
187262

188-
async delete(object: string, id: string, options?: DriverOptions) {
263+
async delete(object: string, id: string | number, options?: DriverOptions) {
189264
const driver = this.getDriver(object);
190-
console.log(`[ObjectQL] Deleting ${object} ${id}...`);
191265
return driver.delete(object, id, options);
192266
}
193267
}

0 commit comments

Comments
 (0)