Skip to content

refactor: implement Zod Schema Audit — eliminate z.any(), deduplicate schemas, update dev plan#543

Merged
hotlong merged 3 commits intomainfrom
copilot/update-development-plan
Feb 8, 2026
Merged

refactor: implement Zod Schema Audit — eliminate z.any(), deduplicate schemas, update dev plan#543
hotlong merged 3 commits intomainfrom
copilot/update-development-plan

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 8, 2026

Implements findings from ZOD_SCHEMA_AUDIT_REPORT.md. Reduces z.any() from 62 → 8 (remaining are legitimate filter operators), deduplicates Presence schemas, and updates DEVELOPMENT_PLAN.md to reflect actual completion state of all phases.

z.any() → z.function() / z.unknown()

  • kernel/plugin.zod.ts (−23): All service method interfaces (logger, storage, i18n, ql, os, router, drivers) and lifecycle hooks now use z.function() with .describe() annotations
  • api/protocol.zod.ts (−28): All ObjectStackProtocolSchema method definitions converted from z.any() to z.function()
  • data/hook.zod.ts, system/core-services.zod.ts, ui/widget.zod.ts (−3): Generic data refs switched to z.unknown()
// Before
logger: z.object({ debug: z.any(), info: z.any(), ... })
onInstall: z.any().optional()

// After
logger: z.object({ debug: z.function().describe('Log debug message'), ... })
onInstall: z.function().optional().describe('Called when plugin is installed')

Presence schema deduplication

WebSocketPresenceStatus now re-exports RealtimePresenceStatus instead of defining a duplicate enum. Added busy to the canonical enum in realtime.zod.ts.

Missing type exports

Added WidgetSource type export in ui/widget.zod.ts.

DEVELOPMENT_PLAN.md

Updated all Phase 1–3 checklists to ✅ with actual metrics. Key metrics table now reflects current state (z.any(): 8, z.unknown(): 340). Phase 4 items remain as future work.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 8, 2026

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

Project Deployment Actions Updated (UTC)
objectstack-studio Error Error Feb 8, 2026 2:32am
spec Ready Ready Preview, Comment Feb 8, 2026 2:32am

Request Review

Copilot AI and others added 2 commits February 8, 2026 02:12
…otocol, hook, widget, core-services schemas

Reduces z.any() count from 62 to 9 (remaining 8 are legitimate filter operators in data/filter.zod.ts)

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… update DEVELOPMENT_PLAN.md

- Deduplicate Presence status enum (websocket imports from realtime)
- Add 'busy' status to RealtimePresenceStatus
- Add missing WidgetSource type export
- Update all Phase 1-3 checklist items to completed status

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Update development plan based on audit report refactor: implement Zod Schema Audit — eliminate z.any(), deduplicate schemas, update dev plan Feb 8, 2026
Copilot AI requested a review from hotlong February 8, 2026 02:20
@hotlong hotlong marked this pull request as ready for review February 8, 2026 03:01
Copilot AI review requested due to automatic review settings February 8, 2026 03:01
@hotlong hotlong merged commit a7984ec into main Feb 8, 2026
4 of 5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the @objectstack/spec Zod definitions based on the schema audit: reducing z.any() usage, deduplicating presence enums across realtime/websocket protocols, and updating the internal development plan to reflect completion status.

Changes:

  • Replace many z.any() occurrences with z.function() / z.unknown() and add missing inferred type exports.
  • Deduplicate Presence status schemas by re-exporting RealtimePresenceStatus from the WebSocket protocol and extending the canonical enum with busy.
  • Update DEVELOPMENT_PLAN.md metrics and phase checklists to reflect current audit results.

Reviewed changes

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

Show a summary per file
File Description
packages/spec/src/ui/widget.zod.ts Exports WidgetSource type; changes widget callback arg schema from z.any() to z.unknown().
packages/spec/src/system/core-services.zod.ts Uses z.unknown() for service instances in the kernel service map.
packages/spec/src/kernel/plugin.zod.ts Replaces service method z.any() fields with z.function() and adds hook .describe() annotations.
packages/spec/src/data/hook.zod.ts Changes hook context ql reference from z.any() to z.unknown().
packages/spec/src/api/websocket.zod.ts Re-exports presence status enum from realtime protocol instead of duplicating it.
packages/spec/src/api/realtime.zod.ts Adds busy to the canonical presence status enum.
packages/spec/src/api/realtime.test.ts Updates tests to accept busy as a valid presence status.
packages/spec/src/api/protocol.zod.ts Replaces protocol method z.any() definitions with z.function().
packages/spec/DEVELOPMENT_PLAN.md Updates audit metrics table and marks phases/tasks as completed.

Comment on lines 445 to 534
export const ObjectStackProtocolSchema = z.object({
// Discovery & Metadata
getDiscovery: z.any()
getDiscovery: z.function()
.describe('Get API discovery information'),

getMetaTypes: z.any()
getMetaTypes: z.function()
.describe('Get available metadata types'),

getMetaItems: z.any()
getMetaItems: z.function()
.describe('Get all items of a metadata type'),

getMetaItem: z.any()
getMetaItem: z.function()
.describe('Get a specific metadata item'),
saveMetaItem: z.any()
saveMetaItem: z.function()
.describe('Save metadata item'),
getMetaItemCached: z.any()
getMetaItemCached: z.function()
.describe('Get a metadata item with cache validation'),

getUiView: z.any()
getUiView: z.function()
.describe('Get UI view definition'),

// Analytics Operations
analyticsQuery: z.any()
analyticsQuery: z.function()
.describe('Execute analytics query'),

getAnalyticsMeta: z.any()
getAnalyticsMeta: z.function()
.describe('Get analytics metadata (cubes)'),

// Automation Operations
triggerAutomation: z.any()
triggerAutomation: z.function()
.describe('Trigger an automation flow or script'),

// Hub Operations
listSpaces: z.any()
listSpaces: z.function()
.describe('List Hub Spaces'),

createSpace: z.any()
createSpace: z.function()
.describe('Create Hub Space'),

installPlugin: z.any()
installPlugin: z.function()
.describe('Install Plugin into Space'),

// Package Management Operations
listPackages: z.any()
listPackages: z.function()
.describe('List installed packages with optional filters'),

getPackage: z.any()
getPackage: z.function()
.describe('Get a specific installed package by ID'),

installPackage: z.any()
installPackage: z.function()
.describe('Install a new package from manifest'),

uninstallPackage: z.any()
uninstallPackage: z.function()
.describe('Uninstall a package by ID'),

enablePackage: z.any()
enablePackage: z.function()
.describe('Enable a disabled package'),

disablePackage: z.any()
disablePackage: z.function()
.describe('Disable an installed package'),

// Data Operations
findData: z.any()
findData: z.function()
.describe('Find data records'),

getData: z.any()
getData: z.function()
.describe('Get single data record'),

createData: z.any()
createData: z.function()
.describe('Create a data record'),

updateData: z.any()
updateData: z.function()
.describe('Update a data record'),

deleteData: z.any()
deleteData: z.function()
.describe('Delete a data record'),

// Batch Operations
batchData: z.any()
batchData: z.function()
.describe('Perform batch operations'),

createManyData: z.any()
createManyData: z.function()
.describe('Create multiple records'),

updateManyData: z.any()
updateManyData: z.function()
.describe('Update multiple records'),

deleteManyData: z.any()
deleteManyData: z.function()
.describe('Delete multiple records'),
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

ObjectStackProtocolSchema methods were switched from z.any() to z.function() but none of them define .args(RequestSchema) / .returns(ResponseSchema) (or z.promise(...)). This conflicts with the docstring (“Each method is defined with its request and response schemas”) and will infer the protocol type as no-arg functions, breaking client/server typing. Please wire each function to its corresponding request/response schemas via .args(...) and .returns(...).

Copilot uses AI. Check for mistakes.
Comment on lines 5 to 48
export const PluginContextSchema = z.object({
ql: z.object({
object: z.any(), // Return any to allow method chaining
query: z.any(),
object: z.function().describe('Get object handle for method chaining'),
query: z.function().describe('Execute a query'),
}).passthrough().describe('ObjectQL Engine Interface'),

os: z.object({
getCurrentUser: z.any(),
getConfig: z.any(),
getCurrentUser: z.function().describe('Get the current authenticated user'),
getConfig: z.function().describe('Get platform configuration'),
}).passthrough().describe('ObjectStack Kernel Interface'),

logger: z.object({
debug: z.any(),
info: z.any(),
warn: z.any(),
error: z.any(),
debug: z.function().describe('Log debug message'),
info: z.function().describe('Log info message'),
warn: z.function().describe('Log warning message'),
error: z.function().describe('Log error message'),
}).passthrough().describe('Logger Interface'),

storage: z.object({
get: z.any(),
set: z.any(),
delete: z.any(),
get: z.function().describe('Get a value from storage'),
set: z.function().describe('Set a value in storage'),
delete: z.function().describe('Delete a value from storage'),
}).passthrough().describe('Storage Interface'),

i18n: z.object({
t: z.any(),
getLocale: z.any(),
t: z.function().describe('Translate a key'),
getLocale: z.function().describe('Get current locale'),
}).passthrough().describe('Internationalization Interface'),

metadata: z.record(z.string(), z.unknown()),
events: z.record(z.string(), z.unknown()),

app: z.object({
router: z.object({
get: z.any(),
post: z.any(),
use: z.any(),
get: z.function().describe('Register GET route handler'),
post: z.function().describe('Register POST route handler'),
use: z.function().describe('Register middleware'),
}).passthrough()
}).passthrough().describe('App Framework Interface'),

drivers: z.object({
register: z.any(),
register: z.function().describe('Register a driver'),
}).passthrough().describe('Driver Registry'),
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

z.function() is used without .args(...) / .returns(...) for the PluginContext methods. In Zod, omitting these typically infers a () => unknown signature, which prevents consumers from calling e.g. ctx.ql.object('account') or ctx.os.getConfig('key') in TypeScript. Please define minimal call signatures (even if using z.unknown() for params/return), similar to the patterns in data/driver.zod.ts / data/data-engine.zod.ts where every function specifies args/returns.

Copilot uses AI. Check for mistakes.
Comment on lines 53 to 63
export const PluginLifecycleSchema = z.object({
onInstall: z.any().optional(),
onInstall: z.function().optional().describe('Called when plugin is installed'),

onEnable: z.any().optional(),
onEnable: z.function().optional().describe('Called when plugin is enabled'),

onDisable: z.any().optional(),
onDisable: z.function().optional().describe('Called when plugin is disabled'),

onUninstall: z.any().optional(),
onUninstall: z.function().optional().describe('Called when plugin is uninstalled'),

onUpgrade: z.any().optional(),
onUpgrade: z.function().optional().describe('Called when plugin is upgraded'),
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

Lifecycle hooks are now z.function() without .args(...) / .returns(...). This will infer as a no-arg function type, but the hooks are used with parameters (e.g., onInstall(context), onUpgrade(context, fromVersion, toVersion) in plugin.test.ts). Please specify the expected args/returns for each hook (at least (context: PluginContextData) => Promise<void> | void, and for onUpgrade include version args).

Copilot uses AI. Check for mistakes.
Sign up for free to 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