Skip to content

Commit a0a3d1a

Browse files
Copilothotlong
andcommitted
refactor(ui): reduce overlap — extract SortItemSchema, extend AppBrandingSchema
- Add shared SortItemSchema to enums.zod.ts (reuses SortDirectionEnum) - ElementDataSourceSchema now uses shared SortItemSchema instead of inline sort - RecordReviewConfigSchema now uses shared SortItemSchema instead of inline sort - InterfaceBrandingSchema now extends AppBrandingSchema (adds coverImage only) - Add SortItemSchema tests to enums.test.ts Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 4406e9b commit a0a3d1a

4 files changed

Lines changed: 35 additions & 11 deletions

File tree

packages/spec/src/shared/enums.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
22
import {
33
AggregationFunctionEnum,
44
SortDirectionEnum,
5+
SortItemSchema,
56
MutationEventEnum,
67
IsolationLevelEnum,
78
CacheStrategyEnum,
@@ -46,6 +47,26 @@ describe('SortDirectionEnum', () => {
4647
});
4748
});
4849

50+
describe('SortItemSchema', () => {
51+
it('should accept valid sort item', () => {
52+
const result = SortItemSchema.parse({ field: 'created_at', order: 'desc' });
53+
expect(result.field).toBe('created_at');
54+
expect(result.order).toBe('desc');
55+
});
56+
57+
it('should reject without field', () => {
58+
expect(() => SortItemSchema.parse({ order: 'asc' })).toThrow();
59+
});
60+
61+
it('should reject without order', () => {
62+
expect(() => SortItemSchema.parse({ field: 'name' })).toThrow();
63+
});
64+
65+
it('should reject invalid order direction', () => {
66+
expect(() => SortItemSchema.parse({ field: 'name', order: 'up' })).toThrow();
67+
});
68+
});
69+
4970
describe('MutationEventEnum', () => {
5071
it('should accept all mutation events', () => {
5172
const valid = ['insert', 'update', 'delete', 'upsert'];

packages/spec/src/shared/enums.zod.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export const SortDirectionEnum = z.enum(['asc', 'desc'])
1818
.describe('Sort order direction');
1919
export type SortDirection = z.infer<typeof SortDirectionEnum>;
2020

21+
/** Reusable sort item — field + direction pair used across views, data sources, filters */
22+
export const SortItemSchema = z.object({
23+
field: z.string().describe('Field name to sort by'),
24+
order: SortDirectionEnum.describe('Sort direction'),
25+
}).describe('Sort field and direction pair');
26+
export type SortItem = z.infer<typeof SortItemSchema>;
27+
2128
/** CRUD mutation events used across hook, validation, object CDC */
2229
export const MutationEventEnum = z.enum([
2330
'insert', 'update', 'delete', 'upsert',

packages/spec/src/ui/interface.zod.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import { z } from 'zod';
44
import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
5+
import { SortItemSchema } from '../shared/enums.zod';
56
import { I18nLabelSchema, AriaPropsSchema } from './i18n.zod';
67
import { PageRegionSchema, PageVariableSchema } from './page.zod';
8+
import { AppBrandingSchema } from './app.zod';
79

810
/**
911
* Interface Page Type Schema
@@ -32,10 +34,7 @@ export const InterfacePageTypeSchema = z.enum([
3234
export const RecordReviewConfigSchema = z.object({
3335
object: z.string().describe('Target object for review'),
3436
filter: z.any().optional().describe('Filter criteria for review queue'),
35-
sort: z.array(z.object({
36-
field: z.string().describe('Field name to sort by'),
37-
order: z.enum(['asc', 'desc']).describe('Sort direction'),
38-
})).optional().describe('Sort order for review queue'),
37+
sort: z.array(SortItemSchema).optional().describe('Sort order for review queue'),
3938
displayFields: z.array(z.string()).optional()
4039
.describe('Fields to display on the review page'),
4140
actions: z.array(z.object({
@@ -92,10 +91,9 @@ export const InterfacePageSchema = z.object({
9291
/**
9392
* Interface Branding Schema
9493
* Visual branding overrides for an interface.
94+
* Extends AppBrandingSchema with interface-specific properties (coverImage).
9595
*/
96-
export const InterfaceBrandingSchema = z.object({
97-
primaryColor: z.string().optional().describe('Primary theme color hex code'),
98-
logo: z.string().optional().describe('Custom logo URL'),
96+
export const InterfaceBrandingSchema = AppBrandingSchema.extend({
9997
coverImage: z.string().optional().describe('Cover image URL for the interface landing'),
10098
});
10199

packages/spec/src/ui/page.zod.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { z } from 'zod';
44
import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
5+
import { SortItemSchema } from '../shared/enums.zod';
56
import { I18nLabelSchema, AriaPropsSchema } from './i18n.zod';
67
import { ResponsiveConfigSchema } from './responsive.zod';
78

@@ -42,10 +43,7 @@ export const ElementDataSourceSchema = z.object({
4243
object: z.string().describe('Object to query'),
4344
view: z.string().optional().describe('Named view to apply'),
4445
filter: z.any().optional().describe('Additional filter criteria'),
45-
sort: z.array(z.object({
46-
field: z.string().describe('Field name to sort by'),
47-
order: z.enum(['asc', 'desc']).describe('Sort direction'),
48-
})).optional().describe('Sort order'),
46+
sort: z.array(SortItemSchema).optional().describe('Sort order'),
4947
limit: z.number().int().positive().optional().describe('Max records to display'),
5048
});
5149

0 commit comments

Comments
 (0)