@objectql/core 4.0.6 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/app.d.ts +1 -1
- package/dist/app.js +21 -4
- package/dist/app.js.map +1 -1
- package/dist/plugin.js +21 -0
- package/dist/plugin.js.map +1 -1
- package/dist/query/query-service.d.ts +1 -0
- package/dist/query/query-service.js +5 -1
- package/dist/query/query-service.js.map +1 -1
- package/dist/repository.d.ts +0 -28
- package/dist/repository.js +10 -173
- package/dist/repository.js.map +1 -1
- package/package.json +4 -4
- package/src/app.ts +23 -4
- package/src/plugin.ts +24 -0
- package/src/query/query-service.ts +6 -1
- package/src/repository.ts +10 -220
- package/test/app.test.ts +6 -6
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectql/core",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Universal runtime engine for ObjectQL - AI-native metadata-driven ORM with validation, repository pattern, and driver orchestration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"objectql",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"@objectstack/runtime": "^1.0.0",
|
|
24
24
|
"@objectstack/spec": "^1.0.0",
|
|
25
25
|
"js-yaml": "^4.1.0",
|
|
26
|
-
"@objectql/plugin-formula": "4.0
|
|
27
|
-
"@objectql/plugin-validator": "4.0
|
|
28
|
-
"@objectql/types": "4.0
|
|
26
|
+
"@objectql/plugin-formula": "4.1.0",
|
|
27
|
+
"@objectql/plugin-validator": "4.1.0",
|
|
28
|
+
"@objectql/types": "4.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/js-yaml": "^4.0.5",
|
package/src/app.ts
CHANGED
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
ActionContext,
|
|
23
23
|
LoaderPlugin
|
|
24
24
|
} from '@objectql/types';
|
|
25
|
-
import { ObjectKernel, type Plugin } from '@objectstack/
|
|
25
|
+
import { ObjectKernel, type Plugin } from '@objectstack/runtime';
|
|
26
26
|
import { ObjectQL as RuntimeObjectQL, SchemaRegistry } from '@objectstack/objectql';
|
|
27
|
+
import { ValidatorPlugin } from '@objectql/plugin-validator';
|
|
28
|
+
import { FormulaPlugin } from '@objectql/plugin-formula';
|
|
27
29
|
import { ObjectRepository } from './repository';
|
|
28
30
|
import { convertIntrospectedSchemaToObjects } from './util';
|
|
29
31
|
import { CompiledHookManager } from './optimizations/CompiledHookManager';
|
|
@@ -81,10 +83,27 @@ export class ObjectQL implements IObjectQL {
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
// Ensure default plugins are present
|
|
87
|
+
if (!this.kernelPlugins.some(p => p.name === 'validator')) {
|
|
88
|
+
this.use(new ValidatorPlugin());
|
|
89
|
+
}
|
|
90
|
+
if (!this.kernelPlugins.some(p => p.name === 'formula')) {
|
|
91
|
+
this.use(new FormulaPlugin());
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
// Create the kernel with registered plugins
|
|
85
|
-
this.kernel = new (ObjectKernel as any)();
|
|
95
|
+
this.kernel = new (ObjectKernel as any)(this.kernelPlugins);
|
|
86
96
|
for (const plugin of this.kernelPlugins) {
|
|
97
|
+
// Fallback for kernels that support .use() but maybe didn't take them in constructor or if we need to support both
|
|
98
|
+
// NOTE: Modern ObjectKernel takes plugins in constructor.
|
|
87
99
|
if ((this.kernel as any).use) {
|
|
100
|
+
// Try to avoid double registration if the kernel is smart, but since we don't know the kernel logic perfectly:
|
|
101
|
+
// Ideally check if already added. But for now, we leave this for backward compat
|
|
102
|
+
// if ObjectKernel DOESN't take constructor args but HAS use().
|
|
103
|
+
|
|
104
|
+
// However, we just instantiated it.
|
|
105
|
+
// Let's assume constructor is the way if available.
|
|
106
|
+
// But we keep this check for .use() just in case the constructor signature is different (e.g. empty)
|
|
88
107
|
(this.kernel as any).use(plugin);
|
|
89
108
|
}
|
|
90
109
|
}
|
|
@@ -406,10 +425,10 @@ export class ObjectQL implements IObjectQL {
|
|
|
406
425
|
for (const plugin of this.kernelPlugins) {
|
|
407
426
|
try {
|
|
408
427
|
if (typeof (plugin as any).init === 'function') {
|
|
409
|
-
await (plugin as any).init();
|
|
428
|
+
await (plugin as any).init(this.kernel);
|
|
410
429
|
}
|
|
411
430
|
if (typeof (plugin as any).start === 'function') {
|
|
412
|
-
await (plugin as any).start();
|
|
431
|
+
await (plugin as any).start(this.kernel);
|
|
413
432
|
}
|
|
414
433
|
} catch (error) {
|
|
415
434
|
console.error(`Failed to initialize plugin ${(plugin as any).name || 'unknown'}:`, error);
|
package/src/plugin.ts
CHANGED
|
@@ -246,6 +246,16 @@ export class ObjectQLPlugin implements RuntimePlugin {
|
|
|
246
246
|
};
|
|
247
247
|
|
|
248
248
|
kernel.find = async (objectName: string, query: any): Promise<{ value: any[]; count: number }> => {
|
|
249
|
+
// Use QueryService if available for advanced query processing (AST, optimizations)
|
|
250
|
+
if ((kernel as any).queryService) {
|
|
251
|
+
const result = await (kernel as any).queryService.find(objectName, query);
|
|
252
|
+
return {
|
|
253
|
+
value: result.value,
|
|
254
|
+
count: result.count !== undefined ? result.count : result.value.length
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Fallback to direct driver call
|
|
249
259
|
const driver = getDriver(objectName);
|
|
250
260
|
const value = await driver.find(objectName, query);
|
|
251
261
|
const count = value.length;
|
|
@@ -253,11 +263,25 @@ export class ObjectQLPlugin implements RuntimePlugin {
|
|
|
253
263
|
};
|
|
254
264
|
|
|
255
265
|
kernel.get = async (objectName: string, id: string): Promise<any> => {
|
|
266
|
+
// Use QueryService if available
|
|
267
|
+
if ((kernel as any).queryService) {
|
|
268
|
+
const result = await (kernel as any).queryService.findOne(objectName, id);
|
|
269
|
+
return result.value;
|
|
270
|
+
}
|
|
271
|
+
|
|
256
272
|
const driver = getDriver(objectName);
|
|
257
273
|
return await driver.findOne(objectName, id);
|
|
258
274
|
};
|
|
259
275
|
|
|
260
276
|
kernel.count = async (objectName: string, filters?: any): Promise<number> => {
|
|
277
|
+
// Use QueryService if available
|
|
278
|
+
if ((kernel as any).queryService) {
|
|
279
|
+
// QueryService.count expects a UnifiedQuery filter or just filter object?
|
|
280
|
+
// Looking at QueryService.count signature: count(objectName: string, where?: Filter, options?: QueryOptions)
|
|
281
|
+
const result = await (kernel as any).queryService.count(objectName, filters);
|
|
282
|
+
return result.value;
|
|
283
|
+
}
|
|
284
|
+
|
|
261
285
|
const driver = getDriver(objectName);
|
|
262
286
|
return await driver.count(objectName, filters || {}, {});
|
|
263
287
|
};
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
import { Data } from '@objectstack/spec';
|
|
17
17
|
type QueryAST = Data.QueryAST;
|
|
18
18
|
import { QueryBuilder } from './query-builder';
|
|
19
|
+
import { QueryCompiler } from '../optimizations/QueryCompiler';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Options for query execution
|
|
@@ -101,12 +102,14 @@ export interface QueryProfile {
|
|
|
101
102
|
*/
|
|
102
103
|
export class QueryService {
|
|
103
104
|
private queryBuilder: QueryBuilder;
|
|
105
|
+
private queryCompiler: QueryCompiler;
|
|
104
106
|
|
|
105
107
|
constructor(
|
|
106
108
|
private datasources: Record<string, Driver>,
|
|
107
109
|
private metadata: MetadataRegistry
|
|
108
110
|
) {
|
|
109
111
|
this.queryBuilder = new QueryBuilder();
|
|
112
|
+
this.queryCompiler = new QueryCompiler(1000);
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
/**
|
|
@@ -142,7 +145,9 @@ export class QueryService {
|
|
|
142
145
|
* @private
|
|
143
146
|
*/
|
|
144
147
|
private buildQueryAST(objectName: string, query: UnifiedQuery): QueryAST {
|
|
145
|
-
|
|
148
|
+
const ast = this.queryBuilder.build(objectName, query);
|
|
149
|
+
const compiled = this.queryCompiler.compile(objectName, ast);
|
|
150
|
+
return compiled.ast;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
/**
|
package/src/repository.ts
CHANGED
|
@@ -11,64 +11,16 @@ import type { ObjectKernel } from '@objectstack/runtime';
|
|
|
11
11
|
import { Data } from '@objectstack/spec';
|
|
12
12
|
type QueryAST = Data.QueryAST;
|
|
13
13
|
type SortNode = Data.SortNode;
|
|
14
|
-
import { Validator } from '@objectql/plugin-validator';
|
|
15
|
-
import { FormulaEngine } from '@objectql/plugin-formula';
|
|
16
|
-
import { QueryBuilder } from './query';
|
|
17
|
-
import { QueryCompiler } from './optimizations/QueryCompiler';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extended ObjectStack Kernel with optional ObjectQL plugin capabilities.
|
|
21
|
-
* These properties are attached by ValidatorPlugin and FormulaPlugin during installation.
|
|
22
|
-
*/
|
|
23
|
-
interface ExtendedKernel extends ObjectKernel {
|
|
24
|
-
validator?: Validator;
|
|
25
|
-
formulaEngine?: FormulaEngine;
|
|
26
|
-
create?: (objectName: string, data: any) => Promise<any>;
|
|
27
|
-
update?: (objectName: string, id: string, data: any) => Promise<any>;
|
|
28
|
-
delete?: (objectName: string, id: string) => Promise<any>;
|
|
29
|
-
find?: (objectName: string, query: any) => Promise<any>;
|
|
30
|
-
get?: (objectName: string, id: string) => Promise<any>;
|
|
31
|
-
}
|
|
32
14
|
|
|
33
15
|
export class ObjectRepository {
|
|
34
|
-
private queryBuilder: QueryBuilder;
|
|
35
|
-
// Shared query compiler for caching compiled queries
|
|
36
|
-
private static queryCompiler = new QueryCompiler(1000);
|
|
37
16
|
|
|
38
17
|
constructor(
|
|
39
18
|
private objectName: string,
|
|
40
19
|
private context: ObjectQLContext,
|
|
41
20
|
private app: IObjectQL
|
|
42
21
|
) {
|
|
43
|
-
this.queryBuilder = new QueryBuilder();
|
|
44
22
|
}
|
|
45
23
|
|
|
46
|
-
/**
|
|
47
|
-
* Get validator instance from kernel (via plugin)
|
|
48
|
-
* Falls back to creating a new instance if not available
|
|
49
|
-
*/
|
|
50
|
-
private getValidator(): Validator {
|
|
51
|
-
const kernel = this.getKernel() as ExtendedKernel;
|
|
52
|
-
if (kernel.validator) {
|
|
53
|
-
return kernel.validator;
|
|
54
|
-
}
|
|
55
|
-
// Fallback for backward compatibility
|
|
56
|
-
return new Validator();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get formula engine instance from kernel (via plugin)
|
|
61
|
-
* Falls back to creating a new instance if not available
|
|
62
|
-
*/
|
|
63
|
-
private getFormulaEngine(): FormulaEngine {
|
|
64
|
-
const kernel = this.getKernel() as ExtendedKernel;
|
|
65
|
-
if (kernel.formulaEngine) {
|
|
66
|
-
return kernel.formulaEngine;
|
|
67
|
-
}
|
|
68
|
-
// Fallback for backward compatibility
|
|
69
|
-
return new FormulaEngine();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
24
|
private getDriver(): Driver {
|
|
73
25
|
const obj = this.getSchema();
|
|
74
26
|
const datasourceName = obj.datasource || 'default';
|
|
@@ -86,17 +38,6 @@ export class ObjectRepository {
|
|
|
86
38
|
};
|
|
87
39
|
}
|
|
88
40
|
|
|
89
|
-
/**
|
|
90
|
-
* Translates ObjectQL UnifiedQuery to ObjectStack QueryAST format
|
|
91
|
-
* Uses query compiler for caching and optimization
|
|
92
|
-
*/
|
|
93
|
-
private buildQueryAST(query: UnifiedQuery): QueryAST {
|
|
94
|
-
const ast = this.queryBuilder.build(this.objectName, query);
|
|
95
|
-
// Use query compiler to cache and optimize the AST
|
|
96
|
-
const compiled = ObjectRepository.queryCompiler.compile(this.objectName, ast);
|
|
97
|
-
return compiled.ast;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
41
|
getSchema(): ObjectConfig {
|
|
101
42
|
const obj = this.app.getObject(this.objectName);
|
|
102
43
|
if (!obj) {
|
|
@@ -129,148 +70,6 @@ export class ObjectRepository {
|
|
|
129
70
|
};
|
|
130
71
|
}
|
|
131
72
|
|
|
132
|
-
/**
|
|
133
|
-
* Validates a record against field-level and object-level validation rules.
|
|
134
|
-
* For updates, only fields present in the update payload are validated at the field level,
|
|
135
|
-
* while object-level rules use the merged record (previousRecord + updates).
|
|
136
|
-
*/
|
|
137
|
-
private async validateRecord(
|
|
138
|
-
operation: 'create' | 'update',
|
|
139
|
-
record: any,
|
|
140
|
-
previousRecord?: any
|
|
141
|
-
): Promise<void> {
|
|
142
|
-
const schema = this.getSchema();
|
|
143
|
-
const allResults: ValidationRuleResult[] = [];
|
|
144
|
-
|
|
145
|
-
// 1. Validate field-level rules
|
|
146
|
-
// For updates, only validate fields that are present in the update payload
|
|
147
|
-
for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
|
|
148
|
-
// Skip field validation for updates if the field is not in the update payload
|
|
149
|
-
if (operation === 'update' && !(fieldName in record)) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const value = record[fieldName];
|
|
154
|
-
const fieldResults = await this.getValidator().validateField(
|
|
155
|
-
fieldName,
|
|
156
|
-
fieldConfig,
|
|
157
|
-
value,
|
|
158
|
-
{
|
|
159
|
-
record,
|
|
160
|
-
previousRecord,
|
|
161
|
-
operation,
|
|
162
|
-
user: this.getUserFromContext(),
|
|
163
|
-
api: this.getHookAPI(),
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
allResults.push(...fieldResults);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 2. Validate object-level validation rules
|
|
170
|
-
if (schema.validation?.rules && schema.validation.rules.length > 0) {
|
|
171
|
-
// For updates, merge the update data with previous record to get the complete final state
|
|
172
|
-
const mergedRecord = operation === 'update' && previousRecord
|
|
173
|
-
? { ...previousRecord, ...record }
|
|
174
|
-
: record;
|
|
175
|
-
|
|
176
|
-
// Track which fields changed (using shallow comparison for performance)
|
|
177
|
-
// IMPORTANT: Shallow comparison does not detect changes in nested objects/arrays.
|
|
178
|
-
// If your validation rules rely on detecting changes in complex nested structures,
|
|
179
|
-
// you may need to implement custom change tracking in hooks.
|
|
180
|
-
const changedFields = previousRecord
|
|
181
|
-
? Object.keys(record).filter(key => record[key] !== previousRecord[key])
|
|
182
|
-
: undefined;
|
|
183
|
-
|
|
184
|
-
const validationContext: ValidationContext = {
|
|
185
|
-
record: mergedRecord,
|
|
186
|
-
previousRecord,
|
|
187
|
-
operation,
|
|
188
|
-
user: this.getUserFromContext(),
|
|
189
|
-
api: this.getHookAPI(),
|
|
190
|
-
changedFields,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const result = await this.getValidator().validate(schema.validation.rules, validationContext);
|
|
194
|
-
allResults.push(...result.results);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 3. Collect errors and throw if any
|
|
198
|
-
const errors = allResults.filter(r => !r.valid && r.severity === 'error');
|
|
199
|
-
if (errors.length > 0) {
|
|
200
|
-
const errorMessage = errors.map(e => e.message).join('; ');
|
|
201
|
-
throw new ValidationError(errorMessage, errors);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Evaluate formula fields for a record
|
|
207
|
-
* Adds computed formula field values to the record
|
|
208
|
-
*/
|
|
209
|
-
private evaluateFormulas(record: any): any {
|
|
210
|
-
const schema = this.getSchema();
|
|
211
|
-
const now = new Date();
|
|
212
|
-
|
|
213
|
-
// Build formula context
|
|
214
|
-
const formulaContext: FormulaContext = {
|
|
215
|
-
record,
|
|
216
|
-
system: {
|
|
217
|
-
today: new Date(now.getFullYear(), now.getMonth(), now.getDate()),
|
|
218
|
-
now: now,
|
|
219
|
-
year: now.getFullYear(),
|
|
220
|
-
month: now.getMonth() + 1,
|
|
221
|
-
day: now.getDate(),
|
|
222
|
-
hour: now.getHours(),
|
|
223
|
-
minute: now.getMinutes(),
|
|
224
|
-
second: now.getSeconds(),
|
|
225
|
-
},
|
|
226
|
-
current_user: {
|
|
227
|
-
id: this.context.userId || '',
|
|
228
|
-
// TODO: Retrieve actual user name from user object if available
|
|
229
|
-
name: undefined,
|
|
230
|
-
email: undefined,
|
|
231
|
-
role: this.context.roles?.[0],
|
|
232
|
-
},
|
|
233
|
-
is_new: false,
|
|
234
|
-
record_id: record._id || record.id,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// Evaluate each formula field
|
|
238
|
-
for (const [fieldName, fieldConfig] of Object.entries(schema.fields)) {
|
|
239
|
-
const formulaExpression = fieldConfig.expression;
|
|
240
|
-
|
|
241
|
-
if (fieldConfig.type === 'formula' && formulaExpression) {
|
|
242
|
-
const result = this.getFormulaEngine().evaluate(
|
|
243
|
-
formulaExpression,
|
|
244
|
-
formulaContext,
|
|
245
|
-
fieldConfig.data_type || 'text',
|
|
246
|
-
{ strict: true }
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
if (result.success) {
|
|
250
|
-
record[fieldName] = result.value;
|
|
251
|
-
} else {
|
|
252
|
-
// In case of error, set to null and log for diagnostics
|
|
253
|
-
record[fieldName] = null;
|
|
254
|
-
// Formula evaluation should not throw here, but we need observability
|
|
255
|
-
// This logging is intentionally minimal and side-effect free
|
|
256
|
-
|
|
257
|
-
console.error(
|
|
258
|
-
'[ObjectQL][FormulaEngine] Formula evaluation failed',
|
|
259
|
-
{
|
|
260
|
-
objectName: this.objectName,
|
|
261
|
-
fieldName,
|
|
262
|
-
recordId: formulaContext.record_id,
|
|
263
|
-
expression: formulaExpression,
|
|
264
|
-
error: result.error,
|
|
265
|
-
stack: result.stack,
|
|
266
|
-
}
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return record;
|
|
273
|
-
}
|
|
274
73
|
|
|
275
74
|
async find(query: UnifiedQuery = {}): Promise<any[]> {
|
|
276
75
|
const hookCtx: RetrievalHookContext = {
|
|
@@ -284,15 +83,12 @@ export class ObjectRepository {
|
|
|
284
83
|
};
|
|
285
84
|
await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
|
|
286
85
|
|
|
287
|
-
//
|
|
288
|
-
const
|
|
289
|
-
const kernelResult = await (this.getKernel() as any).find(this.objectName, ast);
|
|
86
|
+
// Execute via kernel (delegates to QueryService)
|
|
87
|
+
const kernelResult = await (this.getKernel() as any).find(this.objectName, hookCtx.query || {});
|
|
290
88
|
const results = kernelResult.value;
|
|
291
89
|
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
hookCtx.result = resultsWithFormulas;
|
|
90
|
+
// Formula evaluation moved to FormulaPlugin hook
|
|
91
|
+
hookCtx.result = results;
|
|
296
92
|
await this.app.triggerHook('afterFind', this.objectName, hookCtx);
|
|
297
93
|
|
|
298
94
|
return hookCtx.result as any[];
|
|
@@ -314,10 +110,8 @@ export class ObjectRepository {
|
|
|
314
110
|
// Use kernel.get() for direct ID lookup
|
|
315
111
|
const result = await (this.getKernel() as any).get(this.objectName, String(idOrQuery));
|
|
316
112
|
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
hookCtx.result = resultWithFormulas;
|
|
113
|
+
// Formula evaluation moved to FormulaPlugin hook
|
|
114
|
+
hookCtx.result = result;
|
|
321
115
|
await this.app.triggerHook('afterFind', this.objectName, hookCtx);
|
|
322
116
|
return hookCtx.result;
|
|
323
117
|
} else {
|
|
@@ -354,10 +148,8 @@ export class ObjectRepository {
|
|
|
354
148
|
};
|
|
355
149
|
await this.app.triggerHook('beforeCount', this.objectName, hookCtx);
|
|
356
150
|
|
|
357
|
-
//
|
|
358
|
-
const
|
|
359
|
-
const kernelResult = await (this.getKernel() as any).find(this.objectName, ast);
|
|
360
|
-
const result = kernelResult.count;
|
|
151
|
+
// Execute via kernel (delegates to QueryService)
|
|
152
|
+
const result = await (this.getKernel() as any).count(this.objectName, hookCtx.query || {});
|
|
361
153
|
|
|
362
154
|
hookCtx.result = result;
|
|
363
155
|
await this.app.triggerHook('afterCount', this.objectName, hookCtx);
|
|
@@ -380,8 +172,7 @@ export class ObjectRepository {
|
|
|
380
172
|
if (this.context.userId) finalDoc.created_by = this.context.userId;
|
|
381
173
|
if (this.context.spaceId) finalDoc.space_id = this.context.spaceId;
|
|
382
174
|
|
|
383
|
-
//
|
|
384
|
-
await this.validateRecord('create', finalDoc);
|
|
175
|
+
// Validation moved to ValidatorPlugin hook
|
|
385
176
|
|
|
386
177
|
// Execute via kernel
|
|
387
178
|
const result = await (this.getKernel() as any).create(this.objectName, finalDoc, this.getOptions());
|
|
@@ -407,8 +198,7 @@ export class ObjectRepository {
|
|
|
407
198
|
};
|
|
408
199
|
await this.app.triggerHook('beforeUpdate', this.objectName, hookCtx);
|
|
409
200
|
|
|
410
|
-
//
|
|
411
|
-
await this.validateRecord('update', hookCtx.data, previousData);
|
|
201
|
+
// Validation moved to ValidatorPlugin hook
|
|
412
202
|
|
|
413
203
|
// Execute via kernel
|
|
414
204
|
const result = await (this.getKernel() as any).update(this.objectName, String(id), hookCtx.data, this.getOptions());
|
package/test/app.test.ts
CHANGED
|
@@ -295,12 +295,12 @@ describe('ObjectQL App', () => {
|
|
|
295
295
|
|
|
296
296
|
describe('Plugin System', () => {
|
|
297
297
|
it('should initialize runtime plugins on init', async () => {
|
|
298
|
-
const
|
|
299
|
-
const
|
|
298
|
+
const installFn = jest.fn();
|
|
299
|
+
const onStartFn = jest.fn();
|
|
300
300
|
const mockPlugin = {
|
|
301
301
|
name: 'test-plugin',
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
install: installFn,
|
|
303
|
+
onStart: onStartFn
|
|
304
304
|
};
|
|
305
305
|
|
|
306
306
|
const app = new ObjectQL({
|
|
@@ -309,8 +309,8 @@ describe('ObjectQL App', () => {
|
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
await app.init();
|
|
312
|
-
expect(
|
|
313
|
-
expect(
|
|
312
|
+
expect(installFn).toHaveBeenCalled();
|
|
313
|
+
expect(onStartFn).toHaveBeenCalled();
|
|
314
314
|
});
|
|
315
315
|
|
|
316
316
|
it('should use plugin method', () => {
|