@object-ui/core 3.1.5 → 3.3.1

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 (110) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +20 -1
  3. package/dist/actions/ActionRunner.d.ts +9 -0
  4. package/dist/actions/ActionRunner.js +41 -4
  5. package/dist/adapters/ValueDataSource.d.ts +5 -1
  6. package/dist/adapters/ValueDataSource.js +30 -1
  7. package/dist/errors/index.js +2 -3
  8. package/dist/evaluator/ExpressionCache.d.ts +9 -10
  9. package/dist/evaluator/ExpressionCache.js +29 -8
  10. package/dist/evaluator/SafeExpressionParser.d.ts +131 -0
  11. package/dist/evaluator/SafeExpressionParser.js +851 -0
  12. package/dist/evaluator/index.d.ts +1 -0
  13. package/dist/evaluator/index.js +1 -0
  14. package/dist/protocols/DndProtocol.js +2 -14
  15. package/dist/protocols/KeyboardProtocol.js +1 -4
  16. package/dist/protocols/NotificationProtocol.js +3 -13
  17. package/dist/utils/debug.js +2 -1
  18. package/dist/utils/filter-converter.js +25 -5
  19. package/package.json +33 -9
  20. package/.turbo/turbo-build.log +0 -4
  21. package/src/__benchmarks__/core.bench.ts +0 -64
  22. package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
  23. package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
  24. package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
  25. package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
  26. package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
  27. package/src/actions/ActionEngine.ts +0 -268
  28. package/src/actions/ActionRunner.ts +0 -717
  29. package/src/actions/TransactionManager.ts +0 -521
  30. package/src/actions/UndoManager.ts +0 -215
  31. package/src/actions/__tests__/ActionEngine.test.ts +0 -206
  32. package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
  33. package/src/actions/__tests__/ActionRunner.test.ts +0 -711
  34. package/src/actions/__tests__/TransactionManager.test.ts +0 -447
  35. package/src/actions/__tests__/UndoManager.test.ts +0 -320
  36. package/src/actions/index.ts +0 -12
  37. package/src/adapters/ApiDataSource.ts +0 -376
  38. package/src/adapters/README.md +0 -180
  39. package/src/adapters/ValueDataSource.ts +0 -438
  40. package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
  41. package/src/adapters/__tests__/ValueDataSource.test.ts +0 -472
  42. package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
  43. package/src/adapters/index.ts +0 -15
  44. package/src/adapters/resolveDataSource.ts +0 -79
  45. package/src/builder/__tests__/schema-builder.test.ts +0 -235
  46. package/src/builder/schema-builder.ts +0 -584
  47. package/src/data-scope/DataScopeManager.ts +0 -269
  48. package/src/data-scope/ViewDataProvider.ts +0 -282
  49. package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
  50. package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
  51. package/src/data-scope/index.ts +0 -24
  52. package/src/errors/__tests__/errors.test.ts +0 -292
  53. package/src/errors/index.ts +0 -270
  54. package/src/evaluator/ExpressionCache.ts +0 -192
  55. package/src/evaluator/ExpressionContext.ts +0 -118
  56. package/src/evaluator/ExpressionEvaluator.ts +0 -315
  57. package/src/evaluator/FormulaFunctions.ts +0 -398
  58. package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
  59. package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
  60. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -131
  61. package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
  62. package/src/evaluator/index.ts +0 -12
  63. package/src/index.ts +0 -38
  64. package/src/protocols/DndProtocol.ts +0 -184
  65. package/src/protocols/KeyboardProtocol.ts +0 -185
  66. package/src/protocols/NotificationProtocol.ts +0 -159
  67. package/src/protocols/ResponsiveProtocol.ts +0 -210
  68. package/src/protocols/SharingProtocol.ts +0 -185
  69. package/src/protocols/index.ts +0 -13
  70. package/src/query/__tests__/query-ast.test.ts +0 -211
  71. package/src/query/__tests__/window-functions.test.ts +0 -275
  72. package/src/query/index.ts +0 -7
  73. package/src/query/query-ast.ts +0 -341
  74. package/src/registry/PluginScopeImpl.ts +0 -259
  75. package/src/registry/PluginSystem.ts +0 -206
  76. package/src/registry/Registry.ts +0 -219
  77. package/src/registry/WidgetRegistry.ts +0 -316
  78. package/src/registry/__tests__/PluginSystem.test.ts +0 -309
  79. package/src/registry/__tests__/Registry.test.ts +0 -293
  80. package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
  81. package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
  82. package/src/theme/ThemeEngine.ts +0 -530
  83. package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
  84. package/src/theme/index.ts +0 -24
  85. package/src/types/index.ts +0 -21
  86. package/src/utils/__tests__/debug-collector.test.ts +0 -102
  87. package/src/utils/__tests__/debug.test.ts +0 -134
  88. package/src/utils/__tests__/expand-fields.test.ts +0 -120
  89. package/src/utils/__tests__/extract-records.test.ts +0 -50
  90. package/src/utils/__tests__/filter-converter.test.ts +0 -118
  91. package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
  92. package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
  93. package/src/utils/debug-collector.ts +0 -100
  94. package/src/utils/debug.ts +0 -147
  95. package/src/utils/expand-fields.ts +0 -76
  96. package/src/utils/extract-records.ts +0 -33
  97. package/src/utils/filter-converter.ts +0 -133
  98. package/src/utils/merge-views-into-objects.ts +0 -36
  99. package/src/utils/normalize-quick-filter.ts +0 -78
  100. package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
  101. package/src/validation/__tests__/schema-validator.test.ts +0 -118
  102. package/src/validation/__tests__/validation-engine.test.ts +0 -102
  103. package/src/validation/index.ts +0 -10
  104. package/src/validation/schema-validator.ts +0 -344
  105. package/src/validation/validation-engine.ts +0 -528
  106. package/src/validation/validators/index.ts +0 -25
  107. package/src/validation/validators/object-validation-engine.ts +0 -722
  108. package/tsconfig.json +0 -15
  109. package/tsconfig.tsbuildinfo +0 -1
  110. package/vitest.config.ts +0 -2
@@ -1,210 +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
- * @object-ui/core - Responsive Protocol Bridge
11
- *
12
- * Converts spec-aligned ResponsiveConfig schemas into Tailwind CSS
13
- * utility classes for visibility, grid columns, and ordering across
14
- * breakpoints. Also provides runtime width-based visibility checks.
15
- *
16
- * @module protocols/ResponsiveProtocol
17
- * @packageDocumentation
18
- */
19
-
20
- import type { SpecResponsiveConfig } from '@object-ui/types';
21
-
22
- // ============================================================================
23
- // Breakpoint Definitions
24
- // ============================================================================
25
-
26
- /** Breakpoint name type matching Tailwind defaults. */
27
- export type BreakpointKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
28
-
29
- /** Breakpoint minimum pixel widths aligned with Tailwind CSS defaults. */
30
- export const BREAKPOINT_VALUES: Record<BreakpointKey, number> = {
31
- xs: 0,
32
- sm: 640,
33
- md: 768,
34
- lg: 1024,
35
- xl: 1280,
36
- '2xl': 1536,
37
- };
38
-
39
- /** Ordered breakpoint keys from smallest to largest. */
40
- const BREAKPOINT_ORDER: BreakpointKey[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
41
-
42
- // ============================================================================
43
- // Resolved Types
44
- // ============================================================================
45
-
46
- /** Fully resolved responsive configuration. */
47
- export interface ResolvedResponsiveConfig {
48
- breakpoint?: BreakpointKey;
49
- hiddenOn: BreakpointKey[];
50
- columns: Partial<Record<BreakpointKey, number>>;
51
- order: Partial<Record<BreakpointKey, number>>;
52
- }
53
-
54
- // ============================================================================
55
- // Config Resolution
56
- // ============================================================================
57
-
58
- /**
59
- * Resolve a responsive configuration by applying defaults.
60
- *
61
- * @param config - SpecResponsiveConfig from the spec
62
- * @returns Fully resolved responsive configuration
63
- */
64
- export function resolveResponsiveConfig(config: SpecResponsiveConfig): ResolvedResponsiveConfig {
65
- return {
66
- breakpoint: config.breakpoint as BreakpointKey | undefined,
67
- hiddenOn: (config.hiddenOn ?? []) as BreakpointKey[],
68
- columns: (config.columns ?? {}) as Partial<Record<BreakpointKey, number>>,
69
- order: (config.order ?? {}) as Partial<Record<BreakpointKey, number>>,
70
- };
71
- }
72
-
73
- // ============================================================================
74
- // Visibility Classes
75
- // ============================================================================
76
-
77
- /**
78
- * Generate Tailwind CSS classes for responsive visibility.
79
- *
80
- * If `breakpoint` is set, the element is hidden below that breakpoint
81
- * (e.g. breakpoint "md" → `['hidden', 'md:block']`).
82
- *
83
- * If `hiddenOn` contains breakpoints, the element is hidden at those
84
- * specific sizes (e.g. hiddenOn: ["sm", "lg"] → `['sm:hidden', 'md:block', 'lg:hidden', 'xl:block']`).
85
- *
86
- * @param config - SpecResponsiveConfig from the spec
87
- * @returns Array of Tailwind CSS class strings
88
- */
89
- export function getVisibilityClasses(config: SpecResponsiveConfig): string[] {
90
- const classes: string[] = [];
91
-
92
- // Minimum breakpoint visibility
93
- if (config.breakpoint) {
94
- const bp = config.breakpoint as BreakpointKey;
95
- if (bp !== 'xs') {
96
- classes.push('hidden');
97
- classes.push(`${bp}:block`);
98
- }
99
- }
100
-
101
- // Per-breakpoint hidden overrides
102
- const hiddenOn = (config.hiddenOn ?? []) as BreakpointKey[];
103
- if (hiddenOn.length > 0) {
104
- for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
105
- const bp = BREAKPOINT_ORDER[i];
106
- const isHidden = hiddenOn.includes(bp);
107
- const prevHidden = i > 0 ? hiddenOn.includes(BREAKPOINT_ORDER[i - 1]) : false;
108
-
109
- if (isHidden && !prevHidden) {
110
- classes.push(bp === 'xs' ? 'hidden' : `${bp}:hidden`);
111
- } else if (!isHidden && prevHidden) {
112
- classes.push(bp === 'xs' ? 'block' : `${bp}:block`);
113
- }
114
- }
115
- }
116
-
117
- return classes;
118
- }
119
-
120
- // ============================================================================
121
- // Column Classes
122
- // ============================================================================
123
-
124
- /**
125
- * Generate Tailwind grid-cols classes for responsive column layouts.
126
- *
127
- * @param config - SpecResponsiveConfig from the spec
128
- * @returns Array of Tailwind CSS grid column class strings
129
- */
130
- export function getColumnClasses(config: SpecResponsiveConfig): string[] {
131
- const classes: string[] = [];
132
- const columns = (config.columns ?? {}) as Partial<Record<BreakpointKey, number>>;
133
-
134
- for (const bp of BREAKPOINT_ORDER) {
135
- const cols = columns[bp];
136
- if (cols == null) continue;
137
- const prefix = bp === 'xs' ? '' : `${bp}:`;
138
- classes.push(`${prefix}grid-cols-${cols}`);
139
- }
140
-
141
- return classes;
142
- }
143
-
144
- // ============================================================================
145
- // Order Classes
146
- // ============================================================================
147
-
148
- /**
149
- * Generate Tailwind order utility classes for responsive ordering.
150
- *
151
- * @param config - SpecResponsiveConfig from the spec
152
- * @returns Array of Tailwind CSS order class strings
153
- */
154
- export function getOrderClasses(config: SpecResponsiveConfig): string[] {
155
- const classes: string[] = [];
156
- const order = (config.order ?? {}) as Partial<Record<BreakpointKey, number>>;
157
-
158
- for (const bp of BREAKPOINT_ORDER) {
159
- const ord = order[bp];
160
- if (ord == null) continue;
161
- const prefix = bp === 'xs' ? '' : `${bp}:`;
162
- classes.push(`${prefix}order-${ord}`);
163
- }
164
-
165
- return classes;
166
- }
167
-
168
- // ============================================================================
169
- // Runtime Width Check
170
- // ============================================================================
171
-
172
- /**
173
- * Determine whether a component should be hidden at a given viewport width.
174
- *
175
- * Checks both the minimum `breakpoint` threshold and the `hiddenOn` list.
176
- *
177
- * @param config - SpecResponsiveConfig from the spec
178
- * @param width - Current viewport width in pixels
179
- * @returns `true` if the component should be hidden at the given width
180
- */
181
- export function shouldHideAtBreakpoint(config: SpecResponsiveConfig, width: number): boolean {
182
- // Check minimum breakpoint
183
- if (config.breakpoint) {
184
- const minWidth = BREAKPOINT_VALUES[config.breakpoint as BreakpointKey];
185
- if (minWidth !== undefined && width < minWidth) {
186
- return true;
187
- }
188
- }
189
-
190
- // Check hiddenOn list
191
- const hiddenOn = (config.hiddenOn ?? []) as BreakpointKey[];
192
- if (hiddenOn.length > 0) {
193
- const currentBp = getCurrentBreakpoint(width);
194
- return hiddenOn.includes(currentBp);
195
- }
196
-
197
- return false;
198
- }
199
-
200
- /**
201
- * Determine the current breakpoint name for a given width.
202
- */
203
- function getCurrentBreakpoint(width: number): BreakpointKey {
204
- for (let i = BREAKPOINT_ORDER.length - 1; i >= 0; i--) {
205
- if (width >= BREAKPOINT_VALUES[BREAKPOINT_ORDER[i]]) {
206
- return BREAKPOINT_ORDER[i];
207
- }
208
- }
209
- return 'xs';
210
- }
@@ -1,185 +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
- * @object-ui/core - Sharing Protocol Bridge
11
- *
12
- * Converts spec-aligned SharingConfig and EmbedConfig schemas into
13
- * runtime-usable configurations. Provides embed code generation and
14
- * configuration validation.
15
- *
16
- * @module protocols/SharingProtocol
17
- * @packageDocumentation
18
- */
19
-
20
- import type { SharingConfig, EmbedConfig } from '@object-ui/types';
21
-
22
- // ============================================================================
23
- // Resolved Types
24
- // ============================================================================
25
-
26
- /** Fully resolved sharing configuration. */
27
- export interface ResolvedSharingConfig {
28
- enabled: boolean;
29
- publicLink?: string;
30
- password?: string;
31
- allowedDomains: string[];
32
- expiresAt?: string;
33
- allowAnonymous: boolean;
34
- }
35
-
36
- /** Fully resolved embed configuration. */
37
- export interface ResolvedEmbedConfig {
38
- enabled: boolean;
39
- allowedOrigins: string[];
40
- width: string;
41
- height: string;
42
- showHeader: boolean;
43
- showNavigation: boolean;
44
- responsive: boolean;
45
- }
46
-
47
- /** Validation result for sharing configuration. */
48
- export interface SharingValidationResult {
49
- valid: boolean;
50
- errors: string[];
51
- }
52
-
53
- // ============================================================================
54
- // Sharing Config Resolution
55
- // ============================================================================
56
-
57
- /**
58
- * Resolve a sharing configuration by applying spec defaults.
59
- *
60
- * @param config - Partial SharingConfig from the spec
61
- * @returns Fully resolved sharing configuration
62
- */
63
- export function resolveSharingConfig(config: Partial<SharingConfig>): ResolvedSharingConfig {
64
- return {
65
- enabled: config.enabled ?? false,
66
- publicLink: config.publicLink,
67
- password: config.password,
68
- allowedDomains: config.allowedDomains ?? [],
69
- expiresAt: config.expiresAt,
70
- allowAnonymous: config.allowAnonymous ?? false,
71
- };
72
- }
73
-
74
- // ============================================================================
75
- // Embed Config Resolution
76
- // ============================================================================
77
-
78
- /**
79
- * Resolve an embed configuration by applying spec defaults.
80
- *
81
- * @param config - Partial EmbedConfig from the spec
82
- * @returns Fully resolved embed configuration
83
- */
84
- export function resolveEmbedConfig(config: Partial<EmbedConfig>): ResolvedEmbedConfig {
85
- return {
86
- enabled: config.enabled ?? false,
87
- allowedOrigins: config.allowedOrigins ?? [],
88
- width: config.width ?? '100%',
89
- height: config.height ?? '600px',
90
- showHeader: config.showHeader ?? true,
91
- showNavigation: config.showNavigation ?? false,
92
- responsive: config.responsive ?? true,
93
- };
94
- }
95
-
96
- // ============================================================================
97
- // Embed Code Generation
98
- // ============================================================================
99
-
100
- /**
101
- * Generate an HTML iframe embed snippet from an EmbedConfig and URL.
102
- *
103
- * @param config - EmbedConfig from the spec
104
- * @param url - The URL to embed
105
- * @returns HTML string containing an iframe element
106
- */
107
- export function generateEmbedCode(config: EmbedConfig, url: string): string {
108
- const resolved = resolveEmbedConfig(config);
109
- const sanitizedUrl = escapeHtmlAttr(url);
110
- const title = 'Embedded content';
111
-
112
- const parts = [
113
- `<iframe`,
114
- ` src="${sanitizedUrl}"`,
115
- ` width="${escapeHtmlAttr(resolved.width)}"`,
116
- ` height="${escapeHtmlAttr(resolved.height)}"`,
117
- ` title="${title}"`,
118
- ` frameborder="0"`,
119
- ` allowfullscreen`,
120
- ];
121
-
122
- if (resolved.responsive) {
123
- parts.push(` style="max-width: 100%; border: none;"`);
124
- } else {
125
- parts.push(` style="border: none;"`);
126
- }
127
-
128
- parts.push(`></iframe>`);
129
-
130
- return parts.join('\n');
131
- }
132
-
133
- /**
134
- * Escape a string for safe use in an HTML attribute value.
135
- */
136
- function escapeHtmlAttr(value: string): string {
137
- return value
138
- .replace(/&/g, '&amp;')
139
- .replace(/"/g, '&quot;')
140
- .replace(/</g, '&lt;')
141
- .replace(/>/g, '&gt;');
142
- }
143
-
144
- // ============================================================================
145
- // Sharing Config Validation
146
- // ============================================================================
147
-
148
- /**
149
- * Validate a sharing configuration and return any errors.
150
- *
151
- * @param config - SharingConfig to validate
152
- * @returns Validation result with `valid` flag and error messages
153
- */
154
- export function validateSharingConfig(config: SharingConfig): SharingValidationResult {
155
- const errors: string[] = [];
156
-
157
- if (config.enabled && !config.publicLink) {
158
- errors.push('A public link is required when sharing is enabled.');
159
- }
160
-
161
- if (config.expiresAt) {
162
- const expiryDate = new Date(config.expiresAt);
163
- if (isNaN(expiryDate.getTime())) {
164
- errors.push('expiresAt must be a valid ISO 8601 date string.');
165
- }
166
- }
167
-
168
- if (config.allowedDomains) {
169
- for (const domain of config.allowedDomains) {
170
- if (!domain || domain.trim().length === 0) {
171
- errors.push('allowedDomains contains an empty or whitespace-only entry.');
172
- break;
173
- }
174
- }
175
- }
176
-
177
- if (config.password !== undefined && config.password.length === 0) {
178
- errors.push('Password must not be an empty string when provided.');
179
- }
180
-
181
- return {
182
- valid: errors.length === 0,
183
- errors,
184
- };
185
- }
@@ -1,13 +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 * from './DndProtocol.js';
10
- export * from './KeyboardProtocol.js';
11
- export * from './NotificationProtocol.js';
12
- export * from './ResponsiveProtocol.js';
13
- export * from './SharingProtocol.js';
@@ -1,211 +0,0 @@
1
- /**
2
- * @object-ui/core - Query AST Builder Tests
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { QueryASTBuilder } from '../query-ast';
7
- import type { QuerySchema } from '@object-ui/types';
8
-
9
- describe('QueryASTBuilder', () => {
10
- const builder = new QueryASTBuilder();
11
-
12
- describe('Basic Query Building', () => {
13
- it('should build simple SELECT query', () => {
14
- const query: QuerySchema = {
15
- object: 'users',
16
- fields: ['id', 'name', 'email'],
17
- };
18
-
19
- const ast = builder.build(query);
20
-
21
- expect(ast.select.type).toBe('select');
22
- expect(ast.select.fields).toHaveLength(3);
23
- expect(ast.from.table).toBe('users');
24
- });
25
-
26
- it('should build SELECT * when no fields specified', () => {
27
- const query: QuerySchema = {
28
- object: 'users',
29
- };
30
-
31
- const ast = builder.build(query);
32
-
33
- expect(ast.select.fields).toHaveLength(1);
34
- expect(ast.select.fields[0]).toMatchObject({
35
- type: 'field',
36
- name: '*',
37
- });
38
- });
39
-
40
- it('should build query with WHERE clause', () => {
41
- const query: QuerySchema = {
42
- object: 'users',
43
- fields: ['id', 'name'],
44
- filter: {
45
- conditions: [
46
- {
47
- field: 'status',
48
- operator: 'equals',
49
- value: 'active',
50
- },
51
- ],
52
- },
53
- };
54
-
55
- const ast = builder.build(query);
56
-
57
- expect(ast.where).toBeDefined();
58
- expect(ast.where?.type).toBe('where');
59
- expect(ast.where?.condition.type).toBe('operator');
60
- });
61
-
62
- it('should build query with ORDER BY', () => {
63
- const query: QuerySchema = {
64
- object: 'users',
65
- fields: ['id', 'name'],
66
- sort: [
67
- { field: 'created_at', order: 'desc' },
68
- { field: 'name', order: 'asc' },
69
- ],
70
- };
71
-
72
- const ast = builder.build(query);
73
-
74
- expect(ast.order_by).toBeDefined();
75
- expect(ast.order_by?.fields).toHaveLength(2);
76
- expect(ast.order_by?.fields[0].direction).toBe('desc');
77
- });
78
-
79
- it('should build query with LIMIT and OFFSET', () => {
80
- const query: QuerySchema = {
81
- object: 'users',
82
- fields: ['id', 'name'],
83
- limit: 10,
84
- offset: 20,
85
- };
86
-
87
- const ast = builder.build(query);
88
-
89
- expect(ast.limit).toBeDefined();
90
- expect(ast.limit?.value).toBe(10);
91
- expect(ast.offset).toBeDefined();
92
- expect(ast.offset?.value).toBe(20);
93
- });
94
- });
95
-
96
- describe('Advanced Query Building', () => {
97
- it('should build query with JOIN', () => {
98
- const query: QuerySchema = {
99
- object: 'users',
100
- fields: ['id', 'name', 'orders.total'],
101
- joins: [
102
- {
103
- type: 'left',
104
- object: 'orders',
105
- on: {
106
- local_field: 'id',
107
- foreign_field: 'user_id',
108
- },
109
- },
110
- ],
111
- };
112
-
113
- const ast = builder.build(query);
114
-
115
- expect(ast.joins).toBeDefined();
116
- expect(ast.joins).toHaveLength(1);
117
- expect(ast.joins?.[0].join_type).toBe('left');
118
- expect(ast.joins?.[0].table).toBe('orders');
119
- });
120
-
121
- it('should build query with aggregations', () => {
122
- const query: QuerySchema = {
123
- object: 'orders',
124
- aggregations: [
125
- {
126
- function: 'count',
127
- alias: 'total_count',
128
- },
129
- {
130
- function: 'sum',
131
- field: 'amount',
132
- alias: 'total_amount',
133
- },
134
- ],
135
- };
136
-
137
- const ast = builder.build(query);
138
-
139
- expect(ast.select.fields).toHaveLength(2);
140
- expect(ast.select.fields[0]).toMatchObject({
141
- type: 'aggregate',
142
- function: 'count',
143
- alias: 'total_count',
144
- });
145
- });
146
-
147
- it('should build query with GROUP BY', () => {
148
- const query: QuerySchema = {
149
- object: 'orders',
150
- fields: ['user_id'],
151
- group_by: ['user_id'],
152
- aggregations: [
153
- {
154
- function: 'count',
155
- alias: 'order_count',
156
- },
157
- ],
158
- };
159
-
160
- const ast = builder.build(query);
161
-
162
- expect(ast.group_by).toBeDefined();
163
- expect(ast.group_by?.fields).toHaveLength(1);
164
- expect(ast.group_by?.fields[0]).toMatchObject({
165
- type: 'field',
166
- name: 'user_id',
167
- });
168
- });
169
- });
170
-
171
- describe('Complex Filters', () => {
172
- it('should build query with nested AND/OR filters', () => {
173
- const query: QuerySchema = {
174
- object: 'users',
175
- filter: {
176
- operator: 'and',
177
- conditions: [
178
- {
179
- field: 'status',
180
- operator: 'equals',
181
- value: 'active',
182
- },
183
- ],
184
- groups: [
185
- {
186
- operator: 'or',
187
- conditions: [
188
- {
189
- field: 'role',
190
- operator: 'equals',
191
- value: 'admin',
192
- },
193
- {
194
- field: 'role',
195
- operator: 'equals',
196
- value: 'moderator',
197
- },
198
- ],
199
- },
200
- ],
201
- },
202
- };
203
-
204
- const ast = builder.build(query);
205
-
206
- expect(ast.where).toBeDefined();
207
- expect(ast.where?.condition.operator).toBe('and');
208
- expect(ast.where?.condition.operands.length).toBeGreaterThan(0);
209
- });
210
- });
211
- });