@objectql/core 4.0.1 → 4.0.3
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 +4 -0
- package/CHANGELOG.md +32 -0
- package/README.md +14 -12
- package/dist/app.d.ts +9 -6
- package/dist/app.js +151 -29
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/optimizations/CompiledHookManager.d.ts +55 -0
- package/dist/optimizations/CompiledHookManager.js +164 -0
- package/dist/optimizations/CompiledHookManager.js.map +1 -0
- package/dist/optimizations/DependencyGraph.d.ts +82 -0
- package/dist/optimizations/DependencyGraph.js +211 -0
- package/dist/optimizations/DependencyGraph.js.map +1 -0
- package/dist/optimizations/GlobalConnectionPool.d.ts +89 -0
- package/dist/optimizations/GlobalConnectionPool.js +193 -0
- package/dist/optimizations/GlobalConnectionPool.js.map +1 -0
- package/dist/optimizations/LazyMetadataLoader.d.ts +75 -0
- package/dist/optimizations/LazyMetadataLoader.js +149 -0
- package/dist/optimizations/LazyMetadataLoader.js.map +1 -0
- package/dist/optimizations/OptimizedMetadataRegistry.d.ts +26 -0
- package/dist/optimizations/OptimizedMetadataRegistry.js +117 -0
- package/dist/optimizations/OptimizedMetadataRegistry.js.map +1 -0
- package/dist/optimizations/OptimizedValidationEngine.d.ts +73 -0
- package/dist/optimizations/OptimizedValidationEngine.js +141 -0
- package/dist/optimizations/OptimizedValidationEngine.js.map +1 -0
- package/dist/optimizations/QueryCompiler.d.ts +51 -0
- package/dist/optimizations/QueryCompiler.js +216 -0
- package/dist/optimizations/QueryCompiler.js.map +1 -0
- package/dist/optimizations/SQLQueryOptimizer.d.ts +96 -0
- package/dist/optimizations/SQLQueryOptimizer.js +265 -0
- package/dist/optimizations/SQLQueryOptimizer.js.map +1 -0
- package/dist/optimizations/index.d.ts +32 -0
- package/dist/optimizations/index.js +44 -0
- package/dist/optimizations/index.js.map +1 -0
- package/dist/plugin.d.ts +6 -7
- package/dist/plugin.js +39 -22
- package/dist/plugin.js.map +1 -1
- package/dist/query/filter-translator.d.ts +6 -18
- package/dist/query/filter-translator.js +6 -103
- package/dist/query/filter-translator.js.map +1 -1
- package/dist/query/query-analyzer.js +24 -25
- package/dist/query/query-analyzer.js.map +1 -1
- package/dist/query/query-builder.d.ts +9 -3
- package/dist/query/query-builder.js +25 -35
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/query-service.d.ts +2 -2
- package/dist/query/query-service.js +5 -5
- package/dist/query/query-service.js.map +1 -1
- package/dist/repository.d.ts +2 -0
- package/dist/repository.js +24 -17
- package/dist/repository.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +8 -5
- package/src/app.ts +173 -47
- package/src/index.ts +7 -8
- package/src/optimizations/CompiledHookManager.ts +185 -0
- package/src/optimizations/DependencyGraph.ts +255 -0
- package/src/optimizations/GlobalConnectionPool.ts +251 -0
- package/src/optimizations/LazyMetadataLoader.ts +180 -0
- package/src/optimizations/OptimizedMetadataRegistry.ts +132 -0
- package/src/optimizations/OptimizedValidationEngine.ts +172 -0
- package/src/optimizations/QueryCompiler.ts +242 -0
- package/src/optimizations/SQLQueryOptimizer.ts +329 -0
- package/src/optimizations/index.ts +34 -0
- package/src/plugin.ts +51 -28
- package/src/query/filter-translator.ts +8 -115
- package/src/query/query-analyzer.ts +25 -29
- package/src/query/query-builder.ts +26 -43
- package/src/query/query-service.ts +6 -6
- package/src/repository.ts +35 -22
- package/test/__mocks__/@objectstack/runtime.ts +8 -8
- package/test/app.test.ts +11 -8
- package/test/optimizations.test.ts +440 -0
- package/test/plugin-integration.test.ts +30 -19
- package/tsconfig.json +4 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/ai-agent.d.ts +0 -176
- package/dist/ai-agent.js +0 -722
- package/dist/ai-agent.js.map +0 -1
- package/dist/formula-engine.d.ts +0 -102
- package/dist/formula-engine.js +0 -433
- package/dist/formula-engine.js.map +0 -1
- package/dist/formula-plugin.d.ts +0 -52
- package/dist/formula-plugin.js +0 -107
- package/dist/formula-plugin.js.map +0 -1
- package/dist/validator-plugin.d.ts +0 -56
- package/dist/validator-plugin.js +0 -106
- package/dist/validator-plugin.js.map +0 -1
- package/dist/validator.d.ts +0 -80
- package/dist/validator.js +0 -625
- package/dist/validator.js.map +0 -1
- package/src/ai-agent.ts +0 -868
- package/src/formula-engine.ts +0 -572
- package/src/formula-plugin.ts +0 -141
- package/src/validator-plugin.ts +0 -140
- package/src/validator.ts +0 -743
- package/test/formula-engine.test.ts +0 -725
- package/test/formula-integration.test.ts +0 -286
- package/test/formula-plugin.test.ts +0 -197
- package/test/validator-plugin.test.ts +0 -126
- package/test/validator.test.ts +0 -440
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectQL
|
|
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
|
-
/**
|
|
10
|
-
* Formula Integration Tests
|
|
11
|
-
*
|
|
12
|
-
* Tests formula evaluation within repository queries
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { ObjectQL } from '../src/app';
|
|
16
|
-
import { MockDriver } from './mock-driver';
|
|
17
|
-
|
|
18
|
-
describe('Formula Integration', () => {
|
|
19
|
-
let app: ObjectQL;
|
|
20
|
-
let mockDriver: MockDriver;
|
|
21
|
-
|
|
22
|
-
beforeEach(async () => {
|
|
23
|
-
mockDriver = new MockDriver();
|
|
24
|
-
|
|
25
|
-
app = new ObjectQL({
|
|
26
|
-
datasources: {
|
|
27
|
-
default: mockDriver
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Register an object with formula fields
|
|
32
|
-
app.registerObject({
|
|
33
|
-
name: 'contact',
|
|
34
|
-
fields: {
|
|
35
|
-
first_name: {
|
|
36
|
-
type: 'text',
|
|
37
|
-
required: true,
|
|
38
|
-
},
|
|
39
|
-
last_name: {
|
|
40
|
-
type: 'text',
|
|
41
|
-
required: true,
|
|
42
|
-
},
|
|
43
|
-
full_name: {
|
|
44
|
-
type: 'formula',
|
|
45
|
-
formula: 'first_name + " " + last_name',
|
|
46
|
-
data_type: 'text',
|
|
47
|
-
label: 'Full Name',
|
|
48
|
-
},
|
|
49
|
-
quantity: {
|
|
50
|
-
type: 'number',
|
|
51
|
-
},
|
|
52
|
-
unit_price: {
|
|
53
|
-
type: 'currency',
|
|
54
|
-
},
|
|
55
|
-
total: {
|
|
56
|
-
type: 'formula',
|
|
57
|
-
formula: 'quantity * unit_price',
|
|
58
|
-
data_type: 'currency',
|
|
59
|
-
label: 'Total',
|
|
60
|
-
},
|
|
61
|
-
is_active: {
|
|
62
|
-
type: 'boolean',
|
|
63
|
-
},
|
|
64
|
-
status_label: {
|
|
65
|
-
type: 'formula',
|
|
66
|
-
formula: 'is_active ? "Active" : "Inactive"',
|
|
67
|
-
data_type: 'text',
|
|
68
|
-
label: 'Status',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
await app.init();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('Formula Evaluation in Queries', () => {
|
|
77
|
-
it('should evaluate formula fields in find results', async () => {
|
|
78
|
-
// Setup mock data
|
|
79
|
-
mockDriver.setMockData('contact', [
|
|
80
|
-
{
|
|
81
|
-
_id: '1',
|
|
82
|
-
first_name: 'John',
|
|
83
|
-
last_name: 'Doe',
|
|
84
|
-
quantity: 10,
|
|
85
|
-
unit_price: 25.5,
|
|
86
|
-
is_active: true,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
_id: '2',
|
|
90
|
-
first_name: 'Jane',
|
|
91
|
-
last_name: 'Smith',
|
|
92
|
-
quantity: 5,
|
|
93
|
-
unit_price: 30,
|
|
94
|
-
is_active: false,
|
|
95
|
-
},
|
|
96
|
-
]);
|
|
97
|
-
|
|
98
|
-
const ctx = app.createContext({ isSystem: true });
|
|
99
|
-
const results = await ctx.object('contact').find({});
|
|
100
|
-
|
|
101
|
-
expect(results).toHaveLength(2);
|
|
102
|
-
|
|
103
|
-
// Check first record
|
|
104
|
-
expect(results[0].full_name).toBe('John Doe');
|
|
105
|
-
expect(results[0].total).toBe(255); // 10 * 25.5
|
|
106
|
-
expect(results[0].status_label).toBe('Active');
|
|
107
|
-
|
|
108
|
-
// Check second record
|
|
109
|
-
expect(results[1].full_name).toBe('Jane Smith');
|
|
110
|
-
expect(results[1].total).toBe(150); // 5 * 30
|
|
111
|
-
expect(results[1].status_label).toBe('Inactive');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should evaluate formula fields in findOne result', async () => {
|
|
115
|
-
mockDriver.setMockData('contact', [
|
|
116
|
-
{
|
|
117
|
-
_id: '1',
|
|
118
|
-
first_name: 'John',
|
|
119
|
-
last_name: 'Doe',
|
|
120
|
-
quantity: 10,
|
|
121
|
-
unit_price: 25.5,
|
|
122
|
-
is_active: true,
|
|
123
|
-
},
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
const ctx = app.createContext({ isSystem: true });
|
|
127
|
-
const result = await ctx.object('contact').findOne('1');
|
|
128
|
-
|
|
129
|
-
expect(result).toBeDefined();
|
|
130
|
-
expect(result.full_name).toBe('John Doe');
|
|
131
|
-
expect(result.total).toBe(255);
|
|
132
|
-
expect(result.status_label).toBe('Active');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should handle null values in formulas', async () => {
|
|
136
|
-
mockDriver.setMockData('contact', [
|
|
137
|
-
{
|
|
138
|
-
_id: '1',
|
|
139
|
-
first_name: 'John',
|
|
140
|
-
last_name: 'Doe',
|
|
141
|
-
quantity: null,
|
|
142
|
-
unit_price: 25.5,
|
|
143
|
-
is_active: true,
|
|
144
|
-
},
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
const ctx = app.createContext({ isSystem: true });
|
|
148
|
-
const result = await ctx.object('contact').findOne('1');
|
|
149
|
-
|
|
150
|
-
expect(result).toBeDefined();
|
|
151
|
-
expect(result.full_name).toBe('John Doe');
|
|
152
|
-
// In JavaScript, null * number = 0 (null is coerced to 0)
|
|
153
|
-
expect(result.total).toBe(0);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('Complex Formula Examples', () => {
|
|
158
|
-
beforeEach(async () => {
|
|
159
|
-
// Register an object with more complex formulas
|
|
160
|
-
app.registerObject({
|
|
161
|
-
name: 'order',
|
|
162
|
-
fields: {
|
|
163
|
-
subtotal: { type: 'currency' },
|
|
164
|
-
discount_rate: { type: 'percent' },
|
|
165
|
-
tax_rate: { type: 'percent' },
|
|
166
|
-
final_price: {
|
|
167
|
-
type: 'formula',
|
|
168
|
-
formula: 'subtotal * (1 - discount_rate / 100) * (1 + tax_rate / 100)',
|
|
169
|
-
data_type: 'currency',
|
|
170
|
-
label: 'Final Price',
|
|
171
|
-
},
|
|
172
|
-
created_at: { type: 'date' },
|
|
173
|
-
status: { type: 'select', options: ['draft', 'confirmed', 'shipped'] },
|
|
174
|
-
risk_level: {
|
|
175
|
-
type: 'formula',
|
|
176
|
-
formula: `
|
|
177
|
-
if (subtotal > 10000) {
|
|
178
|
-
return 'High';
|
|
179
|
-
} else if (subtotal > 1000) {
|
|
180
|
-
return 'Medium';
|
|
181
|
-
} else {
|
|
182
|
-
return 'Low';
|
|
183
|
-
}
|
|
184
|
-
`,
|
|
185
|
-
data_type: 'text',
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await app.init();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should calculate complex financial formulas', async () => {
|
|
194
|
-
mockDriver.setMockData('order', [
|
|
195
|
-
{
|
|
196
|
-
_id: '1',
|
|
197
|
-
subtotal: 5000,
|
|
198
|
-
discount_rate: 10,
|
|
199
|
-
tax_rate: 8,
|
|
200
|
-
status: 'confirmed',
|
|
201
|
-
created_at: new Date('2026-01-01'),
|
|
202
|
-
},
|
|
203
|
-
]);
|
|
204
|
-
|
|
205
|
-
const ctx = app.createContext({ isSystem: true });
|
|
206
|
-
const result = await ctx.object('order').findOne('1');
|
|
207
|
-
|
|
208
|
-
expect(result).toBeDefined();
|
|
209
|
-
expect(result.final_price).toBeCloseTo(4860, 1);
|
|
210
|
-
expect(result.risk_level).toBe('Medium');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should handle conditional logic in formulas', async () => {
|
|
214
|
-
mockDriver.setMockData('order', [
|
|
215
|
-
{
|
|
216
|
-
_id: '1',
|
|
217
|
-
subtotal: 500,
|
|
218
|
-
discount_rate: 0,
|
|
219
|
-
tax_rate: 0,
|
|
220
|
-
status: 'draft',
|
|
221
|
-
created_at: new Date('2026-01-01'),
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
_id: '2',
|
|
225
|
-
subtotal: 5000,
|
|
226
|
-
discount_rate: 0,
|
|
227
|
-
tax_rate: 0,
|
|
228
|
-
status: 'confirmed',
|
|
229
|
-
created_at: new Date('2026-01-01'),
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
_id: '3',
|
|
233
|
-
subtotal: 15000,
|
|
234
|
-
discount_rate: 0,
|
|
235
|
-
tax_rate: 0,
|
|
236
|
-
status: 'shipped',
|
|
237
|
-
created_at: new Date('2026-01-01'),
|
|
238
|
-
},
|
|
239
|
-
]);
|
|
240
|
-
|
|
241
|
-
const ctx = app.createContext({ isSystem: true });
|
|
242
|
-
const results = await ctx.object('order').find({});
|
|
243
|
-
|
|
244
|
-
expect(results[0].risk_level).toBe('Low');
|
|
245
|
-
expect(results[1].risk_level).toBe('Medium');
|
|
246
|
-
expect(results[2].risk_level).toBe('High');
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
describe('Formula Error Handling', () => {
|
|
251
|
-
beforeEach(async () => {
|
|
252
|
-
app.registerObject({
|
|
253
|
-
name: 'product',
|
|
254
|
-
fields: {
|
|
255
|
-
name: { type: 'text' },
|
|
256
|
-
price: { type: 'currency' },
|
|
257
|
-
invalid_formula: {
|
|
258
|
-
type: 'formula',
|
|
259
|
-
formula: 'nonexistent_field * 2',
|
|
260
|
-
data_type: 'number',
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
await app.init();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should handle formula evaluation errors gracefully', async () => {
|
|
269
|
-
mockDriver.setMockData('product', [
|
|
270
|
-
{
|
|
271
|
-
_id: '1',
|
|
272
|
-
name: 'Widget',
|
|
273
|
-
price: 100,
|
|
274
|
-
},
|
|
275
|
-
]);
|
|
276
|
-
|
|
277
|
-
const ctx = app.createContext({ isSystem: true });
|
|
278
|
-
const result = await ctx.object('product').findOne('1');
|
|
279
|
-
|
|
280
|
-
expect(result).toBeDefined();
|
|
281
|
-
expect(result.name).toBe('Widget');
|
|
282
|
-
// Formula failed, should be null
|
|
283
|
-
expect(result.invalid_formula).toBeNull();
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
});
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectQL Formula Plugin Tests
|
|
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 { FormulaPlugin } from '../src/formula-plugin';
|
|
10
|
-
import { ObjectStackKernel } from '@objectql/runtime';
|
|
11
|
-
|
|
12
|
-
describe('FormulaPlugin', () => {
|
|
13
|
-
let plugin: FormulaPlugin;
|
|
14
|
-
let mockKernel: any;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create a mock kernel with middleware and formula provider support
|
|
18
|
-
mockKernel = {
|
|
19
|
-
use: jest.fn(),
|
|
20
|
-
registerFormulaProvider: jest.fn(),
|
|
21
|
-
};
|
|
22
|
-
plugin = new FormulaPlugin();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('Plugin Metadata', () => {
|
|
26
|
-
it('should have correct name and version', () => {
|
|
27
|
-
expect(plugin.name).toBe('@objectql/formulas');
|
|
28
|
-
expect(plugin.version).toBe('4.0.0');
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('Constructor', () => {
|
|
33
|
-
it('should create plugin with default config', () => {
|
|
34
|
-
const defaultPlugin = new FormulaPlugin();
|
|
35
|
-
expect(defaultPlugin).toBeDefined();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should create plugin with custom config', () => {
|
|
39
|
-
const customPlugin = new FormulaPlugin({
|
|
40
|
-
enable_cache: true,
|
|
41
|
-
cache_ttl: 600,
|
|
42
|
-
autoEvaluateOnQuery: false,
|
|
43
|
-
});
|
|
44
|
-
expect(customPlugin).toBeDefined();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should accept formula engine config', () => {
|
|
48
|
-
const customPlugin = new FormulaPlugin({
|
|
49
|
-
enable_monitoring: true,
|
|
50
|
-
max_execution_time: 5000,
|
|
51
|
-
});
|
|
52
|
-
expect(customPlugin).toBeDefined();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('Installation', () => {
|
|
57
|
-
it('should install successfully with mock kernel', async () => {
|
|
58
|
-
const ctx = { engine: mockKernel };
|
|
59
|
-
await plugin.install(ctx);
|
|
60
|
-
|
|
61
|
-
// Verify that formula provider was registered
|
|
62
|
-
expect(mockKernel.registerFormulaProvider).toHaveBeenCalled();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should register formula provider with evaluate function', async () => {
|
|
66
|
-
const ctx = { engine: mockKernel };
|
|
67
|
-
await plugin.install(ctx);
|
|
68
|
-
|
|
69
|
-
expect(mockKernel.registerFormulaProvider).toHaveBeenCalledWith(
|
|
70
|
-
expect.objectContaining({
|
|
71
|
-
evaluate: expect.any(Function),
|
|
72
|
-
validate: expect.any(Function),
|
|
73
|
-
extractMetadata: expect.any(Function),
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should register formula middleware when auto-evaluation is enabled', async () => {
|
|
79
|
-
const pluginWithAuto = new FormulaPlugin({ autoEvaluateOnQuery: true });
|
|
80
|
-
const ctx = { engine: mockKernel };
|
|
81
|
-
|
|
82
|
-
await pluginWithAuto.install(ctx);
|
|
83
|
-
|
|
84
|
-
// Check that middleware was registered
|
|
85
|
-
expect(mockKernel.use).toHaveBeenCalledWith('afterQuery', expect.any(Function));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should not register formula middleware when auto-evaluation is disabled', async () => {
|
|
89
|
-
const pluginNoAuto = new FormulaPlugin({ autoEvaluateOnQuery: false });
|
|
90
|
-
const ctx = { engine: mockKernel };
|
|
91
|
-
|
|
92
|
-
await pluginNoAuto.install(ctx);
|
|
93
|
-
|
|
94
|
-
// Should not have registered afterQuery hook
|
|
95
|
-
const afterQueryCalls = mockKernel.use.mock.calls.filter(
|
|
96
|
-
(call: any[]) => call[0] === 'afterQuery'
|
|
97
|
-
);
|
|
98
|
-
expect(afterQueryCalls.length).toBe(0);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should handle kernel without registerFormulaProvider', async () => {
|
|
102
|
-
const kernelNoProvider = {
|
|
103
|
-
use: jest.fn(),
|
|
104
|
-
};
|
|
105
|
-
const ctx = { engine: kernelNoProvider };
|
|
106
|
-
|
|
107
|
-
// Should not throw error and should set formulaEngine property
|
|
108
|
-
await expect(plugin.install(ctx)).resolves.not.toThrow();
|
|
109
|
-
expect((kernelNoProvider as any).formulaEngine).toBeDefined();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should handle kernel without middleware support', async () => {
|
|
113
|
-
const kernelNoMiddleware = {
|
|
114
|
-
registerFormulaProvider: jest.fn(),
|
|
115
|
-
};
|
|
116
|
-
const ctx = { engine: kernelNoMiddleware };
|
|
117
|
-
|
|
118
|
-
// Should not throw error
|
|
119
|
-
await expect(plugin.install(ctx)).resolves.not.toThrow();
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('Formula Provider', () => {
|
|
124
|
-
it('should provide evaluate function that works', async () => {
|
|
125
|
-
const ctx = { engine: mockKernel };
|
|
126
|
-
await plugin.install(ctx);
|
|
127
|
-
|
|
128
|
-
// Get the registered provider
|
|
129
|
-
const provider = mockKernel.registerFormulaProvider.mock.calls[0][0];
|
|
130
|
-
|
|
131
|
-
// Test the evaluate function
|
|
132
|
-
const result = provider.evaluate('1 + 1', {
|
|
133
|
-
record: {},
|
|
134
|
-
system: {
|
|
135
|
-
today: new Date(),
|
|
136
|
-
now: new Date(),
|
|
137
|
-
year: 2026,
|
|
138
|
-
month: 1,
|
|
139
|
-
day: 22,
|
|
140
|
-
hour: 12,
|
|
141
|
-
minute: 0,
|
|
142
|
-
second: 0,
|
|
143
|
-
},
|
|
144
|
-
current_user: {},
|
|
145
|
-
is_new: false,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
expect(result).toBeDefined();
|
|
149
|
-
expect(result.success).toBe(true);
|
|
150
|
-
// The result is coerced to 'text' by default, so it's a string "2"
|
|
151
|
-
expect(result.value).toBe('2');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should provide validate function that works', async () => {
|
|
155
|
-
const ctx = { engine: mockKernel };
|
|
156
|
-
await plugin.install(ctx);
|
|
157
|
-
|
|
158
|
-
// Get the registered provider
|
|
159
|
-
const provider = mockKernel.registerFormulaProvider.mock.calls[0][0];
|
|
160
|
-
|
|
161
|
-
// Test the validate function
|
|
162
|
-
const result = provider.validate('quantity * price');
|
|
163
|
-
|
|
164
|
-
expect(result).toBeDefined();
|
|
165
|
-
expect(result.valid).toBe(true);
|
|
166
|
-
expect(result.errors.length).toBe(0);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should provide extractMetadata function that works', async () => {
|
|
170
|
-
const ctx = { engine: mockKernel };
|
|
171
|
-
await plugin.install(ctx);
|
|
172
|
-
|
|
173
|
-
// Get the registered provider
|
|
174
|
-
const provider = mockKernel.registerFormulaProvider.mock.calls[0][0];
|
|
175
|
-
|
|
176
|
-
// Test the extractMetadata function
|
|
177
|
-
const metadata = provider.extractMetadata('total', 'quantity * price', 'number');
|
|
178
|
-
|
|
179
|
-
expect(metadata).toBeDefined();
|
|
180
|
-
expect(metadata.field_name).toBe('total');
|
|
181
|
-
expect(metadata.expression).toBe('quantity * price');
|
|
182
|
-
expect(metadata.data_type).toBe('number');
|
|
183
|
-
expect(metadata.dependencies).toContain('quantity');
|
|
184
|
-
expect(metadata.dependencies).toContain('price');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('Engine Access', () => {
|
|
189
|
-
it('should expose formula engine instance', () => {
|
|
190
|
-
const engine = plugin.getEngine();
|
|
191
|
-
expect(engine).toBeDefined();
|
|
192
|
-
expect(typeof engine.evaluate).toBe('function');
|
|
193
|
-
expect(typeof engine.validate).toBe('function');
|
|
194
|
-
expect(typeof engine.extractMetadata).toBe('function');
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectQL Validator Plugin Tests
|
|
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 { ValidatorPlugin } from '../src/validator-plugin';
|
|
10
|
-
import { ObjectStackKernel } from '@objectql/runtime';
|
|
11
|
-
|
|
12
|
-
describe('ValidatorPlugin', () => {
|
|
13
|
-
let plugin: ValidatorPlugin;
|
|
14
|
-
let mockKernel: any;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create a mock kernel with middleware support
|
|
18
|
-
mockKernel = {
|
|
19
|
-
use: jest.fn(),
|
|
20
|
-
};
|
|
21
|
-
plugin = new ValidatorPlugin();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('Plugin Metadata', () => {
|
|
25
|
-
it('should have correct name and version', () => {
|
|
26
|
-
expect(plugin.name).toBe('@objectql/validator');
|
|
27
|
-
expect(plugin.version).toBe('4.0.0');
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('Constructor', () => {
|
|
32
|
-
it('should create plugin with default config', () => {
|
|
33
|
-
const defaultPlugin = new ValidatorPlugin();
|
|
34
|
-
expect(defaultPlugin).toBeDefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should create plugin with custom config', () => {
|
|
38
|
-
const customPlugin = new ValidatorPlugin({
|
|
39
|
-
language: 'zh-CN',
|
|
40
|
-
enableQueryValidation: false,
|
|
41
|
-
enableMutationValidation: true,
|
|
42
|
-
});
|
|
43
|
-
expect(customPlugin).toBeDefined();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should accept language options', () => {
|
|
47
|
-
const customPlugin = new ValidatorPlugin({
|
|
48
|
-
language: 'fr',
|
|
49
|
-
languageFallback: ['en', 'zh-CN'],
|
|
50
|
-
});
|
|
51
|
-
expect(customPlugin).toBeDefined();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('Installation', () => {
|
|
56
|
-
it('should install successfully with mock kernel', async () => {
|
|
57
|
-
const ctx = { engine: mockKernel };
|
|
58
|
-
await plugin.install(ctx);
|
|
59
|
-
|
|
60
|
-
// Verify that middleware hooks were registered
|
|
61
|
-
expect(mockKernel.use).toHaveBeenCalled();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should register query validation when enabled', async () => {
|
|
65
|
-
const pluginWithQuery = new ValidatorPlugin({ enableQueryValidation: true });
|
|
66
|
-
const ctx = { engine: mockKernel };
|
|
67
|
-
|
|
68
|
-
await pluginWithQuery.install(ctx);
|
|
69
|
-
|
|
70
|
-
// Check that use was called (for query validation)
|
|
71
|
-
expect(mockKernel.use).toHaveBeenCalledWith('beforeQuery', expect.any(Function));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should register mutation validation when enabled', async () => {
|
|
75
|
-
const pluginWithMutation = new ValidatorPlugin({ enableMutationValidation: true });
|
|
76
|
-
const ctx = { engine: mockKernel };
|
|
77
|
-
|
|
78
|
-
await pluginWithMutation.install(ctx);
|
|
79
|
-
|
|
80
|
-
// Check that use was called (for mutation validation)
|
|
81
|
-
expect(mockKernel.use).toHaveBeenCalledWith('beforeMutation', expect.any(Function));
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should not register query validation when disabled', async () => {
|
|
85
|
-
const pluginNoQuery = new ValidatorPlugin({ enableQueryValidation: false });
|
|
86
|
-
const ctx = { engine: mockKernel };
|
|
87
|
-
|
|
88
|
-
await pluginNoQuery.install(ctx);
|
|
89
|
-
|
|
90
|
-
// Should not have registered beforeQuery hook
|
|
91
|
-
const beforeQueryCalls = mockKernel.use.mock.calls.filter(
|
|
92
|
-
(call: any[]) => call[0] === 'beforeQuery'
|
|
93
|
-
);
|
|
94
|
-
expect(beforeQueryCalls.length).toBe(0);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should not register mutation validation when disabled', async () => {
|
|
98
|
-
const pluginNoMutation = new ValidatorPlugin({ enableMutationValidation: false });
|
|
99
|
-
const ctx = { engine: mockKernel };
|
|
100
|
-
|
|
101
|
-
await pluginNoMutation.install(ctx);
|
|
102
|
-
|
|
103
|
-
// Should not have registered beforeMutation hook
|
|
104
|
-
const beforeMutationCalls = mockKernel.use.mock.calls.filter(
|
|
105
|
-
(call: any[]) => call[0] === 'beforeMutation'
|
|
106
|
-
);
|
|
107
|
-
expect(beforeMutationCalls.length).toBe(0);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle kernel without middleware support', async () => {
|
|
111
|
-
const kernelNoMiddleware = {};
|
|
112
|
-
const ctx = { engine: kernelNoMiddleware };
|
|
113
|
-
|
|
114
|
-
// Should not throw error
|
|
115
|
-
await expect(plugin.install(ctx)).resolves.not.toThrow();
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('Validator Access', () => {
|
|
120
|
-
it('should expose validator instance', () => {
|
|
121
|
-
const validator = plugin.getValidator();
|
|
122
|
-
expect(validator).toBeDefined();
|
|
123
|
-
expect(typeof validator.validate).toBe('function');
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|