Skip to content

Commit 9730850

Browse files
authored
Merge pull request #166 from objectstack-ai/copilot/align-types-and-migrate-engines
2 parents f85e32f + 8761077 commit 9730850

File tree

8 files changed

+298
-53
lines changed

8 files changed

+298
-53
lines changed

packages/foundation/core/src/app.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,30 @@ export class ObjectQL implements IObjectQL {
108108

109109
on(event: HookName, objectName: string, handler: HookHandler, packageName?: string) {
110110
// Delegate to kernel hook manager
111-
// Note: Type casting needed due to type incompatibility between ObjectQL HookName (includes beforeCount)
112-
// and runtime HookName. This is safe as the kernel will accept all hook types.
113-
this.kernel.hooks.register(event as any, objectName, handler as any, packageName);
111+
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
112+
// The runtime HookContext supports all fields via index signature, so this is safe
113+
const wrappedHandler = handler as unknown as import('@objectstack/runtime').HookHandler;
114+
this.kernel.hooks.register(event, objectName, wrappedHandler, packageName);
114115
}
115116

116117
async triggerHook(event: HookName, objectName: string, ctx: HookContext) {
117118
// Delegate to kernel hook manager
118-
await this.kernel.hooks.trigger(event as any, objectName, ctx as any);
119+
// Runtime HookContext supports ObjectQL-specific fields via index signature
120+
await this.kernel.hooks.trigger(event, objectName, ctx);
119121
}
120122

121123
registerAction(objectName: string, actionName: string, handler: ActionHandler, packageName?: string) {
122124
// Delegate to kernel action manager
123-
// Note: Type casting needed due to type incompatibility between ObjectQL ActionHandler
124-
// (includes input/api fields) and runtime ActionHandler. This is safe as runtime is more permissive.
125-
this.kernel.actions.register(objectName, actionName, handler as any, packageName);
125+
// We wrap the handler to bridge ObjectQL's rich context types with runtime's base types
126+
// The runtime ActionContext supports all fields via index signature, so this is safe
127+
const wrappedHandler = handler as unknown as import('@objectstack/runtime').ActionHandler;
128+
this.kernel.actions.register(objectName, actionName, wrappedHandler, packageName);
126129
}
127130

128131
async executeAction(objectName: string, actionName: string, ctx: ActionContext) {
129132
// Delegate to kernel action manager
130-
return await this.kernel.actions.execute(objectName, actionName, ctx as any);
133+
// Runtime ActionContext supports ObjectQL-specific fields via index signature
134+
return await this.kernel.actions.execute(objectName, actionName, ctx);
131135
}
132136

133137
createContext(options: ObjectQLContextOptions): ObjectQLContext {
@@ -211,16 +215,14 @@ export class ObjectQL implements IObjectQL {
211215
}
212216

213217
getObject(name: string): ObjectConfig | undefined {
214-
const item = this.kernel.metadata.get<MetadataItem>('object', name);
215-
return item?.content as ObjectConfig | undefined;
218+
return this.kernel.metadata.get<ObjectConfig>('object', name);
216219
}
217220

218221
getConfigs(): Record<string, ObjectConfig> {
219222
const result: Record<string, ObjectConfig> = {};
220-
const items = this.kernel.metadata.list<MetadataItem>('object');
223+
const items = this.kernel.metadata.list<ObjectConfig>('object');
221224
for (const item of items) {
222-
const obj = item.content as ObjectConfig;
223-
result[obj.name] = obj;
225+
result[item.name] = item;
224226
}
225227
return result;
226228
}

packages/foundation/core/src/formula-plugin.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectst
1010
import { FormulaEngine } from './formula-engine';
1111
import type { FormulaEngineConfig } from '@objectql/types';
1212

13+
/**
14+
* Extended ObjectStack Kernel with formula engine capability
15+
*/
16+
interface KernelWithFormulas extends ObjectStackKernel {
17+
formulaEngine?: FormulaEngine;
18+
}
19+
1320
/**
1421
* Configuration for the Formula Plugin
1522
*/
@@ -49,10 +56,13 @@ export class FormulaPlugin implements RuntimePlugin {
4956
* Registers formula evaluation capabilities
5057
*/
5158
async install(ctx: RuntimeContext): Promise<void> {
52-
const kernel = ctx.engine as ObjectStackKernel;
59+
const kernel = ctx.engine as KernelWithFormulas;
5360

5461
console.log(`[${this.name}] Installing formula plugin...`);
5562

63+
// Make formula engine accessible from the kernel for direct usage
64+
kernel.formulaEngine = this.engine;
65+
5666
// Register formula provider if the kernel supports it
5767
this.registerFormulaProvider(kernel);
5868

@@ -68,13 +78,10 @@ export class FormulaPlugin implements RuntimePlugin {
6878
* Register the formula provider with the kernel
6979
* @private
7080
*/
71-
private registerFormulaProvider(kernel: ObjectStackKernel): void {
81+
private registerFormulaProvider(kernel: KernelWithFormulas): void {
7282
// Check if kernel supports formula provider registration
73-
// Note: Using type assertion since registerFormulaProvider may not be in the interface
74-
const kernelWithFormulas = kernel as any;
75-
76-
if (typeof kernelWithFormulas.registerFormulaProvider === 'function') {
77-
kernelWithFormulas.registerFormulaProvider({
83+
if (typeof (kernel as any).registerFormulaProvider === 'function') {
84+
(kernel as any).registerFormulaProvider({
7885
evaluate: (formula: string, context: any) => {
7986
// Delegate to the formula engine
8087
// Note: In a real implementation, we would need to properly construct
@@ -93,24 +100,19 @@ export class FormulaPlugin implements RuntimePlugin {
93100
return this.engine.extractMetadata(fieldName, expression, dataType);
94101
}
95102
});
96-
} else {
97-
// If the kernel doesn't support formula provider registration yet,
98-
// we still register the engine for direct access
99-
kernelWithFormulas.formulaEngine = this.engine;
100103
}
104+
// Note: formulaEngine is already registered in install() method above
101105
}
102106

103107
/**
104108
* Register formula evaluation middleware
105109
* @private
106110
*/
107-
private registerFormulaMiddleware(kernel: ObjectStackKernel): void {
111+
private registerFormulaMiddleware(kernel: KernelWithFormulas): void {
108112
// Check if kernel supports middleware hooks
109-
const kernelWithHooks = kernel as any;
110-
111-
if (typeof kernelWithHooks.use === 'function') {
113+
if (typeof (kernel as any).use === 'function') {
112114
// Register middleware to evaluate formulas after queries
113-
kernelWithHooks.use('afterQuery', async (context: any) => {
115+
(kernel as any).use('afterQuery', async (context: any) => {
114116
// Formula evaluation logic would go here
115117
// This would automatically compute formula fields after data is retrieved
116118
if (context.results && context.metadata?.fields) {

packages/foundation/core/src/repository.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,51 @@ import { Validator } from './validator';
1313
import { FormulaEngine } from './formula-engine';
1414
import { QueryBuilder } from './query';
1515

16+
/**
17+
* Extended ObjectStack Kernel with optional ObjectQL plugin capabilities.
18+
* These properties are attached by ValidatorPlugin and FormulaPlugin during installation.
19+
*/
20+
interface ExtendedKernel extends ObjectStackKernel {
21+
validator?: Validator;
22+
formulaEngine?: FormulaEngine;
23+
}
24+
1625
export class ObjectRepository {
17-
private validator: Validator;
18-
private formulaEngine: FormulaEngine;
1926
private queryBuilder: QueryBuilder;
2027

2128
constructor(
2229
private objectName: string,
2330
private context: ObjectQLContext,
2431
private app: IObjectQL
2532
) {
26-
this.validator = new Validator();
27-
this.formulaEngine = new FormulaEngine();
2833
this.queryBuilder = new QueryBuilder();
2934
}
35+
36+
/**
37+
* Get validator instance from kernel (via plugin)
38+
* Falls back to creating a new instance if not available
39+
*/
40+
private getValidator(): Validator {
41+
const kernel = this.getKernel() as ExtendedKernel;
42+
if (kernel.validator) {
43+
return kernel.validator;
44+
}
45+
// Fallback for backward compatibility
46+
return new Validator();
47+
}
48+
49+
/**
50+
* Get formula engine instance from kernel (via plugin)
51+
* Falls back to creating a new instance if not available
52+
*/
53+
private getFormulaEngine(): FormulaEngine {
54+
const kernel = this.getKernel() as ExtendedKernel;
55+
if (kernel.formulaEngine) {
56+
return kernel.formulaEngine;
57+
}
58+
// Fallback for backward compatibility
59+
return new FormulaEngine();
60+
}
3061

3162
private getDriver(): Driver {
3263
const obj = this.getSchema();
@@ -106,7 +137,7 @@ export class ObjectRepository {
106137
}
107138

108139
const value = record[fieldName];
109-
const fieldResults = await this.validator.validateField(
140+
const fieldResults = await this.getValidator().validateField(
110141
fieldName,
111142
fieldConfig,
112143
value,
@@ -145,7 +176,7 @@ export class ObjectRepository {
145176
changedFields,
146177
};
147178

148-
const result = await this.validator.validate(schema.validation.rules, validationContext);
179+
const result = await this.getValidator().validate(schema.validation.rules, validationContext);
149180
allResults.push(...result.results);
150181
}
151182

@@ -192,7 +223,7 @@ export class ObjectRepository {
192223
// Evaluate each formula field
193224
for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
194225
if (fieldConfig.type === 'formula' && fieldConfig.formula) {
195-
const result = this.formulaEngine.evaluate(
226+
const result = this.getFormulaEngine().evaluate(
196227
fieldConfig.formula,
197228
formulaContext,
198229
fieldConfig.data_type || 'text',

packages/foundation/core/src/validator-plugin.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectstack/runtime';
1010
import { Validator, ValidatorOptions } from './validator';
1111

12+
/**
13+
* Extended ObjectStack Kernel with validator capability
14+
*/
15+
interface KernelWithValidator extends ObjectStackKernel {
16+
validator?: Validator;
17+
}
18+
1219
/**
1320
* Configuration for the Validator Plugin
1421
*/
@@ -58,10 +65,13 @@ export class ValidatorPlugin implements RuntimePlugin {
5865
* Registers validation middleware for queries and mutations
5966
*/
6067
async install(ctx: RuntimeContext): Promise<void> {
61-
const kernel = ctx.engine as ObjectStackKernel;
68+
const kernel = ctx.engine as KernelWithValidator;
6269

6370
console.log(`[${this.name}] Installing validator plugin...`);
6471

72+
// Make validator accessible from the kernel for direct usage
73+
kernel.validator = this.validator;
74+
6575
// Register validation middleware for queries (if enabled)
6676
if (this.config.enableQueryValidation !== false) {
6777
this.registerQueryValidation(kernel);
@@ -79,7 +89,7 @@ export class ValidatorPlugin implements RuntimePlugin {
7989
* Register query validation middleware
8090
* @private
8191
*/
82-
private registerQueryValidation(kernel: ObjectStackKernel): void {
92+
private registerQueryValidation(kernel: KernelWithValidator): void {
8393
// Check if kernel supports middleware hooks
8494
if (typeof (kernel as any).use === 'function') {
8595
(kernel as any).use('beforeQuery', async (context: any) => {
@@ -101,7 +111,7 @@ export class ValidatorPlugin implements RuntimePlugin {
101111
* Register mutation validation middleware
102112
* @private
103113
*/
104-
private registerMutationValidation(kernel: ObjectStackKernel): void {
114+
private registerMutationValidation(kernel: KernelWithValidator): void {
105115
// Check if kernel supports middleware hooks
106116
if (typeof (kernel as any).use === 'function') {
107117
(kernel as any).use('beforeMutation', async (context: any) => {

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ class MockMetadataRegistry {
2121

2222
get<T = any>(type: string, id: string): T | undefined {
2323
const typeMap = this.store.get(type);
24-
return typeMap?.get(id) as T | undefined;
24+
const item = typeMap?.get(id);
25+
return item?.content as T;
2526
}
2627

2728
list<T = any>(type: string): T[] {
2829
const typeMap = this.store.get(type);
2930
if (!typeMap) return [];
30-
return Array.from(typeMap.values()) as T[];
31+
return Array.from(typeMap.values()).map(item => item.content as T);
3132
}
3233

3334
unregister(type: string, id: string): boolean {
@@ -57,25 +58,32 @@ class MockMetadataRegistry {
5758
class MockHookManager {
5859
private hooks = new Map<string, any[]>();
5960

60-
register(hookName: string, handler: any, packageName?: string): void {
61+
register(hookName: string, objectName: string, handler: any, packageName?: string): void {
6162
if (!this.hooks.has(hookName)) {
6263
this.hooks.set(hookName, []);
6364
}
64-
this.hooks.get(hookName)!.push({ handler, packageName });
65+
this.hooks.get(hookName)!.push({ objectName, handler, packageName });
6566
}
6667

67-
async trigger(hookName: string, context: any): Promise<void> {
68+
async trigger(hookName: string, objectName: string, context: any): Promise<void> {
6869
const handlers = this.hooks.get(hookName) || [];
69-
for (const { handler } of handlers) {
70-
await handler(context);
70+
for (const entry of handlers) {
71+
// Match on wildcard '*' or specific object name
72+
if (entry.objectName === '*' || entry.objectName === objectName) {
73+
await entry.handler(context);
74+
}
7175
}
7276
}
7377

74-
unregisterPackage(packageName: string): void {
78+
removePackage(packageName: string): void {
7579
for (const [hookName, handlers] of this.hooks.entries()) {
7680
this.hooks.set(hookName, handlers.filter(h => h.packageName !== packageName));
7781
}
7882
}
83+
84+
clear(): void {
85+
this.hooks.clear();
86+
}
7987
}
8088

8189
class MockActionManager {
@@ -100,7 +108,7 @@ class MockActionManager {
100108
return this.actions.get(key)?.handler;
101109
}
102110

103-
unregisterPackage(packageName: string): void {
111+
removePackage(packageName: string): void {
104112
const toDelete: string[] = [];
105113
for (const [key, action] of this.actions.entries()) {
106114
if (action.packageName === packageName) {
@@ -109,6 +117,10 @@ class MockActionManager {
109117
}
110118
toDelete.forEach(key => this.actions.delete(key));
111119
}
120+
121+
clear(): void {
122+
this.actions.clear();
123+
}
112124
}
113125

114126
export class ObjectStackKernel {

0 commit comments

Comments
 (0)