Skip to content

Add ViewDataSchema for dual-mode data sourcing in Forms and Tables - #95

Merged
hotlong merged 4 commits into
mainfrom
copilot/add-view-data-schema
Jan 24, 2026
Merged

Add ViewDataSchema for dual-mode data sourcing in Forms and Tables#95
hotlong merged 4 commits into
mainfrom
copilot/add-view-data-schema

Conversation

CopilotAI commented Jan 24, 2026

Copy link
Copy Markdown
Contributor

Forms and Tables need to support both protocol-driven (auto-wired to ObjectStack APIs) and custom modes (explicit data sources). This adds a unified data configuration to enable both patterns without schema duplication.

Changes

New Schemas:

  • ViewDataSchema: Discriminated union on provider field with three modes:
    • object: Protocol mode - binds to ObjectStack object by name
    • api: Custom API mode - explicit read/write endpoints
    • value: Static mode - inline data array
  • HttpRequestSchema: API endpoint configuration (url, method, headers, params, body)
  • HttpMethodSchema: HTTP method enum

Schema Updates:

  • ListViewSchema: Added optional data: ViewDataSchema
  • FormViewSchema: Added optional data: ViewDataSchema

Usage

// Protocol mode (default behavior, backward compatible)constlistView: ListView={columns: ['name','status'],data: {provider: 'object',object: 'account'}};// Custom API modeconstcustomList: ListView={columns: ['name','email'],data: {provider: 'api',read: {url: '/api/contacts',method: 'GET'},write: {url: '/api/contacts',method: 'POST'}}};// Static data modeconststaticForm: FormView={data: {provider: 'value',items: [{name: 'Demo',type: 'Customer'}]},sections: [{fields: ['name','type']}]};

Type Safety Notes:

  • Discriminated union enforces provider-specific fields
  • Uses z.unknown() over z.any() for params/body/items
  • data property is optional - existing configs unchanged
Original prompt

The frontend components for Forms and Tables need to support two distinct operating modes:

  1. Standard Protocol: Automatically connects to the ObjectStack Metadata and Data APIs based on a bound object.
  2. Custom/Traditional: Operates like a traditional component where data sources (API URLs) or static data are explicitly provided, and metadata (columns/fields) is fully defined in the schema rather than inferred.

To support this without splitting the schemas into two separate files (keeping a unified definition), please modify packages/spec/src/ui/view.zod.ts:

  1. Create a ViewDataSchema:

    • Define a configuration object for data sourcing.
    • provider: Enum ['object', 'api', 'value'] (Default: 'object').
    • object: String (Target object for 'object' provider).
    • read: Configuration for fetching data (URL, method, params) - used for 'api' provider.
    • write: Configuration for submitting data (URL, method) - used for forms or editable tables.
    • items: z.array(z.any()) (Static data for 'value' provider).
  2. Update ListViewSchema:

    • Add data: ViewDataSchema.optional().
    • Ensure columns uses the enhanced ListColumnSchema (from the previous context/PR).
  3. Update FormViewSchema:

    • Add data: ViewDataSchema.optional().
    • Ensure sections (or fields) uses the enhanced FormFieldSchema (from the previous context/PR).

This allows a single schema to define both "smart" model-driven views and "dumb" custom views.

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel

vercelBot commented Jan 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentReviewUpdated (UTC)
specErrorErrorJan 24, 2026 4:30am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
CopilotAI changed the title [WIP] Add ViewDataSchema for forms and tablesAdd ViewDataSchema for dual-mode data sourcing in Forms and TablesJan 24, 2026
CopilotAI requested a review from hotlongJanuary 24, 2026 04:32
@hotlong
hotlong marked this pull request as ready for review January 24, 2026 04:49
CopilotAI review requested due to automatic review settings January 24, 2026 04:49
@hotlong
hotlong merged commit df0d477 into mainJan 24, 2026
1 of 2 checks passed

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for dual-mode data sourcing in Forms and Tables, enabling both protocol-driven (auto-wired to ObjectStack APIs) and custom modes (explicit data sources). The implementation introduces a ViewDataSchema with three provider modes (object, api, value) and integrates it into existing ListView and FormView schemas.

Changes:

  • Added ViewDataSchema with discriminated union for three data provider modes: protocol-based object binding, custom API endpoints, and static inline data
  • Added HTTP-related schemas (HttpMethodSchema, HttpRequestSchema) to support custom API configuration
  • Integrated optional data property into ListViewSchema and FormViewSchema for backward compatibility

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.

FileDescription
packages/spec/src/ui/view.zod.tsCore schema definitions for ViewData, HttpRequest, and HttpMethod, plus integration with ListView and FormView
packages/spec/src/ui/view.test.tsComprehensive test coverage for new schemas including all three provider modes
packages/spec/package-lock.jsonVersion bump from 0.3.0 to 0.3.1
Files not reviewed (1)
  • packages/spec/package-lock.json: Language not supported

/**
* HTTP Method Enum
*/
export const HttpMethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This HTTP method enum duplicates the existing HttpMethod schema in packages/spec/src/system/api.zod.ts:6. To maintain consistency and avoid duplication, consider importing and reusing the existing HttpMethod schema instead of defining a new HttpMethodSchema. The existing schema is already exported from the System namespace and uses the same enum values.

Copilot uses AI. Check for mistakes.
export const ViewDataSchema = z.discriminatedUnion('provider', [
z.object({
provider: z.literal('object'),
object: z.string().describe('Target object name'),

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object field should include snake_case validation to ensure consistency with the naming convention for machine names throughout the codebase. According to the established pattern (e.g., packages/spec/src/data/dataset.zod.ts:29), object names should be validated with .regex(/^[a-z_][a-z0-9_]*$/) to enforce the snake_case convention for data values.

Copilot generated this review using guidance from repository custom instructions.
url: z.string().describe('API endpoint URL'),
method: HttpMethodSchema.optional().default('GET').describe('HTTP method'),
headers: z.record(z.string()).optional().describe('Custom HTTP headers'),
params: z.record(z.unknown()).optional().describe('Query parameters'),

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of z.unknown() is inconsistent with the rest of the codebase. Throughout this repository, z.any() is used for flexible/dynamic values (as seen in packages/spec/src/api/contract.zod.ts:29, packages/spec/src/data/field.zod.ts:90, and numerous other files). While z.unknown() is technically more type-safe, changing this pattern in isolation creates inconsistency. Consider using z.any() for params to align with established conventions.

Copilot uses AI. Check for mistakes.
method: HttpMethodSchema.optional().default('GET').describe('HTTP method'),
headers: z.record(z.string()).optional().describe('Custom HTTP headers'),
params: z.record(z.unknown()).optional().describe('Query parameters'),
body: z.unknown().optional().describe('Request body for POST/PUT/PATCH'),

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of z.unknown() is inconsistent with the rest of the codebase. Throughout this repository, z.any() is used for flexible/dynamic values (as seen in packages/spec/src/data/workflow.zod.ts:81, packages/spec/src/api/contract.zod.ts:11, and numerous other files). While z.unknown() is technically more type-safe, changing this pattern in isolation creates inconsistency. Consider using z.any() for body to align with established conventions.

Copilot uses AI. Check for mistakes.
}),
z.object({
provider: z.literal('value'),
items: z.array(z.unknown()).describe('Static data array'),

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of z.unknown() is inconsistent with the rest of the codebase. Throughout this repository, z.any() is used for array items with dynamic data (as seen in packages/spec/src/data/dataset.zod.ts:56 and numerous other files). While z.unknown() is technically more type-safe, changing this pattern in isolation creates inconsistency. Consider using z.array(z.any()) for items to align with established conventions.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +36
z.object({
provider: z.literal('api'),
read: HttpRequestSchema.optional().describe('Configuration for fetching data'),
write: HttpRequestSchema.optional().describe('Configuration for submitting data (for forms/editable tables)'),
}),
z.object({

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The api provider allows both read and write to be optional, which means an api provider could be configured without any endpoints. This could lead to runtime issues. Consider adding validation to ensure at least one of read or write is provided when using the api provider, using Zod's .refine() method or making at least the read field required for meaningful API data sources.

Suggested change
z.object({
provider: z.literal('api'),
read: HttpRequestSchema.optional().describe('Configuration for fetching data'),
write: HttpRequestSchema.optional().describe('Configuration for submitting data (for forms/editable tables)'),
}),
z.object({
z
.object({
provider: z.literal('api'),
read: HttpRequestSchema.optional().describe('Configuration for fetching data'),
write: HttpRequestSchema.optional().describe('Configuration for submitting data (for forms/editable tables)'),
})
.refine(
(data)=>data.read!==undefined||data.write!==undefined,
{
message: 'api provider requires at least one of "read" or "write"',
}
),
z.object({

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +103
it('should accept api provider with read and write configurations', () => {
const data: ViewData = {
provider: 'api',
read: {
url: '/api/accounts',
method: 'GET',
},
write: {
url: '/api/accounts',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
},
};

expect(() => ViewDataSchema.parse(data)).not.toThrow();
});

CopilotAIJan 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage is missing for the edge case where an api provider has neither read nor write configured. Consider adding a test case to verify this scenario is handled appropriately (either by validation rejection or documented as acceptable). Additionally, consider testing an api provider with only write configured, which could be a valid use case for write-only forms.

Copilot uses AI. Check for mistakes.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@hotlong