@objectql/core 4.0.2 → 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.
Files changed (98) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +4 -4
  4. package/dist/app.d.ts +9 -6
  5. package/dist/app.js +151 -29
  6. package/dist/app.js.map +1 -1
  7. package/dist/index.d.ts +6 -9
  8. package/dist/index.js +2 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/optimizations/CompiledHookManager.d.ts +55 -0
  11. package/dist/optimizations/CompiledHookManager.js +164 -0
  12. package/dist/optimizations/CompiledHookManager.js.map +1 -0
  13. package/dist/optimizations/DependencyGraph.d.ts +82 -0
  14. package/dist/optimizations/DependencyGraph.js +211 -0
  15. package/dist/optimizations/DependencyGraph.js.map +1 -0
  16. package/dist/optimizations/GlobalConnectionPool.d.ts +89 -0
  17. package/dist/optimizations/GlobalConnectionPool.js +193 -0
  18. package/dist/optimizations/GlobalConnectionPool.js.map +1 -0
  19. package/dist/optimizations/LazyMetadataLoader.d.ts +75 -0
  20. package/dist/optimizations/LazyMetadataLoader.js +149 -0
  21. package/dist/optimizations/LazyMetadataLoader.js.map +1 -0
  22. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +26 -0
  23. package/dist/optimizations/OptimizedMetadataRegistry.js +117 -0
  24. package/dist/optimizations/OptimizedMetadataRegistry.js.map +1 -0
  25. package/dist/optimizations/OptimizedValidationEngine.d.ts +73 -0
  26. package/dist/optimizations/OptimizedValidationEngine.js +141 -0
  27. package/dist/optimizations/OptimizedValidationEngine.js.map +1 -0
  28. package/dist/optimizations/QueryCompiler.d.ts +51 -0
  29. package/dist/optimizations/QueryCompiler.js +216 -0
  30. package/dist/optimizations/QueryCompiler.js.map +1 -0
  31. package/dist/optimizations/SQLQueryOptimizer.d.ts +96 -0
  32. package/dist/optimizations/SQLQueryOptimizer.js +265 -0
  33. package/dist/optimizations/SQLQueryOptimizer.js.map +1 -0
  34. package/dist/optimizations/index.d.ts +32 -0
  35. package/dist/optimizations/index.js +44 -0
  36. package/dist/optimizations/index.js.map +1 -0
  37. package/dist/plugin.d.ts +6 -7
  38. package/dist/plugin.js +39 -22
  39. package/dist/plugin.js.map +1 -1
  40. package/dist/query/query-analyzer.js.map +1 -1
  41. package/dist/query/query-builder.d.ts +6 -1
  42. package/dist/query/query-builder.js +21 -5
  43. package/dist/query/query-builder.js.map +1 -1
  44. package/dist/query/query-service.js.map +1 -1
  45. package/dist/repository.d.ts +2 -0
  46. package/dist/repository.js +15 -9
  47. package/dist/repository.js.map +1 -1
  48. package/jest.config.js +3 -3
  49. package/package.json +8 -5
  50. package/src/app.ts +173 -47
  51. package/src/index.ts +8 -9
  52. package/src/optimizations/CompiledHookManager.ts +185 -0
  53. package/src/optimizations/DependencyGraph.ts +255 -0
  54. package/src/optimizations/GlobalConnectionPool.ts +251 -0
  55. package/src/optimizations/LazyMetadataLoader.ts +180 -0
  56. package/src/optimizations/OptimizedMetadataRegistry.ts +132 -0
  57. package/src/optimizations/OptimizedValidationEngine.ts +172 -0
  58. package/src/optimizations/QueryCompiler.ts +242 -0
  59. package/src/optimizations/SQLQueryOptimizer.ts +329 -0
  60. package/src/optimizations/index.ts +34 -0
  61. package/src/plugin.ts +51 -28
  62. package/src/query/query-analyzer.ts +1 -1
  63. package/src/query/query-builder.ts +21 -7
  64. package/src/query/query-service.ts +1 -1
  65. package/src/repository.ts +25 -13
  66. package/test/__mocks__/@objectstack/runtime.ts +8 -8
  67. package/test/app.test.ts +9 -7
  68. package/test/optimizations.test.ts +440 -0
  69. package/test/plugin-integration.test.ts +30 -19
  70. package/tsconfig.json +4 -6
  71. package/tsconfig.tsbuildinfo +1 -1
  72. package/dist/ai-agent.d.ts +0 -176
  73. package/dist/ai-agent.js +0 -722
  74. package/dist/ai-agent.js.map +0 -1
  75. package/dist/formula-engine.d.ts +0 -102
  76. package/dist/formula-engine.js +0 -433
  77. package/dist/formula-engine.js.map +0 -1
  78. package/dist/formula-plugin.d.ts +0 -52
  79. package/dist/formula-plugin.js +0 -107
  80. package/dist/formula-plugin.js.map +0 -1
  81. package/dist/validator-plugin.d.ts +0 -56
  82. package/dist/validator-plugin.js +0 -106
  83. package/dist/validator-plugin.js.map +0 -1
  84. package/dist/validator.d.ts +0 -80
  85. package/dist/validator.js +0 -625
  86. package/dist/validator.js.map +0 -1
  87. package/src/ai-agent.ts +0 -868
  88. package/src/formula-engine.ts +0 -572
  89. package/src/formula-plugin.ts +0 -141
  90. package/src/validator-plugin.ts +0 -140
  91. package/src/validator.ts +0 -743
  92. package/test/formula-engine.test.ts +0 -725
  93. package/test/formula-integration.test.ts +0 -286
  94. package/test/formula-plugin.test.ts +0 -197
  95. package/test/formula-spec-compliance.test.ts +0 -258
  96. package/test/validation-spec-compliance.test.ts +0 -440
  97. package/test/validator-plugin.test.ts +0 -126
  98. package/test/validator.test.ts +0 -440
@@ -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
- });
@@ -1,440 +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
- import { Validator } from '../src/validator';
10
- import {
11
- ValidationContext,
12
- CrossFieldValidationRule,
13
- StateMachineValidationRule,
14
- UniquenessValidationRule,
15
- BusinessRuleValidationRule,
16
- CustomValidationRule,
17
- FieldConfig,
18
- } from '@objectql/types';
19
-
20
- describe('Validator', () => {
21
- let validator: Validator;
22
-
23
- beforeEach(() => {
24
- validator = new Validator();
25
- });
26
-
27
- describe('Constructor', () => {
28
- it('should create validator with default options', () => {
29
- expect(validator).toBeDefined();
30
- });
31
-
32
- it('should accept custom language option', () => {
33
- const customValidator = new Validator({ language: 'zh-CN' });
34
- expect(customValidator).toBeDefined();
35
- });
36
-
37
- it('should accept language fallback option', () => {
38
- const customValidator = new Validator({
39
- language: 'fr',
40
- languageFallback: ['en', 'zh-CN']
41
- });
42
- expect(customValidator).toBeDefined();
43
- });
44
- });
45
-
46
- describe('Field Validation', () => {
47
- describe('Required Fields', () => {
48
- it('should validate required field with value', async () => {
49
- const fieldConfig: FieldConfig = {
50
- type: 'text',
51
- required: true,
52
- };
53
-
54
- const context: ValidationContext = {
55
- operation: 'create',
56
- data: { name: 'John' },
57
- objectName: 'user',
58
- };
59
-
60
- const results = await validator.validateField('name', fieldConfig, 'John', context);
61
- expect(results).toEqual([]);
62
- });
63
-
64
- it('should fail validation for missing required field', async () => {
65
- const fieldConfig: FieldConfig = {
66
- type: 'text',
67
- required: true,
68
- label: 'Name',
69
- };
70
-
71
- const context: ValidationContext = {
72
- operation: 'create',
73
- data: {},
74
- objectName: 'user',
75
- };
76
-
77
- const results = await validator.validateField('name', fieldConfig, undefined, context);
78
- expect(results.length).toBeGreaterThan(0);
79
- expect(results[0].valid).toBe(false);
80
- expect(results[0].message).toContain('Name is required');
81
- });
82
-
83
- it('should fail validation for empty string in required field', async () => {
84
- const fieldConfig: FieldConfig = {
85
- type: 'text',
86
- required: true,
87
- };
88
-
89
- const context: ValidationContext = {
90
- operation: 'create',
91
- data: { name: '' },
92
- objectName: 'user',
93
- };
94
-
95
- const results = await validator.validateField('name', fieldConfig, '', context);
96
- expect(results.length).toBeGreaterThan(0);
97
- expect(results[0].valid).toBe(false);
98
- });
99
-
100
- it('should fail validation for null in required field', async () => {
101
- const fieldConfig: FieldConfig = {
102
- type: 'text',
103
- required: true,
104
- };
105
-
106
- const context: ValidationContext = {
107
- operation: 'create',
108
- data: { name: null },
109
- objectName: 'user',
110
- };
111
-
112
- const results = await validator.validateField('name', fieldConfig, null, context);
113
- expect(results.length).toBeGreaterThan(0);
114
- expect(results[0].valid).toBe(false);
115
- });
116
- });
117
-
118
- describe('Email Validation', () => {
119
- it('should validate correct email format', async () => {
120
- const fieldConfig: FieldConfig = {
121
- type: 'email',
122
- validation: { format: 'email' },
123
- };
124
-
125
- const context: ValidationContext = {
126
- operation: 'create',
127
- data: { email: 'test@example.com' },
128
- objectName: 'user',
129
- };
130
-
131
- const results = await validator.validateField('email', fieldConfig, 'test@example.com', context);
132
- const errors = results.filter(r => !r.valid);
133
- expect(errors.length).toBe(0);
134
- });
135
-
136
- it('should reject invalid email format', async () => {
137
- const fieldConfig: FieldConfig = {
138
- type: 'email',
139
- validation: { format: 'email' },
140
- };
141
-
142
- const context: ValidationContext = {
143
- operation: 'create',
144
- data: { email: 'invalid-email' },
145
- objectName: 'user',
146
- };
147
-
148
- const results = await validator.validateField('email', fieldConfig, 'invalid-email', context);
149
- const errors = results.filter(r => !r.valid);
150
- expect(errors.length).toBeGreaterThan(0);
151
- });
152
-
153
- it('should reject email without @', async () => {
154
- const fieldConfig: FieldConfig = {
155
- type: 'email',
156
- validation: { format: 'email' },
157
- };
158
-
159
- const context: ValidationContext = {
160
- operation: 'create',
161
- data: { email: 'testexample.com' },
162
- objectName: 'user',
163
- };
164
-
165
- const results = await validator.validateField('email', fieldConfig, 'testexample.com', context);
166
- const errors = results.filter(r => !r.valid);
167
- expect(errors.length).toBeGreaterThan(0);
168
- });
169
-
170
- it('should reject email without domain', async () => {
171
- const fieldConfig: FieldConfig = {
172
- type: 'email',
173
- validation: { format: 'email' },
174
- };
175
-
176
- const context: ValidationContext = {
177
- operation: 'create',
178
- data: { email: 'test@' },
179
- objectName: 'user',
180
- };
181
-
182
- const results = await validator.validateField('email', fieldConfig, 'test@', context);
183
- const errors = results.filter(r => !r.valid);
184
- expect(errors.length).toBeGreaterThan(0);
185
- });
186
- });
187
-
188
- describe('Length Validation', () => {
189
- it('should validate string within min and max length', async () => {
190
- const fieldConfig: FieldConfig = {
191
- type: 'text',
192
- validation: {
193
- min_length: 3,
194
- max_length: 10,
195
- },
196
- };
197
-
198
- const context: ValidationContext = {
199
- operation: 'create',
200
- data: { name: 'John' },
201
- objectName: 'user',
202
- };
203
-
204
- const results = await validator.validateField('name', fieldConfig, 'John', context);
205
- const errors = results.filter(r => !r.valid);
206
- expect(errors.length).toBe(0);
207
- });
208
-
209
- it('should reject string shorter than min_length', async () => {
210
- const fieldConfig: FieldConfig = {
211
- type: 'text',
212
- validation: {
213
- min_length: 5,
214
- },
215
- };
216
-
217
- const context: ValidationContext = {
218
- operation: 'create',
219
- data: { name: 'Bob' },
220
- objectName: 'user',
221
- };
222
-
223
- const results = await validator.validateField('name', fieldConfig, 'Bob', context);
224
- const errors = results.filter(r => !r.valid);
225
- expect(errors.length).toBeGreaterThan(0);
226
- });
227
-
228
- it('should reject string longer than max_length', async () => {
229
- const fieldConfig: FieldConfig = {
230
- type: 'text',
231
- validation: {
232
- max_length: 5,
233
- },
234
- };
235
-
236
- const context: ValidationContext = {
237
- operation: 'create',
238
- data: { name: 'Alexander' },
239
- objectName: 'user',
240
- };
241
-
242
- const results = await validator.validateField('name', fieldConfig, 'Alexander', context);
243
- const errors = results.filter(r => !r.valid);
244
- expect(errors.length).toBeGreaterThan(0);
245
- });
246
- });
247
-
248
- describe('Numeric Validation', () => {
249
- it('should validate number within min and max range', async () => {
250
- const fieldConfig: FieldConfig = {
251
- type: 'number',
252
- validation: {
253
- min: 0,
254
- max: 100,
255
- },
256
- };
257
-
258
- const context: ValidationContext = {
259
- operation: 'create',
260
- data: { age: 25 },
261
- objectName: 'user',
262
- };
263
-
264
- const results = await validator.validateField('age', fieldConfig, 25, context);
265
- const errors = results.filter(r => !r.valid);
266
- expect(errors.length).toBe(0);
267
- });
268
-
269
- it('should reject number below min', async () => {
270
- const fieldConfig: FieldConfig = {
271
- type: 'number',
272
- validation: {
273
- min: 18,
274
- },
275
- };
276
-
277
- const context: ValidationContext = {
278
- operation: 'create',
279
- data: { age: 15 },
280
- objectName: 'user',
281
- };
282
-
283
- const results = await validator.validateField('age', fieldConfig, 15, context);
284
- const errors = results.filter(r => !r.valid);
285
- expect(errors.length).toBeGreaterThan(0);
286
- });
287
-
288
- it('should reject number above max', async () => {
289
- const fieldConfig: FieldConfig = {
290
- type: 'number',
291
- validation: {
292
- max: 120,
293
- },
294
- };
295
-
296
- const context: ValidationContext = {
297
- operation: 'create',
298
- data: { age: 150 },
299
- objectName: 'user',
300
- };
301
-
302
- const results = await validator.validateField('age', fieldConfig, 150, context);
303
- const errors = results.filter(r => !r.valid);
304
- expect(errors.length).toBeGreaterThan(0);
305
- });
306
- });
307
-
308
- describe('Pattern Validation', () => {
309
- it('should validate value matching pattern', async () => {
310
- const fieldConfig: FieldConfig = {
311
- type: 'text',
312
- validation: {
313
- pattern: '^[A-Z][0-9]{3}$', // e.g., A123
314
- },
315
- };
316
-
317
- const context: ValidationContext = {
318
- operation: 'create',
319
- data: { code: 'A123' },
320
- objectName: 'product',
321
- };
322
-
323
- const results = await validator.validateField('code', fieldConfig, 'A123', context);
324
- const errors = results.filter(r => !r.valid);
325
- expect(errors.length).toBe(0);
326
- });
327
-
328
- it('should reject value not matching pattern', async () => {
329
- const fieldConfig: FieldConfig = {
330
- type: 'text',
331
- validation: {
332
- pattern: '^[A-Z][0-9]{3}$',
333
- },
334
- };
335
-
336
- const context: ValidationContext = {
337
- operation: 'create',
338
- data: { code: 'a123' },
339
- objectName: 'product',
340
- };
341
-
342
- const results = await validator.validateField('code', fieldConfig, 'a123', context);
343
- const errors = results.filter(r => !r.valid);
344
- expect(errors.length).toBeGreaterThan(0);
345
- });
346
- });
347
-
348
- describe('URL Validation', () => {
349
- it('should validate correct URL format', async () => {
350
- const fieldConfig: FieldConfig = {
351
- type: 'url',
352
- validation: { format: 'url' },
353
- };
354
-
355
- const context: ValidationContext = {
356
- operation: 'create',
357
- data: { website: 'https://example.com' },
358
- objectName: 'user',
359
- };
360
-
361
- const results = await validator.validateField('website', fieldConfig, 'https://example.com', context);
362
- const errors = results.filter(r => !r.valid);
363
- expect(errors.length).toBe(0);
364
- });
365
-
366
- it('should reject invalid URL format', async () => {
367
- const fieldConfig: FieldConfig = {
368
- type: 'url',
369
- validation: { format: 'url' },
370
- };
371
-
372
- const context: ValidationContext = {
373
- operation: 'create',
374
- data: { website: 'not-a-url' },
375
- objectName: 'user',
376
- };
377
-
378
- const results = await validator.validateField('website', fieldConfig, 'not-a-url', context);
379
- const errors = results.filter(r => !r.valid);
380
- expect(errors.length).toBeGreaterThan(0);
381
- });
382
- });
383
- });
384
-
385
- describe('Empty Value Handling', () => {
386
- it('should skip validation for empty optional fields', async () => {
387
- const fieldConfig: FieldConfig = {
388
- type: 'text',
389
- validation: {
390
- min_length: 5,
391
- },
392
- };
393
-
394
- const context: ValidationContext = {
395
- operation: 'create',
396
- data: {},
397
- objectName: 'user',
398
- };
399
-
400
- const results = await validator.validateField('nickname', fieldConfig, undefined, context);
401
- expect(results.length).toBe(0);
402
- });
403
-
404
- it('should skip validation for null optional fields', async () => {
405
- const fieldConfig: FieldConfig = {
406
- type: 'text',
407
- validation: {
408
- min_length: 5,
409
- },
410
- };
411
-
412
- const context: ValidationContext = {
413
- operation: 'create',
414
- data: { nickname: null },
415
- objectName: 'user',
416
- };
417
-
418
- const results = await validator.validateField('nickname', fieldConfig, null, context);
419
- expect(results.length).toBe(0);
420
- });
421
-
422
- it('should skip validation for empty string optional fields', async () => {
423
- const fieldConfig: FieldConfig = {
424
- type: 'text',
425
- validation: {
426
- min_length: 5,
427
- },
428
- };
429
-
430
- const context: ValidationContext = {
431
- operation: 'create',
432
- data: { nickname: '' },
433
- objectName: 'user',
434
- };
435
-
436
- const results = await validator.validateField('nickname', fieldConfig, '', context);
437
- expect(results.length).toBe(0);
438
- });
439
- });
440
- });