Skip to content

Commit 106a706

Browse files
authored
Merge pull request #157 from objectstack-ai/copilot/modularize-validator-formula-plugin
2 parents 6d6a915 + 31a8ea9 commit 106a706

File tree

7 files changed

+827
-22
lines changed

7 files changed

+827
-22
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* ObjectQL Formula Plugin
3+
* Copyright (c) 2026-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectstack/runtime';
10+
import { FormulaEngine } from './formula-engine';
11+
import type { FormulaEngineConfig } from '@objectql/types';
12+
13+
/**
14+
* Configuration for the Formula Plugin
15+
*/
16+
export interface FormulaPluginConfig extends FormulaEngineConfig {
17+
/**
18+
* Enable automatic formula evaluation on queries
19+
* @default true
20+
*/
21+
autoEvaluateOnQuery?: boolean;
22+
}
23+
24+
/**
25+
* Formula Plugin
26+
*
27+
* Wraps the ObjectQL Formula Engine as an ObjectStack plugin.
28+
* Registers formula evaluation capabilities into the kernel.
29+
*/
30+
export class FormulaPlugin implements RuntimePlugin {
31+
name = '@objectql/formulas';
32+
version = '4.0.0';
33+
34+
private engine: FormulaEngine;
35+
private config: FormulaPluginConfig;
36+
37+
constructor(config: FormulaPluginConfig = {}) {
38+
this.config = {
39+
autoEvaluateOnQuery: true,
40+
...config
41+
};
42+
43+
// Initialize the formula engine with configuration
44+
this.engine = new FormulaEngine(config);
45+
}
46+
47+
/**
48+
* Install the plugin into the kernel
49+
* Registers formula evaluation capabilities
50+
*/
51+
async install(ctx: RuntimeContext): Promise<void> {
52+
const kernel = ctx.engine as ObjectStackKernel;
53+
54+
console.log(`[${this.name}] Installing formula plugin...`);
55+
56+
// Register formula provider if the kernel supports it
57+
this.registerFormulaProvider(kernel);
58+
59+
// Register formula evaluation middleware if auto-evaluation is enabled
60+
if (this.config.autoEvaluateOnQuery !== false) {
61+
this.registerFormulaMiddleware(kernel);
62+
}
63+
64+
console.log(`[${this.name}] Formula plugin installed`);
65+
}
66+
67+
/**
68+
* Register the formula provider with the kernel
69+
* @private
70+
*/
71+
private registerFormulaProvider(kernel: ObjectStackKernel): void {
72+
// 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({
78+
evaluate: (formula: string, context: any) => {
79+
// Delegate to the formula engine
80+
// Note: In a real implementation, we would need to properly construct
81+
// the FormulaContext from the provided context
82+
return this.engine.evaluate(
83+
formula,
84+
context,
85+
'text', // default data type
86+
{}
87+
);
88+
},
89+
validate: (expression: string) => {
90+
return this.engine.validate(expression);
91+
},
92+
extractMetadata: (fieldName: string, expression: string, dataType: any) => {
93+
return this.engine.extractMetadata(fieldName, expression, dataType);
94+
}
95+
});
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;
100+
}
101+
}
102+
103+
/**
104+
* Register formula evaluation middleware
105+
* @private
106+
*/
107+
private registerFormulaMiddleware(kernel: ObjectStackKernel): void {
108+
// Check if kernel supports middleware hooks
109+
const kernelWithHooks = kernel as any;
110+
111+
if (typeof kernelWithHooks.use === 'function') {
112+
// Register middleware to evaluate formulas after queries
113+
kernelWithHooks.use('afterQuery', async (context: any) => {
114+
// Formula evaluation logic would go here
115+
// This would automatically compute formula fields after data is retrieved
116+
if (context.results && context.metadata?.fields) {
117+
// Iterate through fields and evaluate formulas
118+
// const formulaFields = Object.entries(context.metadata.fields)
119+
// .filter(([_, fieldConfig]) => (fieldConfig as any).formula);
120+
//
121+
// for (const record of context.results) {
122+
// for (const [fieldName, fieldConfig] of formulaFields) {
123+
// const formula = (fieldConfig as any).formula;
124+
// const result = this.engine.evaluate(formula, /* context */, /* dataType */);
125+
// record[fieldName] = result.value;
126+
// }
127+
// }
128+
}
129+
});
130+
}
131+
}
132+
133+
/**
134+
* Get the formula engine instance for direct access
135+
*/
136+
getEngine(): FormulaEngine {
137+
return this.engine;
138+
}
139+
}

packages/foundation/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export type { DriverInterface, DriverOptions, QueryAST } from '@objectstack/spec
1717
export * from './repository';
1818
export * from './app';
1919
export * from './plugin';
20+
export * from './validator-plugin';
21+
export * from './formula-plugin';
2022

2123
export * from './action';
2224
export * from './hook';

packages/foundation/core/src/plugin.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import type { RuntimePlugin, RuntimeContext } from '@objectstack/runtime';
1010
import type { ObjectStackKernel } from '@objectstack/runtime';
11+
import { ValidatorPlugin, ValidatorPluginConfig } from './validator-plugin';
12+
import { FormulaPlugin, FormulaPluginConfig } from './formula-plugin';
1113

1214
/**
1315
* Configuration for the ObjectQL Plugin
@@ -25,12 +27,24 @@ export interface ObjectQLPluginConfig {
2527
*/
2628
enableValidator?: boolean;
2729

30+
/**
31+
* Validator plugin configuration
32+
* Only used if enableValidator is not false
33+
*/
34+
validatorConfig?: ValidatorPluginConfig;
35+
2836
/**
2937
* Enable formula engine
3038
* @default true
3139
*/
3240
enableFormulas?: boolean;
3341

42+
/**
43+
* Formula plugin configuration
44+
* Only used if enableFormulas is not false
45+
*/
46+
formulaConfig?: FormulaPluginConfig;
47+
3448
/**
3549
* Enable AI integration
3650
* @default true
@@ -72,12 +86,16 @@ export class ObjectQLPlugin implements RuntimePlugin {
7286
await this.registerRepository(ctx.engine);
7387
}
7488

89+
// Install validator plugin if enabled
7590
if (this.config.enableValidator !== false) {
76-
await this.registerValidator(ctx.engine);
91+
const validatorPlugin = new ValidatorPlugin(this.config.validatorConfig || {});
92+
await validatorPlugin.install(ctx);
7793
}
7894

95+
// Install formula plugin if enabled
7996
if (this.config.enableFormulas !== false) {
80-
await this.registerFormulas(ctx.engine);
97+
const formulaPlugin = new FormulaPlugin(this.config.formulaConfig || {});
98+
await formulaPlugin.install(ctx);
8199
}
82100

83101
if (this.config.enableAI !== false) {
@@ -106,26 +124,6 @@ export class ObjectQLPlugin implements RuntimePlugin {
106124
console.log(`[${this.name}] Repository pattern registered`);
107125
}
108126

109-
/**
110-
* Register the Validator engine
111-
* @private
112-
*/
113-
private async registerValidator(kernel: ObjectStackKernel): Promise<void> {
114-
// TODO: Implement validator registration
115-
// For now, this is a placeholder to establish the structure
116-
console.log(`[${this.name}] Validator engine registered`);
117-
}
118-
119-
/**
120-
* Register the Formula engine
121-
* @private
122-
*/
123-
private async registerFormulas(kernel: ObjectStackKernel): Promise<void> {
124-
// TODO: Implement formula registration
125-
// For now, this is a placeholder to establish the structure
126-
console.log(`[${this.name}] Formula engine registered`);
127-
}
128-
129127
/**
130128
* Register AI integration
131129
* @private
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* ObjectQL Validator Plugin
3+
* Copyright (c) 2026-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import type { RuntimePlugin, RuntimeContext, ObjectStackKernel } from '@objectstack/runtime';
10+
import { Validator, ValidatorOptions } from './validator';
11+
12+
/**
13+
* Configuration for the Validator Plugin
14+
*/
15+
export interface ValidatorPluginConfig extends ValidatorOptions {
16+
/**
17+
* Enable validation on queries
18+
* @default true
19+
*/
20+
enableQueryValidation?: boolean;
21+
22+
/**
23+
* Enable validation on mutations
24+
* @default true
25+
*/
26+
enableMutationValidation?: boolean;
27+
}
28+
29+
/**
30+
* Validator Plugin
31+
*
32+
* Wraps the ObjectQL Validator engine as an ObjectStack plugin.
33+
* Registers validation middleware hooks into the kernel lifecycle.
34+
*/
35+
export class ValidatorPlugin implements RuntimePlugin {
36+
name = '@objectql/validator';
37+
version = '4.0.0';
38+
39+
private validator: Validator;
40+
private config: ValidatorPluginConfig;
41+
42+
constructor(config: ValidatorPluginConfig = {}) {
43+
this.config = {
44+
enableQueryValidation: true,
45+
enableMutationValidation: true,
46+
...config
47+
};
48+
49+
// Initialize the validator with language options
50+
this.validator = new Validator({
51+
language: config.language,
52+
languageFallback: config.languageFallback,
53+
});
54+
}
55+
56+
/**
57+
* Install the plugin into the kernel
58+
* Registers validation middleware for queries and mutations
59+
*/
60+
async install(ctx: RuntimeContext): Promise<void> {
61+
const kernel = ctx.engine as ObjectStackKernel;
62+
63+
console.log(`[${this.name}] Installing validator plugin...`);
64+
65+
// Register validation middleware for queries (if enabled)
66+
if (this.config.enableQueryValidation !== false) {
67+
this.registerQueryValidation(kernel);
68+
}
69+
70+
// Register validation middleware for mutations (if enabled)
71+
if (this.config.enableMutationValidation !== false) {
72+
this.registerMutationValidation(kernel);
73+
}
74+
75+
console.log(`[${this.name}] Validator plugin installed`);
76+
}
77+
78+
/**
79+
* Register query validation middleware
80+
* @private
81+
*/
82+
private registerQueryValidation(kernel: ObjectStackKernel): void {
83+
// Check if kernel supports middleware hooks
84+
if (typeof (kernel as any).use === 'function') {
85+
(kernel as any).use('beforeQuery', async (context: any) => {
86+
// Query validation logic
87+
// In a real implementation, this would validate query parameters
88+
// For now, this is a placeholder that demonstrates the integration pattern
89+
if (context.query && context.metadata?.validation_rules) {
90+
// Validation would happen here
91+
// const result = await this.validator.validate(
92+
// context.metadata.validation_rules,
93+
// { /* validation context */ }
94+
// );
95+
}
96+
});
97+
}
98+
}
99+
100+
/**
101+
* Register mutation validation middleware
102+
* @private
103+
*/
104+
private registerMutationValidation(kernel: ObjectStackKernel): void {
105+
// Check if kernel supports middleware hooks
106+
if (typeof (kernel as any).use === 'function') {
107+
(kernel as any).use('beforeMutation', async (context: any) => {
108+
// Mutation validation logic
109+
// This would validate data before create/update operations
110+
if (context.data && context.metadata?.validation_rules) {
111+
// Validation would happen here
112+
// const result = await this.validator.validate(
113+
// context.metadata.validation_rules,
114+
// { /* validation context */ }
115+
// );
116+
// if (!result.valid) {
117+
// throw new Error('Validation failed: ' + result.errors.map(e => e.message).join(', '));
118+
// }
119+
}
120+
});
121+
}
122+
}
123+
124+
/**
125+
* Get the validator instance for direct access
126+
*/
127+
getValidator(): Validator {
128+
return this.validator;
129+
}
130+
}

0 commit comments

Comments
 (0)