Skip to content

Commit 1c08b5b

Browse files
committed
Refactor and expand Zod schemas for data and UI models
Enhanced and reorganized field, object, action, dashboard, and view schemas for greater flexibility and clarity. Added new system translation schema for i18n support. Improved type safety, expanded configuration options (e.g., for Kanban, Calendar, Gantt views), and standardized naming conventions. Updated exports to include new translation schema.
1 parent 7f0b307 commit 1c08b5b

File tree

7 files changed

+248
-145
lines changed

7 files changed

+248
-145
lines changed

packages/spec/src/data/field.zod.ts

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,93 @@ import { z } from 'zod';
22

33
/**
44
* Field Type Enum
5-
* Defines the available data types for fields.
65
*/
76
export const FieldType = z.enum([
8-
// Basic
9-
'text', 'textarea', 'markdown', 'html', 'password', 'email',
10-
// Number
11-
'number', 'currency', 'percent',
12-
// Date
7+
// Core Text
8+
'text', 'textarea', 'email', 'url', 'phone', 'password',
9+
// Rich Content
10+
'markdown', 'html',
11+
// Numbers
12+
'number', 'currency', 'percent',
13+
// Date & Time
1314
'date', 'datetime', 'time',
1415
// Logic
1516
'boolean',
16-
// Choice
17-
'select', 'multiselect',
17+
// Selection
18+
'select', 'multiselect', // Static options
1819
// Relational
19-
'lookup', 'master_detail',
20-
// Calculated
21-
'formula', 'summary',
20+
'lookup', 'master_detail', // Dynamic reference to other objects
2221
// Media
2322
'image', 'file', 'avatar',
24-
// System
25-
'id', 'owner', 'created_at', 'updated_at'
23+
// Calculated / System
24+
'formula', 'summary', 'autonumber'
2625
]);
2726

2827
export type FieldType = z.infer<typeof FieldType>;
2928

3029
/**
31-
* Schema for select options (for select/multiselect types).
30+
* Select Option Schema
3231
*/
3332
export const SelectOptionSchema = z.object({
34-
label: z.string().describe('Display label for the option'),
35-
value: z.string().describe('Stored value for the option'),
33+
label: z.string().describe('Display label'),
34+
value: z.string().describe('Stored value'),
35+
color: z.string().optional().describe('Color code for badges/charts'),
36+
default: z.boolean().optional().describe('Is default option'),
3637
});
3738

3839
/**
39-
* Base Schema for all fields.
40-
* Contains properties common to all field types.
40+
* Field Schema - Best Practice Enterprise Pattern
4141
*/
4242
export const FieldSchema = z.object({
43-
/** Machine name of the field */
44-
name: z.string().describe('Machine name of the field'),
45-
46-
/** Human readable label */
47-
label: z.string().optional().describe('Human readable label'),
48-
49-
/** Field Data Type */
43+
/** Identity */
44+
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Machine name (snake_case)'),
45+
label: z.string().describe('Human readable label'),
5046
type: FieldType.describe('Field Data Type'),
47+
description: z.string().optional().describe('Tooltip/Help text'),
48+
49+
/** Database Constraints */
50+
required: z.boolean().default(false).describe('Is required'),
51+
multiple: z.boolean().default(false).describe('Allow multiple values (Stores as Array/JSON). Applicable for select, lookup, file, image.'),
52+
unique: z.boolean().default(false).describe('Is unique constraint'),
53+
defaultValue: z.any().optional().describe('Default value'),
5154

52-
/** Whether the field is required */
53-
required: z.boolean().optional().describe('Whether the field is required'),
54-
55-
/** Default value for the field */
56-
defaultValue: z.any().optional().describe('Default value for the field'),
57-
58-
/** Help text / tooltip description */
59-
description: z.string().optional().describe('Help text / tooltip description'),
60-
61-
/** Whether the field is hidden from UI */
62-
hidden: z.boolean().optional().describe('Whether the field is hidden from UI'),
55+
/** Text/String Constraints */
56+
maxLength: z.number().optional().describe('Max character length'),
57+
minLength: z.number().optional().describe('Min character length'),
6358

64-
/** Whether the field is read-only */
65-
readonly: z.boolean().optional().describe('Whether the field is read-only'),
59+
/** Number Constraints */
60+
precision: z.number().optional().describe('Total digits'),
61+
scale: z.number().optional().describe('Decimal places'),
62+
min: z.number().optional().describe('Minimum value'),
63+
max: z.number().optional().describe('Maximum value'),
64+
65+
/** Selection Options */
66+
options: z.array(SelectOptionSchema).optional().describe('Static options for select/multiselect'),
6667

67-
/** Options for select/multiselect types */
68-
options: z.array(SelectOptionSchema).optional().describe('Options for select/multiselect types'),
68+
/** Relationship Config */
69+
reference: z.string().optional().describe('Target Object Name'),
70+
referenceFilters: z.array(z.string()).optional().describe('Filters applied to lookup dialogs (e.g. "active = true")'),
71+
writeRequiresMasterRead: z.boolean().optional().describe('If true, user needs read access to master record to edit this field'),
72+
deleteBehavior: z.enum(['set_null', 'cascade', 'restrict']).optional().default('set_null').describe('What happens if referenced record is deleted'),
6973

70-
/** Target object name for lookup/master_detail types */
71-
reference: z.string().optional().describe('Target object name for lookup/master_detail types'),
74+
/** Calculation */
75+
expression: z.string().optional().describe('Formula expression'), // Changed from formula to expression to match common usage, but keeping one consistent is key. Let's use expression as generic.
76+
formula: z.string().optional().describe('Deprecated: Use expression'), // Backwards compat or just keep.
77+
summaryOperations: z.object({
78+
object: z.string(),
79+
field: z.string(),
80+
function: z.enum(['count', 'sum', 'min', 'max', 'avg'])
81+
}).optional().describe('Roll-up summary definition'),
7282

73-
/** Expression for formula/summary types */
74-
expression: z.string().optional().describe('Expression for formula/summary types'),
83+
/** Security & Visibility */
84+
hidden: z.boolean().default(false).describe('Hidden from default UI'),
85+
readonly: z.boolean().default(false).describe('Read-only in UI'),
86+
encryption: z.boolean().default(false).describe('Encrypt at rest'),
87+
88+
/** Indexing */
89+
index: z.boolean().default(false).describe('Create standard database index'),
90+
externalId: z.boolean().default(false).describe('Is external ID for upsert operations'),
7591
});
7692

77-
/**
78-
* TypeScript type inferred from FieldSchema.
79-
*/
8093
export type Field = z.infer<typeof FieldSchema>;
94+
export type SelectOption = z.infer<typeof SelectOptionSchema>;
Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
import { z } from 'zod';
22
import { FieldSchema } from './field.zod';
33

4+
/**
5+
* Capability Flags
6+
* Defines what system features are enabled for this object.
7+
*/
8+
export const ObjectCapabilities = z.object({
9+
/** Enable history tracking (Audit Trail) */
10+
trackHistory: z.boolean().default(false),
11+
12+
/** Enable global search indexing */
13+
searchable: z.boolean().default(true),
14+
15+
/** Enable REST/GraphQL API access */
16+
apiEnabled: z.boolean().default(true),
17+
18+
/** Enable attachments/files */
19+
files: z.boolean().default(false),
20+
21+
/** Enable discussions/chatter */
22+
feedEnabled: z.boolean().default(false),
23+
24+
/** Enable Recycle Bin mechanics */
25+
trash: z.boolean().default(true),
26+
});
27+
428
/**
529
* Schema for database indexes.
630
*/
@@ -11,36 +35,32 @@ export const IndexSchema = z.object({
1135
});
1236

1337
/**
14-
* Schema for Objects (Models/Tables).
38+
* Object Schema - Enterprise Data Model
1539
*/
1640
export const ObjectSchema = z.object({
17-
/** Machine name (snake_case) */
41+
/** Identify */
1842
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Machine name (snake_case)'),
19-
20-
/** Human readable label */
21-
label: z.string().optional().describe('Human readable label'),
22-
23-
/** Documentation / Description */
24-
description: z.string().optional().describe('Documentation / Description'),
25-
26-
/** Icon name (Lucide) */
27-
icon: z.string().optional().describe('Icon name (Lucide)'),
28-
29-
/** Datasource name */
43+
label: z.string().optional().describe('Singular Label (e.g. "Account")'),
44+
pluralLabel: z.string().optional().describe('Plural Label (e.g. "Accounts")'),
45+
description: z.string().optional().describe('Internal description'),
46+
icon: z.string().optional().describe('Lucide icon name'),
47+
48+
/** Storage Config */
3049
datasource: z.string().default('default').describe('Datasource name'),
50+
tableName: z.string().optional().describe('Physical DB table override'),
51+
isSystem: z.boolean().default(false).describe('Is system object (protected)'),
3152

32-
/** Physical database table name override */
33-
dbName: z.string().optional().describe('Physical database table name override'),
34-
35-
/** Map of field definitions */
53+
/** Fields Definition */
3654
fields: z.record(FieldSchema).describe('Map of field definitions'),
3755

38-
/** Database indexes */
56+
/** Indexes */
3957
indexes: z.array(IndexSchema).optional().describe('Database indexes definition'),
58+
59+
/** Key Fields */
60+
nameField: z.string().optional().describe('Which field represents the record name/title (usually "name")'),
61+
62+
/** Features & Capabilities */
63+
enable: ObjectCapabilities.optional().describe('Enabled system capabilities'),
4064
});
4165

42-
/**
43-
* TypeScript type inferred from ObjectSchema.
44-
* Note: 'Object' is a reserved word in JavaScript/TypeScript, so we use 'ServiceObject'.
45-
*/
4666
export type ServiceObject = z.infer<typeof ObjectSchema>;

packages/spec/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from './ui/action.zod';
2222

2323
// System Protocol (Manifest, Runtime, Constants)
2424
export * from './system/manifest.zod';
25+
export * from './system/translation.zod';
2526
export * from './system/constants';
2627
export * from './system/types';
2728

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* Translation Schema
5+
* Supports i18n for labels, messages, and options.
6+
* Example structure:
7+
* {
8+
* "en": { "objects": { "account": { "label": "Account" } } },
9+
* "zh-CN": { "objects": { "account": { "label": "客户" } } }
10+
* }
11+
*/
12+
export const TranslationDataSchema = z.object({
13+
/** Object translations */
14+
objects: z.record(z.object({
15+
label: z.string(),
16+
pluralLabel: z.string().optional(),
17+
fields: z.record(z.object({
18+
label: z.string().optional(),
19+
help: z.string().optional(),
20+
options: z.record(z.string()).optional(), // Option value -> Label map
21+
})).optional(),
22+
})).optional(),
23+
24+
/** App/Menu translations */
25+
apps: z.record(z.object({
26+
label: z.string(),
27+
description: z.string().optional(),
28+
})).optional(),
29+
30+
/** UI Messages */
31+
messages: z.record(z.string()).optional(),
32+
});
33+
34+
export const LocaleSchema = z.string().describe('BCP-47 Language Tag (e.g. en-US, zh-CN)');
35+
36+
export const TranslationBundleSchema = z.record(LocaleSchema, TranslationDataSchema);
37+
38+
export type TranslationBundle = z.infer<typeof TranslationBundleSchema>;

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

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,20 @@
11
import { z } from 'zod';
2+
import { FieldType } from '../data/field.zod';
23

34
/**
4-
* Action Type Definitions
5-
* Defines the type of execution for the action.
5+
* Action Parameter Schema
6+
* Defines inputs required before executing an action.
67
*/
7-
export const ActionType = z.enum([
8-
'script', // Execute a server-side script
9-
'client', // Execute a client-side function
10-
'url', // Open a URL
11-
'flow', // Trigger a workflow/process
12-
'call_service' // Call a microservice
13-
]);
14-
15-
/**
16-
* Action Location
17-
* Defines where the action button should appear.
18-
*/
19-
export const ActionLocation = z.enum([
20-
'record', // Record detail page
21-
'record_more', // Record detail "More" menu
22-
'list', // List view toolbar
23-
'list_item', // List view row action
24-
'global' // Global header/sidebar
25-
]);
8+
export const ActionParamSchema = z.object({
9+
name: z.string(),
10+
label: z.string(),
11+
type: FieldType,
12+
required: z.boolean().default(false),
13+
options: z.array(z.object({ label: z.string(), value: z.string() })).optional(),
14+
});
2615

2716
/**
28-
* Schema for Actions (Buttons/Operations).
17+
* Action Schema
2918
*/
3019
export const ActionSchema = z.object({
3120
/** Machine name of the action */
@@ -34,31 +23,36 @@ export const ActionSchema = z.object({
3423
/** Display label */
3524
label: z.string().describe('Display label'),
3625

37-
/** Action functionality type */
38-
type: ActionType.default('script').describe('Action functionality type'),
26+
/** Icon name (Lucide) */
27+
icon: z.string().optional().describe('Icon name'),
28+
29+
/** Where does this action appear? */
30+
locations: z.array(z.enum([
31+
'list_toolbar', 'list_item',
32+
'record_header', 'record_more', 'record_related',
33+
'global_nav'
34+
])).optional().describe('Locations where this action is visible'),
3935

40-
/** Locations where this action is visible */
41-
location: z.union([ActionLocation, z.array(ActionLocation)]).describe('Locations where this action is visible'),
36+
/** Legacy location support */
37+
location: z.any().optional(),
38+
39+
/** What type of interaction? */
40+
type: z.enum(['script', 'url', 'modal', 'flow', 'api']).default('script').describe('Action functionality type'),
4241

43-
/**
44-
* The actual logic to execute.
45-
* - For 'script': The script code or file path.
46-
* - For 'url': The URL template.
47-
* - For 'call_service': Service endpoint.
48-
*/
49-
execute: z.string().optional().describe('Execution logic (script, url, or endpoint)'),
42+
/** Payload / Target */
43+
target: z.string().optional().describe('URL, Script Name, Flow ID, or API Endpoint'), // For URL/Flow types
44+
execute: z.string().optional().describe('Legacy execution logic'),
5045

51-
/** Visibility condition expression (Formula returning boolean) */
52-
visible: z.string().optional().describe('Visibility condition (Formula)'),
46+
/** User Input Requirements */
47+
params: z.array(ActionParamSchema).optional().describe('Input parameters required from user'),
5348

54-
/** Confirmation message before execution */
49+
/** UX Behavior */
5550
confirmText: z.string().optional().describe('Confirmation message before execution'),
56-
57-
/** Icon name (Lucide) */
58-
icon: z.string().optional().describe('Icon name'),
59-
60-
/** Success message after execution */
6151
successMessage: z.string().optional().describe('Success message to show after execution'),
52+
refreshAfter: z.boolean().default(false).describe('Refresh view after execution'),
53+
54+
/** Access */
55+
visible: z.string().optional().describe('Formula returning boolean'),
6256
});
6357

6458
export type Action = z.infer<typeof ActionSchema>;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ export const DashboardWidgetSchema = z.object({
3232
filter: z.any().optional().describe('Data filter criteria'),
3333

3434
/** Category Field (X-Axis / Group By) */
35-
category_field: z.string().optional().describe('Field for grouping (X-Axis)'),
35+
categoryField: z.string().optional().describe('Field for grouping (X-Axis)'),
3636

3737
/** Value Field (Y-Axis) */
38-
value_field: z.string().optional().describe('Field for values (Y-Axis)'),
38+
valueField: z.string().optional().describe('Field for values (Y-Axis)'),
3939

4040
/** Aggregate operation */
4141
aggregate: z.enum(['count', 'sum', 'avg', 'min', 'max']).optional().default('count').describe('Aggregate function'),

0 commit comments

Comments
 (0)