@object-ui/core 0.5.0 → 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 +1 -1
- 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 +1 -0
- package/dist/actions/index.js +1 -0
- 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/ExpressionEvaluator.d.ts +11 -1
- package/dist/evaluator/ExpressionEvaluator.js +32 -8
- package/dist/evaluator/FormulaFunctions.d.ts +58 -0
- package/dist/evaluator/FormulaFunctions.js +350 -0
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -2
- package/dist/query/query-ast.d.ts +2 -2
- package/dist/query/query-ast.js +3 -3
- package/dist/registry/Registry.d.ts +10 -0
- package/dist/registry/Registry.js +2 -1
- 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 +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/validation-engine.d.ts +19 -1
- package/dist/validation/validation-engine.js +67 -2
- package/dist/validation/validators/index.d.ts +1 -1
- package/dist/validation/validators/index.js +1 -1
- package/dist/validation/validators/object-validation-engine.d.ts +2 -2
- package/dist/validation/validators/object-validation-engine.js +1 -1
- package/package.json +4 -3
- 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 +1 -0
- 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/ExpressionEvaluator.ts +34 -8
- package/src/evaluator/FormulaFunctions.ts +398 -0
- package/src/evaluator/__tests__/ExpressionContext.test.ts +110 -0
- package/src/evaluator/__tests__/FormulaFunctions.test.ts +447 -0
- package/src/evaluator/index.ts +1 -0
- package/src/index.ts +4 -3
- package/src/query/__tests__/window-functions.test.ts +1 -1
- package/src/query/query-ast.ts +3 -3
- package/src/registry/Registry.ts +12 -1
- package/src/registry/WidgetRegistry.ts +316 -0
- package/src/registry/__tests__/WidgetRegistry.test.ts +321 -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 +1 -1
- package/src/validation/__tests__/schema-validator.test.ts +118 -0
- package/src/validation/index.ts +1 -1
- package/src/validation/validation-engine.ts +61 -2
- package/src/validation/validators/index.ts +1 -1
- package/src/validation/validators/object-validation-engine.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
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
|
+
* WidgetRegistry manages runtime-loadable widgets described by manifests.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const widgets = new WidgetRegistry({ componentRegistry: registry });
|
|
14
|
+
*
|
|
15
|
+
* widgets.register({
|
|
16
|
+
* name: 'custom-chart',
|
|
17
|
+
* version: '1.0.0',
|
|
18
|
+
* type: 'chart',
|
|
19
|
+
* label: 'Custom Chart',
|
|
20
|
+
* source: { type: 'module', url: '/widgets/chart.js' },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const resolved = await widgets.load('custom-chart');
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class WidgetRegistry {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
Object.defineProperty(this, "manifests", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: new Map()
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "resolved", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: new Map()
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "listeners", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: new Set()
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "componentRegistry", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
});
|
|
52
|
+
this.componentRegistry = options.componentRegistry;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Register a widget manifest.
|
|
56
|
+
* Does not load the widget; call `load()` to resolve it.
|
|
57
|
+
*/
|
|
58
|
+
register(manifest) {
|
|
59
|
+
this.manifests.set(manifest.name, manifest);
|
|
60
|
+
this.emit({ type: 'widget:registered', widget: manifest });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register multiple widget manifests at once.
|
|
64
|
+
*/
|
|
65
|
+
registerAll(manifests) {
|
|
66
|
+
for (const manifest of manifests) {
|
|
67
|
+
this.register(manifest);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Unregister a widget by name.
|
|
72
|
+
*/
|
|
73
|
+
unregister(name) {
|
|
74
|
+
const existed = this.manifests.delete(name);
|
|
75
|
+
this.resolved.delete(name);
|
|
76
|
+
if (existed) {
|
|
77
|
+
this.emit({ type: 'widget:unregistered', name });
|
|
78
|
+
}
|
|
79
|
+
return existed;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get a widget manifest by name.
|
|
83
|
+
*/
|
|
84
|
+
getManifest(name) {
|
|
85
|
+
return this.manifests.get(name);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get all registered widget manifests.
|
|
89
|
+
*/
|
|
90
|
+
getAllManifests() {
|
|
91
|
+
return Array.from(this.manifests.values());
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get manifests filtered by category.
|
|
95
|
+
*/
|
|
96
|
+
getByCategory(category) {
|
|
97
|
+
return this.getAllManifests().filter((m) => m.category === category);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a widget is registered.
|
|
101
|
+
*/
|
|
102
|
+
has(name) {
|
|
103
|
+
return this.manifests.has(name);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a widget has been loaded (resolved).
|
|
107
|
+
*/
|
|
108
|
+
isLoaded(name) {
|
|
109
|
+
return this.resolved.has(name);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Load (resolve) a widget by name.
|
|
113
|
+
* If already loaded, returns the cached resolved widget.
|
|
114
|
+
*
|
|
115
|
+
* @throws Error if the widget is not registered or fails to load.
|
|
116
|
+
*/
|
|
117
|
+
async load(name) {
|
|
118
|
+
// Return cached if already loaded
|
|
119
|
+
const cached = this.resolved.get(name);
|
|
120
|
+
if (cached)
|
|
121
|
+
return cached;
|
|
122
|
+
const manifest = this.manifests.get(name);
|
|
123
|
+
if (!manifest) {
|
|
124
|
+
const error = new Error(`Widget "${name}" is not registered`);
|
|
125
|
+
this.emit({ type: 'widget:error', name, error });
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
// Resolve dependencies first
|
|
129
|
+
if (manifest.dependencies) {
|
|
130
|
+
for (const dep of manifest.dependencies) {
|
|
131
|
+
if (!this.isLoaded(dep)) {
|
|
132
|
+
await this.load(dep);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const component = await this.resolveComponent(manifest);
|
|
138
|
+
const resolved = {
|
|
139
|
+
manifest,
|
|
140
|
+
component,
|
|
141
|
+
loadedAt: Date.now(),
|
|
142
|
+
};
|
|
143
|
+
this.resolved.set(name, resolved);
|
|
144
|
+
// Sync to component registry if available
|
|
145
|
+
if (this.componentRegistry) {
|
|
146
|
+
this.componentRegistry.register(manifest.type, component, {
|
|
147
|
+
label: manifest.label,
|
|
148
|
+
icon: manifest.icon,
|
|
149
|
+
category: manifest.category,
|
|
150
|
+
inputs: manifest.inputs?.map((input) => ({
|
|
151
|
+
name: input.name,
|
|
152
|
+
type: input.type,
|
|
153
|
+
label: input.label,
|
|
154
|
+
defaultValue: input.defaultValue,
|
|
155
|
+
required: input.required,
|
|
156
|
+
enum: input.options,
|
|
157
|
+
description: input.description,
|
|
158
|
+
advanced: input.advanced,
|
|
159
|
+
})),
|
|
160
|
+
defaultProps: manifest.defaultProps,
|
|
161
|
+
isContainer: manifest.isContainer,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
this.emit({ type: 'widget:loaded', widget: resolved });
|
|
165
|
+
return resolved;
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
169
|
+
this.emit({ type: 'widget:error', name, error });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Load all registered widgets.
|
|
175
|
+
* Returns an array of results (settled promises).
|
|
176
|
+
*/
|
|
177
|
+
async loadAll() {
|
|
178
|
+
const results = [];
|
|
179
|
+
for (const [name] of this.manifests) {
|
|
180
|
+
try {
|
|
181
|
+
const resolved = await this.load(name);
|
|
182
|
+
results.push({ name, result: resolved });
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
results.push({ name, result: err instanceof Error ? err : new Error(String(err)) });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Subscribe to widget registry events.
|
|
192
|
+
* @returns Unsubscribe function.
|
|
193
|
+
*/
|
|
194
|
+
on(listener) {
|
|
195
|
+
this.listeners.add(listener);
|
|
196
|
+
return () => {
|
|
197
|
+
this.listeners.delete(listener);
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Clear all registered and loaded widgets.
|
|
202
|
+
*/
|
|
203
|
+
clear() {
|
|
204
|
+
this.manifests.clear();
|
|
205
|
+
this.resolved.clear();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get registry statistics.
|
|
209
|
+
*/
|
|
210
|
+
getStats() {
|
|
211
|
+
const categories = new Set();
|
|
212
|
+
for (const m of this.manifests.values()) {
|
|
213
|
+
if (m.category)
|
|
214
|
+
categories.add(m.category);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
registered: this.manifests.size,
|
|
218
|
+
loaded: this.resolved.size,
|
|
219
|
+
categories: Array.from(categories),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// -------------------------------------------------------------------------
|
|
223
|
+
// Private helpers
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
async resolveComponent(manifest) {
|
|
226
|
+
const { source } = manifest;
|
|
227
|
+
switch (source.type) {
|
|
228
|
+
case 'inline':
|
|
229
|
+
return source.component;
|
|
230
|
+
case 'registry': {
|
|
231
|
+
if (!this.componentRegistry) {
|
|
232
|
+
throw new Error(`Widget "${manifest.name}" uses registry source but no component registry is configured`);
|
|
233
|
+
}
|
|
234
|
+
const component = this.componentRegistry.get(source.registryKey);
|
|
235
|
+
if (!component) {
|
|
236
|
+
throw new Error(`Widget "${manifest.name}" references registry key "${source.registryKey}" which is not registered`);
|
|
237
|
+
}
|
|
238
|
+
return component;
|
|
239
|
+
}
|
|
240
|
+
case 'module': {
|
|
241
|
+
// Runtime-only dynamic import for loading widgets from external URLs
|
|
242
|
+
// This uses Function constructor to prevent bundlers (Webpack/Turbopack/Vite)
|
|
243
|
+
// from attempting static analysis at build time, which would fail since
|
|
244
|
+
// source.url is only known at runtime.
|
|
245
|
+
//
|
|
246
|
+
// Security: Widget URLs must be from trusted sources only. Never pass
|
|
247
|
+
// user-supplied URLs directly to WidgetManifest. URLs should be validated
|
|
248
|
+
// and controlled by the application developer.
|
|
249
|
+
//
|
|
250
|
+
// CSP Consideration: If your application uses strict Content Security Policy,
|
|
251
|
+
// ensure dynamic imports are allowed or use 'inline' or 'registry' source types.
|
|
252
|
+
const dynamicImport = new Function('url', 'return import(url)');
|
|
253
|
+
const mod = await dynamicImport(source.url);
|
|
254
|
+
const exportName = source.exportName ?? 'default';
|
|
255
|
+
const component = mod[exportName];
|
|
256
|
+
if (!component) {
|
|
257
|
+
throw new Error(`Widget "${manifest.name}" module at "${source.url}" does not export "${exportName}"`);
|
|
258
|
+
}
|
|
259
|
+
return component;
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
throw new Error(`Unknown widget source type for "${manifest.name}"`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
emit(event) {
|
|
266
|
+
for (const listener of this.listeners) {
|
|
267
|
+
try {
|
|
268
|
+
listener(event);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Swallow listener errors to prevent cascading failures
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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 - Theme Engine
|
|
10
|
+
*
|
|
11
|
+
* Converts a spec-aligned Theme JSON into CSS custom properties
|
|
12
|
+
* that can be injected into the DOM. Also handles theme inheritance
|
|
13
|
+
* (extends), media-query-aware mode resolution, and token merging.
|
|
14
|
+
*
|
|
15
|
+
* @module theme
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
import type { Theme, ColorPalette, ThemeMode } from '@object-ui/types';
|
|
19
|
+
/**
|
|
20
|
+
* Convert a hex color (#RRGGBB or #RGB) to an HSL string "H S% L%".
|
|
21
|
+
* Returns null if the input is not a valid hex color.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hexToHSL(hex: string): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Convert a color to a CSS-ready value.
|
|
26
|
+
* - Hex colors → HSL format for Shadcn CSS variable compatibility
|
|
27
|
+
* - Non-hex colors → passed through as-is (rgb, hsl, oklch, etc.)
|
|
28
|
+
*/
|
|
29
|
+
export declare function toCSSColor(color: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Generate CSS custom properties from a Theme's color palette.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateColorVars(colors: ColorPalette): Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Generate CSS custom properties from a Theme's typography config.
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateTypographyVars(typography: NonNullable<Theme['typography']>): Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Generate CSS custom properties from a Theme's border radius config.
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateBorderRadiusVars(borderRadius: NonNullable<Theme['borderRadius']>): Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* Generate CSS custom properties from a Theme's shadow config.
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateShadowVars(shadows: NonNullable<Theme['shadows']>): Record<string, string>;
|
|
46
|
+
/**
|
|
47
|
+
* Generate CSS custom properties from a Theme's animation config.
|
|
48
|
+
*/
|
|
49
|
+
export declare function generateAnimationVars(animation: NonNullable<Theme['animation']>): Record<string, string>;
|
|
50
|
+
/**
|
|
51
|
+
* Generate CSS custom properties from a Theme's z-index config.
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateZIndexVars(zIndex: NonNullable<Theme['zIndex']>): Record<string, string>;
|
|
54
|
+
/**
|
|
55
|
+
* Generate ALL CSS custom properties from a complete Theme.
|
|
56
|
+
* This is the main entry point for theme → CSS conversion.
|
|
57
|
+
*/
|
|
58
|
+
export declare function generateThemeVars(theme: Theme): Record<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* Deep-merge two Theme objects. The `child` overrides the `parent`.
|
|
61
|
+
* Only defined properties in child override; undefined falls back to parent.
|
|
62
|
+
*/
|
|
63
|
+
export declare function mergeThemes(parent: Theme, child: Partial<Theme>): Theme;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve theme inheritance from a registry of themes.
|
|
66
|
+
* If a theme has `extends`, the parent is looked up and merged recursively.
|
|
67
|
+
*
|
|
68
|
+
* @param theme - The theme to resolve
|
|
69
|
+
* @param registry - Map of theme name → Theme
|
|
70
|
+
* @param visited - Set of already-visited names (cycle detection)
|
|
71
|
+
* @returns The fully resolved theme
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveThemeInheritance(theme: Theme, registry: Map<string, Theme>, visited?: Set<string>): Theme;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the effective mode from a ThemeMode value.
|
|
76
|
+
* 'auto' checks the system preference (prefers-color-scheme).
|
|
77
|
+
*
|
|
78
|
+
* @param mode - The declared mode
|
|
79
|
+
* @param systemDark - Whether the system prefers dark mode (for SSR or testing)
|
|
80
|
+
* @returns 'light' or 'dark'
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveMode(mode?: ThemeMode, systemDark?: boolean): 'light' | 'dark';
|