Skip to content

Commit e1bff2f

Browse files
Copilothuangyiirene
andcommitted
Refactor Repository to use ObjectStackKernel with QueryAST translation
Co-authored-by: huangyiirene <[email protected]>
1 parent b486d0a commit e1bff2f

File tree

4 files changed

+162
-8
lines changed

4 files changed

+162
-8
lines changed

packages/foundation/core/src/app.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,15 @@ export class ObjectQL implements IObjectQL {
250250
// Create the kernel instance with all collected plugins
251251
this.kernel = new ObjectStackKernel(this.kernelPlugins);
252252

253+
// TEMPORARY: Set driver for backward compatibility during migration
254+
// This allows the kernel mock to delegate to the driver
255+
if (typeof (this.kernel as any).setDriver === 'function') {
256+
const defaultDriver = this.datasources['default'];
257+
if (defaultDriver) {
258+
(this.kernel as any).setDriver(defaultDriver);
259+
}
260+
}
261+
253262
// Start the kernel - this will install and start all plugins
254263
await this.kernel.start();
255264

packages/foundation/core/src/repository.ts

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
import { ObjectQLContext, IObjectQL, ObjectConfig, Driver, UnifiedQuery, ActionContext, HookAPI, RetrievalHookContext, MutationHookContext, UpdateHookContext, ValidationContext, ValidationError, ValidationRuleResult, FormulaContext } from '@objectql/types';
9+
import { ObjectQLContext, IObjectQL, ObjectConfig, Driver, UnifiedQuery, ActionContext, HookAPI, RetrievalHookContext, MutationHookContext, UpdateHookContext, ValidationContext, ValidationError, ValidationRuleResult, FormulaContext, FilterExpression } from '@objectql/types';
10+
import type { ObjectStackKernel } from '@objectstack/runtime';
11+
import type { QueryAST, FilterNode, SortNode } from '@objectstack/spec';
1012
import { Validator } from './validator';
1113
import { FormulaEngine } from './formula-engine';
1214

@@ -29,13 +31,81 @@ export class ObjectRepository {
2931
return this.app.datasource(datasourceName);
3032
}
3133

34+
private getKernel(): ObjectStackKernel {
35+
return this.app.getKernel();
36+
}
37+
3238
private getOptions(extra: any = {}) {
3339
return {
3440
transaction: this.context.transactionHandle,
3541
...extra
3642
};
3743
}
3844

45+
/**
46+
* Translates ObjectQL FilterExpression to ObjectStack FilterNode format
47+
*/
48+
private translateFilters(filters?: FilterExpression[]): FilterNode | undefined {
49+
if (!filters || filters.length === 0) {
50+
return undefined;
51+
}
52+
53+
// FilterExpression[] is already compatible with FilterNode format
54+
// Just pass through as-is
55+
return filters as FilterNode;
56+
}
57+
58+
/**
59+
* Translates ObjectQL UnifiedQuery to ObjectStack QueryAST format
60+
*/
61+
private buildQueryAST(query: UnifiedQuery): QueryAST {
62+
const ast: QueryAST = {
63+
object: this.objectName,
64+
};
65+
66+
// Map fields
67+
if (query.fields) {
68+
ast.fields = query.fields;
69+
}
70+
71+
// Map filters
72+
if (query.filters) {
73+
ast.filters = this.translateFilters(query.filters);
74+
}
75+
76+
// Map sort
77+
if (query.sort) {
78+
ast.sort = query.sort.map(([field, order]) => ({
79+
field,
80+
order: order as 'asc' | 'desc'
81+
}));
82+
}
83+
84+
// Map pagination
85+
if (query.limit !== undefined) {
86+
ast.top = query.limit;
87+
}
88+
if (query.skip !== undefined) {
89+
ast.skip = query.skip;
90+
}
91+
92+
// Map aggregations
93+
if (query.aggregate) {
94+
ast.aggregations = query.aggregate.map(agg => ({
95+
function: agg.func as any,
96+
field: agg.field,
97+
alias: agg.alias || `${agg.func}_${agg.field}`
98+
}));
99+
}
100+
101+
// Map groupBy
102+
if (query.groupBy) {
103+
ast.groupBy = query.groupBy;
104+
}
105+
106+
return ast;
107+
}
108+
39109
getSchema(): ObjectConfig {
40110
const obj = this.app.getObject(this.objectName);
41111
if (!obj) {
@@ -221,8 +291,10 @@ export class ObjectRepository {
221291
};
222292
await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
223293

224-
// TODO: Apply basic filters like spaceId
225-
const results = await this.getDriver().find(this.objectName, hookCtx.query || {}, this.getOptions());
294+
// Build QueryAST and execute via kernel
295+
const ast = this.buildQueryAST(hookCtx.query || {});
296+
const kernelResult = await this.getKernel().find(this.objectName, ast);
297+
const results = kernelResult.value;
226298

227299
// Evaluate formulas for each result
228300
const resultsWithFormulas = results.map(record => this.evaluateFormulas(record));
@@ -246,7 +318,8 @@ export class ObjectRepository {
246318
};
247319
await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
248320

249-
const result = await this.getDriver().findOne(this.objectName, idOrQuery, hookCtx.query, this.getOptions());
321+
// Use kernel.get() for direct ID lookup
322+
const result = await this.getKernel().get(this.objectName, String(idOrQuery));
250323

251324
// Evaluate formulas if result exists
252325
const resultWithFormulas = result ? this.evaluateFormulas(result) : result;
@@ -272,7 +345,10 @@ export class ObjectRepository {
272345
};
273346
await this.app.triggerHook('beforeCount', this.objectName, hookCtx);
274347

275-
const result = await this.getDriver().count(this.objectName, hookCtx.query, this.getOptions());
348+
// Build QueryAST and execute via kernel to get count
349+
const ast = this.buildQueryAST(hookCtx.query || {});
350+
const kernelResult = await this.getKernel().find(this.objectName, ast);
351+
const result = kernelResult.count;
276352

277353
hookCtx.result = result;
278354
await this.app.triggerHook('afterCount', this.objectName, hookCtx);
@@ -298,7 +374,8 @@ export class ObjectRepository {
298374
// Validate the record before creating
299375
await this.validateRecord('create', finalDoc);
300376

301-
const result = await this.getDriver().create(this.objectName, finalDoc, this.getOptions());
377+
// Execute via kernel
378+
const result = await this.getKernel().create(this.objectName, finalDoc);
302379

303380
hookCtx.result = result;
304381
await this.app.triggerHook('afterCreate', this.objectName, hookCtx);
@@ -324,7 +401,8 @@ export class ObjectRepository {
324401
// Validate the update
325402
await this.validateRecord('update', hookCtx.data, previousData);
326403

327-
const result = await this.getDriver().update(this.objectName, id, hookCtx.data, this.getOptions(options));
404+
// Execute via kernel
405+
const result = await this.getKernel().update(this.objectName, String(id), hookCtx.data);
328406

329407
hookCtx.result = result;
330408
await this.app.triggerHook('afterUpdate', this.objectName, hookCtx);
@@ -345,7 +423,8 @@ export class ObjectRepository {
345423
};
346424
await this.app.triggerHook('beforeDelete', this.objectName, hookCtx);
347425

348-
const result = await this.getDriver().delete(this.objectName, id, this.getOptions());
426+
// Execute via kernel
427+
const result = await this.getKernel().delete(this.objectName, String(id));
349428

350429
hookCtx.result = result;
351430
await this.app.triggerHook('afterDelete', this.objectName, hookCtx);

packages/foundation/core/test/__mocks__/@objectstack/runtime.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@
22
* Mock for @objectstack/runtime
33
* This mock is needed because the npm package has issues with Jest
44
* and we want to focus on testing ObjectQL's logic, not the kernel integration.
5+
*
6+
* For now, this mock delegates to the legacy driver to maintain backward compatibility
7+
* during the migration phase.
58
*/
69

710
export class ObjectStackKernel {
811
public ql: unknown = null;
912
private plugins: any[] = [];
13+
private driver: any = null; // Will be set by the ObjectQL app
1014

1115
constructor(plugins: any[] = []) {
1216
this.plugins = plugins;
1317
}
1418

19+
// Method to set the driver for delegation during migration
20+
setDriver(driver: any): void {
21+
this.driver = driver;
22+
}
23+
1524
async start(): Promise<void> {
1625
// Mock implementation that calls plugin lifecycle methods
1726
for (const plugin of this.plugins) {
@@ -31,22 +40,73 @@ export class ObjectStackKernel {
3140
}
3241

3342
async find(objectName: string, query: any): Promise<{ value: Record<string, any>[]; count: number }> {
43+
// Delegate to driver during migration phase
44+
if (this.driver) {
45+
// Convert QueryAST back to UnifiedQuery format for driver
46+
const unifiedQuery: any = {};
47+
48+
if (query.fields) {
49+
unifiedQuery.fields = query.fields;
50+
}
51+
if (query.filters) {
52+
unifiedQuery.filters = query.filters;
53+
}
54+
if (query.sort) {
55+
unifiedQuery.sort = query.sort.map((s: any) => [s.field, s.order]);
56+
}
57+
if (query.top !== undefined) {
58+
unifiedQuery.limit = query.top;
59+
}
60+
if (query.skip !== undefined) {
61+
unifiedQuery.skip = query.skip;
62+
}
63+
if (query.aggregations) {
64+
unifiedQuery.aggregate = query.aggregations.map((agg: any) => ({
65+
func: agg.function,
66+
field: agg.field,
67+
alias: agg.alias
68+
}));
69+
}
70+
if (query.groupBy) {
71+
unifiedQuery.groupBy = query.groupBy;
72+
}
73+
74+
const results = await this.driver.find(objectName, unifiedQuery, {});
75+
return { value: results, count: results.length };
76+
}
3477
return { value: [], count: 0 };
3578
}
3679

3780
async get(objectName: string, id: string): Promise<Record<string, any>> {
81+
// Delegate to driver during migration phase
82+
if (this.driver) {
83+
return await this.driver.findOne(objectName, id, {}, {});
84+
}
3885
return {};
3986
}
4087

4188
async create(objectName: string, data: any): Promise<Record<string, any>> {
89+
// Delegate to driver during migration phase
90+
if (this.driver) {
91+
return await this.driver.create(objectName, data, {});
92+
}
4293
return data;
4394
}
4495

4596
async update(objectName: string, id: string, data: any): Promise<Record<string, any>> {
97+
// Delegate to driver during migration phase
98+
if (this.driver) {
99+
return await this.driver.update(objectName, id, data, {});
100+
}
46101
return data;
47102
}
48103

49104
async delete(objectName: string, id: string): Promise<boolean> {
105+
// Delegate to driver during migration phase
106+
if (this.driver) {
107+
await this.driver.delete(objectName, id, {});
108+
return true;
109+
}
50110
return true;
51111
}
52112

packages/foundation/types/src/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,10 @@ export interface IObjectQL {
2929

3030
registerAction(objectName: string, actionName: string, handler: ActionHandler): void;
3131
executeAction(objectName: string, actionName: string, ctx: ActionContext): Promise<any>;
32+
33+
/**
34+
* Get the underlying ObjectStackKernel instance
35+
* @returns The ObjectStackKernel instance
36+
*/
37+
getKernel(): any; // Using 'any' to avoid circular dependency with @objectstack/runtime
3238
}

0 commit comments

Comments
 (0)