@object-ui/types 3.0.3 → 3.1.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 (85) hide show
  1. package/dist/app.d.ts +217 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +85 -1
  4. package/dist/complex.d.ts +118 -0
  5. package/dist/complex.d.ts.map +1 -1
  6. package/dist/data-display.d.ts +105 -1
  7. package/dist/data-display.d.ts.map +1 -1
  8. package/dist/data.d.ts +45 -0
  9. package/dist/data.d.ts.map +1 -1
  10. package/dist/designer.d.ts +197 -35
  11. package/dist/designer.d.ts.map +1 -1
  12. package/dist/designer.js +11 -1
  13. package/dist/index.d.ts +21 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/layout.d.ts +39 -2
  17. package/dist/layout.d.ts.map +1 -1
  18. package/dist/navigation.d.ts +27 -0
  19. package/dist/navigation.d.ts.map +1 -1
  20. package/dist/objectql.d.ts +641 -7
  21. package/dist/objectql.d.ts.map +1 -1
  22. package/dist/record-components.d.ts +160 -0
  23. package/dist/record-components.d.ts.map +1 -0
  24. package/dist/record-components.js +8 -0
  25. package/dist/reports.d.ts +37 -0
  26. package/dist/reports.d.ts.map +1 -1
  27. package/dist/theme.d.ts +5 -0
  28. package/dist/theme.d.ts.map +1 -1
  29. package/dist/views.d.ts +257 -3
  30. package/dist/views.d.ts.map +1 -1
  31. package/dist/workflow.d.ts +198 -0
  32. package/dist/workflow.d.ts.map +1 -1
  33. package/dist/zod/app.zod.d.ts +42 -2
  34. package/dist/zod/app.zod.d.ts.map +1 -1
  35. package/dist/zod/app.zod.js +61 -1
  36. package/dist/zod/complex.zod.d.ts +122 -0
  37. package/dist/zod/complex.zod.d.ts.map +1 -1
  38. package/dist/zod/complex.zod.js +57 -0
  39. package/dist/zod/data-display.zod.d.ts +4 -0
  40. package/dist/zod/data-display.zod.d.ts.map +1 -1
  41. package/dist/zod/data-display.zod.js +2 -0
  42. package/dist/zod/form.zod.d.ts +6 -6
  43. package/dist/zod/index.zod.d.ts +364 -41
  44. package/dist/zod/index.zod.d.ts.map +1 -1
  45. package/dist/zod/index.zod.js +2 -2
  46. package/dist/zod/layout.zod.d.ts +6 -6
  47. package/dist/zod/navigation.zod.d.ts +58 -12
  48. package/dist/zod/navigation.zod.d.ts.map +1 -1
  49. package/dist/zod/navigation.zod.js +21 -9
  50. package/dist/zod/objectql.zod.d.ts +515 -27
  51. package/dist/zod/objectql.zod.d.ts.map +1 -1
  52. package/dist/zod/objectql.zod.js +162 -0
  53. package/dist/zod/reports.zod.d.ts +38 -38
  54. package/dist/zod/views.zod.d.ts +161 -7
  55. package/dist/zod/views.zod.d.ts.map +1 -1
  56. package/dist/zod/views.zod.js +21 -2
  57. package/package.json +2 -2
  58. package/src/__tests__/app-creation-types.test.ts +177 -0
  59. package/src/__tests__/dashboard-config.test.ts +208 -0
  60. package/src/__tests__/examples-metadata-compliance.test.ts +264 -0
  61. package/src/__tests__/navigation-model.test.ts +406 -0
  62. package/src/__tests__/p1-spec-alignment.test.ts +660 -0
  63. package/src/__tests__/p2-spec-exports.test.ts +312 -0
  64. package/src/__tests__/phase2-schemas.test.ts +108 -0
  65. package/src/app.ts +377 -0
  66. package/src/complex.ts +120 -0
  67. package/src/data-display.ts +107 -0
  68. package/src/data.ts +49 -0
  69. package/src/designer.ts +219 -30
  70. package/src/index.ts +192 -3
  71. package/src/layout.ts +55 -2
  72. package/src/navigation.ts +20 -0
  73. package/src/objectql.ts +757 -8
  74. package/src/record-components.ts +188 -0
  75. package/src/reports.ts +43 -0
  76. package/src/theme.ts +6 -0
  77. package/src/views.ts +275 -3
  78. package/src/workflow.ts +226 -0
  79. package/src/zod/app.zod.ts +74 -1
  80. package/src/zod/complex.zod.ts +59 -0
  81. package/src/zod/data-display.zod.ts +2 -0
  82. package/src/zod/index.zod.ts +5 -0
  83. package/src/zod/navigation.zod.ts +22 -10
  84. package/src/zod/objectql.zod.ts +167 -0
  85. package/src/zod/views.zod.ts +21 -2
@@ -0,0 +1,208 @@
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
+ * Tests for DashboardConfig types and Zod validation schemas.
11
+ */
12
+ import { describe, it, expect } from 'vitest';
13
+ import type {
14
+ DashboardConfig,
15
+ DashboardWidgetConfig,
16
+ DashboardColorVariant,
17
+ DashboardWidgetType,
18
+ } from '../designer';
19
+ import {
20
+ DashboardConfigSchema,
21
+ DashboardWidgetConfigSchema,
22
+ } from '../zod/index.zod';
23
+
24
+ describe('DashboardConfig TypeScript Types', () => {
25
+ it('should accept a minimal DashboardConfig', () => {
26
+ const config: DashboardConfig = {};
27
+ expect(config).toBeDefined();
28
+ });
29
+
30
+ it('should accept a full DashboardConfig', () => {
31
+ const config: DashboardConfig = {
32
+ id: 'dash-1',
33
+ title: 'Sales Dashboard',
34
+ description: 'Overview of sales pipeline',
35
+ columns: 12,
36
+ gap: 16,
37
+ refreshInterval: 30,
38
+ widgets: [
39
+ {
40
+ id: 'w1',
41
+ title: 'Total Revenue',
42
+ type: 'metric',
43
+ object: 'deals',
44
+ valueField: 'amount',
45
+ aggregate: 'sum',
46
+ colorVariant: 'success',
47
+ layout: { x: 0, y: 0, w: 3, h: 2 },
48
+ },
49
+ ],
50
+ globalFilters: [['status', '=', 'active']],
51
+ dateRange: {
52
+ enabled: true,
53
+ field: 'created_at',
54
+ presets: ['today', 'this_week', 'this_month'],
55
+ },
56
+ userFilters: [
57
+ { field: 'region', label: 'Region', type: 'select' },
58
+ ],
59
+ showHeader: true,
60
+ showFilters: true,
61
+ showDateRange: true,
62
+ headerActions: [
63
+ { label: 'Export', action: 'export', icon: 'Download', variant: 'outline' },
64
+ ],
65
+ aria: { label: 'Sales dashboard', description: 'Interactive sales overview' },
66
+ };
67
+ expect(config.widgets).toHaveLength(1);
68
+ expect(config.widgets![0].type).toBe('metric');
69
+ });
70
+
71
+ it('should allow DashboardWidgetType values', () => {
72
+ const types: DashboardWidgetType[] = [
73
+ 'metric', 'bar', 'line', 'pie', 'donut', 'area', 'scatter', 'table', 'list', 'custom',
74
+ ];
75
+ expect(types).toHaveLength(10);
76
+ });
77
+
78
+ it('should allow DashboardColorVariant values', () => {
79
+ const colors: DashboardColorVariant[] = [
80
+ 'default', 'blue', 'teal', 'orange', 'purple', 'success', 'warning', 'danger',
81
+ ];
82
+ expect(colors).toHaveLength(8);
83
+ });
84
+
85
+ it('should allow DashboardWidgetConfig with all properties', () => {
86
+ const widget: DashboardWidgetConfig = {
87
+ id: 'w1',
88
+ title: 'Revenue Chart',
89
+ description: 'Monthly revenue',
90
+ type: 'bar',
91
+ object: 'deals',
92
+ filter: [['status', '=', 'closed']],
93
+ categoryField: 'month',
94
+ valueField: 'amount',
95
+ aggregate: 'sum',
96
+ chartConfig: { stacked: true },
97
+ colorVariant: 'blue',
98
+ layout: { x: 0, y: 0, w: 6, h: 4 },
99
+ actionUrl: '/deals',
100
+ };
101
+ expect(widget.id).toBe('w1');
102
+ });
103
+ });
104
+
105
+ describe('DashboardConfig Zod Validation', () => {
106
+ it('should validate a minimal DashboardConfig', () => {
107
+ const result = DashboardConfigSchema.safeParse({});
108
+ expect(result.success).toBe(true);
109
+ });
110
+
111
+ it('should validate a complete DashboardConfig', () => {
112
+ const result = DashboardConfigSchema.safeParse({
113
+ id: 'dash-1',
114
+ title: 'Sales Dashboard',
115
+ description: 'Overview of sales pipeline',
116
+ columns: 12,
117
+ gap: 16,
118
+ refreshInterval: 30,
119
+ widgets: [
120
+ {
121
+ id: 'w1',
122
+ title: 'Total Revenue',
123
+ type: 'metric',
124
+ colorVariant: 'success',
125
+ layout: { x: 0, y: 0, w: 3, h: 2 },
126
+ },
127
+ ],
128
+ showHeader: true,
129
+ showFilters: true,
130
+ });
131
+ expect(result.success).toBe(true);
132
+ });
133
+
134
+ it('should reject invalid column count', () => {
135
+ const result = DashboardConfigSchema.safeParse({ columns: 0 });
136
+ expect(result.success).toBe(false);
137
+ });
138
+
139
+ it('should reject negative gap', () => {
140
+ const result = DashboardConfigSchema.safeParse({ gap: -1 });
141
+ expect(result.success).toBe(false);
142
+ });
143
+
144
+ it('should validate DashboardWidgetConfigSchema', () => {
145
+ const result = DashboardWidgetConfigSchema.safeParse({
146
+ id: 'w1',
147
+ title: 'Revenue',
148
+ type: 'bar',
149
+ object: 'deals',
150
+ colorVariant: 'blue',
151
+ layout: { x: 0, y: 0, w: 6, h: 4 },
152
+ });
153
+ expect(result.success).toBe(true);
154
+ });
155
+
156
+ it('should reject DashboardWidgetConfigSchema without id', () => {
157
+ const result = DashboardWidgetConfigSchema.safeParse({
158
+ title: 'Revenue',
159
+ });
160
+ expect(result.success).toBe(false);
161
+ });
162
+
163
+ it('should reject invalid colorVariant', () => {
164
+ const result = DashboardWidgetConfigSchema.safeParse({
165
+ id: 'w1',
166
+ colorVariant: 'invalid-color',
167
+ });
168
+ expect(result.success).toBe(false);
169
+ });
170
+
171
+ it('should validate dateRange configuration', () => {
172
+ const result = DashboardConfigSchema.safeParse({
173
+ dateRange: {
174
+ enabled: true,
175
+ field: 'created_at',
176
+ presets: ['today', 'this_week'],
177
+ },
178
+ });
179
+ expect(result.success).toBe(true);
180
+ });
181
+
182
+ it('should validate userFilters configuration', () => {
183
+ const result = DashboardConfigSchema.safeParse({
184
+ userFilters: [
185
+ { field: 'region', label: 'Region', type: 'select' },
186
+ { field: 'status' },
187
+ ],
188
+ });
189
+ expect(result.success).toBe(true);
190
+ });
191
+
192
+ it('should validate headerActions configuration', () => {
193
+ const result = DashboardConfigSchema.safeParse({
194
+ headerActions: [
195
+ { label: 'Export', action: 'export', icon: 'Download', variant: 'outline' },
196
+ { label: 'Refresh' },
197
+ ],
198
+ });
199
+ expect(result.success).toBe(true);
200
+ });
201
+
202
+ it('should validate aria accessibility attributes', () => {
203
+ const result = DashboardConfigSchema.safeParse({
204
+ aria: { label: 'Sales dashboard', description: 'Interactive overview' },
205
+ });
206
+ expect(result.success).toBe(true);
207
+ });
208
+ });
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Examples Metadata Spec Compliance Tests
3
+ *
4
+ * Validates that all defineStack configurations in examples/ conform to the
5
+ * ObjectStack spec protocol. Ensures required fields are present and correctly
6
+ * typed across objects, views, dashboards, pages, apps, and manifests.
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { ObjectStackDefinitionSchema } from '@objectstack/spec';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /** Validate a defineStack result against the Zod schema */
16
+ function expectValidStack(config: unknown, name: string) {
17
+ const result = ObjectStackDefinitionSchema.safeParse(config);
18
+ if (!result.success) {
19
+ const issues = result.error.issues.map(
20
+ (i) => ` [${i.path.join('.')}] ${i.message}`
21
+ );
22
+ throw new Error(
23
+ `${name} failed spec validation:\n${issues.join('\n')}`
24
+ );
25
+ }
26
+ expect(result.success).toBe(true);
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Structural helpers — verify required keys exist on metadata objects
31
+ // ---------------------------------------------------------------------------
32
+
33
+ function assertDashboards(dashboards: any[], label: string) {
34
+ for (const d of dashboards) {
35
+ expect(d).toHaveProperty('name');
36
+ expect(d).toHaveProperty('label');
37
+ expect(d).toHaveProperty('description');
38
+ expect(Array.isArray(d.widgets)).toBe(true);
39
+
40
+ for (const w of d.widgets) {
41
+ // defineStack normalizes widgets — id may be stripped, but title/type/layout should remain
42
+ expect(w).toHaveProperty('title');
43
+ expect(w).toHaveProperty('type');
44
+ expect(w).toHaveProperty('layout');
45
+ }
46
+ }
47
+ }
48
+
49
+ function assertManifest(manifest: any) {
50
+ expect(manifest).toHaveProperty('id');
51
+ expect(manifest).toHaveProperty('version');
52
+ expect(manifest).toHaveProperty('type', 'app');
53
+ expect(manifest).toHaveProperty('name');
54
+ expect(manifest).toHaveProperty('description');
55
+ }
56
+
57
+ function assertNavigation(items: any[]) {
58
+ for (const item of items) {
59
+ expect(item).toHaveProperty('id');
60
+ expect(item).toHaveProperty('type');
61
+ expect(item).toHaveProperty('label');
62
+ if (item.children) {
63
+ assertNavigation(item.children);
64
+ }
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Tests
70
+ // ---------------------------------------------------------------------------
71
+
72
+ describe('Example: todo', () => {
73
+ let config: any;
74
+
75
+ it('should import cleanly', async () => {
76
+ const mod = await import('../../../../examples/todo/objectstack.config');
77
+ config = mod.default;
78
+ expect(config).toBeDefined();
79
+ });
80
+
81
+ it('should pass ObjectStackDefinitionSchema validation', () => {
82
+ expectValidStack(config, 'todo');
83
+ });
84
+
85
+ it('should have explicit views', () => {
86
+ expect(Array.isArray(config.views)).toBe(true);
87
+ expect(config.views.length).toBeGreaterThan(0);
88
+ });
89
+
90
+ it('objects should have name, label, and fields', () => {
91
+ for (const obj of config.objects) {
92
+ expect(obj).toHaveProperty('name');
93
+ expect(obj).toHaveProperty('label');
94
+ expect(obj).toHaveProperty('fields');
95
+ }
96
+ });
97
+
98
+ it('dashboards should have name, label, description, and valid widgets', () => {
99
+ assertDashboards(config.dashboards, 'todo');
100
+ });
101
+
102
+ it('pages should have name, label, type, and regions', () => {
103
+ for (const page of config.pages ?? []) {
104
+ expect(page).toHaveProperty('name');
105
+ expect(page).toHaveProperty('label');
106
+ expect(page).toHaveProperty('type');
107
+ expect(page).toHaveProperty('regions');
108
+ }
109
+ });
110
+
111
+ it('manifest should have required fields', () => {
112
+ assertManifest(config.manifest);
113
+ });
114
+
115
+ it('apps navigation items should have id/type/label', () => {
116
+ for (const app of config.apps) {
117
+ assertNavigation(app.navigation ?? []);
118
+ }
119
+ });
120
+ });
121
+
122
+ describe('Example: kitchen-sink', () => {
123
+ let config: any;
124
+
125
+ it('should import cleanly', async () => {
126
+ const mod = await import(
127
+ '../../../../examples/kitchen-sink/objectstack.config'
128
+ );
129
+ config = mod.default;
130
+ expect(config).toBeDefined();
131
+ });
132
+
133
+ it('should pass ObjectStackDefinitionSchema validation', () => {
134
+ expectValidStack(config, 'kitchen-sink');
135
+ });
136
+
137
+ it('objects should have name, label, and fields', () => {
138
+ for (const obj of config.objects) {
139
+ expect(obj).toHaveProperty('name');
140
+ expect(obj).toHaveProperty('label');
141
+ expect(obj).toHaveProperty('fields');
142
+ }
143
+ });
144
+
145
+ it('should have explicit views', () => {
146
+ expect(Array.isArray(config.views)).toBe(true);
147
+ expect(config.views.length).toBeGreaterThan(0);
148
+ });
149
+
150
+ it('dashboards should have name, label, description, and valid widgets', () => {
151
+ assertDashboards(config.dashboards, 'kitchen-sink');
152
+ });
153
+
154
+ it('pages should have name, label, type, and regions', () => {
155
+ for (const page of config.pages ?? []) {
156
+ expect(page).toHaveProperty('name');
157
+ expect(page).toHaveProperty('label');
158
+ expect(page).toHaveProperty('type');
159
+ expect(page).toHaveProperty('regions');
160
+ }
161
+ });
162
+
163
+ it('manifest should have required fields', () => {
164
+ assertManifest(config.manifest);
165
+ });
166
+
167
+ it('apps navigation items should have id/type/label', () => {
168
+ for (const app of config.apps) {
169
+ assertNavigation(app.navigation ?? []);
170
+ }
171
+ });
172
+ });
173
+
174
+ describe('Example: crm', () => {
175
+ let config: any;
176
+
177
+ it('should import cleanly', async () => {
178
+ const mod = await import('../../../../examples/crm/objectstack.config');
179
+ config = mod.default;
180
+ expect(config).toBeDefined();
181
+ });
182
+
183
+ it('should pass ObjectStackDefinitionSchema validation', () => {
184
+ expectValidStack(config, 'crm');
185
+ });
186
+
187
+ it('objects should have name, label, and fields', () => {
188
+ for (const obj of config.objects) {
189
+ expect(obj).toHaveProperty('name');
190
+ expect(obj).toHaveProperty('label');
191
+ expect(obj).toHaveProperty('fields');
192
+ }
193
+ });
194
+
195
+ it('should have explicit views', () => {
196
+ expect(Array.isArray(config.views)).toBe(true);
197
+ expect(config.views.length).toBeGreaterThan(0);
198
+ });
199
+
200
+ it('dashboards should have name, label, description, and valid widgets', () => {
201
+ assertDashboards(config.dashboards, 'crm');
202
+ });
203
+
204
+ it('pages should have name, label, type, and regions', () => {
205
+ for (const page of config.pages ?? []) {
206
+ expect(page).toHaveProperty('name');
207
+ expect(page).toHaveProperty('label');
208
+ expect(page).toHaveProperty('type');
209
+ expect(page).toHaveProperty('regions');
210
+ }
211
+ });
212
+
213
+ it('manifest should have required fields', () => {
214
+ assertManifest(config.manifest);
215
+ });
216
+
217
+ it('apps navigation items should have id/type/label', () => {
218
+ for (const app of config.apps) {
219
+ assertNavigation(app.navigation ?? []);
220
+ }
221
+ });
222
+ });
223
+
224
+ describe('Example: msw-todo', () => {
225
+ let config: any;
226
+
227
+ it('should import cleanly', async () => {
228
+ const mod = await import(
229
+ '../../../../examples/msw-todo/objectstack.config'
230
+ );
231
+ config = mod.default;
232
+ expect(config).toBeDefined();
233
+ });
234
+
235
+ it('should pass ObjectStackDefinitionSchema validation', () => {
236
+ expectValidStack(config, 'msw-todo');
237
+ });
238
+
239
+ it('should have explicit views', () => {
240
+ expect(Array.isArray(config.views)).toBe(true);
241
+ expect(config.views.length).toBeGreaterThan(0);
242
+ });
243
+
244
+ it('objects should use ObjectSchema.create (have normalized defaults)', () => {
245
+ for (const obj of config.objects) {
246
+ // ObjectSchema.create populates normalized defaults like active, isSystem, datasource
247
+ expect(obj).toHaveProperty('name');
248
+ expect(obj).toHaveProperty('label');
249
+ expect(obj).toHaveProperty('fields');
250
+ expect(obj).toHaveProperty('active', true);
251
+ expect(obj).toHaveProperty('datasource', 'default');
252
+ }
253
+ });
254
+
255
+ it('manifest should have required fields', () => {
256
+ assertManifest(config.manifest);
257
+ });
258
+
259
+ it('apps navigation items should have id/type/label', () => {
260
+ for (const app of config.apps) {
261
+ assertNavigation(app.navigation ?? []);
262
+ }
263
+ });
264
+ });