@object-ui/core 0.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +11 -0
- package/dist/actions/ActionRunner.d.ts +228 -4
- package/dist/actions/ActionRunner.js +397 -45
- package/dist/actions/TransactionManager.d.ts +193 -0
- package/dist/actions/TransactionManager.js +410 -0
- package/dist/actions/index.d.ts +2 -1
- package/dist/actions/index.js +2 -1
- package/dist/adapters/ApiDataSource.d.ts +69 -0
- package/dist/adapters/ApiDataSource.js +293 -0
- package/dist/adapters/ValueDataSource.d.ts +55 -0
- package/dist/adapters/ValueDataSource.js +287 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +5 -2
- package/dist/adapters/resolveDataSource.d.ts +40 -0
- package/dist/adapters/resolveDataSource.js +59 -0
- package/dist/data-scope/DataScopeManager.d.ts +127 -0
- package/dist/data-scope/DataScopeManager.js +229 -0
- package/dist/data-scope/index.d.ts +10 -0
- package/dist/data-scope/index.js +10 -0
- package/dist/evaluator/ExpressionCache.d.ts +101 -0
- package/dist/evaluator/ExpressionCache.js +135 -0
- package/dist/evaluator/ExpressionEvaluator.d.ts +30 -2
- package/dist/evaluator/ExpressionEvaluator.js +60 -16
- package/dist/evaluator/FormulaFunctions.d.ts +58 -0
- package/dist/evaluator/FormulaFunctions.js +350 -0
- package/dist/evaluator/index.d.ts +4 -2
- package/dist/evaluator/index.js +4 -2
- package/dist/index.d.ts +14 -7
- package/dist/index.js +13 -9
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +6 -0
- package/dist/query/query-ast.d.ts +32 -0
- package/dist/query/query-ast.js +268 -0
- package/dist/registry/PluginScopeImpl.d.ts +80 -0
- package/dist/registry/PluginScopeImpl.js +243 -0
- package/dist/registry/PluginSystem.d.ts +66 -0
- package/dist/registry/PluginSystem.js +142 -0
- package/dist/registry/Registry.d.ts +83 -4
- package/dist/registry/Registry.js +113 -7
- package/dist/registry/WidgetRegistry.d.ts +120 -0
- package/dist/registry/WidgetRegistry.js +275 -0
- package/dist/theme/ThemeEngine.d.ts +82 -0
- package/dist/theme/ThemeEngine.js +400 -0
- package/dist/theme/index.d.ts +8 -0
- package/dist/theme/index.js +8 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/validation-engine.d.ts +88 -0
- package/dist/validation/validation-engine.js +428 -0
- package/dist/validation/validators/index.d.ts +16 -0
- package/dist/validation/validators/index.js +16 -0
- package/dist/validation/validators/object-validation-engine.d.ts +118 -0
- package/dist/validation/validators/object-validation-engine.js +538 -0
- package/package.json +14 -5
- package/src/actions/ActionRunner.ts +577 -55
- package/src/actions/TransactionManager.ts +521 -0
- package/src/actions/__tests__/ActionRunner.params.test.ts +134 -0
- package/src/actions/__tests__/ActionRunner.test.ts +711 -0
- package/src/actions/__tests__/TransactionManager.test.ts +447 -0
- package/src/actions/index.ts +2 -1
- package/src/adapters/ApiDataSource.ts +349 -0
- package/src/adapters/ValueDataSource.ts +332 -0
- package/src/adapters/__tests__/ApiDataSource.test.ts +418 -0
- package/src/adapters/__tests__/ValueDataSource.test.ts +325 -0
- package/src/adapters/__tests__/resolveDataSource.test.ts +144 -0
- package/src/adapters/index.ts +6 -1
- package/src/adapters/resolveDataSource.ts +79 -0
- package/src/builder/__tests__/schema-builder.test.ts +235 -0
- package/src/data-scope/DataScopeManager.ts +269 -0
- package/src/data-scope/__tests__/DataScopeManager.test.ts +211 -0
- package/src/data-scope/index.ts +16 -0
- package/src/evaluator/ExpressionCache.ts +192 -0
- package/src/evaluator/ExpressionEvaluator.ts +61 -16
- package/src/evaluator/FormulaFunctions.ts +398 -0
- package/src/evaluator/__tests__/ExpressionCache.test.ts +135 -0
- package/src/evaluator/__tests__/ExpressionContext.test.ts +110 -0
- package/src/evaluator/__tests__/FormulaFunctions.test.ts +447 -0
- package/src/evaluator/index.ts +4 -2
- package/src/index.ts +14 -10
- package/src/query/__tests__/query-ast.test.ts +211 -0
- package/src/query/__tests__/window-functions.test.ts +275 -0
- package/src/query/index.ts +7 -0
- package/src/query/query-ast.ts +341 -0
- package/src/registry/PluginScopeImpl.ts +259 -0
- package/src/registry/PluginSystem.ts +161 -0
- package/src/registry/Registry.ts +136 -8
- package/src/registry/WidgetRegistry.ts +316 -0
- package/src/registry/__tests__/PluginSystem.test.ts +226 -0
- package/src/registry/__tests__/Registry.test.ts +293 -0
- package/src/registry/__tests__/WidgetRegistry.test.ts +321 -0
- package/src/registry/__tests__/plugin-scope-integration.test.ts +283 -0
- package/src/theme/ThemeEngine.ts +452 -0
- package/src/theme/__tests__/ThemeEngine.test.ts +606 -0
- package/src/theme/index.ts +22 -0
- package/src/validation/__tests__/object-validation-engine.test.ts +567 -0
- package/src/validation/__tests__/schema-validator.test.ts +118 -0
- package/src/validation/__tests__/validation-engine.test.ts +102 -0
- package/src/validation/index.ts +10 -0
- package/src/validation/validation-engine.ts +520 -0
- package/src/validation/validators/index.ts +25 -0
- package/src/validation/validators/object-validation-engine.ts +722 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +2 -0
- package/src/adapters/index.d.ts +0 -8
- package/src/adapters/index.js +0 -10
- package/src/builder/schema-builder.d.ts +0 -294
- package/src/builder/schema-builder.js +0 -503
- package/src/index.d.ts +0 -13
- package/src/index.js +0 -16
- package/src/registry/Registry.d.ts +0 -56
- package/src/registry/Registry.js +0 -43
- package/src/types/index.d.ts +0 -19
- package/src/types/index.js +0 -8
- package/src/utils/filter-converter.d.ts +0 -57
- package/src/utils/filter-converter.js +0 -100
- package/src/validation/schema-validator.d.ts +0 -94
- package/src/validation/schema-validator.js +0 -278
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI — resolveDataSource
|
|
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
|
+
* Factory function to create the right DataSource from a ViewData config.
|
|
9
|
+
*/
|
|
10
|
+
import { ApiDataSource } from './ApiDataSource.js';
|
|
11
|
+
import { ValueDataSource } from './ValueDataSource.js';
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a ViewData configuration into a concrete DataSource instance.
|
|
14
|
+
*
|
|
15
|
+
* - `provider: 'object'` → returns `fallback` (the context DataSource — typically ObjectStackAdapter)
|
|
16
|
+
* - `provider: 'api'` → returns a new `ApiDataSource`
|
|
17
|
+
* - `provider: 'value'` → returns a new `ValueDataSource`
|
|
18
|
+
*
|
|
19
|
+
* @param viewData - The ViewData configuration from the schema
|
|
20
|
+
* @param fallback - The default DataSource from context (for `provider: 'object'`)
|
|
21
|
+
* @param options - Additional options for adapter construction
|
|
22
|
+
* @returns A DataSource instance, or null if neither viewData nor fallback is available
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const ds = resolveDataSource(
|
|
27
|
+
* { provider: 'api', read: { url: '/api/users' } },
|
|
28
|
+
* contextDataSource,
|
|
29
|
+
* );
|
|
30
|
+
* const result = await ds.find('users');
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function resolveDataSource(viewData, fallback, options) {
|
|
34
|
+
if (!viewData) {
|
|
35
|
+
return fallback ?? null;
|
|
36
|
+
}
|
|
37
|
+
switch (viewData.provider) {
|
|
38
|
+
case 'object':
|
|
39
|
+
// Delegate to the context DataSource (ObjectStackAdapter, etc.)
|
|
40
|
+
return fallback ?? null;
|
|
41
|
+
case 'api': {
|
|
42
|
+
const config = {
|
|
43
|
+
read: viewData.read,
|
|
44
|
+
write: viewData.write,
|
|
45
|
+
fetch: options?.fetch,
|
|
46
|
+
defaultHeaders: options?.defaultHeaders,
|
|
47
|
+
};
|
|
48
|
+
return new ApiDataSource(config);
|
|
49
|
+
}
|
|
50
|
+
case 'value':
|
|
51
|
+
return new ValueDataSource({
|
|
52
|
+
items: (viewData.items ?? []),
|
|
53
|
+
idField: options?.idField,
|
|
54
|
+
});
|
|
55
|
+
default:
|
|
56
|
+
// Unknown provider — fall back to context
|
|
57
|
+
return fallback ?? null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
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 - DataScope Manager
|
|
10
|
+
*
|
|
11
|
+
* Runtime implementation of the DataContext interface for managing
|
|
12
|
+
* named data scopes. Provides row-level data access control and
|
|
13
|
+
* reactive data state management within the UI component tree.
|
|
14
|
+
*
|
|
15
|
+
* @module data-scope
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
import type { DataScope, DataContext, DataSource } from '@object-ui/types';
|
|
19
|
+
/**
|
|
20
|
+
* Row-level filter for restricting data access within a scope
|
|
21
|
+
*/
|
|
22
|
+
export interface RowLevelFilter {
|
|
23
|
+
/** Field to filter on */
|
|
24
|
+
field: string;
|
|
25
|
+
/** Filter operator */
|
|
26
|
+
operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'in' | 'nin' | 'contains';
|
|
27
|
+
/** Filter value */
|
|
28
|
+
value: any;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for creating a data scope
|
|
32
|
+
*/
|
|
33
|
+
export interface DataScopeConfig {
|
|
34
|
+
/** Data source instance */
|
|
35
|
+
dataSource?: DataSource;
|
|
36
|
+
/** Initial data */
|
|
37
|
+
data?: any;
|
|
38
|
+
/** Row-level filters to apply */
|
|
39
|
+
filters?: RowLevelFilter[];
|
|
40
|
+
/** Whether this scope is read-only */
|
|
41
|
+
readOnly?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* DataScopeManager — Runtime implementation of DataContext.
|
|
45
|
+
*
|
|
46
|
+
* Manages named data scopes for the component tree, providing:
|
|
47
|
+
* - Scope registration and lookup
|
|
48
|
+
* - Row-level security via filters
|
|
49
|
+
* - Data state management (data, loading, error)
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const manager = new DataScopeManager();
|
|
54
|
+
* manager.registerScope('contacts', {
|
|
55
|
+
* dataSource: myDataSource,
|
|
56
|
+
* data: [],
|
|
57
|
+
* });
|
|
58
|
+
* const scope = manager.getScope('contacts');
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare class DataScopeManager implements DataContext {
|
|
62
|
+
scopes: Record<string, DataScope>;
|
|
63
|
+
private filters;
|
|
64
|
+
private readOnlyScopes;
|
|
65
|
+
private listeners;
|
|
66
|
+
/**
|
|
67
|
+
* Register a data scope
|
|
68
|
+
*/
|
|
69
|
+
registerScope(name: string, scope: DataScope): void;
|
|
70
|
+
/**
|
|
71
|
+
* Register a data scope with configuration
|
|
72
|
+
*/
|
|
73
|
+
registerScopeWithConfig(name: string, config: DataScopeConfig): void;
|
|
74
|
+
/**
|
|
75
|
+
* Get a data scope by name
|
|
76
|
+
*/
|
|
77
|
+
getScope(name: string): DataScope | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Remove a data scope
|
|
80
|
+
*/
|
|
81
|
+
removeScope(name: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Check if a scope is read-only
|
|
84
|
+
*/
|
|
85
|
+
isReadOnly(name: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Get row-level filters for a scope
|
|
88
|
+
*/
|
|
89
|
+
getFilters(name: string): RowLevelFilter[];
|
|
90
|
+
/**
|
|
91
|
+
* Set row-level filters for a scope
|
|
92
|
+
*/
|
|
93
|
+
setFilters(name: string, filters: RowLevelFilter[]): void;
|
|
94
|
+
/**
|
|
95
|
+
* Apply row-level filters to a dataset
|
|
96
|
+
*/
|
|
97
|
+
applyFilters(name: string, data: any[]): any[];
|
|
98
|
+
/**
|
|
99
|
+
* Update data in a scope
|
|
100
|
+
*/
|
|
101
|
+
updateScopeData(name: string, data: any): void;
|
|
102
|
+
/**
|
|
103
|
+
* Update loading state for a scope
|
|
104
|
+
*/
|
|
105
|
+
updateScopeLoading(name: string, loading: boolean): void;
|
|
106
|
+
/**
|
|
107
|
+
* Update error state for a scope
|
|
108
|
+
*/
|
|
109
|
+
updateScopeError(name: string, error: Error | string | null): void;
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe to scope changes
|
|
112
|
+
*/
|
|
113
|
+
onScopeChange(name: string, listener: (scope: DataScope) => void): () => void;
|
|
114
|
+
/**
|
|
115
|
+
* Get all registered scope names
|
|
116
|
+
*/
|
|
117
|
+
getScopeNames(): string[];
|
|
118
|
+
/**
|
|
119
|
+
* Clear all scopes
|
|
120
|
+
*/
|
|
121
|
+
clear(): void;
|
|
122
|
+
private notifyListeners;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Default DataScopeManager instance
|
|
126
|
+
*/
|
|
127
|
+
export declare const defaultDataScopeManager: DataScopeManager;
|
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
* DataScopeManager — Runtime implementation of DataContext.
|
|
10
|
+
*
|
|
11
|
+
* Manages named data scopes for the component tree, providing:
|
|
12
|
+
* - Scope registration and lookup
|
|
13
|
+
* - Row-level security via filters
|
|
14
|
+
* - Data state management (data, loading, error)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const manager = new DataScopeManager();
|
|
19
|
+
* manager.registerScope('contacts', {
|
|
20
|
+
* dataSource: myDataSource,
|
|
21
|
+
* data: [],
|
|
22
|
+
* });
|
|
23
|
+
* const scope = manager.getScope('contacts');
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class DataScopeManager {
|
|
27
|
+
constructor() {
|
|
28
|
+
Object.defineProperty(this, "scopes", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: {}
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "filters", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: {}
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "readOnlyScopes", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: new Set()
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "listeners", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: new Map()
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register a data scope
|
|
55
|
+
*/
|
|
56
|
+
registerScope(name, scope) {
|
|
57
|
+
this.scopes[name] = scope;
|
|
58
|
+
this.notifyListeners(name, scope);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Register a data scope with configuration
|
|
62
|
+
*/
|
|
63
|
+
registerScopeWithConfig(name, config) {
|
|
64
|
+
const scope = {
|
|
65
|
+
dataSource: config.dataSource,
|
|
66
|
+
data: config.data,
|
|
67
|
+
loading: false,
|
|
68
|
+
error: null,
|
|
69
|
+
};
|
|
70
|
+
if (config.filters) {
|
|
71
|
+
this.filters[name] = config.filters;
|
|
72
|
+
}
|
|
73
|
+
if (config.readOnly) {
|
|
74
|
+
this.readOnlyScopes.add(name);
|
|
75
|
+
}
|
|
76
|
+
this.scopes[name] = scope;
|
|
77
|
+
this.notifyListeners(name, scope);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a data scope by name
|
|
81
|
+
*/
|
|
82
|
+
getScope(name) {
|
|
83
|
+
return this.scopes[name];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove a data scope
|
|
87
|
+
*/
|
|
88
|
+
removeScope(name) {
|
|
89
|
+
delete this.scopes[name];
|
|
90
|
+
delete this.filters[name];
|
|
91
|
+
this.readOnlyScopes.delete(name);
|
|
92
|
+
this.listeners.delete(name);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if a scope is read-only
|
|
96
|
+
*/
|
|
97
|
+
isReadOnly(name) {
|
|
98
|
+
return this.readOnlyScopes.has(name);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get row-level filters for a scope
|
|
102
|
+
*/
|
|
103
|
+
getFilters(name) {
|
|
104
|
+
return this.filters[name] || [];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Set row-level filters for a scope
|
|
108
|
+
*/
|
|
109
|
+
setFilters(name, filters) {
|
|
110
|
+
this.filters[name] = filters;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Apply row-level filters to a dataset
|
|
114
|
+
*/
|
|
115
|
+
applyFilters(name, data) {
|
|
116
|
+
const scopeFilters = this.filters[name];
|
|
117
|
+
if (!scopeFilters || scopeFilters.length === 0) {
|
|
118
|
+
return data;
|
|
119
|
+
}
|
|
120
|
+
return data.filter(row => {
|
|
121
|
+
return scopeFilters.every(filter => {
|
|
122
|
+
const fieldValue = row[filter.field];
|
|
123
|
+
return evaluateFilter(fieldValue, filter.operator, filter.value);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Update data in a scope
|
|
129
|
+
*/
|
|
130
|
+
updateScopeData(name, data) {
|
|
131
|
+
const scope = this.scopes[name];
|
|
132
|
+
if (!scope)
|
|
133
|
+
return;
|
|
134
|
+
if (this.readOnlyScopes.has(name)) {
|
|
135
|
+
throw new Error(`Cannot update read-only scope: ${name}`);
|
|
136
|
+
}
|
|
137
|
+
scope.data = data;
|
|
138
|
+
this.notifyListeners(name, scope);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Update loading state for a scope
|
|
142
|
+
*/
|
|
143
|
+
updateScopeLoading(name, loading) {
|
|
144
|
+
const scope = this.scopes[name];
|
|
145
|
+
if (!scope)
|
|
146
|
+
return;
|
|
147
|
+
scope.loading = loading;
|
|
148
|
+
this.notifyListeners(name, scope);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Update error state for a scope
|
|
152
|
+
*/
|
|
153
|
+
updateScopeError(name, error) {
|
|
154
|
+
const scope = this.scopes[name];
|
|
155
|
+
if (!scope)
|
|
156
|
+
return;
|
|
157
|
+
scope.error = error;
|
|
158
|
+
this.notifyListeners(name, scope);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Subscribe to scope changes
|
|
162
|
+
*/
|
|
163
|
+
onScopeChange(name, listener) {
|
|
164
|
+
if (!this.listeners.has(name)) {
|
|
165
|
+
this.listeners.set(name, []);
|
|
166
|
+
}
|
|
167
|
+
this.listeners.get(name).push(listener);
|
|
168
|
+
return () => {
|
|
169
|
+
const arr = this.listeners.get(name);
|
|
170
|
+
if (arr) {
|
|
171
|
+
const idx = arr.indexOf(listener);
|
|
172
|
+
if (idx >= 0)
|
|
173
|
+
arr.splice(idx, 1);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get all registered scope names
|
|
179
|
+
*/
|
|
180
|
+
getScopeNames() {
|
|
181
|
+
return Object.keys(this.scopes);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Clear all scopes
|
|
185
|
+
*/
|
|
186
|
+
clear() {
|
|
187
|
+
this.scopes = {};
|
|
188
|
+
this.filters = {};
|
|
189
|
+
this.readOnlyScopes.clear();
|
|
190
|
+
this.listeners.clear();
|
|
191
|
+
}
|
|
192
|
+
notifyListeners(name, scope) {
|
|
193
|
+
const arr = this.listeners.get(name);
|
|
194
|
+
if (arr) {
|
|
195
|
+
arr.forEach(listener => listener(scope));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Evaluate a single filter condition against a field value
|
|
201
|
+
*/
|
|
202
|
+
function evaluateFilter(fieldValue, operator, filterValue) {
|
|
203
|
+
switch (operator) {
|
|
204
|
+
case 'eq':
|
|
205
|
+
return fieldValue === filterValue;
|
|
206
|
+
case 'ne':
|
|
207
|
+
return fieldValue !== filterValue;
|
|
208
|
+
case 'gt':
|
|
209
|
+
return fieldValue > filterValue;
|
|
210
|
+
case 'lt':
|
|
211
|
+
return fieldValue < filterValue;
|
|
212
|
+
case 'gte':
|
|
213
|
+
return fieldValue >= filterValue;
|
|
214
|
+
case 'lte':
|
|
215
|
+
return fieldValue <= filterValue;
|
|
216
|
+
case 'in':
|
|
217
|
+
return Array.isArray(filterValue) && filterValue.includes(fieldValue);
|
|
218
|
+
case 'nin':
|
|
219
|
+
return Array.isArray(filterValue) && !filterValue.includes(fieldValue);
|
|
220
|
+
case 'contains':
|
|
221
|
+
return typeof fieldValue === 'string' && fieldValue.includes(String(filterValue));
|
|
222
|
+
default:
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Default DataScopeManager instance
|
|
228
|
+
*/
|
|
229
|
+
export const defaultDataScopeManager = new DataScopeManager();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @object-ui/core - DataScope Module
|
|
3
|
+
*
|
|
4
|
+
* Runtime data scope management for row-level security and
|
|
5
|
+
* reactive data state within the UI component tree.
|
|
6
|
+
*
|
|
7
|
+
* @module data-scope
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
export { DataScopeManager, defaultDataScopeManager, type RowLevelFilter, type DataScopeConfig, } from './DataScopeManager.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @object-ui/core - DataScope Module
|
|
3
|
+
*
|
|
4
|
+
* Runtime data scope management for row-level security and
|
|
5
|
+
* reactive data state within the UI component tree.
|
|
6
|
+
*
|
|
7
|
+
* @module data-scope
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
export { DataScopeManager, defaultDataScopeManager, } from './DataScopeManager.js';
|
|
@@ -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
|
+
* @object-ui/core - Expression Cache
|
|
10
|
+
*
|
|
11
|
+
* Caches compiled expressions to avoid re-parsing on every render.
|
|
12
|
+
* Provides significant performance improvement for frequently evaluated expressions.
|
|
13
|
+
*
|
|
14
|
+
* @module evaluator
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A compiled expression function that can be executed with context values
|
|
19
|
+
*/
|
|
20
|
+
export type CompiledExpression = (...args: any[]) => any;
|
|
21
|
+
/**
|
|
22
|
+
* Expression compilation metadata
|
|
23
|
+
*/
|
|
24
|
+
export interface ExpressionMetadata {
|
|
25
|
+
/**
|
|
26
|
+
* The compiled function
|
|
27
|
+
*/
|
|
28
|
+
fn: CompiledExpression;
|
|
29
|
+
/**
|
|
30
|
+
* Variable names used in the expression
|
|
31
|
+
*/
|
|
32
|
+
varNames: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Original expression string
|
|
35
|
+
*/
|
|
36
|
+
expression: string;
|
|
37
|
+
/**
|
|
38
|
+
* Timestamp when the expression was compiled
|
|
39
|
+
*/
|
|
40
|
+
compiledAt: number;
|
|
41
|
+
/**
|
|
42
|
+
* Number of times this expression has been used
|
|
43
|
+
*/
|
|
44
|
+
hitCount: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Cache for compiled expressions
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const cache = new ExpressionCache();
|
|
52
|
+
* const compiled = cache.compile('data.amount > 1000', ['data']);
|
|
53
|
+
* const result = compiled.fn({ amount: 1500 }); // true
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare class ExpressionCache {
|
|
57
|
+
private cache;
|
|
58
|
+
private maxSize;
|
|
59
|
+
/**
|
|
60
|
+
* Create a new expression cache
|
|
61
|
+
*
|
|
62
|
+
* @param maxSize Maximum number of expressions to cache (default: 1000)
|
|
63
|
+
*/
|
|
64
|
+
constructor(maxSize?: number);
|
|
65
|
+
/**
|
|
66
|
+
* Compile an expression or retrieve from cache
|
|
67
|
+
*
|
|
68
|
+
* @param expr The expression to compile
|
|
69
|
+
* @param varNames Variable names available in the context
|
|
70
|
+
* @returns Compiled expression metadata
|
|
71
|
+
*/
|
|
72
|
+
compile(expr: string, varNames: string[]): ExpressionMetadata;
|
|
73
|
+
/**
|
|
74
|
+
* Compile an expression into a function
|
|
75
|
+
*/
|
|
76
|
+
private compileExpression;
|
|
77
|
+
/**
|
|
78
|
+
* Evict the least frequently used expression from cache
|
|
79
|
+
*/
|
|
80
|
+
private evictLFU;
|
|
81
|
+
/**
|
|
82
|
+
* Check if an expression is cached
|
|
83
|
+
*/
|
|
84
|
+
has(expr: string, varNames: string[]): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Clear the cache
|
|
87
|
+
*/
|
|
88
|
+
clear(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Get cache statistics
|
|
91
|
+
*/
|
|
92
|
+
getStats(): {
|
|
93
|
+
size: number;
|
|
94
|
+
maxSize: number;
|
|
95
|
+
totalHits: number;
|
|
96
|
+
entries: Array<{
|
|
97
|
+
expression: string;
|
|
98
|
+
hitCount: number;
|
|
99
|
+
}>;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -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
|
+
* Cache for compiled expressions
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const cache = new ExpressionCache();
|
|
14
|
+
* const compiled = cache.compile('data.amount > 1000', ['data']);
|
|
15
|
+
* const result = compiled.fn({ amount: 1500 }); // true
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class ExpressionCache {
|
|
19
|
+
/**
|
|
20
|
+
* Create a new expression cache
|
|
21
|
+
*
|
|
22
|
+
* @param maxSize Maximum number of expressions to cache (default: 1000)
|
|
23
|
+
*/
|
|
24
|
+
constructor(maxSize = 1000) {
|
|
25
|
+
Object.defineProperty(this, "cache", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: new Map()
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "maxSize", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
this.maxSize = maxSize;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compile an expression or retrieve from cache
|
|
41
|
+
*
|
|
42
|
+
* @param expr The expression to compile
|
|
43
|
+
* @param varNames Variable names available in the context
|
|
44
|
+
* @returns Compiled expression metadata
|
|
45
|
+
*/
|
|
46
|
+
compile(expr, varNames) {
|
|
47
|
+
// Create a cache key that includes variable names to ensure correct scoping
|
|
48
|
+
const cacheKey = `${expr}::${varNames.join(',')}`;
|
|
49
|
+
if (this.cache.has(cacheKey)) {
|
|
50
|
+
const metadata = this.cache.get(cacheKey);
|
|
51
|
+
metadata.hitCount++;
|
|
52
|
+
return metadata;
|
|
53
|
+
}
|
|
54
|
+
// Evict least frequently used if cache is full
|
|
55
|
+
if (this.cache.size >= this.maxSize) {
|
|
56
|
+
this.evictLFU();
|
|
57
|
+
}
|
|
58
|
+
// Compile the expression
|
|
59
|
+
const fn = this.compileExpression(expr, varNames);
|
|
60
|
+
const metadata = {
|
|
61
|
+
fn,
|
|
62
|
+
varNames: [...varNames],
|
|
63
|
+
expression: expr,
|
|
64
|
+
compiledAt: Date.now(),
|
|
65
|
+
hitCount: 1,
|
|
66
|
+
};
|
|
67
|
+
this.cache.set(cacheKey, metadata);
|
|
68
|
+
return metadata;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compile an expression into a function
|
|
72
|
+
*/
|
|
73
|
+
compileExpression(expression, varNames) {
|
|
74
|
+
// SECURITY NOTE: Using Function constructor for expression evaluation.
|
|
75
|
+
// This is a controlled use case with:
|
|
76
|
+
// 1. Sanitization check (isDangerous) performed by caller
|
|
77
|
+
// 2. Strict mode enabled ("use strict")
|
|
78
|
+
// 3. Limited scope (only varNames variables available)
|
|
79
|
+
// 4. No access to global objects (process, window, etc.)
|
|
80
|
+
return new Function(...varNames, `"use strict"; return (${expression});`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Evict the least frequently used expression from cache
|
|
84
|
+
*/
|
|
85
|
+
evictLFU() {
|
|
86
|
+
let oldestKey = null;
|
|
87
|
+
let oldestTime = Infinity;
|
|
88
|
+
let lowestHits = Infinity;
|
|
89
|
+
// Find the entry with lowest hit count, or oldest if tied
|
|
90
|
+
for (const [key, metadata] of this.cache.entries()) {
|
|
91
|
+
if (metadata.hitCount < lowestHits ||
|
|
92
|
+
(metadata.hitCount === lowestHits && metadata.compiledAt < oldestTime)) {
|
|
93
|
+
oldestKey = key;
|
|
94
|
+
oldestTime = metadata.compiledAt;
|
|
95
|
+
lowestHits = metadata.hitCount;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (oldestKey) {
|
|
99
|
+
this.cache.delete(oldestKey);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if an expression is cached
|
|
104
|
+
*/
|
|
105
|
+
has(expr, varNames) {
|
|
106
|
+
const cacheKey = `${expr}::${varNames.join(',')}`;
|
|
107
|
+
return this.cache.has(cacheKey);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Clear the cache
|
|
111
|
+
*/
|
|
112
|
+
clear() {
|
|
113
|
+
this.cache.clear();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get cache statistics
|
|
117
|
+
*/
|
|
118
|
+
getStats() {
|
|
119
|
+
let totalHits = 0;
|
|
120
|
+
const entries = [];
|
|
121
|
+
for (const metadata of this.cache.values()) {
|
|
122
|
+
totalHits += metadata.hitCount;
|
|
123
|
+
entries.push({
|
|
124
|
+
expression: metadata.expression,
|
|
125
|
+
hitCount: metadata.hitCount,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
size: this.cache.size,
|
|
130
|
+
maxSize: this.maxSize,
|
|
131
|
+
totalHits,
|
|
132
|
+
entries: entries.sort((a, b) => b.hitCount - a.hitCount),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|