@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.
Files changed (90) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +8 -0
  3. package/dist/actions/ActionRunner.d.ts +40 -0
  4. package/dist/actions/ActionRunner.js +160 -0
  5. package/dist/actions/index.d.ts +8 -0
  6. package/dist/actions/index.js +8 -0
  7. package/dist/adapters/index.d.ts +7 -0
  8. package/dist/adapters/index.js +10 -0
  9. package/dist/builder/schema-builder.d.ts +7 -0
  10. package/dist/builder/schema-builder.js +4 -6
  11. package/dist/evaluator/ExpressionCache.d.ts +101 -0
  12. package/dist/evaluator/ExpressionCache.js +135 -0
  13. package/dist/evaluator/ExpressionContext.d.ts +51 -0
  14. package/dist/evaluator/ExpressionContext.js +110 -0
  15. package/dist/evaluator/ExpressionEvaluator.d.ts +117 -0
  16. package/dist/evaluator/ExpressionEvaluator.js +220 -0
  17. package/dist/evaluator/index.d.ts +10 -0
  18. package/dist/evaluator/index.js +10 -0
  19. package/dist/index.d.ts +17 -4
  20. package/dist/index.js +16 -5
  21. package/dist/query/index.d.ts +6 -0
  22. package/dist/query/index.js +6 -0
  23. package/dist/query/query-ast.d.ts +32 -0
  24. package/dist/query/query-ast.js +268 -0
  25. package/dist/registry/PluginScopeImpl.d.ts +80 -0
  26. package/dist/registry/PluginScopeImpl.js +243 -0
  27. package/dist/registry/PluginSystem.d.ts +66 -0
  28. package/dist/registry/PluginSystem.js +142 -0
  29. package/dist/registry/Registry.d.ts +80 -4
  30. package/dist/registry/Registry.js +119 -7
  31. package/dist/types/index.d.ts +7 -0
  32. package/dist/types/index.js +7 -0
  33. package/dist/utils/filter-converter.d.ts +57 -0
  34. package/dist/utils/filter-converter.js +100 -0
  35. package/dist/validation/index.d.ts +9 -0
  36. package/dist/validation/index.js +9 -0
  37. package/dist/validation/schema-validator.d.ts +7 -0
  38. package/dist/validation/schema-validator.js +4 -6
  39. package/dist/validation/validation-engine.d.ts +70 -0
  40. package/dist/validation/validation-engine.js +363 -0
  41. package/dist/validation/validators/index.d.ts +16 -0
  42. package/dist/validation/validators/index.js +16 -0
  43. package/dist/validation/validators/object-validation-engine.d.ts +118 -0
  44. package/dist/validation/validators/object-validation-engine.js +538 -0
  45. package/package.json +26 -7
  46. package/src/actions/ActionRunner.ts +195 -0
  47. package/src/actions/index.ts +9 -0
  48. package/src/adapters/README.md +180 -0
  49. package/src/adapters/index.ts +10 -0
  50. package/src/builder/schema-builder.ts +8 -0
  51. package/src/evaluator/ExpressionCache.ts +192 -0
  52. package/src/evaluator/ExpressionContext.ts +118 -0
  53. package/src/evaluator/ExpressionEvaluator.ts +267 -0
  54. package/src/evaluator/__tests__/ExpressionCache.test.ts +135 -0
  55. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +101 -0
  56. package/src/evaluator/index.ts +11 -0
  57. package/src/index.test.ts +8 -0
  58. package/src/index.ts +18 -5
  59. package/src/query/__tests__/query-ast.test.ts +211 -0
  60. package/src/query/__tests__/window-functions.test.ts +275 -0
  61. package/src/query/index.ts +7 -0
  62. package/src/query/query-ast.ts +341 -0
  63. package/src/registry/PluginScopeImpl.ts +259 -0
  64. package/src/registry/PluginSystem.ts +161 -0
  65. package/src/registry/Registry.ts +133 -8
  66. package/src/registry/__tests__/PluginSystem.test.ts +226 -0
  67. package/src/registry/__tests__/Registry.test.ts +293 -0
  68. package/src/registry/__tests__/plugin-scope-integration.test.ts +283 -0
  69. package/src/types/index.ts +8 -0
  70. package/src/utils/__tests__/filter-converter.test.ts +118 -0
  71. package/src/utils/filter-converter.ts +133 -0
  72. package/src/validation/__tests__/object-validation-engine.test.ts +567 -0
  73. package/src/validation/__tests__/validation-engine.test.ts +102 -0
  74. package/src/validation/index.ts +10 -0
  75. package/src/validation/schema-validator.ts +8 -0
  76. package/src/validation/validation-engine.ts +461 -0
  77. package/src/validation/validators/index.ts +25 -0
  78. package/src/validation/validators/object-validation-engine.ts +722 -0
  79. package/tsconfig.tsbuildinfo +1 -1
  80. package/vitest.config.ts +2 -0
  81. package/src/builder/schema-builder.d.ts +0 -287
  82. package/src/builder/schema-builder.js +0 -505
  83. package/src/index.d.ts +0 -4
  84. package/src/index.js +0 -7
  85. package/src/registry/Registry.d.ts +0 -49
  86. package/src/registry/Registry.js +0 -36
  87. package/src/types/index.d.ts +0 -12
  88. package/src/types/index.js +0 -1
  89. package/src/validation/schema-validator.d.ts +0 -87
  90. 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
+ /**
10
+ * @object-ui/core - Expression Context
11
+ *
12
+ * Manages variable scope and data context for expression evaluation.
13
+ *
14
+ * @module evaluator
15
+ * @packageDocumentation
16
+ */
17
+
18
+ /**
19
+ * Expression context for variable resolution
20
+ */
21
+ export class ExpressionContext {
22
+ private scopes: Map<string, any>[] = [];
23
+
24
+ constructor(initialData: Record<string, any> = {}) {
25
+ this.scopes.push(new Map(Object.entries(initialData)));
26
+ }
27
+
28
+ /**
29
+ * Push a new scope onto the context stack
30
+ */
31
+ pushScope(data: Record<string, any>): void {
32
+ this.scopes.push(new Map(Object.entries(data)));
33
+ }
34
+
35
+ /**
36
+ * Pop the current scope from the context stack
37
+ */
38
+ popScope(): void {
39
+ if (this.scopes.length > 1) {
40
+ this.scopes.pop();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get a variable value from the context
46
+ * Searches from innermost to outermost scope
47
+ */
48
+ get(path: string): any {
49
+ // Split path by dots for nested access
50
+ const parts = path.split('.');
51
+ const varName = parts[0];
52
+
53
+ // Search scopes from innermost to outermost
54
+ for (let i = this.scopes.length - 1; i >= 0; i--) {
55
+ if (this.scopes[i].has(varName)) {
56
+ let value = this.scopes[i].get(varName);
57
+
58
+ // Navigate nested path
59
+ for (let j = 1; j < parts.length; j++) {
60
+ if (value && typeof value === 'object') {
61
+ value = value[parts[j]];
62
+ } else {
63
+ return undefined;
64
+ }
65
+ }
66
+
67
+ return value;
68
+ }
69
+ }
70
+
71
+ return undefined;
72
+ }
73
+
74
+ /**
75
+ * Set a variable value in the current scope
76
+ */
77
+ set(name: string, value: any): void {
78
+ if (this.scopes.length > 0) {
79
+ this.scopes[this.scopes.length - 1].set(name, value);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Check if a variable exists in any scope
85
+ */
86
+ has(name: string): boolean {
87
+ const varName = name.split('.')[0];
88
+ for (let i = this.scopes.length - 1; i >= 0; i--) {
89
+ if (this.scopes[i].has(varName)) {
90
+ return true;
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+
96
+ /**
97
+ * Get all variables from all scopes as a flat object
98
+ */
99
+ toObject(): Record<string, any> {
100
+ const result: Record<string, any> = {};
101
+ // Merge from outermost to innermost (later scopes override earlier ones)
102
+ for (const scope of this.scopes) {
103
+ for (const [key, value] of scope.entries()) {
104
+ result[key] = value;
105
+ }
106
+ }
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * Create a child context with additional data
112
+ */
113
+ createChild(data: Record<string, any> = {}): ExpressionContext {
114
+ const child = new ExpressionContext();
115
+ child.scopes = [...this.scopes, new Map(Object.entries(data))];
116
+ return child;
117
+ }
118
+ }
@@ -0,0 +1,267 @@
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
+ * @object-ui/core - Expression Evaluator
11
+ *
12
+ * Evaluates template string expressions like ${data.amount > 1000} for dynamic UI behavior.
13
+ * Supports variable substitution, comparison operators, and basic JavaScript expressions.
14
+ *
15
+ * @module evaluator
16
+ * @packageDocumentation
17
+ */
18
+
19
+ import { ExpressionContext } from './ExpressionContext.js';
20
+ import { ExpressionCache } from './ExpressionCache.js';
21
+
22
+ /**
23
+ * Options for expression evaluation
24
+ */
25
+ export interface EvaluationOptions {
26
+ /**
27
+ * Default value to return if evaluation fails
28
+ */
29
+ defaultValue?: any;
30
+
31
+ /**
32
+ * Whether to throw errors on evaluation failure
33
+ * @default false
34
+ */
35
+ throwOnError?: boolean;
36
+
37
+ /**
38
+ * Whether to sanitize the expression before evaluation
39
+ * @default true
40
+ */
41
+ sanitize?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Expression evaluator for dynamic UI expressions
46
+ */
47
+ export class ExpressionEvaluator {
48
+ private context: ExpressionContext;
49
+ private cache: ExpressionCache;
50
+
51
+ constructor(context?: ExpressionContext | Record<string, any>, cache?: ExpressionCache) {
52
+ if (context instanceof ExpressionContext) {
53
+ this.context = context;
54
+ } else {
55
+ this.context = new ExpressionContext(context || {});
56
+ }
57
+
58
+ // Use provided cache or create a new one
59
+ this.cache = cache || new ExpressionCache();
60
+ }
61
+
62
+ /**
63
+ * Evaluate a string that may contain template expressions like ${...}
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const evaluator = new ExpressionEvaluator({ data: { amount: 1500 } });
68
+ * evaluator.evaluate('${data.amount > 1000}'); // Returns: true
69
+ * evaluator.evaluate('Amount is ${data.amount}'); // Returns: "Amount is 1500"
70
+ * ```
71
+ */
72
+ evaluate(expression: string | boolean | number | null | undefined, options: EvaluationOptions = {}): any {
73
+ // Handle non-string primitives
74
+ if (typeof expression !== 'string') {
75
+ return expression;
76
+ }
77
+
78
+ const { defaultValue, throwOnError = false, sanitize = true } = options;
79
+
80
+ try {
81
+ // Check if string contains template expressions
82
+ const hasTemplates = expression.includes('${');
83
+
84
+ if (!hasTemplates) {
85
+ // No templates, return as-is
86
+ return expression;
87
+ }
88
+
89
+ // Special case: if the entire string is a single template expression, return the value directly
90
+ const singleTemplateMatch = expression.match(/^\$\{([^}]+)\}$/);
91
+ if (singleTemplateMatch) {
92
+ return this.evaluateExpression(singleTemplateMatch[1].trim(), { sanitize });
93
+ }
94
+
95
+ // Replace all ${...} expressions in a string with multiple parts
96
+ return expression.replace(/\$\{([^}]+)\}/g, (match, expr) => {
97
+ try {
98
+ const result = this.evaluateExpression(expr.trim(), { sanitize });
99
+ return String(result ?? '');
100
+ } catch (error) {
101
+ if (throwOnError) {
102
+ throw error;
103
+ }
104
+ console.warn(`Expression evaluation failed for: ${expr}`, error);
105
+ return match; // Return original if evaluation fails
106
+ }
107
+ });
108
+ } catch (error) {
109
+ if (throwOnError) {
110
+ throw error;
111
+ }
112
+ console.warn(`Failed to evaluate expression: ${expression}`, error);
113
+ return defaultValue ?? expression;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Evaluate a single expression (without ${} wrapper)
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * evaluator.evaluateExpression('data.amount > 1000'); // Returns: true
123
+ * evaluator.evaluateExpression('data.user.name'); // Returns: "John"
124
+ * ```
125
+ */
126
+ evaluateExpression(expression: string, options: { sanitize?: boolean } = {}): any {
127
+ const { sanitize = true } = options;
128
+
129
+ if (!expression || expression.trim() === '') {
130
+ return undefined;
131
+ }
132
+
133
+ // Sanitize expression to prevent dangerous code execution
134
+ if (sanitize && this.isDangerous(expression)) {
135
+ throw new Error(`Potentially dangerous expression detected: ${expression}`);
136
+ }
137
+
138
+ try {
139
+ // Create a safe evaluation function
140
+ const contextObj = this.context.toObject();
141
+
142
+ // Build safe function with context variables
143
+ const varNames = Object.keys(contextObj);
144
+ const varValues = Object.values(contextObj);
145
+
146
+ // Use cached compilation
147
+ const compiled = this.cache.compile(expression, varNames);
148
+
149
+ // Execute with context values
150
+ return compiled.fn(...varValues);
151
+ } catch (error) {
152
+ throw new Error(`Failed to evaluate expression "${expression}": ${(error as Error).message}`);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Check if expression contains potentially dangerous code
158
+ */
159
+ private isDangerous(expression: string): boolean {
160
+ const dangerousPatterns = [
161
+ /eval\s*\(/i,
162
+ /Function\s*\(/i,
163
+ /setTimeout\s*\(/i,
164
+ /setInterval\s*\(/i,
165
+ /import\s*\(/i,
166
+ /require\s*\(/i,
167
+ /process\./i,
168
+ /global\./i,
169
+ /window\./i,
170
+ /document\./i,
171
+ /__proto__/i,
172
+ /constructor\s*\(/i,
173
+ /prototype\./i,
174
+ ];
175
+
176
+ return dangerousPatterns.some(pattern => pattern.test(expression));
177
+ }
178
+
179
+ /**
180
+ * Evaluate a conditional expression and return boolean
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * evaluator.evaluateCondition('${data.age >= 18}'); // Returns: true/false
185
+ * ```
186
+ */
187
+ evaluateCondition(condition: string | boolean | undefined, options: EvaluationOptions = {}): boolean {
188
+ if (typeof condition === 'boolean') {
189
+ return condition;
190
+ }
191
+
192
+ if (!condition) {
193
+ return true; // Default to visible/enabled if no condition
194
+ }
195
+
196
+ const result = this.evaluate(condition, options);
197
+
198
+ // Convert result to boolean
199
+ return Boolean(result);
200
+ }
201
+
202
+ /**
203
+ * Update the context with new data
204
+ */
205
+ updateContext(data: Record<string, any>): void {
206
+ Object.entries(data).forEach(([key, value]) => {
207
+ this.context.set(key, value);
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Get the current context
213
+ */
214
+ getContext(): ExpressionContext {
215
+ return this.context;
216
+ }
217
+
218
+ /**
219
+ * Create a new evaluator with additional context data
220
+ */
221
+ withContext(data: Record<string, any>): ExpressionEvaluator {
222
+ // Share the cache with the new evaluator for maximum efficiency
223
+ return new ExpressionEvaluator(this.context.createChild(data), this.cache);
224
+ }
225
+
226
+ /**
227
+ * Get cache statistics (useful for debugging and optimization)
228
+ */
229
+ getCacheStats() {
230
+ return this.cache.getStats();
231
+ }
232
+
233
+ /**
234
+ * Clear the expression cache
235
+ */
236
+ clearCache(): void {
237
+ this.cache.clear();
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Shared global cache for convenience functions
243
+ */
244
+ const globalCache = new ExpressionCache();
245
+
246
+ /**
247
+ * Convenience function to quickly evaluate an expression
248
+ */
249
+ export function evaluateExpression(
250
+ expression: string | boolean | number | null | undefined,
251
+ context: Record<string, any> = {},
252
+ options: EvaluationOptions = {}
253
+ ): any {
254
+ const evaluator = new ExpressionEvaluator(context, globalCache);
255
+ return evaluator.evaluate(expression, options);
256
+ }
257
+
258
+ /**
259
+ * Convenience function to evaluate a condition
260
+ */
261
+ export function evaluateCondition(
262
+ condition: string | boolean | undefined,
263
+ context: Record<string, any> = {}
264
+ ): boolean {
265
+ const evaluator = new ExpressionEvaluator(context, globalCache);
266
+ return evaluator.evaluateCondition(condition);
267
+ }
@@ -0,0 +1,135 @@
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, beforeEach } from 'vitest';
10
+ import { ExpressionCache } from '../ExpressionCache';
11
+
12
+ describe('ExpressionCache', () => {
13
+ let cache: ExpressionCache;
14
+
15
+ beforeEach(() => {
16
+ cache = new ExpressionCache();
17
+ });
18
+
19
+ it('should compile and cache an expression', () => {
20
+ const expr = 'data.amount > 1000';
21
+ const varNames = ['data'];
22
+
23
+ const compiled = cache.compile(expr, varNames);
24
+
25
+ expect(compiled).toBeDefined();
26
+ expect(compiled.expression).toBe(expr);
27
+ expect(compiled.varNames).toEqual(varNames);
28
+ expect(compiled.hitCount).toBe(1);
29
+ });
30
+
31
+ it('should return cached expression on second call', () => {
32
+ const expr = 'data.amount > 1000';
33
+ const varNames = ['data'];
34
+
35
+ const first = cache.compile(expr, varNames);
36
+ const second = cache.compile(expr, varNames);
37
+
38
+ expect(first).toBe(second);
39
+ expect(second.hitCount).toBe(2);
40
+ });
41
+
42
+ it('should execute compiled expression correctly', () => {
43
+ const expr = 'data.amount > 1000';
44
+ const varNames = ['data'];
45
+
46
+ const compiled = cache.compile(expr, varNames);
47
+ const result = compiled.fn({ amount: 1500 });
48
+
49
+ expect(result).toBe(true);
50
+ });
51
+
52
+ it('should handle multiple expressions', () => {
53
+ const expr1 = 'data.amount > 1000';
54
+ const expr2 = 'data.name === "John"';
55
+ const varNames = ['data'];
56
+
57
+ const compiled1 = cache.compile(expr1, varNames);
58
+ const compiled2 = cache.compile(expr2, varNames);
59
+
60
+ expect(compiled1).not.toBe(compiled2);
61
+ expect(compiled1.fn({ amount: 1500 })).toBe(true);
62
+ expect(compiled2.fn({ name: 'John' })).toBe(true);
63
+ });
64
+
65
+ it('should differentiate between different variable contexts', () => {
66
+ const expr = 'x + y';
67
+
68
+ const compiled1 = cache.compile(expr, ['x', 'y']);
69
+ const compiled2 = cache.compile(expr, ['x', 'y', 'z']);
70
+
71
+ // Different variable contexts should create different cache entries
72
+ expect(compiled1).not.toBe(compiled2);
73
+ });
74
+
75
+ it('should provide cache statistics', () => {
76
+ cache.compile('data.amount > 1000', ['data']);
77
+ cache.compile('data.amount > 1000', ['data']); // Second call, increment hit
78
+ cache.compile('data.name === "John"', ['data']);
79
+
80
+ const stats = cache.getStats();
81
+
82
+ expect(stats.size).toBe(2);
83
+ expect(stats.totalHits).toBe(3);
84
+ expect(stats.entries).toHaveLength(2);
85
+ expect(stats.entries[0].hitCount).toBe(2); // Most frequently used
86
+ });
87
+
88
+ it('should evict LRU when cache is full', () => {
89
+ const smallCache = new ExpressionCache(3);
90
+
91
+ smallCache.compile('expr1', ['x']);
92
+ smallCache.compile('expr2', ['x']);
93
+ smallCache.compile('expr3', ['x']);
94
+
95
+ // Access expr1 multiple times to increase hit count
96
+ smallCache.compile('expr1', ['x']);
97
+ smallCache.compile('expr1', ['x']);
98
+
99
+ // Add a 4th expression, should evict least used (expr2 or expr3)
100
+ smallCache.compile('expr4', ['x']);
101
+
102
+ const stats = smallCache.getStats();
103
+ expect(stats.size).toBe(3);
104
+
105
+ // expr1 should still be cached (highest hit count)
106
+ expect(smallCache.has('expr1', ['x'])).toBe(true);
107
+ });
108
+
109
+ it('should clear cache', () => {
110
+ cache.compile('data.amount > 1000', ['data']);
111
+ cache.compile('data.name === "John"', ['data']);
112
+
113
+ expect(cache.getStats().size).toBe(2);
114
+
115
+ cache.clear();
116
+
117
+ expect(cache.getStats().size).toBe(0);
118
+ });
119
+
120
+ it('should handle complex expressions', () => {
121
+ const expr = 'data.items.filter(item => item.price > 100).length';
122
+ const varNames = ['data'];
123
+
124
+ const compiled = cache.compile(expr, varNames);
125
+ const result = compiled.fn({
126
+ items: [
127
+ { price: 50 },
128
+ { price: 150 },
129
+ { price: 200 },
130
+ ],
131
+ });
132
+
133
+ expect(result).toBe(2);
134
+ });
135
+ });
@@ -0,0 +1,101 @@
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 } from 'vitest';
10
+ import { ExpressionEvaluator, evaluateExpression, evaluateCondition } from '../ExpressionEvaluator';
11
+ import { ExpressionContext } from '../ExpressionContext';
12
+
13
+ describe('ExpressionContext', () => {
14
+ it('should create context with initial data', () => {
15
+ const ctx = new ExpressionContext({ name: 'John', age: 30 });
16
+ expect(ctx.get('name')).toBe('John');
17
+ expect(ctx.get('age')).toBe(30);
18
+ });
19
+
20
+ it('should support nested property access', () => {
21
+ const ctx = new ExpressionContext({
22
+ user: {
23
+ name: 'John',
24
+ profile: {
25
+ email: 'john@example.com'
26
+ }
27
+ }
28
+ });
29
+
30
+ expect(ctx.get('user.name')).toBe('John');
31
+ expect(ctx.get('user.profile.email')).toBe('john@example.com');
32
+ });
33
+
34
+ it('should support scope stacking', () => {
35
+ const ctx = new ExpressionContext({ x: 10 });
36
+ expect(ctx.get('x')).toBe(10);
37
+
38
+ ctx.pushScope({ x: 20, y: 30 });
39
+ expect(ctx.get('x')).toBe(20);
40
+ expect(ctx.get('y')).toBe(30);
41
+
42
+ ctx.popScope();
43
+ expect(ctx.get('x')).toBe(10);
44
+ expect(ctx.get('y')).toBeUndefined();
45
+ });
46
+ });
47
+
48
+ describe('ExpressionEvaluator', () => {
49
+ describe('evaluate', () => {
50
+ it('should evaluate simple template expressions', () => {
51
+ const evaluator = new ExpressionEvaluator({ name: 'John' });
52
+ expect(evaluator.evaluate('Hello ${name}!')).toBe('Hello John!');
53
+ });
54
+
55
+ it('should evaluate nested property access', () => {
56
+ const evaluator = new ExpressionEvaluator({
57
+ data: { amount: 1500 }
58
+ });
59
+ expect(evaluator.evaluate('Amount: ${data.amount}')).toBe('Amount: 1500');
60
+ });
61
+
62
+ it('should handle non-string values', () => {
63
+ const evaluator = new ExpressionEvaluator({});
64
+ expect(evaluator.evaluate(true)).toBe(true);
65
+ expect(evaluator.evaluate(42)).toBe(42);
66
+ expect(evaluator.evaluate(null)).toBe(null);
67
+ });
68
+ });
69
+
70
+ describe('evaluateExpression', () => {
71
+ it('should evaluate comparison operators', () => {
72
+ const evaluator = new ExpressionEvaluator({ data: { amount: 1500 } });
73
+
74
+ expect(evaluator.evaluateExpression('data.amount > 1000')).toBe(true);
75
+ expect(evaluator.evaluateExpression('data.amount < 1000')).toBe(false);
76
+ });
77
+
78
+ it('should block dangerous expressions', () => {
79
+ const evaluator = new ExpressionEvaluator({});
80
+
81
+ expect(() => evaluator.evaluateExpression('eval("malicious")')).toThrow();
82
+ expect(() => evaluator.evaluateExpression('process.exit()')).toThrow();
83
+ });
84
+ });
85
+
86
+ describe('evaluateCondition', () => {
87
+ it('should return boolean for condition expressions', () => {
88
+ const evaluator = new ExpressionEvaluator({ data: { age: 25 } });
89
+
90
+ expect(evaluator.evaluateCondition('${data.age >= 18}')).toBe(true);
91
+ expect(evaluator.evaluateCondition('${data.age < 18}')).toBe(false);
92
+ });
93
+
94
+ it('should handle boolean values directly', () => {
95
+ const evaluator = new ExpressionEvaluator({});
96
+
97
+ expect(evaluator.evaluateCondition(true)).toBe(true);
98
+ expect(evaluator.evaluateCondition(false)).toBe(false);
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,11 @@
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
+ export * from './ExpressionContext.js';
10
+ export * from './ExpressionEvaluator.js';
11
+ export * from './ExpressionCache.js';
package/src/index.test.ts CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  import { describe, it, expect } from 'vitest';
2
10
 
3
11
  describe('core', () => {
package/src/index.ts CHANGED
@@ -1,8 +1,21 @@
1
- export * from './types';
2
- export * from './registry/Registry';
3
- export * from './validation/schema-validator';
4
- export * from './builder/schema-builder';
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
+ export type { SchemaNode, ComponentRendererProps } from './types/index.js';
10
+ export * from './registry/Registry.js';
11
+ export * from './registry/PluginSystem.js';
12
+ export * from './registry/PluginScopeImpl.js';
13
+ export * from './validation/index.js';
14
+ export * from './builder/schema-builder.js';
15
+ export * from './utils/filter-converter.js';
16
+ export * from './evaluator/index.js';
17
+ export * from './actions/index.js';
18
+ export * from './query/index.js';
5
19
  // export * from './data-scope'; // TODO
6
- // export * from './evaluator'; // TODO
7
20
  // export * from './validators'; // TODO
8
21