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

# Utility Functions: configDiff and MigrationStep Reference

> API reference for configDiff(), which computes structural diffs between config objects, and MigrationStep, for defining schema version migrations.

tauri-plugin-configurate ships two utility exports that complement the main `Configurate` class. `configDiff()` is a standalone function that computes a structural diff between any two config-shaped objects — useful for logging, auditing, or selectively reacting to changes. `MigrationStep` is the interface you implement to upgrade stored configs from an older schema version to a newer one.

***

## configDiff(oldData, newData)

Computes a structural diff between two config objects and returns an array of change descriptors. Nested objects are compared recursively, and each change is reported at its deepest differing path using dot-separated notation.

```typescript theme={null}
function configDiff(
  oldData: Record<string, unknown>,
  newData: Record<string, unknown>,
): DiffEntry[]
```

<ParamField path="oldData" type="Record<string, unknown>" required>
  The baseline config object — the "before" state.
</ParamField>

<ParamField path="newData" type="Record<string, unknown>" required>
  The updated config object — the "after" state.
</ParamField>

<ResponseField name="returns" type="DiffEntry[]">
  An array of `DiffEntry` objects, one for each key that was added, removed, or changed. The array is empty when the two objects are deeply equal. Order follows the iteration order of the union of both objects' keys.
</ResponseField>

```typescript theme={null}
import { configDiff } from "tauri-plugin-configurate-api";

const changes = configDiff(
  { theme: "light", fontSize: 14 },
  { theme: "dark", fontSize: 14, lang: "en" },
);
// [
//   { path: "theme", type: "changed", oldValue: "light", newValue: "dark" },
//   { path: "lang", type: "added", newValue: "en" },
// ]
```

Nested objects are walked recursively and paths are dot-separated:

```typescript theme={null}
const changes = configDiff(
  { database: { host: "localhost", port: 5432 } },
  { database: { host: "db.example.com", port: 5432 } },
);
// [
//   { path: "database.host", type: "changed", oldValue: "localhost", newValue: "db.example.com" },
// ]
```

<Note>
  Recursion depth is capped at 64 levels. If either object contains nesting deeper than 64 levels, `configDiff()` throws `"configDiff: maximum nesting depth exceeded"`. In practice, config objects are never this deep.
</Note>

### Using configDiff with onChange

A common pattern is to call `configDiff` inside an `onChange` listener to react only to specific fields:

```typescript theme={null}
import { Configurate, configDiff } from "tauri-plugin-configurate-api";

let previousData = (await config.load().run()).data;

const unlisten = await config.onChange(async () => {
  const nextData = (await config.load().run()).data;
  const changes = configDiff(
    previousData as Record<string, unknown>,
    nextData as Record<string, unknown>,
  );

  for (const change of changes) {
    if (change.path === "theme") {
      applyTheme(change.newValue as string);
    }
  }

  previousData = nextData;
});
```

***

## DiffEntry

Each element in the array returned by `configDiff()` conforms to this interface.

```typescript theme={null}
interface DiffEntry {
  path: string;
  type: "added" | "removed" | "changed";
  oldValue?: unknown;
  newValue?: unknown;
}
```

<ResponseField name="path" type="string">
  Dot-separated path to the changed key, relative to the root of the config object. For example, a change to `database.host` produces `"database.host"`.
</ResponseField>

<ResponseField name="type" type="&#x22;added&#x22; | &#x22;removed&#x22; | &#x22;changed&#x22;">
  The kind of change:

  * `"added"` — the key is present in `newData` but absent in `oldData`.
  * `"removed"` — the key is present in `oldData` but absent in `newData`.
  * `"changed"` — the key is present in both but the values differ.
</ResponseField>

<ResponseField name="oldValue" type="unknown">
  The previous value. Present for `"removed"` and `"changed"` entries; absent for `"added"`.
</ResponseField>

<ResponseField name="newValue" type="unknown">
  The new value. Present for `"added"` and `"changed"` entries; absent for `"removed"`.
</ResponseField>

```typescript theme={null}
const changes = configDiff(
  { theme: "light", fontSize: 14, lang: "en" },
  { theme: "dark", fontSize: 14 },
);
// [
//   { path: "theme",  type: "changed", oldValue: "light", newValue: "dark" },
//   { path: "lang",   type: "removed", oldValue: "en" },
// ]
```

***

## MigrationStep

`MigrationStep` is the interface you implement to define how config data should be transformed when the stored version is older than the current `version` declared in the `Configurate` constructor. Migrations are applied automatically during `load()`; if any step runs, the migrated result is auto-saved back to storage.

```typescript theme={null}
interface MigrationStep<
  TData extends Record<string, unknown> = Record<string, unknown>
> {
  version: number;
  up: (data: TData) => TData;
}
```

<ResponseField name="version" type="number">
  The schema version this step upgrades **from**. A step with `version: 1` transforms data that was saved at version 1 into version 2.
</ResponseField>

<ResponseField name="up" type="(data: TData) => TData">
  A pure transform function. Receives the config data at the declared `version` and must return the data shaped for the next version. Do not mutate the input object — return a new one.
</ResponseField>

### How versioning works

When you set `version: N` in the constructor, the plugin stores `__configurate_version__: N` alongside your data on every write. On `load()`, it reads the stored version and runs every `MigrationStep` whose `version` is less than the stored version, in ascending order, until the data reaches the current version.

```typescript theme={null}
import {
  Configurate,
  defineConfig,
  JsonProvider,
} from "tauri-plugin-configurate-api";
import { BaseDirectory } from "@tauri-apps/api/path";

const schema = defineConfig({
  theme: String,
  fontSize: Number,
  // "lang" was added in v1
  // "legacyFlag" was removed in v2
});

const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  version: 2,
  migrations: [
    {
      // Upgrade from version 0 → 1: add the "lang" field
      version: 0,
      up: (data) => ({ ...data, lang: "en" }),
    },
    {
      // Upgrade from version 1 → 2: remove the deprecated "legacyFlag" field
      version: 1,
      up: (data) => {
        const { legacyFlag, ...rest } = data as typeof data & { legacyFlag?: unknown };
        return rest as typeof data;
      },
    },
  ],
});
```

<Tip>
  Write each `up` function as a pure transform — copy the input with the spread operator and add or omit fields rather than mutating in place. This makes migration logic easier to unit-test and reason about.
</Tip>

<Note>
  If a migrated config cannot be auto-saved back (for example, because the disk is full), the failure is logged as a warning but does not surface to your code. The migrated data is still returned in memory for the current session.
</Note>
