Skip to content

Commit 7992974

Browse files
Copilothotlong
andcommitted
Implement IDataEngine interface for ObjectQL
- Created data-engine.zod.ts in spec/system with Zod schemas - Updated ObjectQL class to implement IDataEngine interface - Modified method signatures to match IDataEngine contract - Updated ObjectQLPlugin to register as both 'objectql' and 'data-engine' - Re-exported IDataEngine from runtime for backward compatibility Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent ec8fd1a commit 7992974

File tree

5 files changed

+180
-136
lines changed

5 files changed

+180
-136
lines changed

packages/objectql/src/index.ts

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { QueryAST, HookContext } from '@objectstack/spec/data';
22
import { ObjectStackManifest } from '@objectstack/spec/system';
33
import { DriverInterface, DriverOptions } from '@objectstack/spec/system';
4+
import { IDataEngine, QueryOptions } from '@objectstack/spec/system';
45
import { SchemaRegistry } from './registry';
56

67
// Export Registry for consumers
@@ -20,8 +21,10 @@ export interface PluginContext {
2021

2122
/**
2223
* ObjectQL Engine
24+
*
25+
* Implements the IDataEngine interface for data persistence.
2326
*/
24-
export class ObjectQL {
27+
export class ObjectQL implements IDataEngine {
2528
private drivers = new Map<string, DriverInterface>();
2629
private defaultDriver: string | null = null;
2730

@@ -211,27 +214,52 @@ export class ObjectQL {
211214
}
212215

213216
// ============================================
214-
// Data Access Methods
217+
// Data Access Methods (IDataEngine Interface)
215218
// ============================================
216219

217-
async find(object: string, query: any = {}, options?: DriverOptions) {
220+
/**
221+
* Find records matching a query (IDataEngine interface)
222+
*
223+
* @param object - Object name
224+
* @param query - Query options (IDataEngine format)
225+
* @returns Promise resolving to array of records
226+
*/
227+
async find(object: string, query?: QueryOptions): Promise<any[]> {
218228
const driver = this.getDriver(object);
219229

220-
// Normalize QueryAST
221-
let ast: QueryAST;
222-
if (query.where || query.fields || query.orderBy || query.limit) {
223-
ast = { object, ...query } as QueryAST;
224-
} else {
225-
ast = { object, where: query } as QueryAST;
230+
// Convert QueryOptions to QueryAST
231+
let ast: QueryAST = { object };
232+
233+
if (query) {
234+
// Map QueryOptions to QueryAST
235+
if (query.filter) {
236+
ast.where = query.filter;
237+
}
238+
if (query.select) {
239+
ast.fields = query.select;
240+
}
241+
if (query.sort) {
242+
ast.orderBy = query.sort;
243+
}
244+
// Handle both limit and top (top takes precedence)
245+
if (query.top !== undefined) {
246+
ast.limit = query.top;
247+
} else if (query.limit !== undefined) {
248+
ast.limit = query.limit;
249+
}
250+
if (query.skip !== undefined) {
251+
ast.offset = query.skip;
252+
}
226253
}
227254

255+
// Set default limit if not specified
228256
if (ast.limit === undefined) ast.limit = 100;
229257

230258
// Trigger Before Hook
231259
const hookContext: HookContext = {
232260
object,
233261
event: 'beforeFind',
234-
input: { ast, options }, // Hooks can modify AST here
262+
input: { ast, options: undefined },
235263
ql: this
236264
};
237265
await this.triggerHooks('beforeFind', hookContext);
@@ -275,7 +303,14 @@ export class ObjectQL {
275303
return driver.findOne(object, ast, options);
276304
}
277305

278-
async insert(object: string, data: Record<string, any>, options?: DriverOptions) {
306+
/**
307+
* Insert a new record (IDataEngine interface)
308+
*
309+
* @param object - Object name
310+
* @param data - Data to insert
311+
* @returns Promise resolving to the created record
312+
*/
313+
async insert(object: string, data: any): Promise<any> {
279314
const driver = this.getDriver(object);
280315

281316
// 1. Get Schema
@@ -290,7 +325,7 @@ export class ObjectQL {
290325
const hookContext: HookContext = {
291326
object,
292327
event: 'beforeInsert',
293-
input: { data, options },
328+
input: { data, options: undefined },
294329
ql: this
295330
};
296331
await this.triggerHooks('beforeInsert', hookContext);
@@ -306,13 +341,21 @@ export class ObjectQL {
306341
return hookContext.result;
307342
}
308343

309-
async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions) {
344+
/**
345+
* Update a record by ID (IDataEngine interface)
346+
*
347+
* @param object - Object name
348+
* @param id - Record ID
349+
* @param data - Updated data
350+
* @returns Promise resolving to the updated record
351+
*/
352+
async update(object: string, id: any, data: any): Promise<any> {
310353
const driver = this.getDriver(object);
311354

312355
const hookContext: HookContext = {
313356
object,
314357
event: 'beforeUpdate',
315-
input: { id, data, options },
358+
input: { id, data, options: undefined },
316359
ql: this
317360
};
318361
await this.triggerHooks('beforeUpdate', hookContext);
@@ -326,13 +369,20 @@ export class ObjectQL {
326369
return hookContext.result;
327370
}
328371

329-
async delete(object: string, id: string | number, options?: DriverOptions) {
372+
/**
373+
* Delete a record by ID (IDataEngine interface)
374+
*
375+
* @param object - Object name
376+
* @param id - Record ID
377+
* @returns Promise resolving to true if deleted, false otherwise
378+
*/
379+
async delete(object: string, id: any): Promise<boolean> {
330380
const driver = this.getDriver(object);
331381

332382
const hookContext: HookContext = {
333383
object,
334384
event: 'beforeDelete',
335-
input: { id, options },
385+
input: { id, options: undefined },
336386
ql: this
337387
};
338388
await this.triggerHooks('beforeDelete', hookContext);
@@ -343,6 +393,8 @@ export class ObjectQL {
343393
hookContext.result = result;
344394
await this.triggerHooks('afterDelete', hookContext);
345395

346-
return hookContext.result;
396+
// Return boolean - true if deletion was successful
397+
// The driver.delete should return the deleted record or null
398+
return hookContext.result !== null && hookContext.result !== undefined;
347399
}
348400
}
Lines changed: 3 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,8 @@
11
/**
22
* IDataEngine - Standard Data Engine Interface
33
*
4-
* Abstract interface for data persistence capabilities.
5-
* This allows plugins to interact with data engines without knowing
6-
* the underlying implementation (SQL, MongoDB, Memory, etc.).
7-
*
8-
* Follows Dependency Inversion Principle - plugins depend on this interface,
9-
* not on concrete database implementations.
10-
*/
11-
12-
/**
13-
* Query filter conditions
14-
*/
15-
export interface QueryFilter {
16-
[field: string]: any;
17-
}
18-
19-
/**
20-
* Query options for find operations
4+
* Re-exports the data engine interface from the spec package.
5+
* This provides backward compatibility for imports from @objectstack/runtime.
216
*/
22-
export interface QueryOptions {
23-
/** Filter conditions */
24-
filter?: QueryFilter;
25-
/** Fields to select */
26-
select?: string[];
27-
/** Sort order */
28-
sort?: Record<string, 1 | -1 | 'asc' | 'desc'>;
29-
/** Limit number of results (alternative name for top, used by some drivers) */
30-
limit?: number;
31-
/** Skip number of results (for pagination) */
32-
skip?: number;
33-
/** Maximum number of results (OData-style, takes precedence over limit if both specified) */
34-
top?: number;
35-
}
367

37-
/**
38-
* IDataEngine - Data persistence capability interface
39-
*
40-
* Defines the contract for data engine implementations.
41-
* Concrete implementations (ObjectQL, Prisma, TypeORM) should implement this interface.
42-
*/
43-
export interface IDataEngine {
44-
/**
45-
* Insert a new record
46-
*
47-
* @param objectName - Name of the object/table (e.g., 'user', 'order')
48-
* @param data - Data to insert
49-
* @returns Promise resolving to the created record (including generated ID)
50-
*
51-
* @example
52-
* ```ts
53-
* const user = await engine.insert('user', {
54-
* name: 'John Doe',
55-
* email: 'john@example.com'
56-
* });
57-
* console.log(user.id); // Auto-generated ID
58-
* ```
59-
*/
60-
insert(objectName: string, data: any): Promise<any>;
61-
62-
/**
63-
* Find records matching a query
64-
*
65-
* @param objectName - Name of the object/table
66-
* @param query - Query conditions (optional)
67-
* @returns Promise resolving to an array of matching records
68-
*
69-
* @example
70-
* ```ts
71-
* // Find all users
72-
* const allUsers = await engine.find('user');
73-
*
74-
* // Find with filter
75-
* const activeUsers = await engine.find('user', {
76-
* filter: { status: 'active' }
77-
* });
78-
*
79-
* // Find with limit and sort
80-
* const recentUsers = await engine.find('user', {
81-
* sort: { createdAt: -1 },
82-
* limit: 10
83-
* });
84-
* ```
85-
*/
86-
find(objectName: string, query?: QueryOptions): Promise<any[]>;
87-
88-
/**
89-
* Update a record by ID
90-
*
91-
* @param objectName - Name of the object/table
92-
* @param id - Record ID
93-
* @param data - Updated data (partial update)
94-
* @returns Promise resolving to the updated record
95-
*
96-
* @example
97-
* ```ts
98-
* const updatedUser = await engine.update('user', '123', {
99-
* name: 'Jane Doe',
100-
* email: 'jane@example.com'
101-
* });
102-
* ```
103-
*/
104-
update(objectName: string, id: any, data: any): Promise<any>;
105-
106-
/**
107-
* Delete a record by ID
108-
*
109-
* @param objectName - Name of the object/table
110-
* @param id - Record ID
111-
* @returns Promise resolving to true if deleted, false otherwise
112-
*
113-
* @example
114-
* ```ts
115-
* const deleted = await engine.delete('user', '123');
116-
* if (deleted) {
117-
* console.log('User deleted successfully');
118-
* }
119-
* ```
120-
*/
121-
delete(objectName: string, id: any): Promise<boolean>;
122-
}
8+
export type { IDataEngine, QueryOptions, QueryFilter } from '@objectstack/spec/system';

packages/runtime/src/objectql-plugin.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ export class ObjectQLPlugin implements Plugin {
4444
* Init phase - Register ObjectQL as a service
4545
*/
4646
async init(ctx: PluginContext) {
47-
// Register ObjectQL engine as a service
47+
// Register ObjectQL engine as 'objectql' service (legacy name)
4848
ctx.registerService('objectql', this.ql);
49-
ctx.logger.log('[ObjectQLPlugin] ObjectQL engine registered as service');
49+
50+
// Register ObjectQL engine as 'data-engine' service (IDataEngine interface)
51+
ctx.registerService('data-engine', this.ql);
52+
53+
ctx.logger.log('[ObjectQLPlugin] ObjectQL engine registered as services: objectql, data-engine');
5054
}
5155

5256
/**

0 commit comments

Comments
 (0)