npm package: @paralov/rojo-json-validator
Description: Validates Rojo project JSON files using Zod schemas with TypeScript support and AI-friendly error messages.
Author: Paralov
Latest version: 1.0.0

# Rojo JSON Validator

A comprehensive TypeScript validator for Rojo project JSON files using Zod schemas. This library validates the structure, properties, and relationships in Rojo project files, providing detailed, AI-friendly error messages.

## Features

- ✅ **Complete Rojo Support**: Validates all Rojo project JSON structures based on [rojo-rbx/rojo](https://github.com/rojo-rbx/rojo)
- ✅ **Type Safety**: Full TypeScript support with inferred types
- ✅ **Detailed Errors**: Zod-powered validation with clear, actionable error messages
- ✅ **AI-Ready**: Error format designed for consumption by AI agents
- ✅ **Test-Driven**: Developed using TDD with comprehensive test coverage

## Installation

```bash
pnpm add @paralov/rojo-json-validator
# or
npm install @paralov/rojo-json-validator
# or
yarn add @paralov/rojo-json-validator
```

## Usage

### Basic Validation

```typescript
import { validateRojoProject } from '@paralov/rojo-json-validator';

const projectData = {
  name: 'MyGame',
  tree: {
    $className: 'DataModel',
    ServerScriptService: {
      $path: 'src/server',
    },
    ReplicatedStorage: {
      $path: 'src/shared',
    },
  },
};

const result = validateRojoProject(projectData);

if (result.success) {
  console.log('✅ Valid project:', result.data);
} else {
  console.error('❌ Validation errors:', result.error.issues);
}
```

### Strict Validation (Throws on Error)

```typescript
import { validateRojoProjectStrict } from '@paralov/rojo-json-validator';

try {
  const project = validateRojoProjectStrict(projectData);
  console.log('Valid project:', project);
} catch (error) {
  if (error instanceof z.ZodError) {
    console.error('Validation errors:', error.issues);
  }
}
```

### Validating Individual Nodes

```typescript
import { validateProjectNode } from '@paralov/rojo-json-validator';

const node = {
  $className: 'Model',
  $properties: {
    PrimaryPart: null,
  },
  Part1: {
    $className: 'Part',
  },
};

const result = validateProjectNode(node);
```

## Supported Project Structure

The validator supports all Rojo project features:

### Top-Level Project Fields

- `$schema` - Optional schema URI for IDE support
- `name` - Project name (optional, can be inferred from filename)
- `tree` - Project instance tree (required)
- `servePort` - Default port for `rojo serve`
- `servePlaceIds` - Array of compatible place IDs
- `blockedPlaceIds` - Array of incompatible place IDs
- `placeId` - Place ID to set when connecting
- `gameId` - Game/Universe ID to set when connecting
- `serveAddress` - IP address for the server
- `emitLegacyScripts` - Whether to emit legacy Scripts/LocalScripts
- `globIgnorePaths` - Array of glob patterns to ignore
- `syncRules` - Custom sync rules for file types

### ProjectNode Fields

- `$className` - Roblox instance class name
- `$id` - ID for referent properties
- `$path` - Path to file or folder (string or `{ optional: string }`)
- `$properties` - Instance properties
- `$attributes` - Instance attributes
- `$ignoreUnknownInstances` - Live sync behavior for unknown instances
- Child nodes (any key not starting with `$`)

### Property Types

The validator supports both shorthand and explicit property syntax:

```typescript
### Property Type Syntax

Rojo supports both shorthand and explicit type syntax for properties:

```typescript
// Shorthand (inferred types)
{
  $properties: {
    Name: "MyPart",
    Position: [0, 10, 0],
    Size: [4, 1, 2],
    Transparency: 0.5
  }
}

// Explicit type syntax
{
  $properties: {
    Name: { String: "MyPart" },
    Position: { Vector3: [0, 10, 0] },
    Size: { Vector3: [4, 1, 2] },
    Color: { Color3: [1, 0, 0] }
  }
}
```

### Special Property Types

The validator supports all of Rojo's special property types that have unique XML serialization:

#### CFrame
```typescript
// 12-element array: [x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22]
CFrame: [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]

// Or explicit object syntax
CFrame: {
  CFrame: {
    position: [0, 0, 0],
    orientation: [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
  }
}
```

#### NumberSequence / ColorSequence
```typescript
// NumberSequence (e.g., for Beam Transparency)
Transparency: {
  NumberSequence: {
    keypoints: [
      { time: 0, value: 0, envelope: 0 },
      { time: 1, value: 1, envelope: 0 }
    ]
  }
}

// ColorSequence (e.g., for Beam Color)
Color: {
  ColorSequence: {
    keypoints: [
      { time: 0, color: [1, 0, 0] },
      { time: 1, color: [0, 0, 1] }
    ]
  }
}
```

#### PhysicalProperties
```typescript
// Default material properties
CustomPhysicalProperties: {
  PhysicalProperties: "Default"
}

// Custom values
CustomPhysicalProperties: {
  PhysicalProperties: {
    density: 0.7,
    friction: 0.3,
    elasticity: 0.5,
    frictionWeight: 1.0,
    elasticityWeight: 1.0
  }
}
```

#### Font
```typescript
FontFace: {
  family: "rbxasset://fonts/families/RobotoMono.json",
  weight: "Thin",  // Thin, ExtraLight, Light, Regular, Medium, SemiBold, Bold, ExtraBold, Heavy
  style: "Normal"  // Normal, Italic
}
```

#### Other Special Types
```typescript
// Rect (e.g., ImageLabel SliceCenter)
SliceCenter: {
  Rect: {
    min: [0, 0],
    max: [100, 100]
  }
}

// Ray
Value: {
  Ray: {
    origin: [0, 10, 0],
    direction: [0, -1, 0]
  }
}

// BrickColor
BrickColor: { BrickColor: "Bright red" }

// Content/ContentId
SoundId: { Content: "rbxassetid://1234567" }
Texture: { ContentId: "rbxassetid://7654321" }

// Tags (array of strings)
Tags: { Tags: ["Enemy", "NPC", "Damageable"] }

// Enum
Material: { Enum: "Plastic" }

// NumberRange
Lifetime: { NumberRange: [0.5, 2.0] }

// UDim / UDim2
AnchorPoint: { UDim: [0.5, 0] }
Size: { UDim2: [[0.5, 0], [1, -20]] }

// Color3uint8 (RGB 0-255)
Color: { Color3uint8: [255, 128, 64] }

// Vector2int16
CustomProperty: { Vector2int16: [100, 200] }

// MaterialColors (Terrain only)
MaterialColors: {
  Grass: [10, 20, 30],
  Asphalt: [40, 50, 60]
}
```
```

## Examples

### Minimal Project

```json
{
  "name": "MyProject",
  "tree": {
    "$className": "DataModel"
  }
}
```

### Full Game Project

```json
{
  "name": "MyGame",
  "servePort": 34872,
  "servePlaceIds": [123456],
  "emitLegacyScripts": false,
  "tree": {
    "$className": "DataModel",
    "ServerScriptService": {
      "$path": "src/server"
    },
    "ReplicatedStorage": {
      "$path": "src/shared"
    },
    "StarterPlayer": {
      "StarterPlayerScripts": {
        "$path": "src/client"
      }
    }
  }
}
```

### Model with Properties

```json
{
  "name": "MyModel",
  "tree": {
    "$className": "Model",
    "$properties": {
      "PrimaryPart": null
    },
    "Part1": {
      "$className": "Part",
      "$properties": {
        "Position": [0, 5, 0],
        "Size": [4, 1, 2],
        "BrickColor": {
          "BrickColor": "Bright red"
        }
      }
    }
  }
}
```

### Using Ref Properties

```json
{
  "name": "RefExample",
  "tree": {
    "$className": "Folder",
    "Target": {
      "$className": "Part",
      "$id": "my-target"
    },
    "Pointer": {
      "$className": "ObjectValue",
      "$attributes": {
        "Rojo_Target_Value": "my-target"
      }
    }
  }
}
```

## Error Handling

The validator provides detailed, structured errors:

```typescript
const result = validateRojoProject(invalidData);

if (!result.success) {
  result.error.issues.forEach((issue) => {
    console.log({
      path: issue.path.join('.'),
      message: issue.message,
      code: issue.code,
    });
  });
}
```

Example error output:

```
{
  path: "tree.Child.GrandChild",
  message: "ProjectNode must have either $className, $path, or be a known service/special instance",
  code: "custom"
}
```

## API Reference

### `validateRojoProject(data: unknown)`

Validates a Rojo project JSON object.

**Returns:** `SafeParseReturnType<RojoProject>`

### `validateRojoProjectStrict(data: unknown)`

Validates a Rojo project JSON object and throws if invalid.

**Returns:** `RojoProject`

**Throws:** `ZodError` if validation fails

### `validateProjectNode(data: unknown)`

Validates a ProjectNode independently.

**Returns:** `SafeParseReturnType<ProjectNode>`

### Type Exports

- `RojoProject` - The validated project type
- `ProjectNode` - A project tree node type
- `PathNode` - Path specification (string or `{ optional: string }`)
- `SyncRule` - Sync rule configuration

### Schema Exports

```typescript
import { schemas } from '@paralov/rojo-json-validator';

// Access individual schemas for advanced usage
schemas.project;
schemas.projectNode;
schemas.pathNode;
schemas.syncRule;
schemas.unresolvedValue;
```

## Development

This project was developed using test-driven development (TDD):

```bash
# Install dependencies
pnpm install

# Run tests
pnpm test

# Run tests in watch mode
pnpm test --watch
```

## References

- [Rojo Documentation](https://rojo.space/docs/)
- [Rojo GitHub Repository](https://github.com/rojo-rbx/rojo)
- [Project File Format](https://rojo.space/docs/v7/project-format/)

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Contributing

Issues and pull requests are welcome! Please ensure all tests pass before submitting.

## Changelog

### 1.0.0

- Initial release
- Complete Rojo project JSON validation
- Support for all project fields and node types
- Comprehensive test coverage
- AI-friendly error messages