@object-ui/core 0.3.0 → 0.3.1

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 (57) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/actions/ActionRunner.d.ts +40 -0
  3. package/dist/actions/ActionRunner.js +160 -0
  4. package/dist/actions/index.d.ts +8 -0
  5. package/dist/actions/index.js +8 -0
  6. package/dist/adapters/index.d.ts +7 -0
  7. package/dist/adapters/index.js +10 -0
  8. package/dist/builder/schema-builder.d.ts +7 -0
  9. package/dist/builder/schema-builder.js +4 -6
  10. package/dist/evaluator/ExpressionContext.d.ts +51 -0
  11. package/dist/evaluator/ExpressionContext.js +110 -0
  12. package/dist/evaluator/ExpressionEvaluator.d.ts +99 -0
  13. package/dist/evaluator/ExpressionEvaluator.js +200 -0
  14. package/dist/evaluator/index.d.ts +9 -0
  15. package/dist/evaluator/index.js +9 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.js +10 -1
  18. package/dist/registry/Registry.d.ts +7 -0
  19. package/dist/registry/Registry.js +7 -0
  20. package/dist/types/index.d.ts +7 -0
  21. package/dist/types/index.js +7 -0
  22. package/dist/utils/filter-converter.d.ts +57 -0
  23. package/dist/utils/filter-converter.js +100 -0
  24. package/dist/validation/schema-validator.d.ts +7 -0
  25. package/dist/validation/schema-validator.js +4 -6
  26. package/package.json +16 -5
  27. package/src/actions/ActionRunner.ts +195 -0
  28. package/src/actions/index.ts +9 -0
  29. package/src/adapters/README.md +180 -0
  30. package/src/adapters/index.d.ts +8 -0
  31. package/src/adapters/index.js +10 -0
  32. package/src/adapters/index.ts +10 -0
  33. package/src/builder/schema-builder.d.ts +7 -0
  34. package/src/builder/schema-builder.js +4 -6
  35. package/src/builder/schema-builder.ts +8 -0
  36. package/src/evaluator/ExpressionContext.ts +118 -0
  37. package/src/evaluator/ExpressionEvaluator.ts +248 -0
  38. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +101 -0
  39. package/src/evaluator/index.ts +10 -0
  40. package/src/index.d.ts +9 -0
  41. package/src/index.js +10 -1
  42. package/src/index.test.ts +8 -0
  43. package/src/index.ts +11 -1
  44. package/src/registry/Registry.d.ts +7 -0
  45. package/src/registry/Registry.js +7 -0
  46. package/src/registry/Registry.ts +8 -0
  47. package/src/types/index.d.ts +7 -0
  48. package/src/types/index.js +7 -0
  49. package/src/types/index.ts +8 -0
  50. package/src/utils/__tests__/filter-converter.test.ts +118 -0
  51. package/src/utils/filter-converter.d.ts +57 -0
  52. package/src/utils/filter-converter.js +100 -0
  53. package/src/utils/filter-converter.ts +133 -0
  54. package/src/validation/schema-validator.d.ts +7 -0
  55. package/src/validation/schema-validator.js +4 -6
  56. package/src/validation/schema-validator.ts +8 -0
  57. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,200 @@
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
+ * @object-ui/core - Expression Evaluator
10
+ *
11
+ * Evaluates template string expressions like ${data.amount > 1000} for dynamic UI behavior.
12
+ * Supports variable substitution, comparison operators, and basic JavaScript expressions.
13
+ *
14
+ * @module evaluator
15
+ * @packageDocumentation
16
+ */
17
+ import { ExpressionContext } from './ExpressionContext';
18
+ /**
19
+ * Expression evaluator for dynamic UI expressions
20
+ */
21
+ export class ExpressionEvaluator {
22
+ constructor(context) {
23
+ Object.defineProperty(this, "context", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: void 0
28
+ });
29
+ if (context instanceof ExpressionContext) {
30
+ this.context = context;
31
+ }
32
+ else {
33
+ this.context = new ExpressionContext(context || {});
34
+ }
35
+ }
36
+ /**
37
+ * Evaluate a string that may contain template expressions like ${...}
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const evaluator = new ExpressionEvaluator({ data: { amount: 1500 } });
42
+ * evaluator.evaluate('${data.amount > 1000}'); // Returns: true
43
+ * evaluator.evaluate('Amount is ${data.amount}'); // Returns: "Amount is 1500"
44
+ * ```
45
+ */
46
+ evaluate(expression, options = {}) {
47
+ // Handle non-string primitives
48
+ if (typeof expression !== 'string') {
49
+ return expression;
50
+ }
51
+ const { defaultValue, throwOnError = false, sanitize = true } = options;
52
+ try {
53
+ // Check if string contains template expressions
54
+ const hasTemplates = expression.includes('${');
55
+ if (!hasTemplates) {
56
+ // No templates, return as-is
57
+ return expression;
58
+ }
59
+ // Special case: if the entire string is a single template expression, return the value directly
60
+ const singleTemplateMatch = expression.match(/^\$\{([^}]+)\}$/);
61
+ if (singleTemplateMatch) {
62
+ return this.evaluateExpression(singleTemplateMatch[1].trim(), { sanitize });
63
+ }
64
+ // Replace all ${...} expressions in a string with multiple parts
65
+ return expression.replace(/\$\{([^}]+)\}/g, (match, expr) => {
66
+ try {
67
+ const result = this.evaluateExpression(expr.trim(), { sanitize });
68
+ return String(result ?? '');
69
+ }
70
+ catch (error) {
71
+ if (throwOnError) {
72
+ throw error;
73
+ }
74
+ console.warn(`Expression evaluation failed for: ${expr}`, error);
75
+ return match; // Return original if evaluation fails
76
+ }
77
+ });
78
+ }
79
+ catch (error) {
80
+ if (throwOnError) {
81
+ throw error;
82
+ }
83
+ console.warn(`Failed to evaluate expression: ${expression}`, error);
84
+ return defaultValue ?? expression;
85
+ }
86
+ }
87
+ /**
88
+ * Evaluate a single expression (without ${} wrapper)
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * evaluator.evaluateExpression('data.amount > 1000'); // Returns: true
93
+ * evaluator.evaluateExpression('data.user.name'); // Returns: "John"
94
+ * ```
95
+ */
96
+ evaluateExpression(expression, options = {}) {
97
+ const { sanitize = true } = options;
98
+ if (!expression || expression.trim() === '') {
99
+ return undefined;
100
+ }
101
+ // Sanitize expression to prevent dangerous code execution
102
+ if (sanitize && this.isDangerous(expression)) {
103
+ throw new Error(`Potentially dangerous expression detected: ${expression}`);
104
+ }
105
+ try {
106
+ // Create a safe evaluation function
107
+ const contextObj = this.context.toObject();
108
+ // Build safe function with context variables
109
+ const varNames = Object.keys(contextObj);
110
+ const varValues = Object.values(contextObj);
111
+ // SECURITY NOTE: Using Function constructor for expression evaluation.
112
+ // This is a controlled use case with:
113
+ // 1. Sanitization check (isDangerous) blocks dangerous patterns
114
+ // 2. Strict mode enabled ("use strict")
115
+ // 3. Limited scope (only contextObj variables available)
116
+ // 4. No access to global objects (process, window, etc.)
117
+ // For production use, consider: expr-eval, safe-eval, or a custom parser
118
+ const fn = new Function(...varNames, `"use strict"; return (${expression});`);
119
+ // Execute with context values
120
+ return fn(...varValues);
121
+ }
122
+ catch (error) {
123
+ throw new Error(`Failed to evaluate expression "${expression}": ${error.message}`);
124
+ }
125
+ }
126
+ /**
127
+ * Check if expression contains potentially dangerous code
128
+ */
129
+ isDangerous(expression) {
130
+ const dangerousPatterns = [
131
+ /eval\s*\(/i,
132
+ /Function\s*\(/i,
133
+ /setTimeout\s*\(/i,
134
+ /setInterval\s*\(/i,
135
+ /import\s*\(/i,
136
+ /require\s*\(/i,
137
+ /process\./i,
138
+ /global\./i,
139
+ /window\./i,
140
+ /document\./i,
141
+ /__proto__/i,
142
+ /constructor\s*\(/i,
143
+ /prototype\./i,
144
+ ];
145
+ return dangerousPatterns.some(pattern => pattern.test(expression));
146
+ }
147
+ /**
148
+ * Evaluate a conditional expression and return boolean
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * evaluator.evaluateCondition('${data.age >= 18}'); // Returns: true/false
153
+ * ```
154
+ */
155
+ evaluateCondition(condition, options = {}) {
156
+ if (typeof condition === 'boolean') {
157
+ return condition;
158
+ }
159
+ if (!condition) {
160
+ return true; // Default to visible/enabled if no condition
161
+ }
162
+ const result = this.evaluate(condition, options);
163
+ // Convert result to boolean
164
+ return Boolean(result);
165
+ }
166
+ /**
167
+ * Update the context with new data
168
+ */
169
+ updateContext(data) {
170
+ Object.entries(data).forEach(([key, value]) => {
171
+ this.context.set(key, value);
172
+ });
173
+ }
174
+ /**
175
+ * Get the current context
176
+ */
177
+ getContext() {
178
+ return this.context;
179
+ }
180
+ /**
181
+ * Create a new evaluator with additional context data
182
+ */
183
+ withContext(data) {
184
+ return new ExpressionEvaluator(this.context.createChild(data));
185
+ }
186
+ }
187
+ /**
188
+ * Convenience function to quickly evaluate an expression
189
+ */
190
+ export function evaluateExpression(expression, context = {}, options = {}) {
191
+ const evaluator = new ExpressionEvaluator(context);
192
+ return evaluator.evaluate(expression, options);
193
+ }
194
+ /**
195
+ * Convenience function to evaluate a condition
196
+ */
197
+ export function evaluateCondition(condition, context = {}) {
198
+ const evaluator = new ExpressionEvaluator(context);
199
+ return evaluator.evaluateCondition(condition);
200
+ }
@@ -0,0 +1,9 @@
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
+ export * from './ExpressionContext';
9
+ export * from './ExpressionEvaluator';
@@ -0,0 +1,9 @@
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
+ export * from './ExpressionContext';
9
+ export * from './ExpressionEvaluator';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,14 @@
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
+ */
1
8
  export * from './types';
2
9
  export * from './registry/Registry';
3
10
  export * from './validation/schema-validator';
4
11
  export * from './builder/schema-builder';
12
+ export * from './utils/filter-converter';
13
+ export * from './evaluator';
14
+ export * from './actions';
package/dist/index.js CHANGED
@@ -1,7 +1,16 @@
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
+ */
1
8
  export * from './types';
2
9
  export * from './registry/Registry';
3
10
  export * from './validation/schema-validator';
4
11
  export * from './builder/schema-builder';
12
+ export * from './utils/filter-converter';
13
+ export * from './evaluator';
14
+ export * from './actions';
5
15
  // export * from './data-scope'; // TODO
6
- // export * from './evaluator'; // TODO
7
16
  // export * from './validators'; // TODO
@@ -1,3 +1,10 @@
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
+ */
1
8
  import type { SchemaNode } from '../types';
2
9
  export type ComponentRenderer<T = any> = T;
3
10
  export type ComponentInput = {
@@ -1,3 +1,10 @@
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
+ */
1
8
  export class Registry {
2
9
  constructor() {
3
10
  Object.defineProperty(this, "components", {
@@ -1,3 +1,10 @@
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
+ */
1
8
  export interface SchemaNode {
2
9
  type: string;
3
10
  id?: string;
@@ -1 +1,8 @@
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
+ */
1
8
  export {};
@@ -0,0 +1,57 @@
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
+ * Filter Converter Utilities
10
+ *
11
+ * Shared utilities for converting MongoDB-like filter operators
12
+ * to ObjectStack FilterNode AST format.
13
+ */
14
+ /**
15
+ * FilterNode AST type definition
16
+ * Represents a filter condition or a logical combination of conditions
17
+ *
18
+ * @example
19
+ * // Simple condition
20
+ * ['status', '=', 'active']
21
+ *
22
+ * // Logical combination
23
+ * ['and', ['age', '>=', 18], ['status', '=', 'active']]
24
+ */
25
+ export type FilterNode = [string, string, any] | [string, ...FilterNode[]];
26
+ /**
27
+ * Map MongoDB-like operators to ObjectStack filter operators.
28
+ *
29
+ * @param operator - MongoDB-style operator (e.g., '$gte', '$in')
30
+ * @returns ObjectStack operator or null if not recognized
31
+ */
32
+ export declare function convertOperatorToAST(operator: string): string | null;
33
+ /**
34
+ * Convert object-based filters to ObjectStack FilterNode AST format.
35
+ * Converts MongoDB-like operators to ObjectStack filter expressions.
36
+ *
37
+ * @param filter - Object-based filter with optional operators
38
+ * @returns FilterNode AST array
39
+ *
40
+ * @example
41
+ * // Simple filter - converted to AST
42
+ * convertFiltersToAST({ status: 'active' })
43
+ * // => ['status', '=', 'active']
44
+ *
45
+ * @example
46
+ * // Complex filter with operators
47
+ * convertFiltersToAST({ age: { $gte: 18 } })
48
+ * // => ['age', '>=', 18]
49
+ *
50
+ * @example
51
+ * // Multiple conditions
52
+ * convertFiltersToAST({ age: { $gte: 18, $lte: 65 }, status: 'active' })
53
+ * // => ['and', ['age', '>=', 18], ['age', '<=', 65], ['status', '=', 'active']]
54
+ *
55
+ * @throws {Error} If an unknown operator is encountered
56
+ */
57
+ export declare function convertFiltersToAST(filter: Record<string, any>): FilterNode | Record<string, any>;
@@ -0,0 +1,100 @@
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
+ * Map MongoDB-like operators to ObjectStack filter operators.
10
+ *
11
+ * @param operator - MongoDB-style operator (e.g., '$gte', '$in')
12
+ * @returns ObjectStack operator or null if not recognized
13
+ */
14
+ export function convertOperatorToAST(operator) {
15
+ const operatorMap = {
16
+ '$eq': '=',
17
+ '$ne': '!=',
18
+ '$gt': '>',
19
+ '$gte': '>=',
20
+ '$lt': '<',
21
+ '$lte': '<=',
22
+ '$in': 'in',
23
+ '$nin': 'notin',
24
+ '$notin': 'notin',
25
+ '$contains': 'contains',
26
+ '$startswith': 'startswith',
27
+ '$between': 'between',
28
+ };
29
+ return operatorMap[operator] || null;
30
+ }
31
+ /**
32
+ * Convert object-based filters to ObjectStack FilterNode AST format.
33
+ * Converts MongoDB-like operators to ObjectStack filter expressions.
34
+ *
35
+ * @param filter - Object-based filter with optional operators
36
+ * @returns FilterNode AST array
37
+ *
38
+ * @example
39
+ * // Simple filter - converted to AST
40
+ * convertFiltersToAST({ status: 'active' })
41
+ * // => ['status', '=', 'active']
42
+ *
43
+ * @example
44
+ * // Complex filter with operators
45
+ * convertFiltersToAST({ age: { $gte: 18 } })
46
+ * // => ['age', '>=', 18]
47
+ *
48
+ * @example
49
+ * // Multiple conditions
50
+ * convertFiltersToAST({ age: { $gte: 18, $lte: 65 }, status: 'active' })
51
+ * // => ['and', ['age', '>=', 18], ['age', '<=', 65], ['status', '=', 'active']]
52
+ *
53
+ * @throws {Error} If an unknown operator is encountered
54
+ */
55
+ export function convertFiltersToAST(filter) {
56
+ const conditions = [];
57
+ for (const [field, value] of Object.entries(filter)) {
58
+ if (value === null || value === undefined)
59
+ continue;
60
+ // Check if value is a complex operator object
61
+ if (typeof value === 'object' && !Array.isArray(value)) {
62
+ // Handle operator-based filters
63
+ for (const [operator, operatorValue] of Object.entries(value)) {
64
+ // Special handling for $regex - warn users about limited support
65
+ if (operator === '$regex') {
66
+ console.warn(`[ObjectUI] Warning: $regex operator is not fully supported. ` +
67
+ `Converting to 'contains' which only supports substring matching, not regex patterns. ` +
68
+ `Field: '${field}', Value: ${JSON.stringify(operatorValue)}. ` +
69
+ `Consider using $contains or $startswith instead.`);
70
+ conditions.push([field, 'contains', operatorValue]);
71
+ continue;
72
+ }
73
+ const astOperator = convertOperatorToAST(operator);
74
+ if (astOperator) {
75
+ conditions.push([field, astOperator, operatorValue]);
76
+ }
77
+ else {
78
+ // Unknown operator - throw error to avoid silent failure
79
+ throw new Error(`[ObjectUI] Unknown filter operator '${operator}' for field '${field}'. ` +
80
+ `Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $startswith, $between. ` +
81
+ `If you need exact object matching, use the value directly without an operator.`);
82
+ }
83
+ }
84
+ }
85
+ else {
86
+ // Simple equality filter
87
+ conditions.push([field, '=', value]);
88
+ }
89
+ }
90
+ // If no conditions, return original filter
91
+ if (conditions.length === 0) {
92
+ return filter;
93
+ }
94
+ // If only one condition, return it directly
95
+ if (conditions.length === 1) {
96
+ return conditions[0];
97
+ }
98
+ // Multiple conditions: combine with 'and'
99
+ return ['and', ...conditions];
100
+ }
@@ -1,3 +1,10 @@
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
+ */
1
8
  /**
2
9
  * @object-ui/core - Schema Validation
3
10
  *
@@ -1,11 +1,9 @@
1
1
  /**
2
- * @object-ui/core - Schema Validation
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
3
4
  *
4
- * Runtime validation utilities for Object UI schemas.
5
- * These utilities help ensure schemas are valid before rendering.
6
- *
7
- * @module validation
8
- * @packageDocumentation
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
9
7
  */
10
8
  /**
11
9
  * Validation rules for base schema
package/package.json CHANGED
@@ -1,17 +1,28 @@
1
1
  {
2
2
  "name": "@object-ui/core",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
+ "description": "Core logic, types, and validation for Object UI. Zero React dependencies.",
6
+ "homepage": "https://www.objectui.org",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/objectstack-ai/objectui.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/objectstack-ai/objectui/issues"
14
+ },
5
15
  "main": "dist/index.js",
6
16
  "types": "dist/index.d.ts",
7
17
  "dependencies": {
8
- "lodash": "^4.17.21",
9
- "zod": "^3.22.4",
10
- "@object-ui/types": "0.3.0"
18
+ "@objectstack/spec": "^0.3.3",
19
+ "lodash": "^4.17.23",
20
+ "zod": "^4.3.6",
21
+ "@object-ui/types": "0.3.1"
11
22
  },
12
23
  "devDependencies": {
13
24
  "typescript": "^5.9.3",
14
- "vitest": "^4.0.17"
25
+ "vitest": "^4.0.18"
15
26
  },
16
27
  "scripts": {
17
28
  "build": "tsc",