@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,142 @@
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
+ import { PluginScopeImpl } from './PluginScopeImpl.js';
9
+ export class PluginSystem {
10
+ constructor() {
11
+ Object.defineProperty(this, "plugins", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: new Map()
16
+ });
17
+ Object.defineProperty(this, "loaded", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: new Set()
22
+ });
23
+ Object.defineProperty(this, "scopes", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: new Map()
28
+ });
29
+ }
30
+ /**
31
+ * Load a plugin into the system with optional scope isolation
32
+ * @param plugin The plugin definition to load
33
+ * @param registry The component registry to use for registration
34
+ * @param useScope Whether to use scoped loading (default: true for better isolation)
35
+ * @throws Error if dependencies are missing
36
+ */
37
+ async loadPlugin(plugin, registry, useScope = true) {
38
+ // Check if already loaded
39
+ if (this.loaded.has(plugin.name)) {
40
+ console.warn(`Plugin "${plugin.name}" is already loaded. Skipping.`);
41
+ return;
42
+ }
43
+ // Check dependencies
44
+ for (const dep of plugin.dependencies || []) {
45
+ if (!this.loaded.has(dep)) {
46
+ throw new Error(`Missing dependency: ${dep} required by ${plugin.name}`);
47
+ }
48
+ }
49
+ try {
50
+ if (useScope) {
51
+ // Create scoped environment for plugin
52
+ const scope = new PluginScopeImpl(plugin.name, plugin.version, registry, plugin.scopeConfig);
53
+ // Store scope for cleanup
54
+ this.scopes.set(plugin.name, scope);
55
+ // Execute registration with scope
56
+ plugin.register(scope);
57
+ }
58
+ else {
59
+ // Legacy mode: direct registry access
60
+ plugin.register(registry);
61
+ }
62
+ // Store plugin definition
63
+ this.plugins.set(plugin.name, plugin);
64
+ // Execute lifecycle hook
65
+ await plugin.onLoad?.();
66
+ // Mark as loaded
67
+ this.loaded.add(plugin.name);
68
+ }
69
+ catch (error) {
70
+ // Clean up on failure
71
+ this.plugins.delete(plugin.name);
72
+ this.scopes.delete(plugin.name);
73
+ throw error;
74
+ }
75
+ }
76
+ /**
77
+ * Unload a plugin from the system
78
+ * @param name The name of the plugin to unload
79
+ * @throws Error if other plugins depend on this plugin
80
+ */
81
+ async unloadPlugin(name) {
82
+ const plugin = this.plugins.get(name);
83
+ if (!plugin) {
84
+ throw new Error(`Plugin "${name}" is not loaded`);
85
+ }
86
+ // Check if any loaded plugins depend on this one
87
+ for (const [pluginName, pluginDef] of this.plugins.entries()) {
88
+ if (this.loaded.has(pluginName) && pluginDef.dependencies?.includes(name)) {
89
+ throw new Error(`Cannot unload plugin "${name}" - plugin "${pluginName}" depends on it`);
90
+ }
91
+ }
92
+ // Execute lifecycle hook
93
+ await plugin.onUnload?.();
94
+ // Clean up scope if exists
95
+ const scope = this.scopes.get(name);
96
+ if (scope) {
97
+ scope.cleanup();
98
+ this.scopes.delete(name);
99
+ }
100
+ // Remove from loaded set
101
+ this.loaded.delete(name);
102
+ this.plugins.delete(name);
103
+ }
104
+ /**
105
+ * Get the scope for a loaded plugin
106
+ * @param name The name of the plugin
107
+ * @returns The plugin scope or undefined
108
+ */
109
+ getScope(name) {
110
+ return this.scopes.get(name);
111
+ }
112
+ /**
113
+ * Check if a plugin is loaded
114
+ * @param name The name of the plugin
115
+ * @returns true if the plugin is loaded
116
+ */
117
+ isLoaded(name) {
118
+ return this.loaded.has(name);
119
+ }
120
+ /**
121
+ * Get a loaded plugin definition
122
+ * @param name The name of the plugin
123
+ * @returns The plugin definition or undefined
124
+ */
125
+ getPlugin(name) {
126
+ return this.plugins.get(name);
127
+ }
128
+ /**
129
+ * Get all loaded plugin names
130
+ * @returns Array of loaded plugin names
131
+ */
132
+ getLoadedPlugins() {
133
+ return Array.from(this.loaded);
134
+ }
135
+ /**
136
+ * Get all plugin definitions
137
+ * @returns Array of all plugin definitions
138
+ */
139
+ getAllPlugins() {
140
+ return Array.from(this.plugins.values());
141
+ }
142
+ }
@@ -1,4 +1,11 @@
1
- import type { SchemaNode } from '../types';
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
+ import type { SchemaNode } from '../types/index.js';
2
9
  export type ComponentRenderer<T = any> = T;
3
10
  export type ComponentInput = {
4
11
  name: string;
@@ -18,6 +25,7 @@ export type ComponentMeta = {
18
25
  label?: string;
19
26
  icon?: string;
20
27
  category?: string;
28
+ namespace?: string;
21
29
  inputs?: ComponentInput[];
22
30
  defaultProps?: Record<string, any>;
23
31
  defaultChildren?: SchemaNode[];
@@ -39,11 +47,79 @@ export type ComponentConfig<T = any> = ComponentMeta & {
39
47
  };
40
48
  export declare class Registry<T = any> {
41
49
  private components;
50
+ /**
51
+ * Register a component with optional namespace support.
52
+ * If namespace is provided in meta, the component will be registered as "namespace:type".
53
+ *
54
+ * @param type - Component type identifier
55
+ * @param component - Component renderer
56
+ * @param meta - Component metadata (including optional namespace)
57
+ *
58
+ * @example
59
+ * // Register with namespace
60
+ * registry.register('button', ButtonComponent, { namespace: 'ui' });
61
+ * // Accessible as 'ui:button' or 'button' (fallback)
62
+ *
63
+ * @example
64
+ * // Register without namespace (backward compatible)
65
+ * registry.register('button', ButtonComponent);
66
+ * // Accessible as 'button'
67
+ */
42
68
  register(type: string, component: ComponentRenderer<T>, meta?: ComponentMeta): void;
43
- get(type: string): ComponentRenderer<T> | undefined;
44
- getConfig(type: string): ComponentConfig<T> | undefined;
45
- has(type: string): boolean;
69
+ /**
70
+ * Get a component by type. Supports both namespaced and non-namespaced lookups.
71
+ *
72
+ * @param type - Component type (e.g., 'button' or 'ui:button')
73
+ * @param namespace - Optional namespace for lookup priority
74
+ * @returns Component renderer or undefined
75
+ *
76
+ * @example
77
+ * // Direct lookup
78
+ * registry.get('ui:button') // Gets ui:button
79
+ *
80
+ * @example
81
+ * // Fallback lookup
82
+ * registry.get('button') // Gets first registered button
83
+ *
84
+ * @example
85
+ * // Namespaced lookup with priority
86
+ * registry.get('button', 'ui') // Tries 'ui:button' first, then 'button'
87
+ */
88
+ get(type: string, namespace?: string): ComponentRenderer<T> | undefined;
89
+ /**
90
+ * Get component configuration by type with namespace support.
91
+ *
92
+ * @param type - Component type (e.g., 'button' or 'ui:button')
93
+ * @param namespace - Optional namespace for lookup priority
94
+ * @returns Component configuration or undefined
95
+ */
96
+ getConfig(type: string, namespace?: string): ComponentConfig<T> | undefined;
97
+ /**
98
+ * Check if a component type is registered.
99
+ *
100
+ * @param type - Component type (e.g., 'button' or 'ui:button')
101
+ * @param namespace - Optional namespace for lookup
102
+ * @returns True if component is registered
103
+ */
104
+ has(type: string, namespace?: string): boolean;
105
+ /**
106
+ * Get all registered component types.
107
+ *
108
+ * @returns Array of all component type identifiers
109
+ */
46
110
  getAllTypes(): string[];
111
+ /**
112
+ * Get all registered component configurations.
113
+ *
114
+ * @returns Array of all component configurations
115
+ */
47
116
  getAllConfigs(): ComponentConfig<T>[];
117
+ /**
118
+ * Get all components in a specific namespace.
119
+ *
120
+ * @param namespace - Namespace to filter by
121
+ * @returns Array of component configurations in the namespace
122
+ */
123
+ getNamespaceComponents(namespace: string): ComponentConfig<T>[];
48
124
  }
49
125
  export declare const ComponentRegistry: Registry<any>;
@@ -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", {
@@ -7,30 +14,135 @@ export class Registry {
7
14
  value: new Map()
8
15
  });
9
16
  }
17
+ /**
18
+ * Register a component with optional namespace support.
19
+ * If namespace is provided in meta, the component will be registered as "namespace:type".
20
+ *
21
+ * @param type - Component type identifier
22
+ * @param component - Component renderer
23
+ * @param meta - Component metadata (including optional namespace)
24
+ *
25
+ * @example
26
+ * // Register with namespace
27
+ * registry.register('button', ButtonComponent, { namespace: 'ui' });
28
+ * // Accessible as 'ui:button' or 'button' (fallback)
29
+ *
30
+ * @example
31
+ * // Register without namespace (backward compatible)
32
+ * registry.register('button', ButtonComponent);
33
+ * // Accessible as 'button'
34
+ */
10
35
  register(type, component, meta) {
11
- if (this.components.has(type)) {
12
- console.warn(`Component type "${type}" is already registered. Overwriting.`);
36
+ const fullType = meta?.namespace ? `${meta.namespace}:${type}` : type;
37
+ // Warn if registering without namespace (deprecated pattern)
38
+ if (!meta?.namespace) {
39
+ console.warn(`Registering component "${type}" without a namespace is deprecated. ` +
40
+ `Please provide a namespace in the meta parameter.`);
13
41
  }
14
- this.components.set(type, {
15
- type,
42
+ if (this.components.has(fullType)) {
43
+ // console.warn(`Component type "${fullType}" is already registered. Overwriting.`);
44
+ }
45
+ this.components.set(fullType, {
46
+ type: fullType,
16
47
  component,
17
48
  ...meta
18
49
  });
50
+ // Also register without namespace for backward compatibility
51
+ // This allows "button" to work even when registered as "ui:button"
52
+ // Note: If multiple namespaced components share the same short name,
53
+ // the last registration wins for non-namespaced lookups
54
+ if (meta?.namespace) {
55
+ this.components.set(type, {
56
+ type: fullType, // Keep reference to namespaced type
57
+ component,
58
+ ...meta
59
+ });
60
+ }
19
61
  }
20
- get(type) {
62
+ /**
63
+ * Get a component by type. Supports both namespaced and non-namespaced lookups.
64
+ *
65
+ * @param type - Component type (e.g., 'button' or 'ui:button')
66
+ * @param namespace - Optional namespace for lookup priority
67
+ * @returns Component renderer or undefined
68
+ *
69
+ * @example
70
+ * // Direct lookup
71
+ * registry.get('ui:button') // Gets ui:button
72
+ *
73
+ * @example
74
+ * // Fallback lookup
75
+ * registry.get('button') // Gets first registered button
76
+ *
77
+ * @example
78
+ * // Namespaced lookup with priority
79
+ * registry.get('button', 'ui') // Tries 'ui:button' first, then 'button'
80
+ */
81
+ get(type, namespace) {
82
+ // If namespace is explicitly provided, ONLY look in that namespace (no fallback)
83
+ if (namespace) {
84
+ const namespacedType = `${namespace}:${type}`;
85
+ return this.components.get(namespacedType)?.component;
86
+ }
87
+ // When no namespace provided, use backward compatibility lookup
21
88
  return this.components.get(type)?.component;
22
89
  }
23
- getConfig(type) {
90
+ /**
91
+ * Get component configuration by type with namespace support.
92
+ *
93
+ * @param type - Component type (e.g., 'button' or 'ui:button')
94
+ * @param namespace - Optional namespace for lookup priority
95
+ * @returns Component configuration or undefined
96
+ */
97
+ getConfig(type, namespace) {
98
+ // If namespace is explicitly provided, ONLY look in that namespace (no fallback)
99
+ if (namespace) {
100
+ const namespacedType = `${namespace}:${type}`;
101
+ return this.components.get(namespacedType);
102
+ }
103
+ // When no namespace provided, use backward compatibility lookup
24
104
  return this.components.get(type);
25
105
  }
26
- has(type) {
106
+ /**
107
+ * Check if a component type is registered.
108
+ *
109
+ * @param type - Component type (e.g., 'button' or 'ui:button')
110
+ * @param namespace - Optional namespace for lookup
111
+ * @returns True if component is registered
112
+ */
113
+ has(type, namespace) {
114
+ // If namespace is explicitly provided, ONLY look in that namespace (no fallback)
115
+ if (namespace) {
116
+ const namespacedType = `${namespace}:${type}`;
117
+ return this.components.has(namespacedType);
118
+ }
119
+ // When no namespace provided, use backward compatibility lookup
27
120
  return this.components.has(type);
28
121
  }
122
+ /**
123
+ * Get all registered component types.
124
+ *
125
+ * @returns Array of all component type identifiers
126
+ */
29
127
  getAllTypes() {
30
128
  return Array.from(this.components.keys());
31
129
  }
130
+ /**
131
+ * Get all registered component configurations.
132
+ *
133
+ * @returns Array of all component configurations
134
+ */
32
135
  getAllConfigs() {
33
136
  return Array.from(this.components.values());
34
137
  }
138
+ /**
139
+ * Get all components in a specific namespace.
140
+ *
141
+ * @param namespace - Namespace to filter by
142
+ * @returns Array of component configurations in the namespace
143
+ */
144
+ getNamespaceComponents(namespace) {
145
+ return Array.from(this.components.values()).filter(config => config.namespace === namespace);
146
+ }
35
147
  }
36
148
  export const ComponentRegistry = new Registry();
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @object-ui/core - Validation Module
3
+ *
4
+ * Phase 3.5: Validation engine
5
+ * ObjectStack Spec v0.7.1: Object-level validation
6
+ */
7
+ export * from './validation-engine.js';
8
+ export * from './schema-validator.js';
9
+ export * from './validators/index.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @object-ui/core - Validation Module
3
+ *
4
+ * Phase 3.5: Validation engine
5
+ * ObjectStack Spec v0.7.1: Object-level validation
6
+ */
7
+ export * from './validation-engine.js';
8
+ export * from './schema-validator.js';
9
+ export * from './validators/index.js';
@@ -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