@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
|
@@ -1,123 +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
|
-
import { describe, it, expect } from 'vitest';
|
|
10
|
-
import { normalizeQuickFilter, normalizeQuickFilters } from '../normalize-quick-filter';
|
|
11
|
-
|
|
12
|
-
describe('normalizeQuickFilter', () => {
|
|
13
|
-
it('should pass through ObjectUI format unchanged', () => {
|
|
14
|
-
const input = { id: 'active', label: 'Active', filters: [['status', '=', 'active']] };
|
|
15
|
-
const result = normalizeQuickFilter(input);
|
|
16
|
-
expect(result).toBe(input); // same reference
|
|
17
|
-
expect(result.id).toBe('active');
|
|
18
|
-
expect(result.label).toBe('Active');
|
|
19
|
-
expect(result.filters).toEqual([['status', '=', 'active']]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should convert spec format { field, operator, value } to ObjectUI format', () => {
|
|
23
|
-
const result = normalizeQuickFilter({
|
|
24
|
-
field: 'status',
|
|
25
|
-
operator: 'eq',
|
|
26
|
-
value: 'active',
|
|
27
|
-
label: 'Active',
|
|
28
|
-
});
|
|
29
|
-
expect(result.id).toBe('status-eq-active');
|
|
30
|
-
expect(result.label).toBe('Active');
|
|
31
|
-
expect(result.filters).toEqual([['status', '=', 'active']]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should auto-generate label when omitted', () => {
|
|
35
|
-
const result = normalizeQuickFilter({
|
|
36
|
-
field: 'status',
|
|
37
|
-
operator: 'eq',
|
|
38
|
-
value: 'active',
|
|
39
|
-
});
|
|
40
|
-
expect(result.label).toBe('status eq active');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should handle null value', () => {
|
|
44
|
-
const result = normalizeQuickFilter({
|
|
45
|
-
field: 'archived',
|
|
46
|
-
operator: 'eq',
|
|
47
|
-
value: null,
|
|
48
|
-
label: 'Not Archived',
|
|
49
|
-
});
|
|
50
|
-
expect(result.id).toBe('archived-eq-');
|
|
51
|
-
expect(result.filters).toEqual([['archived', '=', null]]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should map "equals" operator to "="', () => {
|
|
55
|
-
const result = normalizeQuickFilter({
|
|
56
|
-
field: 'status',
|
|
57
|
-
operator: 'equals',
|
|
58
|
-
value: 'active',
|
|
59
|
-
});
|
|
60
|
-
expect(result.filters).toEqual([['status', '=', 'active']]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should map "gt" operator to ">"', () => {
|
|
64
|
-
const result = normalizeQuickFilter({
|
|
65
|
-
field: 'amount',
|
|
66
|
-
operator: 'gt',
|
|
67
|
-
value: 100,
|
|
68
|
-
});
|
|
69
|
-
expect(result.filters).toEqual([['amount', '>', 100]]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should map "lte" operator to "<="', () => {
|
|
73
|
-
const result = normalizeQuickFilter({
|
|
74
|
-
field: 'age',
|
|
75
|
-
operator: 'lte',
|
|
76
|
-
value: 18,
|
|
77
|
-
});
|
|
78
|
-
expect(result.filters).toEqual([['age', '<=', 18]]);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should pass through unknown operators', () => {
|
|
82
|
-
const result = normalizeQuickFilter({
|
|
83
|
-
field: 'status',
|
|
84
|
-
operator: 'custom_op',
|
|
85
|
-
value: 'x',
|
|
86
|
-
});
|
|
87
|
-
expect(result.filters).toEqual([['status', 'custom_op', 'x']]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should preserve icon and defaultActive', () => {
|
|
91
|
-
const result = normalizeQuickFilter({
|
|
92
|
-
field: 'status',
|
|
93
|
-
operator: 'eq',
|
|
94
|
-
value: 'active',
|
|
95
|
-
label: 'Active',
|
|
96
|
-
icon: 'check',
|
|
97
|
-
defaultActive: true,
|
|
98
|
-
});
|
|
99
|
-
expect(result.icon).toBe('check');
|
|
100
|
-
expect(result.defaultActive).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('normalizeQuickFilters', () => {
|
|
105
|
-
it('should return undefined for undefined input', () => {
|
|
106
|
-
expect(normalizeQuickFilters(undefined)).toBeUndefined();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should return undefined for empty array', () => {
|
|
110
|
-
expect(normalizeQuickFilters([])).toBeUndefined();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should normalize mixed format arrays', () => {
|
|
114
|
-
const result = normalizeQuickFilters([
|
|
115
|
-
{ id: 'vip', label: 'VIP', filters: [['vip', '=', true]] },
|
|
116
|
-
{ field: 'status', operator: 'eq', value: 'active', label: 'Active' },
|
|
117
|
-
]);
|
|
118
|
-
expect(result).toHaveLength(2);
|
|
119
|
-
expect(result![0].id).toBe('vip');
|
|
120
|
-
expect(result![1].id).toBe('status-eq-active');
|
|
121
|
-
expect(result![1].filters).toEqual([['status', '=', 'active']]);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1,100 +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
|
-
* DebugCollector — lightweight, tree-shakeable event collector
|
|
11
|
-
* for perf / expression / action debug data.
|
|
12
|
-
*
|
|
13
|
-
* Usage in production is a no-op when the singleton is never imported.
|
|
14
|
-
* Consumers call `DebugCollector.getInstance()` and subscribe via
|
|
15
|
-
* `.subscribe()`.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export interface PerfEntry {
|
|
19
|
-
type: string;
|
|
20
|
-
id?: string;
|
|
21
|
-
durationMs: number;
|
|
22
|
-
timestamp: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ExprEntry {
|
|
26
|
-
expression: string;
|
|
27
|
-
result: unknown;
|
|
28
|
-
context?: Record<string, unknown>;
|
|
29
|
-
timestamp: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface EventEntry {
|
|
33
|
-
action: string;
|
|
34
|
-
payload?: unknown;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type DebugEntry =
|
|
39
|
-
| { kind: 'perf'; data: PerfEntry }
|
|
40
|
-
| { kind: 'expr'; data: ExprEntry }
|
|
41
|
-
| { kind: 'event'; data: EventEntry };
|
|
42
|
-
|
|
43
|
-
type DebugSubscriber = (entry: DebugEntry) => void;
|
|
44
|
-
|
|
45
|
-
const MAX_ENTRIES = 200;
|
|
46
|
-
|
|
47
|
-
export class DebugCollector {
|
|
48
|
-
private static instance: DebugCollector | null = null;
|
|
49
|
-
|
|
50
|
-
private entries: DebugEntry[] = [];
|
|
51
|
-
private subscribers = new Set<DebugSubscriber>();
|
|
52
|
-
|
|
53
|
-
static getInstance(): DebugCollector {
|
|
54
|
-
if (!DebugCollector.instance) {
|
|
55
|
-
DebugCollector.instance = new DebugCollector();
|
|
56
|
-
}
|
|
57
|
-
return DebugCollector.instance;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Reset singleton — only used for testing */
|
|
61
|
-
static resetInstance(): void {
|
|
62
|
-
DebugCollector.instance = null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
addPerf(entry: PerfEntry): void {
|
|
66
|
-
this.push({ kind: 'perf', data: entry });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
addExpr(entry: ExprEntry): void {
|
|
70
|
-
this.push({ kind: 'expr', data: entry });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
addEvent(entry: EventEntry): void {
|
|
74
|
-
this.push({ kind: 'event', data: entry });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
getEntries(kind?: DebugEntry['kind']): DebugEntry[] {
|
|
78
|
-
if (!kind) return this.entries.slice();
|
|
79
|
-
return this.entries.filter((e) => e.kind === kind);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
clear(): void {
|
|
83
|
-
this.entries = [];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
subscribe(fn: DebugSubscriber): () => void {
|
|
87
|
-
this.subscribers.add(fn);
|
|
88
|
-
return () => this.subscribers.delete(fn);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private push(entry: DebugEntry): void {
|
|
92
|
-
this.entries.push(entry);
|
|
93
|
-
if (this.entries.length > MAX_ENTRIES) {
|
|
94
|
-
this.entries = this.entries.slice(-MAX_ENTRIES);
|
|
95
|
-
}
|
|
96
|
-
for (const fn of this.subscribers) {
|
|
97
|
-
try { fn(entry); } catch { /* subscriber errors must not break debug flow */ }
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/utils/debug.ts
DELETED
|
@@ -1,148 +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
|
-
export type DebugCategory = 'schema' | 'registry' | 'expression' | 'action' | 'plugin' | 'render' | 'dashboard';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Fine-grained debug flags parsed from URL parameters.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```
|
|
16
|
-
* ?__debug → { enabled: true }
|
|
17
|
-
* ?__debug_schema → { enabled: true, schema: true }
|
|
18
|
-
* ?__debug_perf&__debug_data → { enabled: true, perf: true, data: true }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export interface DebugFlags {
|
|
22
|
-
/** Master switch — true when any debug parameter is present */
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
schema?: boolean;
|
|
25
|
-
perf?: boolean;
|
|
26
|
-
data?: boolean;
|
|
27
|
-
expr?: boolean;
|
|
28
|
-
events?: boolean;
|
|
29
|
-
registry?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DEBUG_PARAM_PREFIX = '__debug';
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Parse debug flags from a URL search string (e.g. `?__debug&__debug_schema`).
|
|
36
|
-
* SSR-safe — returns `{ enabled: false }` when `window` is unavailable.
|
|
37
|
-
*
|
|
38
|
-
* @param search — Optional search string. Defaults to `window.location.search` when available.
|
|
39
|
-
*/
|
|
40
|
-
export function parseDebugFlags(search?: string): DebugFlags {
|
|
41
|
-
let qs: string | undefined = search;
|
|
42
|
-
if (qs === undefined) {
|
|
43
|
-
try {
|
|
44
|
-
qs = typeof window !== 'undefined' ? window.location.search : '';
|
|
45
|
-
} catch {
|
|
46
|
-
qs = '';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const params = new URLSearchParams(qs);
|
|
51
|
-
const hasMain = params.has(DEBUG_PARAM_PREFIX);
|
|
52
|
-
const schema = params.has(`${DEBUG_PARAM_PREFIX}_schema`);
|
|
53
|
-
const perf = params.has(`${DEBUG_PARAM_PREFIX}_perf`);
|
|
54
|
-
const data = params.has(`${DEBUG_PARAM_PREFIX}_data`);
|
|
55
|
-
const expr = params.has(`${DEBUG_PARAM_PREFIX}_expr`);
|
|
56
|
-
const events = params.has(`${DEBUG_PARAM_PREFIX}_events`);
|
|
57
|
-
const registry = params.has(`${DEBUG_PARAM_PREFIX}_registry`);
|
|
58
|
-
|
|
59
|
-
const anySub = schema || perf || data || expr || events || registry;
|
|
60
|
-
const enabled = hasMain || anySub;
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
enabled,
|
|
64
|
-
...(schema && { schema }),
|
|
65
|
-
...(perf && { perf }),
|
|
66
|
-
...(data && { data }),
|
|
67
|
-
...(expr && { expr }),
|
|
68
|
-
...(events && { events }),
|
|
69
|
-
...(registry && { registry }),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Check whether debug mode is enabled.
|
|
75
|
-
*
|
|
76
|
-
* Resolution order (first truthy wins):
|
|
77
|
-
* 1. URL parameter `?__debug` (browser only)
|
|
78
|
-
* 2. `globalThis.OBJECTUI_DEBUG`
|
|
79
|
-
* 3. `process.env.OBJECTUI_DEBUG`
|
|
80
|
-
*/
|
|
81
|
-
export function isDebugEnabled(): boolean {
|
|
82
|
-
try {
|
|
83
|
-
// 1. URL parameter (browser only, SSR-safe)
|
|
84
|
-
if (typeof window !== 'undefined') {
|
|
85
|
-
try {
|
|
86
|
-
const flags = parseDebugFlags(window.location.search);
|
|
87
|
-
if (flags.enabled) return true;
|
|
88
|
-
} catch { /* ignore */ }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 2. globalThis flag
|
|
92
|
-
const g = typeof globalThis !== 'undefined' && (globalThis as any).OBJECTUI_DEBUG;
|
|
93
|
-
if (g === true || g === 'true') return true;
|
|
94
|
-
|
|
95
|
-
// 3. process.env
|
|
96
|
-
const proc = (globalThis as any).process;
|
|
97
|
-
if (proc?.env?.OBJECTUI_DEBUG === 'true') return true;
|
|
98
|
-
|
|
99
|
-
return false;
|
|
100
|
-
} catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Log a debug message when OBJECTUI_DEBUG is enabled.
|
|
107
|
-
* No-op in production or when debug mode is off.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* // Enable debug mode
|
|
112
|
-
* globalThis.OBJECTUI_DEBUG = true;
|
|
113
|
-
*
|
|
114
|
-
* debugLog('schema', 'Resolving component', { type: 'Button' });
|
|
115
|
-
* // [ObjectUI Debug][schema] Resolving component { type: 'Button' }
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
export function debugLog(category: DebugCategory, message: string, data?: unknown): void {
|
|
119
|
-
if (!isDebugEnabled()) return;
|
|
120
|
-
if (data !== undefined) {
|
|
121
|
-
console.log(`[ObjectUI Debug][${category}] ${message}`, data);
|
|
122
|
-
} else {
|
|
123
|
-
console.log(`[ObjectUI Debug][${category}] ${message}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const timers = new Map<string, number>();
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Start a debug timer. Pair with {@link debugTimeEnd}.
|
|
131
|
-
*/
|
|
132
|
-
export function debugTime(label: string): void {
|
|
133
|
-
if (!isDebugEnabled()) return;
|
|
134
|
-
timers.set(label, performance.now());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* End a debug timer and log the elapsed time.
|
|
139
|
-
*/
|
|
140
|
-
export function debugTimeEnd(label: string): void {
|
|
141
|
-
if (!isDebugEnabled()) return;
|
|
142
|
-
const start = timers.get(label);
|
|
143
|
-
if (start !== undefined) {
|
|
144
|
-
const elapsed = (performance.now() - start).toFixed(2);
|
|
145
|
-
console.log(`[ObjectUI Debug][perf] ${label}: ${elapsed}ms`);
|
|
146
|
-
timers.delete(label);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI — expand-fields utility
|
|
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
|
-
* Build an array of field names that should be included in `$expand`
|
|
11
|
-
* when fetching data. This scans the given object schema fields
|
|
12
|
-
* (and optional column configuration) for `lookup` and `master_detail`
|
|
13
|
-
* field types, so the backend (e.g. objectql) returns expanded objects
|
|
14
|
-
* instead of raw foreign-key IDs.
|
|
15
|
-
*
|
|
16
|
-
* @param schemaFields - Object map of field metadata from `getObjectSchema()`,
|
|
17
|
-
* e.g. `{ account: { type: 'lookup', reference_to: 'accounts' }, ... }`.
|
|
18
|
-
* @param columns - Optional explicit column list. When provided, only
|
|
19
|
-
* lookup/master_detail fields that appear in `columns` are expanded.
|
|
20
|
-
* Accepts `string[]` or `ListColumn[]` (objects with a `field` property).
|
|
21
|
-
* @returns Array of field names to pass as `$expand`.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* const fields = {
|
|
26
|
-
* name: { type: 'text' },
|
|
27
|
-
* account: { type: 'lookup', reference_to: 'accounts' },
|
|
28
|
-
* parent: { type: 'master_detail', reference_to: 'contacts' },
|
|
29
|
-
* };
|
|
30
|
-
* buildExpandFields(fields);
|
|
31
|
-
* // → ['account', 'parent']
|
|
32
|
-
*
|
|
33
|
-
* buildExpandFields(fields, ['name', 'account']);
|
|
34
|
-
* // → ['account']
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export function buildExpandFields(
|
|
38
|
-
schemaFields?: Record<string, any> | null,
|
|
39
|
-
columns?: (string | { field?: string; name?: string; fieldName?: string })[],
|
|
40
|
-
): string[] {
|
|
41
|
-
if (!schemaFields || typeof schemaFields !== 'object') {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Collect all lookup / master_detail field names from the schema
|
|
46
|
-
const lookupFieldNames: string[] = [];
|
|
47
|
-
for (const [fieldName, fieldDef] of Object.entries(schemaFields)) {
|
|
48
|
-
if (
|
|
49
|
-
fieldDef &&
|
|
50
|
-
typeof fieldDef === 'object' &&
|
|
51
|
-
(fieldDef.type === 'lookup' || fieldDef.type === 'master_detail')
|
|
52
|
-
) {
|
|
53
|
-
lookupFieldNames.push(fieldName);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (lookupFieldNames.length === 0) {
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// When columns are provided, restrict expansion to visible columns only
|
|
62
|
-
if (columns && Array.isArray(columns) && columns.length > 0) {
|
|
63
|
-
const columnFieldNames = new Set<string>();
|
|
64
|
-
for (const col of columns) {
|
|
65
|
-
if (typeof col === 'string') {
|
|
66
|
-
columnFieldNames.add(col);
|
|
67
|
-
} else if (col && typeof col === 'object') {
|
|
68
|
-
const name = col.field ?? col.name ?? col.fieldName;
|
|
69
|
-
if (name) columnFieldNames.add(name);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return lookupFieldNames.filter((f) => columnFieldNames.has(f));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return lookupFieldNames;
|
|
76
|
-
}
|
|
@@ -1,33 +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
|
-
* Extract an array of records from various API response formats.
|
|
11
|
-
* Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
|
|
12
|
-
*
|
|
13
|
-
* This utility normalises the different shapes returned by ObjectStack,
|
|
14
|
-
* OData, and MSW mock endpoints so that every data-fetching component
|
|
15
|
-
* can rely on a single extraction path.
|
|
16
|
-
*/
|
|
17
|
-
export function extractRecords(results: unknown): any[] {
|
|
18
|
-
if (Array.isArray(results)) {
|
|
19
|
-
return results;
|
|
20
|
-
}
|
|
21
|
-
if (results && typeof results === 'object') {
|
|
22
|
-
if (Array.isArray((results as any).records)) {
|
|
23
|
-
return (results as any).records;
|
|
24
|
-
}
|
|
25
|
-
if (Array.isArray((results as any).data)) {
|
|
26
|
-
return (results as any).data;
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray((results as any).value)) {
|
|
29
|
-
return (results as any).value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
@@ -1,133 +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
|
-
* Filter Converter Utilities
|
|
11
|
-
*
|
|
12
|
-
* Shared utilities for converting MongoDB-like filter operators
|
|
13
|
-
* to ObjectStack FilterNode AST format.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* FilterNode AST type definition
|
|
18
|
-
* Represents a filter condition or a logical combination of conditions
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* // Simple condition
|
|
22
|
-
* ['status', '=', 'active']
|
|
23
|
-
*
|
|
24
|
-
* // Logical combination
|
|
25
|
-
* ['and', ['age', '>=', 18], ['status', '=', 'active']]
|
|
26
|
-
*/
|
|
27
|
-
export type FilterNode =
|
|
28
|
-
| [string, string, any] // [field, operator, value]
|
|
29
|
-
| [string, ...FilterNode[]]; // [logic, ...conditions]
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Map MongoDB-like operators to ObjectStack filter operators.
|
|
33
|
-
*
|
|
34
|
-
* @param operator - MongoDB-style operator (e.g., '$gte', '$in')
|
|
35
|
-
* @returns ObjectStack operator or null if not recognized
|
|
36
|
-
*/
|
|
37
|
-
export function convertOperatorToAST(operator: string): string | null {
|
|
38
|
-
const operatorMap: Record<string, string> = {
|
|
39
|
-
'$eq': '=',
|
|
40
|
-
'$ne': '!=',
|
|
41
|
-
'$gt': '>',
|
|
42
|
-
'$gte': '>=',
|
|
43
|
-
'$lt': '<',
|
|
44
|
-
'$lte': '<=',
|
|
45
|
-
'$in': 'in',
|
|
46
|
-
'$nin': 'notin',
|
|
47
|
-
'$notin': 'notin',
|
|
48
|
-
'$contains': 'contains',
|
|
49
|
-
'$startswith': 'startswith',
|
|
50
|
-
'$between': 'between',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return operatorMap[operator] || null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Convert object-based filters to ObjectStack FilterNode AST format.
|
|
58
|
-
* Converts MongoDB-like operators to ObjectStack filter expressions.
|
|
59
|
-
*
|
|
60
|
-
* @param filter - Object-based filter with optional operators
|
|
61
|
-
* @returns FilterNode AST array
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* // Simple filter - converted to AST
|
|
65
|
-
* convertFiltersToAST({ status: 'active' })
|
|
66
|
-
* // => ['status', '=', 'active']
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* // Complex filter with operators
|
|
70
|
-
* convertFiltersToAST({ age: { $gte: 18 } })
|
|
71
|
-
* // => ['age', '>=', 18]
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* // Multiple conditions
|
|
75
|
-
* convertFiltersToAST({ age: { $gte: 18, $lte: 65 }, status: 'active' })
|
|
76
|
-
* // => ['and', ['age', '>=', 18], ['age', '<=', 65], ['status', '=', 'active']]
|
|
77
|
-
*
|
|
78
|
-
* @throws {Error} If an unknown operator is encountered
|
|
79
|
-
*/
|
|
80
|
-
export function convertFiltersToAST(filter: Record<string, any>): FilterNode | Record<string, any> {
|
|
81
|
-
const conditions: FilterNode[] = [];
|
|
82
|
-
|
|
83
|
-
for (const [field, value] of Object.entries(filter)) {
|
|
84
|
-
if (value === null || value === undefined) continue;
|
|
85
|
-
|
|
86
|
-
// Check if value is a complex operator object
|
|
87
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
88
|
-
// Handle operator-based filters
|
|
89
|
-
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
90
|
-
// Special handling for $regex - warn users about limited support
|
|
91
|
-
if (operator === '$regex') {
|
|
92
|
-
console.warn(
|
|
93
|
-
`[ObjectUI] Warning: $regex operator is not fully supported. ` +
|
|
94
|
-
`Converting to 'contains' which only supports substring matching, not regex patterns. ` +
|
|
95
|
-
`Field: '${field}', Value: ${JSON.stringify(operatorValue)}. ` +
|
|
96
|
-
`Consider using $contains or $startswith instead.`
|
|
97
|
-
);
|
|
98
|
-
conditions.push([field, 'contains', operatorValue]);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const astOperator = convertOperatorToAST(operator);
|
|
103
|
-
|
|
104
|
-
if (astOperator) {
|
|
105
|
-
conditions.push([field, astOperator, operatorValue]);
|
|
106
|
-
} else {
|
|
107
|
-
// Unknown operator - throw error to avoid silent failure
|
|
108
|
-
throw new Error(
|
|
109
|
-
`[ObjectUI] Unknown filter operator '${operator}' for field '${field}'. ` +
|
|
110
|
-
`Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $startswith, $between. ` +
|
|
111
|
-
`If you need exact object matching, use the value directly without an operator.`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
// Simple equality filter
|
|
117
|
-
conditions.push([field, '=', value]);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If no conditions, return original filter
|
|
122
|
-
if (conditions.length === 0) {
|
|
123
|
-
return filter;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// If only one condition, return it directly
|
|
127
|
-
if (conditions.length === 1) {
|
|
128
|
-
return conditions[0];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Multiple conditions: combine with 'and'
|
|
132
|
-
return ['and', ...conditions];
|
|
133
|
-
}
|
|
@@ -1,36 +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
|
-
* Adapter: merge stack-level views into object definitions.
|
|
11
|
-
*
|
|
12
|
-
* Views are defined at the stack level (views[].listViews) but the runtime
|
|
13
|
-
* expects listViews on each object definition. This bridges the gap until
|
|
14
|
-
* the runtime/provider layer handles it natively.
|
|
15
|
-
*
|
|
16
|
-
* @param objects - Object definitions from composed stack
|
|
17
|
-
* @param views - View definitions containing listViews keyed by object name
|
|
18
|
-
* @returns Objects with listViews merged in from matching views
|
|
19
|
-
*/
|
|
20
|
-
export function mergeViewsIntoObjects(objects: any[], views: any[]): any[] {
|
|
21
|
-
const viewsByObject: Record<string, Record<string, any>> = {};
|
|
22
|
-
for (const view of views) {
|
|
23
|
-
if (!view.listViews) continue;
|
|
24
|
-
for (const [viewName, listView] of Object.entries(view.listViews as Record<string, any>)) {
|
|
25
|
-
const objectName = listView?.data?.object;
|
|
26
|
-
if (!objectName) continue;
|
|
27
|
-
if (!viewsByObject[objectName]) viewsByObject[objectName] = {};
|
|
28
|
-
viewsByObject[objectName][viewName] = listView;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return objects.map((obj: any) => {
|
|
32
|
-
const v = viewsByObject[obj.name];
|
|
33
|
-
if (!v) return obj;
|
|
34
|
-
return { ...obj, listViews: { ...(obj.listViews || {}), ...v } };
|
|
35
|
-
});
|
|
36
|
-
}
|