@object-ui/core 0.5.0 → 3.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.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/dist/__benchmarks__/core.bench.d.ts +8 -0
  4. package/dist/__benchmarks__/core.bench.js +53 -0
  5. package/dist/actions/ActionRunner.d.ts +228 -4
  6. package/dist/actions/ActionRunner.js +397 -45
  7. package/dist/actions/TransactionManager.d.ts +193 -0
  8. package/dist/actions/TransactionManager.js +410 -0
  9. package/dist/actions/index.d.ts +1 -0
  10. package/dist/actions/index.js +1 -0
  11. package/dist/adapters/ApiDataSource.d.ts +69 -0
  12. package/dist/adapters/ApiDataSource.js +293 -0
  13. package/dist/adapters/ValueDataSource.d.ts +55 -0
  14. package/dist/adapters/ValueDataSource.js +287 -0
  15. package/dist/adapters/index.d.ts +3 -0
  16. package/dist/adapters/index.js +5 -2
  17. package/dist/adapters/resolveDataSource.d.ts +40 -0
  18. package/dist/adapters/resolveDataSource.js +59 -0
  19. package/dist/data-scope/DataScopeManager.d.ts +127 -0
  20. package/dist/data-scope/DataScopeManager.js +229 -0
  21. package/dist/data-scope/index.d.ts +10 -0
  22. package/dist/data-scope/index.js +10 -0
  23. package/dist/errors/index.d.ts +75 -0
  24. package/dist/errors/index.js +224 -0
  25. package/dist/evaluator/ExpressionEvaluator.d.ts +11 -1
  26. package/dist/evaluator/ExpressionEvaluator.js +32 -8
  27. package/dist/evaluator/FormulaFunctions.d.ts +58 -0
  28. package/dist/evaluator/FormulaFunctions.js +350 -0
  29. package/dist/evaluator/index.d.ts +1 -0
  30. package/dist/evaluator/index.js +1 -0
  31. package/dist/index.d.ts +6 -0
  32. package/dist/index.js +6 -2
  33. package/dist/query/query-ast.d.ts +2 -2
  34. package/dist/query/query-ast.js +3 -3
  35. package/dist/registry/Registry.d.ts +10 -0
  36. package/dist/registry/Registry.js +9 -2
  37. package/dist/registry/WidgetRegistry.d.ts +120 -0
  38. package/dist/registry/WidgetRegistry.js +275 -0
  39. package/dist/theme/ThemeEngine.d.ts +105 -0
  40. package/dist/theme/ThemeEngine.js +469 -0
  41. package/dist/theme/index.d.ts +8 -0
  42. package/dist/theme/index.js +8 -0
  43. package/dist/utils/debug.d.ts +31 -0
  44. package/dist/utils/debug.js +62 -0
  45. package/dist/validation/index.d.ts +1 -1
  46. package/dist/validation/index.js +1 -1
  47. package/dist/validation/validation-engine.d.ts +19 -1
  48. package/dist/validation/validation-engine.js +74 -3
  49. package/dist/validation/validators/index.d.ts +1 -1
  50. package/dist/validation/validators/index.js +1 -1
  51. package/dist/validation/validators/object-validation-engine.d.ts +2 -2
  52. package/dist/validation/validators/object-validation-engine.js +1 -1
  53. package/package.json +4 -3
  54. package/src/__benchmarks__/core.bench.ts +64 -0
  55. package/src/actions/ActionRunner.ts +577 -55
  56. package/src/actions/TransactionManager.ts +521 -0
  57. package/src/actions/__tests__/ActionRunner.params.test.ts +134 -0
  58. package/src/actions/__tests__/ActionRunner.test.ts +711 -0
  59. package/src/actions/__tests__/TransactionManager.test.ts +447 -0
  60. package/src/actions/index.ts +1 -0
  61. package/src/adapters/ApiDataSource.ts +349 -0
  62. package/src/adapters/ValueDataSource.ts +332 -0
  63. package/src/adapters/__tests__/ApiDataSource.test.ts +418 -0
  64. package/src/adapters/__tests__/ValueDataSource.test.ts +325 -0
  65. package/src/adapters/__tests__/resolveDataSource.test.ts +144 -0
  66. package/src/adapters/index.ts +6 -1
  67. package/src/adapters/resolveDataSource.ts +79 -0
  68. package/src/builder/__tests__/schema-builder.test.ts +235 -0
  69. package/src/data-scope/DataScopeManager.ts +269 -0
  70. package/src/data-scope/__tests__/DataScopeManager.test.ts +211 -0
  71. package/src/data-scope/index.ts +16 -0
  72. package/src/errors/__tests__/errors.test.ts +292 -0
  73. package/src/errors/index.ts +270 -0
  74. package/src/evaluator/ExpressionEvaluator.ts +34 -8
  75. package/src/evaluator/FormulaFunctions.ts +398 -0
  76. package/src/evaluator/__tests__/ExpressionContext.test.ts +110 -0
  77. package/src/evaluator/__tests__/FormulaFunctions.test.ts +447 -0
  78. package/src/evaluator/index.ts +1 -0
  79. package/src/index.ts +6 -3
  80. package/src/query/__tests__/window-functions.test.ts +1 -1
  81. package/src/query/query-ast.ts +3 -3
  82. package/src/registry/Registry.ts +19 -2
  83. package/src/registry/WidgetRegistry.ts +316 -0
  84. package/src/registry/__tests__/WidgetRegistry.test.ts +321 -0
  85. package/src/theme/ThemeEngine.ts +530 -0
  86. package/src/theme/__tests__/ThemeEngine.test.ts +668 -0
  87. package/src/theme/index.ts +24 -0
  88. package/src/utils/__tests__/debug.test.ts +83 -0
  89. package/src/utils/debug.ts +66 -0
  90. package/src/validation/__tests__/object-validation-engine.test.ts +1 -1
  91. package/src/validation/__tests__/schema-validator.test.ts +118 -0
  92. package/src/validation/index.ts +1 -1
  93. package/src/validation/validation-engine.ts +70 -3
  94. package/src/validation/validators/index.ts +1 -1
  95. package/src/validation/validators/object-validation-engine.ts +2 -2
  96. 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,105 @@
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';
83
+ /**
84
+ * Calculate the WCAG 2.1 contrast ratio between two hex colors.
85
+ * Returns a value between 1 and 21.
86
+ *
87
+ * @param hex1 - First color in hex format (#RGB or #RRGGBB)
88
+ * @param hex2 - Second color in hex format (#RGB or #RRGGBB)
89
+ * @returns Contrast ratio (1-21), or null if colors are invalid
90
+ */
91
+ export declare function contrastRatio(hex1: string, hex2: string): number | null;
92
+ /**
93
+ * Check if two colors meet the specified WCAG contrast level.
94
+ *
95
+ * WCAG levels:
96
+ * - AA: 4.5:1 for normal text, 3:1 for large text
97
+ * - AAA: 7:1 for normal text, 4.5:1 for large text
98
+ *
99
+ * @param hex1 - First color in hex format
100
+ * @param hex2 - Second color in hex format
101
+ * @param level - WCAG level: 'AA' or 'AAA'
102
+ * @param isLargeText - Whether the text is large (18pt+ or 14pt+ bold)
103
+ * @returns true if the color pair meets the required contrast level
104
+ */
105
+ export declare function meetsContrastLevel(hex1: string, hex2: string, level?: 'AA' | 'AAA', isLargeText?: boolean): boolean;