@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,83 @@
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
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { debugLog, debugTime, debugTimeEnd } from '../debug';
11
+
12
+ describe('Debug Utilities', () => {
13
+ let consoleSpy: ReturnType<typeof vi.spyOn>;
14
+
15
+ beforeEach(() => {
16
+ consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
17
+ });
18
+
19
+ afterEach(() => {
20
+ consoleSpy.mockRestore();
21
+ (globalThis as any).OBJECTUI_DEBUG = undefined;
22
+ });
23
+
24
+ describe('debugLog', () => {
25
+ it('should not log when debug is disabled', () => {
26
+ (globalThis as any).OBJECTUI_DEBUG = false;
27
+ debugLog('schema', 'test message');
28
+ expect(consoleSpy).not.toHaveBeenCalled();
29
+ });
30
+
31
+ it('should log when OBJECTUI_DEBUG is true', () => {
32
+ (globalThis as any).OBJECTUI_DEBUG = true;
33
+ debugLog('schema', 'Resolving component');
34
+ expect(consoleSpy).toHaveBeenCalledWith('[ObjectUI Debug][schema] Resolving component');
35
+ });
36
+
37
+ it('should log with data when provided', () => {
38
+ (globalThis as any).OBJECTUI_DEBUG = true;
39
+ debugLog('registry', 'Registered', { type: 'Button' });
40
+ expect(consoleSpy).toHaveBeenCalledWith(
41
+ '[ObjectUI Debug][registry] Registered',
42
+ { type: 'Button' }
43
+ );
44
+ });
45
+
46
+ it('should not log when debug is undefined', () => {
47
+ debugLog('action', 'test');
48
+ expect(consoleSpy).not.toHaveBeenCalled();
49
+ });
50
+ });
51
+
52
+ describe('debugTime / debugTimeEnd', () => {
53
+ it('should not log timing when debug is disabled', () => {
54
+ (globalThis as any).OBJECTUI_DEBUG = false;
55
+ debugTime('test-timer');
56
+ debugTimeEnd('test-timer');
57
+ expect(consoleSpy).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it('should measure and log elapsed time when debug is enabled', () => {
61
+ (globalThis as any).OBJECTUI_DEBUG = true;
62
+ debugTime('render-test');
63
+
64
+ // Simulate some delay via a busy loop
65
+ const start = performance.now();
66
+ while (performance.now() - start < 5) {
67
+ // wait ~5ms
68
+ }
69
+
70
+ debugTimeEnd('render-test');
71
+ expect(consoleSpy).toHaveBeenCalledTimes(1);
72
+ expect(consoleSpy).toHaveBeenCalledWith(
73
+ expect.stringMatching(/^\[ObjectUI Debug\]\[perf\] render-test: \d+\.\d{2}ms$/)
74
+ );
75
+ });
76
+
77
+ it('should not log if debugTimeEnd is called without debugTime', () => {
78
+ (globalThis as any).OBJECTUI_DEBUG = true;
79
+ debugTimeEnd('nonexistent');
80
+ expect(consoleSpy).not.toHaveBeenCalled();
81
+ });
82
+ });
83
+ });
@@ -0,0 +1,66 @@
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
+ type DebugCategory = 'schema' | 'registry' | 'expression' | 'action' | 'plugin' | 'render';
10
+
11
+ function isDebugEnabled(): boolean {
12
+ try {
13
+ const g = typeof globalThis !== 'undefined' && (globalThis as any).OBJECTUI_DEBUG;
14
+ return (
15
+ (g === true || g === 'true') ||
16
+ (typeof process !== 'undefined' && process.env?.OBJECTUI_DEBUG === 'true')
17
+ );
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Log a debug message when OBJECTUI_DEBUG is enabled.
25
+ * No-op in production or when debug mode is off.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Enable debug mode
30
+ * globalThis.OBJECTUI_DEBUG = true;
31
+ *
32
+ * debugLog('schema', 'Resolving component', { type: 'Button' });
33
+ * // [ObjectUI Debug][schema] Resolving component { type: 'Button' }
34
+ * ```
35
+ */
36
+ export function debugLog(category: DebugCategory, message: string, data?: unknown): void {
37
+ if (!isDebugEnabled()) return;
38
+ if (data !== undefined) {
39
+ console.log(`[ObjectUI Debug][${category}] ${message}`, data);
40
+ } else {
41
+ console.log(`[ObjectUI Debug][${category}] ${message}`);
42
+ }
43
+ }
44
+
45
+ const timers = new Map<string, number>();
46
+
47
+ /**
48
+ * Start a debug timer. Pair with {@link debugTimeEnd}.
49
+ */
50
+ export function debugTime(label: string): void {
51
+ if (!isDebugEnabled()) return;
52
+ timers.set(label, performance.now());
53
+ }
54
+
55
+ /**
56
+ * End a debug timer and log the elapsed time.
57
+ */
58
+ export function debugTimeEnd(label: string): void {
59
+ if (!isDebugEnabled()) return;
60
+ const start = timers.get(label);
61
+ if (start !== undefined) {
62
+ const elapsed = (performance.now() - start).toFixed(2);
63
+ console.log(`[ObjectUI Debug][perf] ${label}: ${elapsed}ms`);
64
+ timers.delete(label);
65
+ }
66
+ }
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * @object-ui/core - Object Validation Engine Tests
11
11
  *
12
- * Tests for ObjectStack Spec v0.7.1 object-level validation
12
+ * Tests for ObjectStack Spec v2.0.1 object-level validation
13
13
  */
14
14
 
15
15
  import { describe, it, expect, vi } from 'vitest';
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ validateSchema,
4
+ assertValidSchema,
5
+ isValidSchema,
6
+ formatValidationErrors,
7
+ } from '../../validation/schema-validator';
8
+
9
+ describe('schema-validator', () => {
10
+ describe('validateSchema', () => {
11
+ it('validates a minimal valid schema', () => {
12
+ const result = validateSchema({ type: 'form' });
13
+ expect(result.valid).toBe(true);
14
+ expect(result.errors).toHaveLength(0);
15
+ });
16
+
17
+ it('rejects schema without type', () => {
18
+ const result = validateSchema({} as any);
19
+ expect(result.valid).toBe(false);
20
+ expect(result.errors.length).toBeGreaterThan(0);
21
+ });
22
+
23
+ it('validates CRUD schema with columns', () => {
24
+ const result = validateSchema({
25
+ type: 'crud',
26
+ columns: [{ name: 'id', label: 'ID' }],
27
+ api: '/api/users',
28
+ });
29
+ expect(result.valid).toBe(true);
30
+ });
31
+
32
+ it('warns about CRUD without columns', () => {
33
+ const result = validateSchema({ type: 'crud' });
34
+ const hasColumnsIssue = [...result.errors, ...result.warnings].some(
35
+ (e) => e.message.toLowerCase().includes('column'),
36
+ );
37
+ expect(hasColumnsIssue).toBe(true);
38
+ });
39
+
40
+ it('validates form with fields', () => {
41
+ const result = validateSchema({
42
+ type: 'form',
43
+ fields: [
44
+ { name: 'email', label: 'Email', type: 'string' },
45
+ ],
46
+ });
47
+ expect(result.valid).toBe(true);
48
+ });
49
+
50
+ it('detects duplicate field names in forms', () => {
51
+ const result = validateSchema({
52
+ type: 'form',
53
+ fields: [
54
+ { name: 'email', label: 'Email', type: 'string' },
55
+ { name: 'email', label: 'Email 2', type: 'string' },
56
+ ],
57
+ });
58
+ const hasDuplicateWarning = [...result.errors, ...result.warnings].some(
59
+ (e) => e.message.toLowerCase().includes('duplicate'),
60
+ );
61
+ expect(hasDuplicateWarning).toBe(true);
62
+ });
63
+
64
+ it('validates nested children', () => {
65
+ const result = validateSchema({
66
+ type: 'grid',
67
+ children: [
68
+ { type: 'button', label: 'OK' },
69
+ ],
70
+ });
71
+ expect(result.valid).toBe(true);
72
+ });
73
+ });
74
+
75
+ describe('isValidSchema', () => {
76
+ it('returns true for valid schema', () => {
77
+ expect(isValidSchema({ type: 'form' })).toBe(true);
78
+ });
79
+
80
+ it('returns false for invalid schema', () => {
81
+ expect(isValidSchema({})).toBe(false);
82
+ });
83
+
84
+ it('returns false for empty object', () => {
85
+ expect(isValidSchema({} as any)).toBe(false);
86
+ });
87
+
88
+ it('returns false for non-object values', () => {
89
+ expect(isValidSchema('string' as any)).toBe(false);
90
+ expect(isValidSchema(42 as any)).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('assertValidSchema', () => {
95
+ it('does not throw for valid schema', () => {
96
+ expect(() => assertValidSchema({ type: 'form' })).not.toThrow();
97
+ });
98
+
99
+ it('throws for invalid schema', () => {
100
+ expect(() => assertValidSchema({} as any)).toThrow();
101
+ });
102
+ });
103
+
104
+ describe('formatValidationErrors', () => {
105
+ it('formats validation errors', () => {
106
+ const result = validateSchema({} as any);
107
+ const formatted = formatValidationErrors(result);
108
+ expect(typeof formatted).toBe('string');
109
+ expect(formatted.length).toBeGreaterThan(0);
110
+ });
111
+
112
+ it('returns empty string for valid schemas', () => {
113
+ const result = validateSchema({ type: 'form' });
114
+ const formatted = formatValidationErrors(result);
115
+ expect(formatted).toBe('');
116
+ });
117
+ });
118
+ });
@@ -2,7 +2,7 @@
2
2
  * @object-ui/core - Validation Module
3
3
  *
4
4
  * Phase 3.5: Validation engine
5
- * ObjectStack Spec v0.7.1: Object-level validation
5
+ * ObjectStack Spec v2.0.1: Object-level validation
6
6
  */
7
7
 
8
8
  export * from './validation-engine.js';
@@ -34,6 +34,39 @@ import type {
34
34
  * Validation Engine - Executes validation rules
35
35
  */
36
36
  export class ValidationEngine {
37
+ private customValidators = new Map<string, ValidationFunction>();
38
+ private customAsyncValidators = new Map<string, AsyncValidationFunction>();
39
+
40
+ /**
41
+ * Register a custom synchronous validator by name
42
+ */
43
+ registerValidator(name: string, fn: ValidationFunction): void {
44
+ this.customValidators.set(name, fn);
45
+ }
46
+
47
+ /**
48
+ * Register a custom asynchronous validator by name
49
+ */
50
+ registerAsyncValidator(name: string, fn: AsyncValidationFunction): void {
51
+ this.customAsyncValidators.set(name, fn);
52
+ }
53
+
54
+ /**
55
+ * Check if a custom validator is registered
56
+ */
57
+ hasValidator(name: string): boolean {
58
+ return this.customValidators.has(name) || this.customAsyncValidators.has(name);
59
+ }
60
+
61
+ /**
62
+ * Get all registered custom validator names
63
+ */
64
+ getValidatorNames(): string[] {
65
+ return [
66
+ ...Array.from(this.customValidators.keys()),
67
+ ...Array.from(this.customAsyncValidators.keys()),
68
+ ];
69
+ }
37
70
  /**
38
71
  * Validate a value against validation schema
39
72
  */
@@ -80,7 +113,7 @@ export class ValidationEngine {
80
113
  rule: AdvancedValidationRule,
81
114
  context?: ValidationContext
82
115
  ): Promise<string | null> {
83
- // Custom async validator
116
+ // Custom async validator (inline)
84
117
  if (rule.async_validator) {
85
118
  const result = await rule.async_validator(value, context);
86
119
  if (result === false) {
@@ -92,7 +125,7 @@ export class ValidationEngine {
92
125
  return null;
93
126
  }
94
127
 
95
- // Custom sync validator
128
+ // Custom sync validator (inline)
96
129
  if (rule.validator) {
97
130
  const result = rule.validator(value, context);
98
131
  if (result === false) {
@@ -104,6 +137,32 @@ export class ValidationEngine {
104
137
  return null;
105
138
  }
106
139
 
140
+ // Registered custom async validator (by name)
141
+ const registeredAsync = this.customAsyncValidators.get(rule.type);
142
+ if (registeredAsync) {
143
+ const result = await registeredAsync(value, context);
144
+ if (result === false) {
145
+ return rule.message || 'Async validation failed';
146
+ }
147
+ if (typeof result === 'string') {
148
+ return result;
149
+ }
150
+ return null;
151
+ }
152
+
153
+ // Registered custom sync validator (by name)
154
+ const registeredSync = this.customValidators.get(rule.type);
155
+ if (registeredSync) {
156
+ const result = registeredSync(value, context);
157
+ if (result === false) {
158
+ return rule.message || 'Validation failed';
159
+ }
160
+ if (typeof result === 'string') {
161
+ return result;
162
+ }
163
+ return null;
164
+ }
165
+
107
166
  // Built-in validators
108
167
  return this.validateBuiltInRule(value, rule, context);
109
168
  }
@@ -342,7 +401,15 @@ export class ValidationEngine {
342
401
  */
343
402
  private evaluateCondition(condition: any, values: Record<string, any>): boolean {
344
403
  if (typeof condition === 'function') {
345
- console.warn('Function-based conditions are deprecated and will be removed. Use declarative conditions instead.');
404
+ console.warn(
405
+ 'Function-based conditions are deprecated and will be removed. Use declarative conditions instead.\n\n' +
406
+ ' Migration:\n' +
407
+ ' // Before (deprecated):\n' +
408
+ ' { condition: (values) => values.age > 18 }\n\n' +
409
+ ' // After:\n' +
410
+ ' { condition: { field: "age", operator: ">", value: 18 } }\n\n' +
411
+ ' See: https://github.com/objectstack-ai/objectui/blob/main/MIGRATION_GUIDE.md'
412
+ );
346
413
  return false; // Security: reject function-based conditions
347
414
  }
348
415
 
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * @object-ui/core - Validators
11
11
  *
12
- * ObjectStack Spec v0.7.1 compliant validators
12
+ * ObjectStack Spec v2.0.1 compliant validators
13
13
  *
14
14
  * @module validators
15
15
  * @packageDocumentation
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * @object-ui/core - Object-Level Validation Engine
11
11
  *
12
- * ObjectStack Spec v0.7.1 compliant validation engine for object-level validation rules.
12
+ * ObjectStack Spec v2.0.1 compliant validation engine for object-level validation rules.
13
13
  * Supports all 9 validation types from the specification:
14
14
  * - ScriptValidation
15
15
  * - UniquenessValidation
@@ -256,7 +256,7 @@ class SimpleExpressionEvaluator implements ValidationExpressionEvaluator {
256
256
 
257
257
  /**
258
258
  * Object-Level Validation Engine
259
- * Implements ObjectStack Spec v0.7.1 validation framework
259
+ * Implements ObjectStack Spec v2.0.1 validation framework
260
260
  */
261
261
  export class ObjectValidationEngine {
262
262
  private expressionEvaluator: ValidationExpressionEvaluator;