@object-ui/core 0.3.0 → 0.5.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 +4 -0
- package/CHANGELOG.md +8 -0
- package/dist/actions/ActionRunner.d.ts +40 -0
- package/dist/actions/ActionRunner.js +160 -0
- package/dist/actions/index.d.ts +8 -0
- package/dist/actions/index.js +8 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +10 -0
- package/dist/builder/schema-builder.d.ts +7 -0
- package/dist/builder/schema-builder.js +4 -6
- package/dist/evaluator/ExpressionCache.d.ts +101 -0
- package/dist/evaluator/ExpressionCache.js +135 -0
- package/dist/evaluator/ExpressionContext.d.ts +51 -0
- package/dist/evaluator/ExpressionContext.js +110 -0
- package/dist/evaluator/ExpressionEvaluator.d.ts +117 -0
- package/dist/evaluator/ExpressionEvaluator.js +220 -0
- package/dist/evaluator/index.d.ts +10 -0
- package/dist/evaluator/index.js +10 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +16 -5
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +6 -0
- package/dist/query/query-ast.d.ts +32 -0
- package/dist/query/query-ast.js +268 -0
- package/dist/registry/PluginScopeImpl.d.ts +80 -0
- package/dist/registry/PluginScopeImpl.js +243 -0
- package/dist/registry/PluginSystem.d.ts +66 -0
- package/dist/registry/PluginSystem.js +142 -0
- package/dist/registry/Registry.d.ts +80 -4
- package/dist/registry/Registry.js +119 -7
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/filter-converter.d.ts +57 -0
- package/dist/utils/filter-converter.js +100 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/schema-validator.d.ts +7 -0
- package/dist/validation/schema-validator.js +4 -6
- package/dist/validation/validation-engine.d.ts +70 -0
- package/dist/validation/validation-engine.js +363 -0
- package/dist/validation/validators/index.d.ts +16 -0
- package/dist/validation/validators/index.js +16 -0
- package/dist/validation/validators/object-validation-engine.d.ts +118 -0
- package/dist/validation/validators/object-validation-engine.js +538 -0
- package/package.json +26 -7
- package/src/actions/ActionRunner.ts +195 -0
- package/src/actions/index.ts +9 -0
- package/src/adapters/README.md +180 -0
- package/src/adapters/index.ts +10 -0
- package/src/builder/schema-builder.ts +8 -0
- package/src/evaluator/ExpressionCache.ts +192 -0
- package/src/evaluator/ExpressionContext.ts +118 -0
- package/src/evaluator/ExpressionEvaluator.ts +267 -0
- package/src/evaluator/__tests__/ExpressionCache.test.ts +135 -0
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +101 -0
- package/src/evaluator/index.ts +11 -0
- package/src/index.test.ts +8 -0
- package/src/index.ts +18 -5
- package/src/query/__tests__/query-ast.test.ts +211 -0
- package/src/query/__tests__/window-functions.test.ts +275 -0
- package/src/query/index.ts +7 -0
- package/src/query/query-ast.ts +341 -0
- package/src/registry/PluginScopeImpl.ts +259 -0
- package/src/registry/PluginSystem.ts +161 -0
- package/src/registry/Registry.ts +133 -8
- package/src/registry/__tests__/PluginSystem.test.ts +226 -0
- package/src/registry/__tests__/Registry.test.ts +293 -0
- package/src/registry/__tests__/plugin-scope-integration.test.ts +283 -0
- package/src/types/index.ts +8 -0
- package/src/utils/__tests__/filter-converter.test.ts +118 -0
- package/src/utils/filter-converter.ts +133 -0
- package/src/validation/__tests__/object-validation-engine.test.ts +567 -0
- package/src/validation/__tests__/validation-engine.test.ts +102 -0
- package/src/validation/index.ts +10 -0
- package/src/validation/schema-validator.ts +8 -0
- package/src/validation/validation-engine.ts +461 -0
- package/src/validation/validators/index.ts +25 -0
- package/src/validation/validators/object-validation-engine.ts +722 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +2 -0
- package/src/builder/schema-builder.d.ts +0 -287
- package/src/builder/schema-builder.js +0 -505
- package/src/index.d.ts +0 -4
- package/src/index.js +0 -7
- package/src/registry/Registry.d.ts +0 -49
- package/src/registry/Registry.js +0 -36
- package/src/types/index.d.ts +0 -12
- package/src/types/index.js +0 -1
- package/src/validation/schema-validator.d.ts +0 -87
- package/src/validation/schema-validator.js +0 -280
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-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 { describe, it, expect, vi } from 'vitest';
|
|
10
|
+
import { convertFiltersToAST, convertOperatorToAST, type FilterNode } from '../filter-converter';
|
|
11
|
+
|
|
12
|
+
describe('Filter Converter Utilities', () => {
|
|
13
|
+
describe('convertOperatorToAST', () => {
|
|
14
|
+
it('should convert known operators', () => {
|
|
15
|
+
expect(convertOperatorToAST('$eq')).toBe('=');
|
|
16
|
+
expect(convertOperatorToAST('$ne')).toBe('!=');
|
|
17
|
+
expect(convertOperatorToAST('$gt')).toBe('>');
|
|
18
|
+
expect(convertOperatorToAST('$gte')).toBe('>=');
|
|
19
|
+
expect(convertOperatorToAST('$lt')).toBe('<');
|
|
20
|
+
expect(convertOperatorToAST('$lte')).toBe('<=');
|
|
21
|
+
expect(convertOperatorToAST('$in')).toBe('in');
|
|
22
|
+
expect(convertOperatorToAST('$nin')).toBe('notin');
|
|
23
|
+
expect(convertOperatorToAST('$notin')).toBe('notin');
|
|
24
|
+
expect(convertOperatorToAST('$contains')).toBe('contains');
|
|
25
|
+
expect(convertOperatorToAST('$startswith')).toBe('startswith');
|
|
26
|
+
expect(convertOperatorToAST('$between')).toBe('between');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return null for unknown operators', () => {
|
|
30
|
+
expect(convertOperatorToAST('$unknown')).toBe(null);
|
|
31
|
+
expect(convertOperatorToAST('$exists')).toBe(null);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('convertFiltersToAST', () => {
|
|
36
|
+
it('should convert simple equality filter', () => {
|
|
37
|
+
const result = convertFiltersToAST({ status: 'active' });
|
|
38
|
+
expect(result).toEqual(['status', '=', 'active']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should convert single operator filter', () => {
|
|
42
|
+
const result = convertFiltersToAST({ age: { $gte: 18 } });
|
|
43
|
+
expect(result).toEqual(['age', '>=', 18]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should convert multiple operators on same field', () => {
|
|
47
|
+
const result = convertFiltersToAST({ age: { $gte: 18, $lte: 65 } }) as FilterNode;
|
|
48
|
+
expect(result[0]).toBe('and');
|
|
49
|
+
expect(result.slice(1)).toContainEqual(['age', '>=', 18]);
|
|
50
|
+
expect(result.slice(1)).toContainEqual(['age', '<=', 65]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should convert multiple fields with and logic', () => {
|
|
54
|
+
const result = convertFiltersToAST({
|
|
55
|
+
age: { $gte: 18 },
|
|
56
|
+
status: 'active'
|
|
57
|
+
}) as FilterNode;
|
|
58
|
+
expect(result[0]).toBe('and');
|
|
59
|
+
expect(result.slice(1)).toContainEqual(['age', '>=', 18]);
|
|
60
|
+
expect(result.slice(1)).toContainEqual(['status', '=', 'active']);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle $in operator', () => {
|
|
64
|
+
const result = convertFiltersToAST({
|
|
65
|
+
status: { $in: ['active', 'pending'] }
|
|
66
|
+
});
|
|
67
|
+
expect(result).toEqual(['status', 'in', ['active', 'pending']]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle $nin operator', () => {
|
|
71
|
+
const result = convertFiltersToAST({
|
|
72
|
+
status: { $nin: ['archived'] }
|
|
73
|
+
});
|
|
74
|
+
expect(result).toEqual(['status', 'notin', ['archived']]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should warn on $regex operator and convert to contains', () => {
|
|
78
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
79
|
+
|
|
80
|
+
const result = convertFiltersToAST({
|
|
81
|
+
name: { $regex: '^John' }
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(result).toEqual(['name', 'contains', '^John']);
|
|
85
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
86
|
+
expect.stringContaining('[ObjectUI] Warning: $regex operator is not fully supported')
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
consoleSpy.mockRestore();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should throw error on unknown operator', () => {
|
|
93
|
+
expect(() => {
|
|
94
|
+
convertFiltersToAST({ age: { $unknown: 18 } });
|
|
95
|
+
}).toThrow('[ObjectUI] Unknown filter operator');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should skip null and undefined values', () => {
|
|
99
|
+
const result = convertFiltersToAST({
|
|
100
|
+
name: 'John',
|
|
101
|
+
age: null,
|
|
102
|
+
email: undefined
|
|
103
|
+
});
|
|
104
|
+
expect(result).toEqual(['name', '=', 'John']);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return original filter if empty after filtering', () => {
|
|
108
|
+
const result = convertFiltersToAST({
|
|
109
|
+
age: null,
|
|
110
|
+
email: undefined
|
|
111
|
+
});
|
|
112
|
+
expect(result).toEqual({
|
|
113
|
+
age: null,
|
|
114
|
+
email: undefined
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-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
|
+
* Filter Converter Utilities
|
|
11
|
+
*
|
|
12
|
+
* Shared utilities for converting MongoDB-like filter operators
|
|
13
|
+
* to ObjectStack FilterNode AST format.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FilterNode AST type definition
|
|
18
|
+
* Represents a filter condition or a logical combination of conditions
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Simple condition
|
|
22
|
+
* ['status', '=', 'active']
|
|
23
|
+
*
|
|
24
|
+
* // Logical combination
|
|
25
|
+
* ['and', ['age', '>=', 18], ['status', '=', 'active']]
|
|
26
|
+
*/
|
|
27
|
+
export type FilterNode =
|
|
28
|
+
| [string, string, any] // [field, operator, value]
|
|
29
|
+
| [string, ...FilterNode[]]; // [logic, ...conditions]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Map MongoDB-like operators to ObjectStack filter operators.
|
|
33
|
+
*
|
|
34
|
+
* @param operator - MongoDB-style operator (e.g., '$gte', '$in')
|
|
35
|
+
* @returns ObjectStack operator or null if not recognized
|
|
36
|
+
*/
|
|
37
|
+
export function convertOperatorToAST(operator: string): string | null {
|
|
38
|
+
const operatorMap: Record<string, string> = {
|
|
39
|
+
'$eq': '=',
|
|
40
|
+
'$ne': '!=',
|
|
41
|
+
'$gt': '>',
|
|
42
|
+
'$gte': '>=',
|
|
43
|
+
'$lt': '<',
|
|
44
|
+
'$lte': '<=',
|
|
45
|
+
'$in': 'in',
|
|
46
|
+
'$nin': 'notin',
|
|
47
|
+
'$notin': 'notin',
|
|
48
|
+
'$contains': 'contains',
|
|
49
|
+
'$startswith': 'startswith',
|
|
50
|
+
'$between': 'between',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return operatorMap[operator] || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert object-based filters to ObjectStack FilterNode AST format.
|
|
58
|
+
* Converts MongoDB-like operators to ObjectStack filter expressions.
|
|
59
|
+
*
|
|
60
|
+
* @param filter - Object-based filter with optional operators
|
|
61
|
+
* @returns FilterNode AST array
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Simple filter - converted to AST
|
|
65
|
+
* convertFiltersToAST({ status: 'active' })
|
|
66
|
+
* // => ['status', '=', 'active']
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Complex filter with operators
|
|
70
|
+
* convertFiltersToAST({ age: { $gte: 18 } })
|
|
71
|
+
* // => ['age', '>=', 18]
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Multiple conditions
|
|
75
|
+
* convertFiltersToAST({ age: { $gte: 18, $lte: 65 }, status: 'active' })
|
|
76
|
+
* // => ['and', ['age', '>=', 18], ['age', '<=', 65], ['status', '=', 'active']]
|
|
77
|
+
*
|
|
78
|
+
* @throws {Error} If an unknown operator is encountered
|
|
79
|
+
*/
|
|
80
|
+
export function convertFiltersToAST(filter: Record<string, any>): FilterNode | Record<string, any> {
|
|
81
|
+
const conditions: FilterNode[] = [];
|
|
82
|
+
|
|
83
|
+
for (const [field, value] of Object.entries(filter)) {
|
|
84
|
+
if (value === null || value === undefined) continue;
|
|
85
|
+
|
|
86
|
+
// Check if value is a complex operator object
|
|
87
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
88
|
+
// Handle operator-based filters
|
|
89
|
+
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
90
|
+
// Special handling for $regex - warn users about limited support
|
|
91
|
+
if (operator === '$regex') {
|
|
92
|
+
console.warn(
|
|
93
|
+
`[ObjectUI] Warning: $regex operator is not fully supported. ` +
|
|
94
|
+
`Converting to 'contains' which only supports substring matching, not regex patterns. ` +
|
|
95
|
+
`Field: '${field}', Value: ${JSON.stringify(operatorValue)}. ` +
|
|
96
|
+
`Consider using $contains or $startswith instead.`
|
|
97
|
+
);
|
|
98
|
+
conditions.push([field, 'contains', operatorValue]);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const astOperator = convertOperatorToAST(operator);
|
|
103
|
+
|
|
104
|
+
if (astOperator) {
|
|
105
|
+
conditions.push([field, astOperator, operatorValue]);
|
|
106
|
+
} else {
|
|
107
|
+
// Unknown operator - throw error to avoid silent failure
|
|
108
|
+
throw new Error(
|
|
109
|
+
`[ObjectUI] Unknown filter operator '${operator}' for field '${field}'. ` +
|
|
110
|
+
`Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $startswith, $between. ` +
|
|
111
|
+
`If you need exact object matching, use the value directly without an operator.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// Simple equality filter
|
|
117
|
+
conditions.push([field, '=', value]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If no conditions, return original filter
|
|
122
|
+
if (conditions.length === 0) {
|
|
123
|
+
return filter;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If only one condition, return it directly
|
|
127
|
+
if (conditions.length === 1) {
|
|
128
|
+
return conditions[0];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Multiple conditions: combine with 'and'
|
|
132
|
+
return ['and', ...conditions];
|
|
133
|
+
}
|