@object-ui/core 3.3.0 → 3.3.2
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/CHANGELOG.md +12 -0
- package/README.md +20 -1
- package/dist/actions/ActionRunner.d.ts +9 -0
- package/dist/actions/ActionRunner.js +41 -4
- package/dist/adapters/ValueDataSource.js +3 -1
- package/dist/registry/Registry.d.ts +47 -0
- package/dist/registry/Registry.js +92 -0
- package/dist/utils/filter-converter.js +25 -5
- package/package.json +32 -8
- package/.turbo/turbo-build.log +0 -4
- package/src/__benchmarks__/core.bench.ts +0 -64
- package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
- package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
- package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
- package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
- package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
- package/src/actions/ActionEngine.ts +0 -268
- package/src/actions/ActionRunner.ts +0 -717
- package/src/actions/TransactionManager.ts +0 -521
- package/src/actions/UndoManager.ts +0 -215
- package/src/actions/__tests__/ActionEngine.test.ts +0 -206
- package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
- package/src/actions/__tests__/ActionRunner.test.ts +0 -711
- package/src/actions/__tests__/TransactionManager.test.ts +0 -447
- package/src/actions/__tests__/UndoManager.test.ts +0 -320
- package/src/actions/index.ts +0 -12
- package/src/adapters/ApiDataSource.ts +0 -376
- package/src/adapters/README.md +0 -180
- package/src/adapters/ValueDataSource.ts +0 -459
- package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
- package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
- package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
- package/src/adapters/index.ts +0 -15
- package/src/adapters/resolveDataSource.ts +0 -79
- package/src/builder/__tests__/schema-builder.test.ts +0 -235
- package/src/builder/schema-builder.ts +0 -584
- package/src/data-scope/DataScopeManager.ts +0 -269
- package/src/data-scope/ViewDataProvider.ts +0 -282
- package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
- package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
- package/src/data-scope/index.ts +0 -24
- package/src/errors/__tests__/errors.test.ts +0 -292
- package/src/errors/index.ts +0 -269
- package/src/evaluator/ExpressionCache.ts +0 -206
- package/src/evaluator/ExpressionContext.ts +0 -118
- package/src/evaluator/ExpressionEvaluator.ts +0 -315
- package/src/evaluator/FormulaFunctions.ts +0 -398
- package/src/evaluator/SafeExpressionParser.ts +0 -893
- package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
- package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
- package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
- package/src/evaluator/index.ts +0 -13
- package/src/index.ts +0 -38
- package/src/protocols/DndProtocol.ts +0 -168
- package/src/protocols/KeyboardProtocol.ts +0 -181
- package/src/protocols/NotificationProtocol.ts +0 -150
- package/src/protocols/ResponsiveProtocol.ts +0 -210
- package/src/protocols/SharingProtocol.ts +0 -185
- package/src/protocols/index.ts +0 -13
- package/src/query/__tests__/query-ast.test.ts +0 -211
- package/src/query/__tests__/window-functions.test.ts +0 -275
- package/src/query/index.ts +0 -7
- package/src/query/query-ast.ts +0 -341
- package/src/registry/PluginScopeImpl.ts +0 -259
- package/src/registry/PluginSystem.ts +0 -206
- package/src/registry/Registry.ts +0 -219
- package/src/registry/WidgetRegistry.ts +0 -316
- package/src/registry/__tests__/PluginSystem.test.ts +0 -309
- package/src/registry/__tests__/Registry.test.ts +0 -293
- package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
- package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
- package/src/theme/ThemeEngine.ts +0 -530
- package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
- package/src/theme/index.ts +0 -24
- package/src/types/index.ts +0 -21
- package/src/utils/__tests__/debug-collector.test.ts +0 -102
- package/src/utils/__tests__/debug.test.ts +0 -134
- package/src/utils/__tests__/expand-fields.test.ts +0 -120
- package/src/utils/__tests__/extract-records.test.ts +0 -50
- package/src/utils/__tests__/filter-converter.test.ts +0 -118
- package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
- package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
- package/src/utils/debug-collector.ts +0 -100
- package/src/utils/debug.ts +0 -148
- package/src/utils/expand-fields.ts +0 -76
- package/src/utils/extract-records.ts +0 -33
- package/src/utils/filter-converter.ts +0 -133
- package/src/utils/merge-views-into-objects.ts +0 -36
- package/src/utils/normalize-quick-filter.ts +0 -78
- package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
- package/src/validation/__tests__/schema-validator.test.ts +0 -118
- package/src/validation/__tests__/validation-engine.test.ts +0 -102
- package/src/validation/index.ts +0 -10
- package/src/validation/schema-validator.ts +0 -344
- package/src/validation/validation-engine.ts +0 -528
- package/src/validation/validators/index.ts +0 -25
- package/src/validation/validators/object-validation-engine.ts +0 -722
- package/tsconfig.json +0 -15
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -79,6 +79,25 @@ This allows the core types and logic to be used in:
|
|
|
79
79
|
|
|
80
80
|
See [full documentation](https://objectui.org/api/core) for detailed API reference.
|
|
81
81
|
|
|
82
|
+
<!-- release-metadata:v3.3.0 -->
|
|
83
|
+
|
|
84
|
+
## Compatibility
|
|
85
|
+
|
|
86
|
+
- **Node.js:** ≥ 18
|
|
87
|
+
- **TypeScript:** ≥ 5.0 (strict mode)
|
|
88
|
+
- **`@objectstack/spec`:** ^3.3.0
|
|
89
|
+
- **`@objectstack/client`:** ^3.3.0
|
|
90
|
+
- **Tailwind CSS:** ≥ 3.4 (for packages with UI)
|
|
91
|
+
|
|
92
|
+
## Links
|
|
93
|
+
|
|
94
|
+
- 📚 [Documentation](https://www.objectui.org/docs/core)
|
|
95
|
+
- 📦 [npm package](https://www.npmjs.com/package/@object-ui/core)
|
|
96
|
+
- 📝 [Changelog](./CHANGELOG.md)
|
|
97
|
+
- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
|
|
98
|
+
- 🤝 [Contributing Guide](https://github.com/objectstack-ai/objectui/blob/main/CONTRIBUTING.md)
|
|
99
|
+
- 🗺️ [Roadmap](https://github.com/objectstack-ai/objectui/blob/main/ROADMAP.md)
|
|
100
|
+
|
|
82
101
|
## License
|
|
83
102
|
|
|
84
|
-
MIT
|
|
103
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -178,6 +178,7 @@ export interface ActionParamDef {
|
|
|
178
178
|
}
|
|
179
179
|
export declare class ActionRunner {
|
|
180
180
|
private handlers;
|
|
181
|
+
private scripts;
|
|
181
182
|
private evaluator;
|
|
182
183
|
private context;
|
|
183
184
|
private confirmHandler;
|
|
@@ -209,6 +210,14 @@ export declare class ActionRunner {
|
|
|
209
210
|
setParamCollectionHandler(handler: ParamCollectionHandler): void;
|
|
210
211
|
registerHandler(actionName: string, handler: ActionHandler): void;
|
|
211
212
|
unregisterHandler(actionName: string): void;
|
|
213
|
+
/**
|
|
214
|
+
* Register a named script handler. When a `script` action's
|
|
215
|
+
* `target`/`execute` matches the registered name, the handler runs
|
|
216
|
+
* instead of the expression evaluator. Lets dashboards/views wire
|
|
217
|
+
* symbolic action names (e.g. 'export_dashboard_pdf') to JS callbacks.
|
|
218
|
+
*/
|
|
219
|
+
registerScript(scriptName: string, handler: ActionHandler): void;
|
|
220
|
+
unregisterScript(scriptName: string): void;
|
|
212
221
|
execute(action: ActionDef): Promise<ActionResult>;
|
|
213
222
|
/**
|
|
214
223
|
* Execute multiple actions in sequence or parallel.
|
|
@@ -22,6 +22,12 @@ export class ActionRunner {
|
|
|
22
22
|
writable: true,
|
|
23
23
|
value: new Map()
|
|
24
24
|
});
|
|
25
|
+
Object.defineProperty(this, "scripts", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: new Map()
|
|
30
|
+
});
|
|
25
31
|
Object.defineProperty(this, "evaluator", {
|
|
26
32
|
enumerable: true,
|
|
27
33
|
configurable: true,
|
|
@@ -110,6 +116,18 @@ export class ActionRunner {
|
|
|
110
116
|
unregisterHandler(actionName) {
|
|
111
117
|
this.handlers.delete(actionName);
|
|
112
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Register a named script handler. When a `script` action's
|
|
121
|
+
* `target`/`execute` matches the registered name, the handler runs
|
|
122
|
+
* instead of the expression evaluator. Lets dashboards/views wire
|
|
123
|
+
* symbolic action names (e.g. 'export_dashboard_pdf') to JS callbacks.
|
|
124
|
+
*/
|
|
125
|
+
registerScript(scriptName, handler) {
|
|
126
|
+
this.scripts.set(scriptName, handler);
|
|
127
|
+
}
|
|
128
|
+
unregisterScript(scriptName) {
|
|
129
|
+
this.scripts.delete(scriptName);
|
|
130
|
+
}
|
|
113
131
|
async execute(action) {
|
|
114
132
|
try {
|
|
115
133
|
// Resolve the action type
|
|
@@ -143,14 +161,21 @@ export class ActionRunner {
|
|
|
143
161
|
}
|
|
144
162
|
// Param collection: if the action defines ActionParam[] to collect,
|
|
145
163
|
// show a dialog to gather user input before executing.
|
|
146
|
-
|
|
164
|
+
// Spec defines this as `params: ActionParam[]`; ActionRunner historically
|
|
165
|
+
// used `actionParams` to disambiguate from the static-params object that
|
|
166
|
+
// some custom handlers consume. Accept both — when `params` is an array,
|
|
167
|
+
// treat it as the input-collection definition.
|
|
168
|
+
const paramDefs = action.actionParams && Array.isArray(action.actionParams) ? action.actionParams
|
|
169
|
+
: (Array.isArray(action.params) ? action.params : undefined);
|
|
170
|
+
if (paramDefs && paramDefs.length > 0) {
|
|
147
171
|
if (this.paramCollectionHandler) {
|
|
148
|
-
const collected = await this.paramCollectionHandler(
|
|
172
|
+
const collected = await this.paramCollectionHandler(paramDefs);
|
|
149
173
|
if (collected === null) {
|
|
150
174
|
return { success: false, error: 'Action cancelled by user (params)' };
|
|
151
175
|
}
|
|
152
|
-
// Merge collected params into action.params
|
|
153
|
-
|
|
176
|
+
// Merge collected params into action.params as a values map for downstream consumers.
|
|
177
|
+
// (Replace the array form with a values object once collected.)
|
|
178
|
+
action.params = { ...(Array.isArray(action.params) ? {} : (action.params || {})), ...collected };
|
|
154
179
|
}
|
|
155
180
|
}
|
|
156
181
|
// Check for a registered custom handler first
|
|
@@ -293,6 +318,18 @@ export class ActionRunner {
|
|
|
293
318
|
if (!script) {
|
|
294
319
|
return { success: false, error: 'No script provided for script action' };
|
|
295
320
|
}
|
|
321
|
+
// Named script registry wins over the expression evaluator. This lets
|
|
322
|
+
// dashboards/views bind a symbolic action name (e.g. 'export_dashboard_pdf')
|
|
323
|
+
// to a JS callback without piping the literal through ExpressionEvaluator.
|
|
324
|
+
const named = this.scripts.get(script);
|
|
325
|
+
if (named) {
|
|
326
|
+
try {
|
|
327
|
+
return await named(action, this.context);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
return { success: false, error: `Script execution failed: ${error.message}` };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
296
333
|
try {
|
|
297
334
|
const result = this.evaluator.evaluate(`\${${script}}`);
|
|
298
335
|
return { success: true, data: result };
|
|
@@ -53,7 +53,9 @@ function matchesASTFilter(record, filterNode) {
|
|
|
53
53
|
case 'in':
|
|
54
54
|
return Array.isArray(target) && target.includes(value);
|
|
55
55
|
case 'not in':
|
|
56
|
-
case '
|
|
56
|
+
case 'not_in':
|
|
57
|
+
case 'nin': // canonical (per spec)
|
|
58
|
+
case 'notin': // legacy alias
|
|
57
59
|
return Array.isArray(target) && !target.includes(value);
|
|
58
60
|
case 'contains': {
|
|
59
61
|
const lv = typeof value === 'string' ? value.toLowerCase() : '';
|
|
@@ -55,8 +55,23 @@ export type ComponentConfig<T = any> = ComponentMeta & {
|
|
|
55
55
|
type: string;
|
|
56
56
|
component: ComponentRenderer<T>;
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Lazy loader function used by `Registry.registerLazy`. The loader is invoked
|
|
60
|
+
* the first time a missing component type is requested through `getAsync`/the
|
|
61
|
+
* SchemaRenderer fallback path, and is expected to perform a dynamic
|
|
62
|
+
* `import()` of a plugin module whose top-level side-effects call
|
|
63
|
+
* `register()` for that type.
|
|
64
|
+
*/
|
|
65
|
+
export type LazyComponentLoader = () => Promise<unknown>;
|
|
58
66
|
export declare class Registry<T = any> {
|
|
59
67
|
private components;
|
|
68
|
+
private lazyEntries;
|
|
69
|
+
/**
|
|
70
|
+
* Notifies subscribers that the registry has changed (new components
|
|
71
|
+
* registered). Used by SchemaRenderer to re-render after a lazy plugin
|
|
72
|
+
* load completes.
|
|
73
|
+
*/
|
|
74
|
+
private listeners;
|
|
60
75
|
/**
|
|
61
76
|
* Register a component with optional namespace support.
|
|
62
77
|
* If namespace is provided in meta, the component will be registered as "namespace:type".
|
|
@@ -76,6 +91,38 @@ export declare class Registry<T = any> {
|
|
|
76
91
|
* // Accessible as 'button'
|
|
77
92
|
*/
|
|
78
93
|
register(type: string, component: ComponentRenderer<T>, meta?: ComponentMeta): void;
|
|
94
|
+
/**
|
|
95
|
+
* Register a lazy-loaded component. The `loader` is a function returning a
|
|
96
|
+
* dynamic `import()` whose target module performs `register()` calls for
|
|
97
|
+
* the given `type` as a top-level side effect.
|
|
98
|
+
*
|
|
99
|
+
* The loader will be invoked the first time `loadLazy(type)` is called (or
|
|
100
|
+
* the first time the SchemaRenderer encounters an unknown component that
|
|
101
|
+
* matches a registered lazy type). Subsequent registrations are idempotent.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ComponentRegistry.registerLazy('object-map', () => import('@object-ui/plugin-map'), { namespace: 'plugin-map' });
|
|
105
|
+
*/
|
|
106
|
+
registerLazy(type: string, loader: LazyComponentLoader, meta?: ComponentMeta): void;
|
|
107
|
+
/**
|
|
108
|
+
* Returns true if `type` (or its namespaced form) has a registered lazy
|
|
109
|
+
* loader awaiting first use.
|
|
110
|
+
*/
|
|
111
|
+
hasLazy(type: string, namespace?: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Trigger the lazy loader for `type`, if any. Resolves once the loader
|
|
114
|
+
* completes (whether or not the loaded module actually registered the
|
|
115
|
+
* expected type — caller should re-check the registry afterwards).
|
|
116
|
+
* Returns `undefined` if no lazy entry matches.
|
|
117
|
+
*/
|
|
118
|
+
loadLazy(type: string, namespace?: string): Promise<unknown> | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Subscribe to registry changes (component registrations). Returns an
|
|
121
|
+
* unsubscribe function. Used by React renderers to re-render when a
|
|
122
|
+
* lazy-loaded plugin finishes registering its components.
|
|
123
|
+
*/
|
|
124
|
+
subscribe(listener: () => void): () => void;
|
|
125
|
+
private notify;
|
|
79
126
|
/**
|
|
80
127
|
* Get a component by type. Supports both namespaced and non-namespaced lookups.
|
|
81
128
|
*
|
|
@@ -13,6 +13,23 @@ export class Registry {
|
|
|
13
13
|
writable: true,
|
|
14
14
|
value: new Map()
|
|
15
15
|
});
|
|
16
|
+
Object.defineProperty(this, "lazyEntries", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: new Map()
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Notifies subscribers that the registry has changed (new components
|
|
24
|
+
* registered). Used by SchemaRenderer to re-render after a lazy plugin
|
|
25
|
+
* load completes.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(this, "listeners", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
value: new Set()
|
|
32
|
+
});
|
|
16
33
|
}
|
|
17
34
|
/**
|
|
18
35
|
* Register a component with optional namespace support.
|
|
@@ -65,6 +82,81 @@ export class Registry {
|
|
|
65
82
|
...meta
|
|
66
83
|
});
|
|
67
84
|
}
|
|
85
|
+
// A real component is now available — clear any matching lazy stub so we
|
|
86
|
+
// don't keep holding the loader reference, and notify subscribers.
|
|
87
|
+
this.lazyEntries.delete(fullType);
|
|
88
|
+
this.lazyEntries.delete(type);
|
|
89
|
+
this.notify();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Register a lazy-loaded component. The `loader` is a function returning a
|
|
93
|
+
* dynamic `import()` whose target module performs `register()` calls for
|
|
94
|
+
* the given `type` as a top-level side effect.
|
|
95
|
+
*
|
|
96
|
+
* The loader will be invoked the first time `loadLazy(type)` is called (or
|
|
97
|
+
* the first time the SchemaRenderer encounters an unknown component that
|
|
98
|
+
* matches a registered lazy type). Subsequent registrations are idempotent.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ComponentRegistry.registerLazy('object-map', () => import('@object-ui/plugin-map'), { namespace: 'plugin-map' });
|
|
102
|
+
*/
|
|
103
|
+
registerLazy(type, loader, meta) {
|
|
104
|
+
const fullType = meta?.namespace ? `${meta.namespace}:${type}` : type;
|
|
105
|
+
const entry = { loader, meta };
|
|
106
|
+
this.lazyEntries.set(fullType, entry);
|
|
107
|
+
if (meta?.namespace && !meta?.skipFallback) {
|
|
108
|
+
this.lazyEntries.set(type, entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns true if `type` (or its namespaced form) has a registered lazy
|
|
113
|
+
* loader awaiting first use.
|
|
114
|
+
*/
|
|
115
|
+
hasLazy(type, namespace) {
|
|
116
|
+
if (namespace)
|
|
117
|
+
return this.lazyEntries.has(`${namespace}:${type}`);
|
|
118
|
+
return this.lazyEntries.has(type);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Trigger the lazy loader for `type`, if any. Resolves once the loader
|
|
122
|
+
* completes (whether or not the loaded module actually registered the
|
|
123
|
+
* expected type — caller should re-check the registry afterwards).
|
|
124
|
+
* Returns `undefined` if no lazy entry matches.
|
|
125
|
+
*/
|
|
126
|
+
loadLazy(type, namespace) {
|
|
127
|
+
const key = namespace ? `${namespace}:${type}` : type;
|
|
128
|
+
const entry = this.lazyEntries.get(key);
|
|
129
|
+
if (!entry)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (!entry.pending) {
|
|
132
|
+
entry.pending = entry.loader().catch((err) => {
|
|
133
|
+
// Allow retries on failure by clearing the cached promise.
|
|
134
|
+
entry.pending = undefined;
|
|
135
|
+
throw err;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return entry.pending;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Subscribe to registry changes (component registrations). Returns an
|
|
142
|
+
* unsubscribe function. Used by React renderers to re-render when a
|
|
143
|
+
* lazy-loaded plugin finishes registering its components.
|
|
144
|
+
*/
|
|
145
|
+
subscribe(listener) {
|
|
146
|
+
this.listeners.add(listener);
|
|
147
|
+
return () => {
|
|
148
|
+
this.listeners.delete(listener);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
notify() {
|
|
152
|
+
for (const listener of this.listeners) {
|
|
153
|
+
try {
|
|
154
|
+
listener();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error('[Registry] listener error', err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
68
160
|
}
|
|
69
161
|
/**
|
|
70
162
|
* Get a component by type. Supports both namespaced and non-namespaced lookups.
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* @returns ObjectStack operator or null if not recognized
|
|
13
13
|
*/
|
|
14
14
|
export function convertOperatorToAST(operator) {
|
|
15
|
+
// Spec reference: framework/packages/spec/src/data/filter.zod.ts
|
|
16
|
+
// Canonical MongoDB-style keys are camelCase ($startsWith, $endsWith, $notContains).
|
|
17
|
+
// Lowercase aliases are accepted for tolerance.
|
|
15
18
|
const operatorMap = {
|
|
16
19
|
'$eq': '=',
|
|
17
20
|
'$ne': '!=',
|
|
@@ -20,11 +23,16 @@ export function convertOperatorToAST(operator) {
|
|
|
20
23
|
'$lt': '<',
|
|
21
24
|
'$lte': '<=',
|
|
22
25
|
'$in': 'in',
|
|
23
|
-
'$nin': '
|
|
24
|
-
'$notin': '
|
|
26
|
+
'$nin': 'nin',
|
|
27
|
+
'$notin': 'nin',
|
|
28
|
+
'$between': 'between',
|
|
25
29
|
'$contains': 'contains',
|
|
30
|
+
'$notContains': 'notcontains',
|
|
31
|
+
'$notcontains': 'notcontains',
|
|
32
|
+
'$startsWith': 'startswith',
|
|
26
33
|
'$startswith': 'startswith',
|
|
27
|
-
'$
|
|
34
|
+
'$endsWith': 'endswith',
|
|
35
|
+
'$endswith': 'endswith',
|
|
28
36
|
};
|
|
29
37
|
return operatorMap[operator] || null;
|
|
30
38
|
}
|
|
@@ -66,10 +74,21 @@ export function convertFiltersToAST(filter) {
|
|
|
66
74
|
console.warn(`[ObjectUI] Warning: $regex operator is not fully supported. ` +
|
|
67
75
|
`Converting to 'contains' which only supports substring matching, not regex patterns. ` +
|
|
68
76
|
`Field: '${field}', Value: ${JSON.stringify(operatorValue)}. ` +
|
|
69
|
-
`Consider using $contains or $
|
|
77
|
+
`Consider using $contains or $startsWith instead.`);
|
|
70
78
|
conditions.push([field, 'contains', operatorValue]);
|
|
71
79
|
continue;
|
|
72
80
|
}
|
|
81
|
+
// $null / $exists translate based on their boolean value (per spec semantics).
|
|
82
|
+
// $null: true → IS NULL | $null: false → IS NOT NULL
|
|
83
|
+
// $exists: true → IS NOT NULL | $exists: false → IS NULL
|
|
84
|
+
if (operator === '$null') {
|
|
85
|
+
conditions.push([field, operatorValue ? 'is_null' : 'is_not_null', true]);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (operator === '$exists') {
|
|
89
|
+
conditions.push([field, operatorValue ? 'is_not_null' : 'is_null', true]);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
73
92
|
const astOperator = convertOperatorToAST(operator);
|
|
74
93
|
if (astOperator) {
|
|
75
94
|
conditions.push([field, astOperator, operatorValue]);
|
|
@@ -77,7 +96,8 @@ export function convertFiltersToAST(filter) {
|
|
|
77
96
|
else {
|
|
78
97
|
// Unknown operator - throw error to avoid silent failure
|
|
79
98
|
throw new Error(`[ObjectUI] Unknown filter operator '${operator}' for field '${field}'. ` +
|
|
80
|
-
`Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $
|
|
99
|
+
`Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $between, ` +
|
|
100
|
+
`$contains, $notContains, $startsWith, $endsWith, $null, $exists. ` +
|
|
81
101
|
`If you need exact object matching, use the value directly without an operator.`);
|
|
82
102
|
}
|
|
83
103
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/core",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"description": "Core logic, types, and validation for Object UI. Zero React dependencies.",
|
|
8
|
-
"homepage": "https://www.objectui.org",
|
|
8
|
+
"homepage": "https://www.objectui.org/docs/core",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/objectstack-ai/objectui.git",
|
|
11
|
+
"url": "git+https://github.com/objectstack-ai/objectui.git",
|
|
12
12
|
"directory": "packages/core"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
@@ -25,15 +25,39 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@objectstack/spec": "^4.0.
|
|
28
|
+
"@objectstack/spec": "^4.0.4",
|
|
29
29
|
"lodash": "^4.18.1",
|
|
30
|
-
"zod": "^4.3
|
|
31
|
-
"@object-ui/types": "3.3.
|
|
30
|
+
"zod": "^4.4.3",
|
|
31
|
+
"@object-ui/types": "3.3.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"typescript": "^6.0.
|
|
35
|
-
"vitest": "^4.1.
|
|
34
|
+
"typescript": "^6.0.3",
|
|
35
|
+
"vitest": "^4.1.5"
|
|
36
36
|
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"objectui",
|
|
39
|
+
"sdui",
|
|
40
|
+
"schema-driven-ui",
|
|
41
|
+
"react",
|
|
42
|
+
"tailwind",
|
|
43
|
+
"shadcn",
|
|
44
|
+
"objectstack",
|
|
45
|
+
"core",
|
|
46
|
+
"engine",
|
|
47
|
+
"expression-engine",
|
|
48
|
+
"action-engine",
|
|
49
|
+
"registry"
|
|
50
|
+
],
|
|
51
|
+
"author": "ObjectStack Team <team@objectstack.ai>",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist",
|
|
57
|
+
"README.md",
|
|
58
|
+
"CHANGELOG.md",
|
|
59
|
+
"LICENSE"
|
|
60
|
+
],
|
|
37
61
|
"scripts": {
|
|
38
62
|
"build": "tsc",
|
|
39
63
|
"test": "vitest run",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
* Performance benchmark suite for @object-ui/core.
|
|
11
|
-
*
|
|
12
|
-
* Part of Q1 2026 roadmap §1.4 Test Coverage Improvement.
|
|
13
|
-
*
|
|
14
|
-
* Run with: npx vitest bench packages/core/src/__benchmarks__/
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { bench, describe } from 'vitest';
|
|
18
|
-
import { ExpressionEvaluator } from '@object-ui/core';
|
|
19
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
20
|
-
import { contrastRatio, meetsContrastLevel, hexToHSL } from '@object-ui/core';
|
|
21
|
-
|
|
22
|
-
describe('ExpressionEvaluator performance', () => {
|
|
23
|
-
const evaluator = new ExpressionEvaluator({ data: { name: 'Alice', age: 30, active: true } });
|
|
24
|
-
|
|
25
|
-
bench('evaluate simple string', () => {
|
|
26
|
-
evaluator.evaluate('Hello ${data.name}');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
bench('evaluate 100 expressions', () => {
|
|
30
|
-
for (let i = 0; i < 100; i++) {
|
|
31
|
-
evaluator.evaluate('Hello ${data.name}');
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('ComponentRegistry performance', () => {
|
|
37
|
-
bench('get registered component', () => {
|
|
38
|
-
ComponentRegistry.get('button');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
bench('has check', () => {
|
|
42
|
-
ComponentRegistry.has('button');
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('Theme utilities performance', () => {
|
|
47
|
-
bench('hexToHSL conversion', () => {
|
|
48
|
-
hexToHSL('#336699');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
bench('contrastRatio calculation', () => {
|
|
52
|
-
contrastRatio('#000000', '#ffffff');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
bench('meetsContrastLevel check', () => {
|
|
56
|
-
meetsContrastLevel('#000000', '#ffffff', 'AA');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
bench('100 contrast checks', () => {
|
|
60
|
-
for (let i = 0; i < 100; i++) {
|
|
61
|
-
meetsContrastLevel('#336699', '#ffffff', 'AA');
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
});
|