Schema validation
Validate state updates with built-in schemas or Zod.
Schema validation ensures state updates conform to an expected structure before being broadcast. Export a schema from any sync handler to enable automatic validation. Supports both a built-in schema format and Zod.
Built-in schema
Define schemas as a record of field definitions. No external dependencies required.
import type { BuiltInSchema } from '@robojs/sync/server'
export const schema: BuiltInSchema = {
score: { type: 'number', min: 0 },
phase: { type: 'string', enum: ['lobby', 'playing', 'ended'] },
winner: { type: 'string', nullable: true, optional: true },
players: { type: 'object' }
}
export const schema = {
score: { type: 'number', min: 0 },
phase: { type: 'string', enum: ['lobby', 'playing', 'ended'] },
winner: { type: 'string', nullable: true, optional: true },
players: { type: 'object' }
}SchemaField reference
| Field | Type | Description |
|---|---|---|
type | 'string' | 'number' | 'boolean' | 'object' | 'array' | Field type (required) |
nullable | boolean | Allow null values |
optional | boolean | Allow undefined / missing values |
min | number | Minimum value (numbers) |
max | number | Maximum value (numbers) |
minLength | number | Minimum length (strings, arrays) |
maxLength | number | Maximum length (strings, arrays) |
pattern | string | Regex pattern (strings) |
enum | (string | number)[] | Allowed values |
items | SchemaField | Schema for array elements |
properties | Record<string, SchemaField> | Nested schema for object properties |
Advanced example
Nested validation with arrays and objects.
import type { BuiltInSchema } from '@robojs/sync/server'
export const schema: BuiltInSchema = {
name: { type: 'string', minLength: 1, maxLength: 50 },
health: { type: 'number', min: 0, max: 100 },
inventory: {
type: 'array',
maxLength: 20,
items: {
type: 'object',
properties: {
id: { type: 'string' },
quantity: { type: 'number', min: 1 }
}
}
},
position: {
type: 'object',
properties: {
x: { type: 'number', min: 0, max: 1000 },
y: { type: 'number', min: 0, max: 1000 }
}
}
}
export const schema = {
name: { type: 'string', minLength: 1, maxLength: 50 },
health: { type: 'number', min: 0, max: 100 },
inventory: {
type: 'array',
maxLength: 20,
items: {
type: 'object',
properties: {
id: { type: 'string' },
quantity: { type: 'number', min: 1 }
}
}
},
position: {
type: 'object',
properties: {
x: { type: 'number', min: 0, max: 1000 },
y: { type: 'number', min: 0, max: 1000 }
}
}
}Zod schemas
If you prefer Zod, export a Zod schema instead. The validation system detects Zod schemas automatically via the .safeParse method.
import { z } from 'zod'
export const schema = z.object({
score: z.number().min(0),
phase: z.enum(['lobby', 'playing', 'ended']),
winner: z.string().nullable().optional(),
players: z.record(z.object({
name: z.string(),
ready: z.boolean()
}))
})import { z } from 'zod'
export const schema = z.object({
score: z.number().min(0),
phase: z.enum(['lobby', 'playing', 'ended']),
winner: z.string().nullable().optional(),
players: z.record(z.object({
name: z.string(),
ready: z.boolean()
}))
})Zod is not included as a dependency. Install it separately if you want to use Zod schemas.
Validation errors
When validation fails, the update is rejected. For direct state updates, the client receives a validation_error message. For RPC calls, the error is returned in the CallResult:
const result = await call('action', payload)
if (!result.success) {
console.log('Validation failed:', result.error) // 'schema_validation_failed'
}const result = await call('action', payload)
if (!result.success) {
console.log('Validation failed:', result.error) // 'schema_validation_failed'
}Combining with validate
Schema validation runs before the custom validate function. Both must pass for the update to be accepted.
import type { BuiltInSchema, SyncUpdateContext } from '@robojs/sync/server'
export const schema: BuiltInSchema = {
score: { type: 'number', min: 0 }
}
export function validate(ctx: SyncUpdateContext): boolean | string {
// Schema already ensured score is a non-negative number
// Additional business logic here
if (ctx.newState.score > ctx.oldState?.score + 10) {
return 'score_increase_too_large'
}
return true
}
export const schema = {
score: { type: 'number', min: 0 }
}
export function validate(ctx) {
// Schema already ensured score is a non-negative number
// Additional business logic here
if (ctx.newState.score > ctx.oldState?.score + 10) {
return 'score_increase_too_large'
}
return true
}