> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freezeray.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# @FreezeSchema

> Mark schemas for freezing

# @FreezeSchema

The `@FreezeSchema` macro marks a SwiftData `VersionedSchema` for freezing and generates helper functions for freezing and drift detection.

## Overview

`@FreezeSchema` is an attached macro that generates two static functions:

* `__freezeray_freeze_X_Y_Z()` - Exports the schema to SQLite fixtures
* `__freezeray_check_X_Y_Z()` - Validates the schema hasn't drifted from the frozen version

These functions are called by the FreezeRay CLI and scaffolded tests.

## Declaration

```swift theme={null}
@attached(member, names: arbitrary)
public macro FreezeSchema(version: String)
```

## Parameters

### `version`

**Type:** `String`

**Required:** Yes

The version identifier for this schema. Used to organize fixtures in `FreezeRay/Fixtures/<VERSION>/`.

**Format:** Any string, but semantic versioning is recommended:

* `"1.0.0"` - Full semantic version
* `"1"`, `"2"` - Simple incremental
* `"2024-01-15"` - Date-based

<Info>
  The `version` string is separate from SwiftData's `versionIdentifier`. Use it to organize your frozen fixtures.
</Info>

## Usage

### Basic Example

```swift theme={null}
import SwiftData
import FreezeRay

@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    static let versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self]
    }

    @Model
    final class User {
        var name: String
        var email: String

        init(name: String, email: String) {
            self.name = name
            self.email = email
        }
    }
}
```

### Multiple Schema Versions

```swift theme={null}
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    static let versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self]
    }

    // ... models ...
}

@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema {
    static let versionIdentifier = Schema.Version(2, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self, Post.self]
    }

    // ... models ...
}

@FreezeSchema(version: "3.0.0")
enum AppSchemaV3: VersionedSchema {
    static let versionIdentifier = Schema.Version(3, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self, Post.self, Comment.self]
    }

    // ... models ...
}
```

## Generated Code

When you add `@FreezeSchema(version: "1.0.0")` to a schema, the macro generates:

### Freeze Function

```swift theme={null}
@available(macOS 14, iOS 17, *)
static func __freezeray_freeze_1_0_0() throws {
    try FreezeRayRuntime.freeze(
        schema: AppSchemaV1.self,
        version: "1.0.0"
    )
}
```

**Purpose:** Exports the schema to SQLite fixtures.

**Called by:**

* FreezeRay CLI during `freezeray freeze 1.0.0`
* Temporary test generated by CLI

**What it does:**

1. Creates a ModelContainer with the schema
2. Generates a SQLite database in iOS Simulator
3. Exports fixtures to `/tmp/FreezeRay/Fixtures/1.0.0/`
4. CLI copies fixtures to your project

### Drift Check Function

```swift theme={null}
@available(macOS 14, iOS 17, *)
static func __freezeray_check_1_0_0() throws {
    try FreezeRayRuntime.checkDrift(
        schema: AppSchemaV1.self,
        version: "1.0.0"
    )
}
```

**Purpose:** Validates the schema matches the frozen fixtures.

**Called by:**

* Scaffolded drift tests
* Your test suite (on every run)

**What it does:**

1. Loads frozen fixtures from `FreezeRay/Fixtures/1.0.0/`
2. Generates current schema and computes checksum
3. Compares current checksum with frozen checksum
4. Throws `FreezeRayError.schemaDrift` if they don't match

## Function Naming

The generated function names use underscores instead of dots:

| Version   | Freeze Function              | Drift Check Function        |
| --------- | ---------------------------- | --------------------------- |
| `"1.0.0"` | `__freezeray_freeze_1_0_0()` | `__freezeray_check_1_0_0()` |
| `"2.1.3"` | `__freezeray_freeze_2_1_3()` | `__freezeray_check_2_1_3()` |
| `"1"`     | `__freezeray_freeze_1()`     | `__freezeray_check_1()`     |

<Info>
  The double underscore prefix (`__`) indicates these are internal functions not meant for direct use.
</Info>

## Availability

Both generated functions are marked with:

```swift theme={null}
@available(macOS 14, iOS 17, *)
```

This matches SwiftData's minimum requirements.

## Requirements

### Must Be Applied to Enum

```swift theme={null}
✅ Correct:
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

❌ Error:
@FreezeSchema(version: "1.0.0")
struct AppSchemaV1: VersionedSchema { /* ... */ }
// Error: @Freeze can only be applied to enum declarations
```

### Must Have Version Parameter

```swift theme={null}
✅ Correct:
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

❌ Error:
@FreezeSchema
enum AppSchemaV1: VersionedSchema { /* ... */ }
// Error: @Freeze requires version argument: @Freeze(version: "1.4.0")
```

### Must Conform to VersionedSchema

```swift theme={null}
✅ Correct:
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

❌ Error (no compile error, but won't work):
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1 { /* ... */ }
// Schema must conform to VersionedSchema
```

## How It Works

### Macro Expansion

The macro is a `MemberMacro` that adds members (functions) to the schema enum.

**Before macro expansion:**

```swift theme={null}
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    static let versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self]
    }
}
```

**After macro expansion:**

```swift theme={null}
enum AppSchemaV1: VersionedSchema {
    static let versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        [User.self]
    }

    // ✅ Generated by macro:
    @available(macOS 14, iOS 17, *)
    static func __freezeray_freeze_1_0_0() throws {
        try FreezeRayRuntime.freeze(
            schema: AppSchemaV1.self,
            version: "1.0.0"
        )
    }

    @available(macOS 14, iOS 17, *)
    static func __freezeray_check_1_0_0() throws {
        try FreezeRayRuntime.checkDrift(
            schema: AppSchemaV1.self,
            version: "1.0.0"
        )
    }
}
```

### Runtime Integration

The generated functions call `FreezeRayRuntime` methods:

```swift theme={null}
public enum FreezeRayRuntime {
    /// Freeze a schema version by generating fixture artifacts
    public static func freeze<S: VersionedSchema>(
        schema: S.Type,
        version: String,
        outputDirectory: URL? = nil
    ) throws

    /// Check if sealed schema has drifted from current definition
    public static func checkDrift<S: VersionedSchema>(
        schema: S.Type,
        version: String
    ) throws
}
```

See [FreezeRayRuntime Reference](/reference/runtime) for details on runtime behavior.

## Common Use Cases

### Freezing Multiple Schemas

Annotate each schema version:

```swift theme={null}
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema { /* ... */ }

@FreezeSchema(version: "3.0.0")
enum AppSchemaV3: VersionedSchema { /* ... */ }
```

Then freeze each:

```bash theme={null}
freezeray freeze 1.0.0
freezeray freeze 2.0.0
freezeray freeze 3.0.0
```

### Version Naming Strategies

**Semantic Versioning (Recommended):**

```swift theme={null}
@FreezeSchema(version: "1.0.0")  // Initial
@FreezeSchema(version: "2.0.0")  // Breaking changes
@FreezeSchema(version: "2.1.0")  // New features
@FreezeSchema(version: "2.1.1")  // Bug fixes
```

**Simple Incremental:**

```swift theme={null}
@FreezeSchema(version: "1")
@FreezeSchema(version: "2")
@FreezeSchema(version: "3")
```

**Date-Based:**

```swift theme={null}
@FreezeSchema(version: "2024-01-15")
@FreezeSchema(version: "2024-03-20")
```

### Schema Organization

**Option 1: One file per version**

```
Models/
├── SchemaV1.swift
├── SchemaV2.swift
└── SchemaV3.swift
```

```swift theme={null}
// SchemaV1.swift
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

// SchemaV2.swift
@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema { /* ... */ }
```

**Option 2: All schemas in one file**

```swift theme={null}
// Schemas.swift
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema { /* ... */ }

@FreezeSchema(version: "3.0.0")
enum AppSchemaV3: VersionedSchema { /* ... */ }

enum AppMigrations: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [AppSchemaV1.self, AppSchemaV2.self, AppSchemaV3.self]
    }
    // ...
}
```

## Debugging Macro Expansion

To see the expanded code, use Xcode's macro expansion tool:

1. Right-click on `@FreezeSchema`
2. Select **Expand Macro**
3. View the generated code inline

Or use Swift's `-print-expanded-macro` flag:

```bash theme={null}
swift build -Xswiftc -print-expanded-macro
```

## Error Messages

### Invalid Version Argument

```swift theme={null}
@FreezeSchema  // ❌ Missing version
enum AppSchemaV1: VersionedSchema { /* ... */ }
```

**Error:**

```
@Freeze requires version argument: @Freeze(version: "1.4.0")
```

### Not Applied to Enum

```swift theme={null}
@FreezeSchema(version: "1.0.0")
struct AppSchemaV1: VersionedSchema { /* ... */ }  // ❌ struct instead of enum
```

**Error:**

```
@Freeze can only be applied to enum declarations
```

## Best Practices

<Warning>
  **Never remove `@FreezeSchema` from a shipped schema.** The generated drift check functions are called by tests.
</Warning>

### 1. Annotate All Schema Versions

```swift theme={null}
// ✅ Good: All versions annotated
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema { /* ... */ }
```

```swift theme={null}
// ❌ Bad: V2 not annotated
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

enum AppSchemaV2: VersionedSchema { /* ... */ }  // Missing @FreezeSchema!
```

### 2. Use Consistent Version Format

```swift theme={null}
// ✅ Good: Consistent format
@FreezeSchema(version: "1.0.0")
@FreezeSchema(version: "2.0.0")
@FreezeSchema(version: "3.0.0")
```

```swift theme={null}
// ❌ Bad: Inconsistent format
@FreezeSchema(version: "1.0.0")
@FreezeSchema(version: "2")
@FreezeSchema(version: "2024-03-20")
```

### 3. Freeze Before Shipping

Add `@FreezeSchema` **before** releasing to production:

```swift theme={null}
// During development (not frozen yet)
enum AppSchemaV2: VersionedSchema { /* ... */ }

// Before App Store submission
@FreezeSchema(version: "2.0.0")  // ← Add this
enum AppSchemaV2: VersionedSchema { /* ... */ }
```

Then run:

```bash theme={null}
freezeray freeze 2.0.0
git add FreezeRay/
git commit -m "Freeze schema v2.0.0 for release"
```

### 4. Keep Annotations After Freezing

```swift theme={null}
// ✅ Good: Keep annotation
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema { /* ... */ }

@FreezeSchema(version: "2.0.0")
enum AppSchemaV2: VersionedSchema { /* ... */ }
```

Don't remove annotations from old schemas - drift tests depend on the generated functions.

## Related

<Columns cols={2}>
  <Card title="freeze command" icon="terminal" href="/cli/freeze">
    CLI command that calls the freeze function
  </Card>

  <Card title="Schema Freezing" icon="snowflake" href="/concepts/schema-freezing">
    Learn how schema freezing works
  </Card>

  <Card title="Drift Detection" icon="triangle-exclamation" href="/concepts/drift-detection">
    How drift check functions detect changes
  </Card>

  <Card title="First Freeze Guide" icon="rocket" href="/guides/first-freeze">
    Tutorial for using @FreezeSchema
  </Card>
</Columns>
